Skip to main content

Transport

Streamable HTTP, stateless. One JSON-RPC 2.0 message per POST, one JSON response. There is no Mcp-Session-Id, no resumability and no server→client stream: the application runs several replicas, and every long operation already answers immediately and finishes in a background worker rather than on the MCP channel.

Protocol versions​

The server speaks two eras of the protocol on the same endpoint:

VersionEraStatus
2026-07-28Stateless, no handshake — discovered through server/discoverSupported
2025-06-18initialize handshake — the version initialize settles on by defaultSupported
2025-03-26initialize handshakeSupported

The era is decided by the request body, with the headers as a cross-check:

  • params._meta carries io.modelcontextprotocol/protocolVersion → 2026-07-28 era (see below).
  • No such envelope, with a 2025 MCP-Protocol-Version or no header at all → 2025 era, with initialize.
  • MCP-Protocol-Version: 2026-07-28 without the envelope → 400 with -32602. The server does not silently fall back to the old era: you asked for stateless semantics and would get the other ones.

A version the server does not speak — in the header or in the envelope, in either era — is answered 400 with the JSON-RPC error -32022, listing the supported ones:

{ "jsonrpc": "2.0", "id": 1, "error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": { "supported": ["2026-07-28", "2025-06-18", "2025-03-26"], "requested": "1999-01-01" }
} }

The version in use comes back on the MCP-Protocol-Version response header.

The 2026-07-28 era​

There is no handshake: every request carries its own version and the client's capabilities in params._meta, and the server remembers nothing from one request to the next.

params._meta keyRequiredMeaning
io.modelcontextprotocol/protocolVersionyes2026-07-28
io.modelcontextprotocol/clientCapabilitiesyesThe client's capabilities (an object; {} will do)
io.modelcontextprotocol/clientInforecommendedThe client's name and version

The headers repeat what the body says, so intermediaries can route without reading it — which is why they have to agree with it:

HeaderMust equal
MCP-Protocol-VersionThe envelope version
Mcp-MethodThe message method
Mcp-NameOn tools/call only: params.name, raw or as =?base64?…?=

Any disagreement is 400 with -32020 (Header mismatch: …). Missing clientCapabilities is 400 with -32602.

Results in this era always carry resultType: "complete" and the server in _meta["io.modelcontextprotocol/serverInfo"]. server/discover, tools/list, resources/list, resources/templates/list, resources/read and prompts/list also carry ttlMs: 0 and cacheScope: "private": what comes back is filtered by the caller's permissions, so it must not be reused or shared. A tools/call to a destructive tool may come back with resultType: "input_required" — see Confirming destructive tools.

Request headers​

HeaderRequiredMeaning
AuthorizationyesBearer <access token> issued for MCP — see Authentication
Content-Typeyesapplication/json
Acceptrecommendedapplication/json
MCP-Protocol-Versionyes in the 2026-07-28 era; recommended in the 2025 oneProtocol revision
Mcp-Methodyes in the 2026-07-28 eraThe message method
Mcp-Nameyes on tools/call in the 2026-07-28 eraThe tool name
OriginconditionalSent by browser-based clients; validated against an allow-list

There is no tenant-selection header: the organization comes from the token itself, and no tool takes an organization argument.

The request body is capped at 1 MB. Above that — by the declared Content-Length or by what actually arrives — the answer is 413 with { "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "Request body too large (limit 1048576 bytes)" } }. No tool takes a file, so a legitimate call stays far below it.

Response headers​

HeaderMeaning
MCP-Protocol-VersionRevision in use
Cache-ControlAlways no-store
Retry-AfterOn 429 only — see Rate limiting

Methods​

Method2026-07-28 era2025 eraPurpose
server/discoveryes—Supported versions, server capabilities and instructions
initialize—yesVersion negotiation, server capabilities and instructions
ping—yesLiveness check; answers an empty result
tools/listyesyesThe tools this caller can run, with their JSON Schema and annotations
tools/callyesyesRuns one tool
resources/listyesyesThe fixed-URI documents this caller can read
resources/templates/listyesyesThe URI templates (kobana://dashboards/portal_users/{id}, …) this caller can read
resources/readyesyesReads one document by URI
prompts/listyesyesThe ready-made prompts this caller can use
prompts/getyesyesRenders one prompt with the given arguments
completion/completeyesyesSuggestions for a prompt or template argument

Resources, prompts and completions are covered in Resources and prompts.

Any other method is -32601 Method not found — with HTTP 404 in the 2026-07-28 era, in the body of a 200 in the 2025 one. JSON-RPC batching is not supported — it was removed in the 2025-06-18 revision, and an array body is answered 400.

A notification (a message with no id, such as notifications/initialized) is accepted with 202 and an empty body, in both eras.

Authentication comes before any method, server/discover included: with no token, the answer is the 401 with the challenge that sends the client to the OAuth login — see Authentication.

server/discover (2026-07-28 era)​

POST / HTTP/1.1
Authorization: Bearer <access token>
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: server/discover
{ "jsonrpc": "2.0", "id": 1, "method": "server/discover",
"params": { "_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" }
} } }
{ "jsonrpc": "2.0", "id": 1, "result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28", "2025-06-18", "2025-03-26"],
"capabilities": { "tools": {}, "resources": {}, "prompts": {}, "completions": {} },
"instructions": "Tools for Magic Dashboard: ...",
"ttlMs": 0,
"cacheScope": "private",
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "kobana-dashboards",
"title": "Magic Dashboard",
"version": "1.0.0"
}
}
} }

initialize (2025-era handshake)​

{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": { "protocolVersion": "2025-06-18" } }
{ "jsonrpc": "2.0", "id": 1, "result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": false },
"resources": { "subscribe": false, "listChanged": false },
"prompts": { "listChanged": false },
"completions": {}
},
"serverInfo": {
"name": "kobana-dashboards",
"title": "Magic Dashboard",
"version": "1.0.0"
},
"instructions": "Tools for Magic Dashboard: ..."
} }

tools/list​

Returns only the tools the caller can run, filtered by the dashboard permissions of whoever authorized the client. Every tool carries annotations, which is what makes the client ask the user to confirm:

AnnotationMeaning
readOnlyHintThe tool only reads
destructiveHintThe tool deletes, closes, suspends or deactivates a record, or sends an email or invitation. The server asks for confirmation on the 2026-07-28 era and refuses the tool (confirmation_required) to a client that cannot confirm
idempotentHintRepeating the call has the same effect
openWorldHintAlways false — the tool reaches nothing outside Magic Dashboard

tools/call​

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "dashboards_portal_users_list",
"arguments": { "status": "active", "per_page": 10,
"fields": ["email", "status", "created_at"] } } }

The result carries the endpoint's JSON response as text content:

{ "jsonrpc": "2.0", "id": 2, "result": {
"content": [{ "type": "text", "text": "{ \"data\": [ ... ], \"meta\": { \"total\": 42, ... } }" }]
} }

Errors the caller can act on come back with isError: true inside the result, not as a JSON-RPC error — see Errors.

In-process dispatch​

Every tool dispatches in-process to the /api/v1 route handler that implements the operation. There is no second HTTP round trip and no second implementation: listing filters, upsert matching, validation, soft-delete and tenant isolation are the same code the REST API runs. A tool cannot drift from the endpoint it mirrors.