Chapter 02
Messages and lifecycle
Everything in MCP is a JSON-RPC 2.0 message. The current revision is stateless: every request carries the version and capabilities it relies on, instead of a handshake at the start of a connection.
JSON-RPC 2.0
- What
- A small JSON message format. Requests have an
id, amethodandparams; responses echo theidwith aresultor anerror; notifications have noidand get no reply. - Why
- Every MCP feature, from
tools/listtoresources/read, is a JSON-RPC method, so reading raw messages is the fastest way to debug.
{
"jsonrpc": "2.0", "id": 7,
"method": "tools/call",
"params": {
"name": "search_notes",
"arguments": { "query": "invoices" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}Watch out
Using null as a request id, or reusing an id that is still in flight; both are invalid.
Stateless requests and _meta
Also called _meta, per-request metadata
- What
- Since 2026-07-28, each request carries its protocol version and client capabilities (and usually client info) in
_meta. Servers must not rely on earlier requests on the same connection. - Why
- Any server instance can answer any request, so remote servers scale behind a load balancer without sticky sessions.
Watch out
Storing per-user state keyed by the connection. Return an explicit handle from a tool and accept it as an argument instead.
server/discover and version negotiation
- What
- Servers implement
server/discoverto advertise their supported versions, capabilities and identity. A request with an unsupported version gets anUnsupportedProtocolVersionerror. - Why
- Clients can pick a version up front, and use it as a probe to detect servers that only speak an older revision.
The initialize handshake (older revisions)
Also called initialize, notifications/initialized
- What
- Up to 2025-11-25, a connection began with an
initializerequest exchanging versions and capabilities, then aninitializednotification. HTTP connections also carried anMcp-Session-Id. - Why
- Many hosts and servers still speak these revisions, so you will meet the handshake in logs, SDKs and tutorials.
Watch out
Mixing the two models in one server, for example relying on session ids while claiming the stateless revision.
Capabilities
- What
- Declarations of optional features each side supports, such as
toolswithlistChangedon a server orelicitationon a client. - Why
- A server must not use a client feature the client has not declared, and a client only calls methods the server advertises.
{
"capabilities": {
"tools": { "listChanged": true },
"resources": {},
"prompts": {}
}
}Multi round-trip requests
Also called MRTR, input_required
- What
- When a server needs more from the client mid-request (user input, for example), it returns a result with
resultType: "input_required"and the questions. The client retries the request with the answers. - Why
- It replaces server-initiated requests, so servers stay stateless and work over plain request and response.
Watch out
Retrying with the same JSON-RPC id. The retry is a new request with a new id, carrying the answers and any requestState.
Notifications and subscriptions
Also called listChanged, subscriptions/listen
- What
- One-way messages such as progress updates or
notifications/tools/list_changed. Change notifications flow on asubscriptions/listenstream the client opens and chooses topics for. - Why
- They let a client refresh its tool list or a resource when it changes, instead of polling.
Protocol errors and tool errors
- What
- Protocol problems (unknown tool, malformed params) are JSON-RPC errors. Failures while running a tool (bad date, API down) are normal results with
isError: trueand a readable message. - Why
- The model sees tool errors and can fix its arguments and retry; protocol errors usually cannot be fixed by the model.
Watch out
Throwing a JSON-RPC error for a validation problem, hiding the helpful message from the model.