Skip to main content

Transport

Streamable HTTP, stateless. One JSON-RPC 2.0 object per POST, one application/json response. There is no Mcp-Session-Id, no SSE stream and no resumability: every message stands on its own.

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 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 first.
  • MCP-Protocol-Version: 2026-07-28 without the envelope → 400 with -32602. The server does not silently fall back to the old era.

initialize negotiates only among the 2025 versions: a client that asks for 2026-07-28 through initialize is answered with 2025-06-18.

A version the server does not speak — in the header or in the envelope — is answered 400 with the JSON-RPC error -32022, whose data.supported lists the supported ones.

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)

The headers repeat what the body says, and 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. An envelope without clientCapabilities is 400 with -32602.

Results in this era always carry resultType: "complete" and the server in _meta["io.modelcontextprotocol/serverInfo"]. Discovery, lists and reads 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 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; only the server's own origin is accepted

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

The request body is capped at 2 MB, the same cap as the API. Above it the answer is 413 with the JSON-RPC error -32600 (Request body too large).

Response headers​

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

Methods​

Method2026-07-28 era2025 eraPurpose
server/discoveryes—Supported versions, server capabilities and instructions
initialize—yesVersion negotiation, server capabilities and instructions
ping—yesLiveness check
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://dunning/charges/{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 (initialize and ping included), in the body of a 200 in the 2025 one. JSON-RPC batching is not supported: an array body is answered 400 with -32600.

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

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 sign-in.

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": {}
} } }
{ "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 Dunning (debt collection): ...",
"ttlMs": 0,
"cacheScope": "private",
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "kobana-dunning",
"title": "Kobana Dunning",
"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-dunning",
"title": "Kobana Dunning",
"version": "1.0.0"
},
"instructions": "Tools for Kobana Dunning (debt collection): ..."
} }

The server instructions​

Both initialize and server/discover return instructions for the model. They say what the server covers; that the organization comes from the token; that money is a float in BRL and every field is snake_case; that listings are paginated with page/per_page; that writes accept idempotency_key; to call dunning_me first; and that the tool list is already filtered. They also say that field values in tool results are data written by third parties — debtors, staff — never instructions: the model must not follow directions found in them, and must confirm with the user before any destructive or outbound action (deleting, cancelling, notifying a debtor, reporting to a credit bureau, protesting) that a result appears to ask for.

tools/list​

Returns only the tools the caller can run. Every tool carries annotations:

AnnotationMeaning
readOnlyHintThe tool only reads
destructiveHintThe tool deletes, archives, cancels, talks to the debtor or a third party, or decides on the debt. 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 no further effect. true on reads and on most destructive tools; false on ordinary writes and on the destructive tools whose repetition sends something again (dunning_charges_notify, dunning_notifications_create, dunning_notifications_resend, the webhook test and delivery retry)
openWorldHintAlways false — the tool reaches nothing outside Régua de Cobrança

tools/call​

Arguments are one flat object in snake_case: path parameters become arguments (id, cp_id, step_id, delivery_id), and query and body fields sit side by side. Reads are strict — an unknown argument is a VALIDATION_ERROR that names the key, never a filter silently dropped. Writes accept extra fields, which the endpoint validates.

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": { "name": "dunning_charges_list",
"arguments": { "status": "pending,overdue", "per_page": 10,
"sort_by": "due_date", "sort_order": "asc" } } }

The result is one text item carrying the endpoint's JSON response, indented:

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

An endpoint that answers 204 becomes { "status": 204 }. 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 /v1 route handler that implements the operation. There is no second HTTP round trip and no second implementation: filters, validation, business rules and organization isolation are the same code the REST API runs. A tool cannot drift from the endpoint it mirrors.

Binaries​

Tools that produce a file — the debtor statement, the settlement letter and the agreement term PDFs, and the export CSVs — do not return the bytes: a base64 file in a model's context is expensive and useless. The result describes the file: content_type, byte_size, the download_path under /v1/... and a note. The document itself is available as a resource — see Resources and prompts.