Model Context Protocol · Interactive explainer

Stateless MCP — the 2026-07-28 spec

What is MCP?

Model Context Protocol is an open protocol that gives AI applications a common way to connect to external tools and data. The host is the AI application; an MCP client inside it speaks the protocol; an MCP server exposes capabilities such as tools, resources, and prompts.

MCP standardizes the messages between those parts. It does not prescribe the model, the user interface, or the server's business logic. Step through the diagram to see each boundary.

Running example, used everywhere below: Nimbus is an AI agent (the MCP client). Cartwheel is a document-tools MCP server exposing a search_docs tool, running as three replicas behind a load balancer. Priya is Nimbus's human user. The one call we trace: search_docs(query="Q3 report"). All names and values are fictional.
Real thingIn the bank
initialize handshake + negotiated session (old) Sitting down with a personal banker; introductions before any business
Mcp-Session-Id header (old) "My banker knows me" — you must keep returning to the same desk
_meta protocol version + client info + capabilities, on every request Every slip carries your ID and which forms you understand
server/discover The branch pamphlet: services offered, languages spoken
Server-minted handle passed as a tool argument A claim ticket number you copy onto your next slip
MRTR: resultType:"input_required" → retry with inputResponses Your slip comes back with a sticky note "need one more thing"; you resubmit it with the answer attached
subscriptions/listen One announcements board you choose to stand near

1 · The handshake era — the personal banker

What. Before 2026-07-28, every MCP conversation opened with ceremony: Nimbus sent initialize (protocol version, capabilities, client info), Cartwheel answered with its own capabilities and — over Streamable HTTP — usually minted a session (Mcp-Session-Id header), then Nimbus confirmed with notifications/initialized. Only then could the first real request flow, and every later request replayed that session header.

Why it existed. MCP began as a bidirectional, stateful protocol: the server could call the client back mid-conversation (sampling — "run this through your LLM", roots, elicitation). That needs a live, remembered channel — a banker who knows you.

Where it breaks. The session pins Nimbus to one Cartwheel replica. Load balancers need sticky routing; a replica restart loses every session it held; list results could vary per-connection, so nothing was cacheable. Removing this wall was one of the most-requested changes in MCP's history — it's the headline of the 2026-07-28 release.

2 · One call, two eras

The same search_docs call, on the old wire and the new. Toggle the era and step through.

What. In the stateless era there is no handshake and no session. Each request is self-describing: its _meta carries io.modelcontextprotocol/protocolVersion, clientCapabilities, and (recommended) clientInfo; each result carries the server's identity back in _meta. If Cartwheel doesn't speak the requested version, it answers with UnsupportedProtocolVersionError (code -32022) instead of half-working.

Why. A slip that carries everything can be served by any teller: any replica behind a plain load balancer can answer any request, with no shared session store. Two new required HTTP headers — Mcp-Method and Mcp-Name — let gateways route and meter without opening the JSON body at all.

Where it breaks. Every request repeats those _meta bytes — the price of self-description. And a client that wants to pick a version before sending real traffic should read the pamphlet first: server/discover, a mandatory-to-implement RPC that advertises the server's supported versions, capabilities, and identity (it also doubles as a compatibility probe on stdio).

The slip itself

POST /mcp
Mcp-Method: tools/call
Mcp-Name: search_docs

{ "jsonrpc": "2.0", "id": 7,
  "method": "tools/call",
  "params": {
    "name": "search_docs",
    "arguments": { "query": "Q3 report" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "nimbus" },
      "io.modelcontextprotocol/clientCapabilities": { }
    }
  }
}
  • Headers name the method and tool so infrastructure can route/rate-limit without parsing the body.
  • _meta is the ID card: version + capabilities on every request — the introductions that used to happen once at initialize.
  • No Mcp-Session-Id anywhere. There is nothing to pin.
  • The reply carries resultType (see 5) and the server's _meta serverInfo.

3 · The replica game — why statelessness is the point

This is the payoff. Kill the replica that's serving Nimbus and see what each era does. In stateful mode the session pins Nimbus to one teller's desk; in stateless mode any teller reads the slip.

4 · Claim tickets, not memory

What. Real work still needs continuity — paging through search results, a multi-step upload. Without sessions, Cartwheel mints an explicit handle and returns it in the tool result: search_docs answers page one plus cursor: "cur_7f3a". Nimbus passes cursor="cur_7f3a" as an ordinary argument on the next call. The claim ticket is the continuity.

