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.
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 thing | In 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.
_metais the ID card: version + capabilities on every request — the introductions that used to happen once atinitialize.- No
Mcp-Session-Idanywhere. There is nothing to pin. - The reply carries
resultType(see 5) and the server's_metaserverInfo.
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
- Notifications: the HTTP GET stream and
resources/subscribeare replaced bysubscriptions/listen— one long-lived POST stream Nimbus opts into per notification type (tools/prompts/resources list changes, resource subscriptions). Request-scoped notifications (progress, log messages) still flow on the request's own response stream. - No more resumability: SSE event IDs and
Last-Event-IDare gone. A broken response stream loses the in-flight request — re-issue it as a new request with a new ID. - Removed:
ping,logging/setLevel(log level is now per-request_meta),notifications/roots/list_changed. - Deprecated (≥12-month window, don't adopt in new code): Roots, Sampling, Logging; the old HTTP+SSE transport; Dynamic Client Registration (superseded by Client ID Metadata Documents).
- Caching became first-class: list/read results must carry
ttlMsandcacheScope; servers should returntools/listin deterministic order so clients (and LLM prompt caches) can reuse it. - Tasks (long-running work) moved out of the core into the
io.modelcontextprotocol/tasksextension: poll withtasks/get, feed input withtasks/update.
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
extensionsfield. - 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
- 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?
search_docshas 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?- 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
- MCP 2026-07-28 — Key Changes (official changelog)
- MCP 2026-07-28 specification
- The 2026-07-28 Specification — MCP blog
- Beta SDKs for the 2026-07-28 release — MCP blog
- MCP Python SDK releases (v2 supports 2026-07-28)
- MCP Went Stateless — spec analysis (dev.to)
Facts verified against the changelog and release announcements above, August 2026. Nimbus, Cartwheel, and Priya are fictional.