SYSTEM NOTE
MCP went stateless: what the 2026-07-28 spec changes if you run agents
The Model Context Protocol dropped sessions at the protocol layer. No initialize handshake, no Mcp-Session-Id header, every request self-describing. Here is what actually breaks, what gets easier, and what I would change first.
The Model Context Protocol shipped a new specification on 28 July 2026, and the headline is not a new feature. It is a removal. MCP is now stateless at the protocol layer.
If you have been treating MCP as a detail of your agent stack, this is the release that makes it an architecture decision. The change is small to describe and large in what it implies, so it is worth being precise about what actually moved.
What was removed
Two things, both load-bearing in the old design.
The initialize / initialized exchange is gone. There is no longer an opening
handshake in which a client and server agree on a protocol version and exchange
capabilities before real work starts.
The Mcp-Session-Id header is gone with it. There is no transport-level session
that subsequent requests belong to.
What replaces both is per-request self-description. In the spec's own words:
"Each request now travels on its own, carrying its protocol version, client
identity, and client capabilities in _meta." Every call is complete on its
own terms. Nothing earlier in the conversation is required to interpret it.
Why sessions were the problem
The reason given is reliability and scalability, and anyone who has run a remote MCP server in production will recognise the shape of it.
A session identifier is an affinity requirement wearing a disguise. The moment request two must reach the same process that handled request one, you have committed to one of two things: sticky sessions at the load balancer, or a shared session store that every instance can read. Both are operationally annoying in a way that compounds.
Sticky routing means your gateway has to look inside the request to find the session header, which means it is parsing application-layer data to make a routing decision. Your deploys get harder, because draining an instance now means draining conversations rather than connections. Autoscaling gets worse, because a new instance cannot pick up existing work. And a single instance restart takes real sessions with it.
The shared-store alternative trades that for a hot dependency on the path of every single call, which is its own kind of fragile.
Statelessness deletes the question. Any request can go to any instance. A plain round-robin load balancer is now sufficient. That is the whole payoff, and for anyone running MCP servers at more than one replica it is a significant one.
What this means if your server held state
Some servers genuinely need to carry something across calls. A long-running export, a cursor into a large result set, an authenticated connection to a downstream system. Those did not stop existing because the session did.
The spec's answer is to make the state explicit: "If your server needs to carry state across calls, mint an explicit handle from a tool and have the model pass it back as an argument."
That is a bigger change than it looks, and I think it is the right one.
Under the old design, state lived in the transport, where the model could not see it and could not reason about it. A tool call either landed in the right session or it did not, and when it did not the failure was invisible to the thing driving the conversation. Under the new design, the handle is a value the model received and must pass back. It is in the context. It is inspectable. It shows up in your traces as an argument rather than as a header your tracing probably was not recording.
It also forces a question that session-based designs let you avoid: what is the lifetime of this handle, and what happens when it expires? You now have to answer that in the tool contract, in language the model can act on, rather than letting a session timeout produce an error nobody planned for.
This is the same argument I made about agents inheriting your permissions model in production agents: the parts of the system a model cannot see are the parts that fail in ways nobody can debug.
The other changes worth knowing
Statelessness got the headline, but several other changes matter if you are building seriously.
Multi Round-Trip Requests
MRTR allows a mid-call user confirmation without holding a stream open. This is the "are you sure you want me to send this invoice" problem, and previously it pushed you toward long-lived connections precisely when you were least able to afford them. Confirmation flows are exactly where a request is likely to sit idle for a human-scale amount of time.
Header-based routing
Mcp-Method and Mcp-Name headers let infrastructure route on what a request
is doing without parsing the body. If you run a gateway in front of your MCP
servers, this is what lets it stop doing deep packet inspection to make basic
decisions. It also makes per-tool rate limiting and per-tool authorisation
enforceable at the edge rather than inside application code.
Cacheable list responses
ttlMs and cacheScope on list responses. Tool and resource listings are
requested constantly and change rarely, so this removes a meaningful amount of
pointless traffic.
A twelve-month deprecation window
The spec adopts a twelve-month deprecation policy, and tasks move to a formal extension with poll-based operations. The deprecation window is the part I care about most as someone shipping into other people's systems: it turns "the protocol might move under me" into a scheduling problem with a known horizon.
RFC 9207 issuer validation
Authorization servers must now do issuer validation per RFC 9207. If you are wiring MCP into an enterprise identity provider, check this one specifically rather than assuming your existing OAuth setup satisfies it.
Who has adopted it
The spec names AWS, Cloudflare, Figma, Google Cloud, Honeycomb, Manufact, Microsoft, Netlify, Replit, Supabase and Xero among others. That breadth matters more than any single name on the list. A protocol change is only cheap when the ecosystem moves together, and a stateless design that half the servers implement is worse than either option on its own.
What I would do this week
If you maintain an MCP server:
- Grep for the session header.
Mcp-Session-Idand anything reading it. That search result is your actual migration scope, and for many servers it is empty. - Find every piece of state that outlives a call. For each one, decide whether it becomes an explicit handle or whether it should never have been stateful. In my experience a surprising share is the second category: state that existed because sessions made it easy, not because the problem required it.
- Write the expiry behaviour into the tool description. If a handle can go stale, the model needs to know what to do about it in words, not discover it through an error.
- Delete the sticky-session configuration. This is the reward. Check that your load balancer, ingress and any service mesh in front of it stop pinning requests, then confirm your server survives an instance restart mid-task.
If you only consume MCP servers, the SDK update is most of your work, and the practical benefit arrives without you doing anything: the servers you depend on get easier for their maintainers to keep up.
The part that generalises
Strip out the protocol specifics and there is a design position underneath that applies well beyond MCP.
Hidden state is cheap to write and expensive to operate. A session identifier is convenient exactly once, at the moment you are implementing the second request, and it charges you rent for the rest of the system's life in the form of routing constraints, deploy complexity and failures that do not appear in the logs of the component that caused them.
Making state explicit costs more up front. You have to name the thing, decide its lifetime, and write down what happens when it ends. That is not overhead. It is the design work that the session was letting you postpone.
I have made the same trade the wrong way often enough to recognise it. This is the protocol making it for you.