On July 28, 2026, the Model Context Protocol shipped its fifth revision — and its first true architectural rewrite. Every previous version (2024-11-05 → 2025-03-26 → 2025-06-18 → 2025-11-25) evolved the same core design: a stateful session, opened by an initialize handshake, in which client and server negotiated capabilities once and remembered them for the life of the connection.
2026-07-28 deletes that design. No handshake. No sessions. No server-initiated requests. Two of the protocol's original client features — sampling and roots — deprecated in the same release.
If that sounds drastic, it is. It's also, from where I sit running governed MCP platforms, the most operationally sensible thing the spec has ever done. Let me walk through it the way I'd explain it to my own team.
01One tool call, two protocol eras
Abstract descriptions of statelessness tend to slide off. So here is a single, boring, entirely ordinary job, run under both versions.
A user asks an agent to check their pending invoices. The server needs one more detail before it can answer: for which date? The user says 2026-08-02. The server replies: three invoices pending. Then, in the same chat, the user asks a follow-up — and for next week? Between those two turns, a routine deploy restarts one replica.
That deploy is the whole story. It's the thing that happens on any given Tuesday, and it's where the two eras come apart.
Under 2025-11-25, that job takes nine messages. Client sends initialize. Server replies with its capabilities and mints Mcp-Session-Id: 7f3a…. Client sends notifications/initialized. Only now the real request: tools/call check_pending_invoices, carrying the session id. The server can't answer yet, so it calls the client back — a server-initiated elicitation/create travelling backwards down a long-lived stream, asking for the date. The client answers. The server returns the result. It works.
But all of that context lives inside Replica A's memory, keyed to that session. The load balancer must pin every subsequent request there. So when the deploy restarts Replica A and the follow-up question routes to Replica B, the answer is HTTP 404 — session not found, and the client starts over from the handshake. Sticky sessions weren't a tuning preference. They were mandatory.
Under 2026-07-28, the same job takes four messages. The tools/call goes out carrying its own protocol version and capabilities. The server needs the date, so it returns resultType: "input_required" along with a cryptographically sealed requestState blob — and then forgets the exchange entirely. The client retries with a brand-new request id, attaching the answer and the sealed blob. The retry lands on Replica B, which has never seen this conversation, verifies the seal, and finishes the job.
Then the deploy happens. Then the follow-up arrives. Nothing notices, because there is no session to lose.
Nine messages became four. One server-side session became none.
The same job under both eras, side by side — watch the 404 happen on the left and not happen on the right.
02The old world: a session protocol wearing HTTP clothes
Until this revision, everything after the handshake leaned on connection memory. On Streamable HTTP, the server could mint an Mcp-Session-Id the client had to echo on every request. Servers could push their own JSON-RPC requests back at clients over a long-lived GET/SSE stream — that's how sampling/createMessage (server borrows the client's LLM) and elicitation/create (server asks the user something) worked. Broken streams were resumable with Last-Event-ID. There was even a ping.
Elegant on a whiteboard. Painful behind a load balancer. Session state forced sticky routing or a shared session store. The GET stream meant every server replica had to be able to reach the specific connection a client was listening on. List results could vary per connection, so caching was fraught. And "the server sends the client a request" inverts everything HTTP infrastructure assumes — try explaining to your API gateway that the response stream may contain inbound RPCs.
None of this is hypothetical. If you've deployed a remote MCP server across more than one replica, you've met each of these problems personally.
03The new world: every request stands alone
The 2026-07-28 model fits in one sentence: every request carries everything needed to process it.
Concretely, each request's _meta block now carries the protocol version and the client's capabilities (required), plus its identity (recommended):
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "check_pending_invoices",
"arguments": { "date": "2026-08-02" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": { "elicitation": {} },
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0.0" }
}
}
}Omit protocolVersion or clientCapabilities and you get -32602 with an HTTP 400 — they are not optional decoration. The server processes the request with zero connection history. A different replica can serve the next one. A restarted stdio process just picks up where nothing was, because there was never a "where."
Capability discovery didn't disappear; it inverted. A new server/discover request — which servers must implement and clients may call — returns supported versions, capabilities, identity, and instructions in one cacheable response. And "cacheable" is now a first-class concept: discovery and the list/read operations must return ttlMs and cacheScope (public/private) hints, HTTP-cache semantics brought inside the protocol. Version mismatch? The server answers any request with UnsupportedProtocolVersionError (-32022) listing what it supports, and the client simply retries with a mutual version. Negotiation without a handshake.
04The cleverest part: MRTR
Here's the puzzle statelessness had to solve. Mid-request, servers legitimately need things from the client's side: user input, an LLM completion, a folder list. The old answer was server-initiated requests over that troublesome stream. The new answer is the Multi Round-Trip Request pattern, and it's the most design-relevant piece of the revision:
- Client:
tools/call(id: 1) - Server: instead of a final result, returns
resultType: "input_required"carryinginputRequests(what it needs) and/orrequestState(an opaque blob encoding everything the server needs to resume) - Client gathers the input, then retries the original request with a new id, attaching
inputResponsesand echoingrequestStatebyte-for-byte - Any replica verifies
requestState, reconstitutes context, finishes the job
MRTR is allowed on exactly three operations — tools/call, resources/read, and prompts/get — and the retry must use a fresh JSON-RPC id, because it is a genuinely independent request.
The server kept no state between those two requests. It mailed its state to the client and got it back. If that reminds you of encrypted JWT session tokens or continuation-passing style, you have exactly the right intuition. And the spec is refreshingly paranoid about the obvious risk: requestState round-trips through an untrusted party, so servers must treat it as attacker-controlled — integrity-protect it (HMAC/AEAD), bind it to the principal and originating request, give it a TTL.
The flip side: server→client JSON-RPC requests are gone. A modern MCP server never initiates. Which brings us to the deprecations.
05Sampling and roots: deprecated, and honestly, correctly
Sampling was one of MCP's founding ideas — servers could stay LLM-agnostic by borrowing the client's model. Roots let clients scope servers to workspace folders. Both are now formally deprecated (removal eligible from 2027-07-28), alongside the logging utility and, over in OAuth-land, Dynamic Client Registration (replaced by Client ID Metadata Documents).
The stated migrations tell you the real story: integrate directly with LLM provider APIs instead of sampling; pass paths as tool parameters or configuration instead of roots; log to stderr or OpenTelemetry instead of notifications/message. In practice, adoption data made the call — hosts rarely implemented sampling well, servers couldn't rely on it, and a feature nobody can rely on is worse than no feature.
There's a real trade here worth naming: sampling's "bring your own model" promise was philosophically lovely, and its loss makes servers a bit less portable and a bit more key-laden. The spec chose operational honesty over architectural romance. As someone who has to run these things: correct call, small tear shed.
One nuance platform teams should note: deprecated is not removed. These features remain fully specified for at least twelve months. Legacy interop is a first-class part of the revision — the spec defines "modern," "legacy," and "dual-era" implementations, detection recipes, and a compatibility matrix. Your 2025-11-25 servers don't stop working; they just stopped being the future.
06What this buys you at scale
The scaling story is why this revision exists, so let's be concrete about what changes behind a load balancer:
Any replica can serve any request. No sticky sessions, no shared session store, no draining ceremony on deploys. MCP servers become the same shape as every other stateless service you already run — horizontal scaling is a replica count, not an architecture project. Your replicas share a signing key, not a session store.
Caching becomes real. tools/list no longer varies per connection, must be deterministically ordered, and carries explicit TTL and scope. A cacheScope: "public" tool list can sit in your CDN. (Corollary: scope it wrong and you've built a cross-tenant data leak — the spec's security notes on this are worth reading twice.)
Gateways can finally see. New required headers mirror the body — MCP-Protocol-Version, Mcp-Method, Mcp-Name, and optional Mcp-Param-* from annotated tool parameters — so L7 infrastructure can route, rate-limit, and observe per-method and per-tool without parsing JSON bodies. Header/body mismatch is a protocol error (-32020, HTTP 400) precisely so intermediaries and servers can't be split-brained by an attacker. Add first-class OpenTelemetry trace-context keys in _meta, and MCP traffic finally looks like something an SRE can operate.
Failure is boring again. A broken SSE stream just means re-issue the request. A dead stdio process just means restart and retry. Cancellation on HTTP is closing the stream. The recovery story fits on an index card.
The cost? Notifications needed a new home — that's the subscriptions/listen pattern, an opt-in filtered stream with acknowledged subscriptions and best-effort delivery. Per-request _meta adds bytes. And everyone's muscle memory from four spec versions needs retraining. Speaking as someone whose muscle memory includes shipping the old handshake in three languages: the retraining is real, and worth it.
07The governance footnote that isn't a footnote
There's a detail easy to skim past. This is the first full revision published since MCP stopped being a single vendor's protocol.
Anthropic open-sourced MCP in November 2024. On December 9, 2025, the Linux Foundation announced the Agentic AI Foundation, with MCP contributed alongside Block's goose and OpenAI's AGENTS.md, putting the protocol under vendor-neutral stewardship. The 2025-11-25 revision landed two weeks before that. 2026-07-28 is the first one shipped from the other side of the line.
I don't think that's a coincidence, and not because a foundation writes better specs. It's that the constituency changed. A protocol maintained by the company that ships the flagship client optimizes for what that client can do. A protocol maintained across a foundation with more than ten thousand published servers in the wild optimizes for what everyone else has to operate. Deleting sessions is a change that costs the client implementers something and pays the platform teams back. That trade is much easier to make when the platform teams are in the room.
08If you run MCP today: a short field guide
Audit which of your servers assume session identity, and start treating any cross-request state as an explicit, integrity-protected handle. If you built on sampling, begin the provider-API migration now — twelve months evaporates. Put server/discover on your roadmap; it's a server MUST. Decide your era story per service: dual-era for anything with existing clients, modern-only for anything new. And if you're a gateway or platform team, the new headers are your early win — method-level routing and rate-limiting with zero body parsing is the feature you've been simulating with regex.
The strategic read
MCP started as a protocol for connecting a desktop app to local tools, and its architecture said so. This revision is the protocol admitting what it became — enterprise infrastructure serving many clients from many replicas behind real load balancers. Stateless, cacheable, observable, boring to operate.
In protocol design, boring to operate is the highest compliment there is.
I built two runnable demos to go with this. The first is real MCP — the actual mcp Python SDK (v2.0.0, protocol 2026-07-28): two real server replicas on their own ports, a real client over streamable HTTP, and an elicitation that starts on Replica A and completes on Replica B, because the replicas share only a RequestStateSecurity key — with the SDK's real AES-GCM rejecting a tampered blob and a wrong-key replica. The second is a zero-dependency conceptual version that also shows the legacy 404-on-failover the modern SDK can no longer even express. Clone and run: github.com/Suryals/mcp-stateless-demo.
Primary sources: the 2026-07-28 specification, its changelog, the architecture overview, and the Linux Foundation's AAIF formation announcement.