---
title: "MCP server endpoint (JSON-RPC 2.0 over Streamable HTTP)"
method: POST
path: "/v1/agent/mcp/"
tags: ["MCP"]
---

# MCP server endpoint (JSON-RPC 2.0 over Streamable HTTP)

`POST /v1/agent/mcp/`

## Request

- Base URL: `https://api.educaplay.com`
- URL: `https://api.educaplay.com/v1/agent/mcp/`
- Auth: HTTP bearer

Model Context Protocol server: a single endpoint through which AI clients (Claude, ChatGPT, Claude Code or any MCP client) read, create, edit and publish the activities of the authenticated user by calling tools.

**Transport.** Streamable HTTP, stateless: every JSON-RPC request or notification is its own `POST`, the answer is always one JSON object (no SSE streams), no session is ever issued (`Mcp-Session-Id` is ignored) and notifications are acknowledged with `202`. `GET` is not served.

**Protocol revisions.** Two eras are served on the same endpoint.
- Handshake revisions `2024-11-05`, `2025-03-26`, `2025-06-18` and `2025-11-25`: the client sends `initialize` and the server answers with the negotiated version (the newest it supports when the requested one is unknown), `serverInfo`, `capabilities` (`tools`) and `instructions`.
- Revision `2026-07-28`: no handshake. Each request declares `params._meta["io.modelcontextprotocol/protocolVersion"]` and mirrors it in the `MCP-Protocol-Version` header, together with `Mcp-Method` (the method) and, on `tools/call`, `Mcp-Name` (the tool name, Base64 sentinel encoding accepted). Results carry `resultType: "complete"`, `_meta["io.modelcontextprotocol/serverInfo"]` and, on lists, `ttlMs` and `cacheScope`. `server/discover` returns the supported versions, capabilities, identity and instructions and can be called under either era.

**Methods.** `server/discover`, `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list` and `prompts/list` (both empty). Anything else answers `-32601` (`404` under `2026-07-28`, `200` under the handshake revisions).

**Tools.** `tools/list` returns 26 tools, each with its `title`, its JSON Schema and `annotations` (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`):
- Discover: `list_activity_types`, `get_activity_schema`, `list_my_activities`, `get_activity`, `get_activity_content`, `get_publish_blockers`.
- Create and edit: `create_activity`, `set_activity_content`, `attach_video`, `update_activity`, `set_activity_audience`, `classify_activity`, `update_activity_settings`, `add_label`, `remove_label`.
- Media and audio: `list_my_media`, `illustrate_items`, `search_image_library`, `set_item_images`, `remove_item_media`, `set_item_text_visibility`, `describe_image`, `generate_field_audio`.
- Publication and visibility: `publish_activity`, `unpublish_activity`, `set_activity_privacy`.

Every tool answers a JSON text, `{"ok": true, ...}` or `{"ok": false, "error": "<code>", ...}`, and `isError` mirrors `ok`. Tools that write or publish execute immediately: asking the user for confirmation is the client's job, which is what `destructiveHint` is for. Calls are independent of each other (there is no server-side conversation); the image catalogue returned by `list_my_media` and `search_image_library` stays valid for one hour so that `set_item_images` and `describe_image` can refer to it from later requests.

**Authentication.** Any Bearer token of this API: an application token generated from the profile settings (for clients where the user pastes a token, for example `claude mcp add --transport http educaplay <this URL> --header "Authorization: Bearer <token>"`), or an access token obtained through the OAuth 2.1 flow described under the OAuth tag (for connectors that sign the user in, such as the Claude or ChatGPT connector settings, where only this URL is needed). Both grant exactly the same access as any other token of the API.

**Quotas.** Calls count against the API rate limit of the plan like any other request. `describe_image` spends the AI budget the user shares with the site's assistant, the audio tools spend the text to speech quota of the plan, and publishing is subject to the limits of the plan, which come back as `blockers`.

## Request body

- JsonRpcRequest
  - `jsonrpc` '2.0', required
  - `id` union: Request identifier, string or integer. Omit it to send a notification.
    - string
    - integer
  - `method` string, required
  - `params` object: Method parameters. `tools/call` takes `name` and `arguments`; under `2026-07-28` every request also carries `_meta` with `io.modelcontextprotocol/protocolVersion`.

## Response `200`

JSON-RPC response: `result` on success or `error` on a protocol failure. A tool that fails answers inside `result` with `isError: true` and a JSON text such as `{"ok": false, "error": "forbidden"}`.

- JsonRpcResponse
  - `jsonrpc` '2.0', required
  - `id` union, required
    - string
    - integer
  - `result` object: Present on success. For `tools/call`: `content`, a list with one `{"type": "text", "text": "..."}` block holding the JSON answer of the tool, and `isError`.
  - `error` object
    - `code` integer
    - `message` string
    - `data` object

## Other responses

- `202`: Notification accepted. Empty body.
- `400`: Only for `2026-07-28` requests: the protocol version is not supported (`-32022`, the supported versions travel in `error.data.supported`) or the `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name` headers are missing or do not match the body (`-32020`).
- `401`: Missing or invalid Bearer token. The response carries `WWW-Authenticate: Bearer resource_metadata="..."` pointing at the protected resource metadata, which is how OAuth-capable MCP clients discover the authorization flow.
- `403`: `license_required` when the subscription plan of the user cannot use the MCP server, `profile_not_allowed` for ticket accounts.
- `404`: The MCP server is disabled (`feature_disabled`), or, only for `2026-07-28` requests, the method does not exist (`-32601` in a JSON-RPC error body).
- `429`: The request rate limit for the account has been exceeded. Limits depend on the subscription plan (Basic 120, Advanced 300, Academic 600 and Commercial 1200 requests per minute) and are measured over a fixed 60 second window. Wait the number of seconds given in the `Retry-After` header before retrying.

---

[API](https://skmtc.dev/educaplay/apis/educaplay-api.md) · [All operations](https://skmtc.dev/educaplay/apis/educaplay-api/llms.txt) · [OpenAPI document](https://skmtc.dev/educaplay/apis/educaplay-api/revisions/e31d97013d0d?raw)
