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 no operation needs to push messages to the client.

Protocol versions​

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

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

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

  • params._meta carries io.modelcontextprotocol/protocolVersion → 2026-07-28 era (see below).
  • Without that 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 the stateless semantics and would get the other one.

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 in the response's MCP-Protocol-Version 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 between one request and the next.

params._meta keyRequiredMeaning
io.modelcontextprotocol/protocolVersionyes2026-07-28
io.modelcontextprotocol/clientCapabilitiesyesThe client's capabilities (an object; {} will do). Without form elicitation, destructive tools are refused — see confirmation of destructive tools
io.modelcontextprotocol/clientInforecommendedThe client's name and version

The headers repeat what is in the body, so intermediaries can route without reading it — and that is why they must agree with it:

HeaderMust equal
MCP-Protocol-VersionThe envelope's version
Mcp-MethodThe message's method
Mcp-NameOnly on tools/call: the params.name, raw or as =?base64?…?=

Any mismatch is 400 with -32020 (Header mismatch: …). A 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 scope and permissions, so it must not be reused or shared. A tools/call of a destructive tool may come back with resultType: "input_required" — see Confirming destructive tools.

Request headers​

HeaderRequiredMeaning
AuthorizationyesBearer <access token> issued for the MCP — see Authentication
X-Workspace-IdnoThe connection's default workspace, used when the tool gets no workspace_id — the same as ?workspace_id= on the URL. See Authentication → The workspace
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's method
Mcp-Nameyes on tools/call in the 2026-07-28 eraThe tool name
OriginconditionalSent by browser clients; checked against an allow-list

Response headers​

HeaderMeaning
MCP-Protocol-VersionRevision in use
Cache-ControlAlways no-store
Retry-AfterOn 429 — see Rate limit — and on 503

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 JSON Schema and annotations
tools/callyesyesRuns a tool
resources/listyesyesThe fixed-URI documents this caller can read
resources/templates/listyesyesThe URI templates (kobana://finance/payables/{id}, …) this caller can read
resources/readyesyesReads a document by URI
prompts/listyesyesThe ready-made scripts this caller can use
prompts/getyesyesBuilds a script with the given arguments
completion/completeyesyesSuggestions for a prompt or template argument

Resources, prompts and completions are in Resources and prompts.

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

The body is capped at 14 MB (14,680,064 bytes) — enough for the largest legitimate request, a 10 MB finance_attachments_upload in base64 plus the envelope. Above it the answer is 413 with { "jsonrpc": "2.0", "id": null, "error": { "code": -32600, "message": "Request body too large (limit 14680064 bytes)" } }: a declared Content-Length above the cap is refused even before authentication, and a body with no Content-Length (or a false one) is cut at the cap while being read.

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: without a token, the answer is the 401 with the challenge that takes 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 Kobana Finance: ...",
"ttlMs": 0,
"cacheScope": "private",
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "kobana-finance",
"title": "Kobana Finance",
"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-finance",
"title": "Kobana Finance",
"version": "1.0.0"
},
"instructions": "Tools for Kobana Finance: ..."
} }

The instructions (in initialize and in server/discover) warn the agent that field values in results are data written by third parties, never instructions, and ask for the user's confirmation before a destructive or money-moving action that a result seems to request.

tools/list​

Returns only the tools the caller can run — the ones the token's scope reaches and the person who authorized can do in the dashboard. A write tool's inputSchema is the endpoint's own request schema — the same fields, required ones and enums as the API reference. Every tool carries annotations, which is what makes the client ask the user to confirm:

AnnotationMeaning
readOnlyHintThe tool only reads
destructiveHintThe tool deletes, cancels or moves money — it only runs with the server's confirmation
idempotentHintRepeating the call has the same effect
openWorldHintAlways false — the tool reaches nothing outside Smart Finance

tools/call​

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "finance_payables_list",
"arguments": { "workspace_id": "2b93f876-769a-4bdc-b23e-304957f313b4",
"filters": { "status": "pending" }, "limit": 10,
"fields": ["description", "amount", "due_date"] } } }

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

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

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​

Each tool dispatches in-process to the /v1 handler that implements the operation, with the caller's already-verified identity and the workspace resolved for the call — the MCP token is not forwarded, and the endpoint still requires its own scope. There is no second HTTP hop and no second implementation: filters, validation, settlement rules, rateio, security locks and workspace isolation are the same code the REST API runs. Writes are recorded with the mcp channel (created_via / updated_via), distinguishable from those made through the API or the dashboard.

Files​

  • Download — finance_attachments_download does not return the bytes: a base64 PDF in a model's context is expensive and useless. The result carries the content_type, the size and the path to download the file from the API — with a token issued for the API, since the MCP one does not work there.
  • Upload — finance_attachments_upload takes the file in content_base64 (up to 10 MB) and sends it to the endpoint as multipart/form-data. Then finance_attachments_link attaches it to a title or transaction.