MCPbundler
< Todas las publicaciones
Esta publicación no está disponible en inglés. Se muestra la versión original.
Engineering

Is MCP Stateless Now? What the 2026-07-28 Spec Actually Changed

MCP Team-9 de agosto de 2026-6 min de lectura
mcpprotocolspec

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 returns UnsupportedProtocolVersionError instead of failing at connection time.
  • server/discover is 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, and elicitation/create - anything where the server used to reach back into an open connection - are replaced by Multi Round-Trip Requests (MRTR): the server returns an InputRequiredResult, 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-ID and 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, and resources/read carry ttlMs and cacheScope, so clients (and shared intermediaries, if cacheScope allows 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.

En esta página

What changed, concretely"Stateless" undersells what actually happenedThe infra win is real, and it's not subtleThe part getting less airtime than it shouldWhat to actually do about itWhere this is heading

Publicaciones relacionadas

  • What Actually Happens When One MCP Server Has 200 Tools
  • One MCP Call, 24K Characters: Why Tool Results Keep Blowing Up Your Context Window
  • A bundle is just a URL now