Why. State becomes visible, transferable, and shardable: the ticket names it, and any replica that can look the ticket up can serve the next request. Contrast the session, which was invisible state trapped in one process's memory.

Where it breaks. The burden moves server-side: Cartwheel must store what cur_7f3a means somewhere all replicas can reach (a database, a cache), and tickets can expire or be revoked. A server that used to stash things on the session object has real migration work — this is the honest cost of the new spec.

5 · The sticky note — Multi Round-Trip Requests

The old server could call the client mid-request (elicitation: "ask your user something"). No session, no callback channel — so 2026-07-28 inverts it: the server returns your slip annotated.

What. Every result now carries a required resultType: "complete" for a real answer, "input_required" for an interim one whose inputRequests list what the server still needs, plus an opaque requestState token. The client gathers answers and retries the same request with inputResponses (and the token) attached. Results from older servers that omit resultType must be treated as "complete".

Why. Every leg stays an ordinary client→server request — stateless, load-balancer-safe — yet tools can still pause for human input. This pattern replaces the deprecated server-initiated trio (sampling, roots, elicitation-as-server-call).

Where it breaks. A client that ignores resultType will happily present a sticky note as a final answer. And the server must encode everything it needs to resume inside requestState — another explicit-state discipline.

6 · The cleanup — what else moved

Where the analogy breaks

Concurrency. A drive-through serves one car at a time; Nimbus can have many slips in flight at once, each independent. The bank queue is a lie — think "mail room", not "queue".

The banker could call you. The old era wasn't only ceremony: the live session let the bank phone the customer (sampling, elicitation). Mail can't model incoming calls — which is exactly why MRTR replaces them with an annotated returned slip.

Claim tickets aren't the goods. A handle is a key, not the state itself: Cartwheel must keep the state somewhere durable and shared, and may expire the ticket. A real bank vault doesn't forget your deposit after 15 minutes.

Glossary

MCP
Model Context Protocol — standard for exposing tools/resources/prompts to AI agents. (Nimbus ↔ Cartwheel speak it.)
JSON-RPC
The request/response message format MCP rides on. (Every slip is a JSON-RPC call.)
session
Old per-connection server memory, pinned via Mcp-Session-Id. Removed in 2026-07-28. (Nimbus's desk at replica 2.)
handshake
Old opening exchange: initialize → response → notifications/initialized. Removed. (The introductions.)
Streamable HTTP
MCP's HTTP transport: POST per request, streamed response. (The teller window.)
SSE
Server-Sent Events — how responses stream over HTTP; the older HTTP+SSE transport is deprecated.
_meta
Per-message metadata block; now carries protocol version, capabilities, identity. (Nimbus's ID card on each slip.)
capabilities
What features each side supports; now declared per-request, plus an extensions field.
server/discover
Mandatory RPC advertising a server's versions, capabilities, identity. (Cartwheel's pamphlet.)
handle
Server-minted token for cross-call state, passed as a normal tool argument. (cur_7f3a, the claim ticket.)
MRTR
Multi Round-Trip Requests — server returns "input_required"; client retries with answers attached. (The sticky note.)
resultType
Required field on every result: "complete" or "input_required"; missing means complete.
requestState
Opaque token letting the server resume a retried request without remembering it. (st_84c1 on Priya's slip.)
subscriptions/listen
Single long-lived stream for opted-in change notifications; replaces the GET stream. (The announcements board.)
sampling
Deprecated server→client "run this through your LLM" request; migrate to direct LLM APIs.
elicitation
Server asking the user for input; now expressed through MRTR instead of a server-initiated call. (Folder scope question to Priya.)

Check yourself

  1. The Cartwheel replica serving Nimbus restarts mid-run. In bank terms, what happens in each era — and what does Nimbus have to do next in each?
  2. search_docs has 300 results and Nimbus wants them three pages at a time, with no sessions anywhere. Using the analogy, how does Cartwheel remember where Nimbus left off?
  3. Mid-call, Cartwheel needs Priya to choose between finance/ and all folders — but it can't call Nimbus. Describe the wire exchange, in slips and sticky notes.

Sources

Facts verified against the changelog and release announcements above, August 2026. Nimbus, Cartwheel, and Priya are fictional.