Is MCP Stateless Now? What the 2026-07-28 Spec Actually Changed
Short answer: yes. As of the 2026-07-28 specification,
MCP no longer has protocol-level sessions. The initialize/notifications/initialized
handshake is gone, the Mcp-Session-Id header is gone, and any request can now land on
any server instance with no shared history required. If you've seen "MCP just went
stateless" in your feed this week - from Jeff Auriemma's write-up
to Microsoft's take on what it means for App Service -
that part is real. What's getting flattened in the retelling is why it matters and
what it costs you, so here's the actual spec, not the headline version of it.
What changed, concretely
Pulling directly from the official changelog:
- No more handshake. Every request now carries its protocol version and client
capabilities inline, in
_meta(io.modelcontextprotocol/protocolVersion,io.modelcontextprotocol/clientCapabilities). A version mismatch returnsUnsupportedProtocolVersionErrorinstead of failing at connection time. server/discoveris new and mandatory. Servers must implement it so clients can probe supported versions and capabilities up front, including over STDIO.- Server-initiated requests are gone.
roots/list,sampling/createMessage, andelicitation/create- anything where the server used to reach back into an open connection - are replaced by Multi Round-Trip Requests (MRTR): the server returns anInputRequiredResult, and the client retries the same request with the answer attached. No connection has to stay open for that round trip to complete. - Stream resumability is gone too.
Last-Event-IDand SSE event redelivery are removed from Streamable HTTP. If a response stream breaks mid-request, the client re-issues it as a new request with a new ID. No resuming a half-finished stream. - List responses are cacheable now.
tools/list,resources/list,prompts/list, andresources/readcarryttlMsandcacheScope, so clients (and shared intermediaries, ifcacheScopeallows it) can stop re-fetching on every call. - Roots, Sampling, and Logging are deprecated, along with the old HTTP+SSE transport
- all on a mandatory twelve-month deprecation clock, not removed outright.
That's a lot more than "sessions are gone." It's the protocol shedding almost every piece of bidirectional, connection-pinned plumbing it had, not just the one everyone's quoting.
"Stateless" undersells what actually happened
Here's the framing that's more useful than the headline: the state didn't disappear,
it moved. Auriemma's piece puts it
well - a server that needs to remember something across calls now mints an explicit
handle (a browser_id, a basket_id) and hands it back to the model as an ordinary
tool argument. The protocol used to carry that continuity invisibly, in a session the
model never saw. Now it's a value sitting in the model's context window, indistinguishable
from any other argument.
That's a real architectural shift, not a simplification. Continuity that used to live in transport-layer plumbing the model couldn't touch now lives in tokens the model reads, reasons about, and could - in principle - be prompted into misusing. GitHub pulled the Redis session store out of its MCP server entirely once this landed, which is a genuinely good outcome. It also means "the state" is no longer a database row a security review can reason about in isolation - it's a string the model can see, and whatever the model can see, it can be manipulated into misusing.
The infra win is real, and it's not subtle
The scaling story checks out. Google's writeup on their migration is blunt about what sessions cost them: they were "pinned to the specific container or pod," which meant horizontal scaling required sticky routing or a shared session store - infrastructure every deployment paid for, whether or not it actually needed cross-call state. With the handshake gone, "any container instance can handle any incoming request," so a plain round-robin load balancer works, pod restarts and autoscaling events become invisible to the client, and servers can scale to zero on Cloud Run without anyone noticing a reconnect. If you're running MCP servers on Kubernetes, App Service, or Cloud Run, this is the part of the spec that changes your bill, not just your code.
The part getting less airtime than it should
Buried under the scaling headlines is a security read worth taking seriously: with MRTR,
the requestState a server hands back on an input_required result is, by design,
attacker-controlled input that round-trips through model reasoning. Equixly's
analysis is direct about it - that
payload needs integrity protection, principal binding, and an expiration window, or a
retried request can be replayed or tampered with in ways a stateful, server-held session
never exposed. Combine that with a protocol that defines no idempotency keys - a broken
stream just means "re-issue as a new request" - and any tool doing something
non-idempotent (a payment, a deployment, a delete) needs to have solved that problem
itself, because the protocol explicitly won't solve it for you anymore.
None of this is a flaw in the spec. It's the tradeoff of moving trust decisions out of transport plumbing and into application code - which is exactly what makes the scaling story work. But "your MCP server is now stateless" and "your MCP server's state is now attacker-visible" are the same sentence, and only one of them made the headlines.
What to actually do about it
Deprecated features (Roots, Sampling, Logging, HTTP+SSE) stay functional for a mandatory
twelve-month window, so nothing breaks today. All four Tier-1 SDKs - TypeScript, Python,
Go, C# - support 2026-07-28 now; Rust is in beta. If your server currently depends on
Mcp-Session-Id for anything beyond convenience, that's the one thing to find and fix
first - everything else on the list has a grace period, that one doesn't once you adopt
the new protocol version.
Where this is heading
Line up the last few MCP revisions and the pattern is consistent: sessions, sampling,
roots, logging, SSE resumability - every release trims another piece of protocol-level
"smart" plumbing in favor of explicit, application-owned state and dumb, cacheable,
horizontally-scalable HTTP underneath it. MCP isn't becoming a simpler protocol. It's
becoming a thinner one, with the complexity it used to hide now sitting in plain sight -
in your tool schemas, in your _meta fields, in the strings the model itself can read.
That's a better trade for anyone running these things at scale. It's a worse one for
anyone who was quietly relying on the protocol to keep that complexity out of the
model's hands.
Further reading: the official 2026-07-28 changelog, Jeff Auriemma's take, and Microsoft's look at what it means for scaling on App Service.
How we keep two languages honest without a cron job
Tracking translation staleness with a content hash instead of a timestamp, and why the pipeline still refuses to publish its own translations.
One MCP Call, 24K Characters: Why Tool Results Keep Blowing Up Your Context Window
A single MCP tool call putting 24,568 characters into someone's context isn't a fluke - it's the default outcome of how most MCP servers respond. Here's why, and what actually fixes it.