MCP’s 2026 Stateless Redesign: What Changed and Why It Matters
The redesign did not delete state. It relocated it — and everything downstream follows from where it landed.

Key topics
The redesign did not delete state. It relocated it — and everything downstream follows from where it landed.
If you have shipped a remote MCP server over Streamable HTTP, you know the recipe. Run the server in stateless HTTP mode. Scale out to N instances. Disable client affinity so no ARR cookie pins a client to one box. Then, because the protocol still carried a session, stand up a shared session store so every instance could see the same session data. If you wanted to route or throttle at the gateway, you inspected request bodies, because the operation name lived inside the JSON-RPC payload rather than in a header.
That recipe is now obsolete at the protocol layer. The 2026-07-28 release candidate is the largest revision of the Model Context Protocol since launch, and the headline change is that MCP is stateless at the protocol layer. The initialize/initialized handshake is retired. The Mcp-Session-Id header is gone. Any request can land on any instance.
The trap is reading that as "state disappeared." It did not. Three different kinds of state were previously conflated inside the protocol session — connection identity, negotiated capabilities, and application state — and the redesign splits them apart. Connection identity and capability negotiation become per-request data. Application state becomes either an explicit handle the model threads through tool arguments or storage the server owns and addresses itself. Once you see that split, scaling, recovery, and capability ownership all become predictable consequences rather than surprises.
What the Stateless Redesign Actually Removed
Start with the concrete deltas, because the rest of the analysis needs a factual floor. Six Specification Enhancement Proposals work together to get to a stateless core.
The handshake is gone (SEP-2575). Protocol version, client info, and client capabilities that used to be exchanged once at connect time now ride along in _meta on every request (io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientCapabilities). Clients should identify themselves on each request via io.modelcontextprotocol/clientInfo; servers should identify themselves in each result's _meta via io.modelcontextprotocol/serverInfo. A new server/discover method lets a client ask for server capabilities when it actually wants them. Version mismatches return UnsupportedProtocolVersionError.
The session is gone (SEP-2567). The Mcp-Session-Id header and the protocol-level session are removed from Streamable HTTP. List endpoints (tools/list, resources/list, prompts/list) no longer vary per connection. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments.
Supporting changes make statelessness operable. Required Mcp-Method and Mcp-Name headers on Streamable HTTP POST requests let load balancers, gateways, and rate limiters route or throttle on the operation without cracking open the body; servers reject requests where headers and body disagree (SEP-2243). List and read results now carry ttlMs and cacheScope, modeled on HTTP Cache-Control, so clients know how long a tool list is fresh and whether it is safe to share across users (SEP-2549). Servers should return tools/list in deterministic order to improve LLM prompt-cache hit rates. OpenTelemetry trace-context propagation (traceparent, tracestate, baggage) is now pinned down in _meta (SEP-414).
Some surface area is removed outright. ping, logging/setLevel, and notifications/roots/list_changed are gone. Log level is now set per-request via io.modelcontextprotocol/logLevel in _meta, and servers must not emit notifications/message for requests that did not include that field. The notifications/elicitation/complete notification and the elicitationId field of URL-mode elicitation requests are also removed.
This is a release candidate with breaking changes, not a drop-in patch. Clients and servers must move together. Any code reading
Mcp-Session-Idor relying on the handshake needs to change.
Knowledge check
Check your understanding
Answer this question before you continue.
The Invariant: State Moved, It Did Not Vanish
Here is the mental model that makes every later tradeoff predictable. The old protocol session was a bundle. It carried three things that had nothing to do with each other:
| State kind | What it answered | Where it lives now |
|---|---|---|
| Connection identity | Which client is this, and what version does it speak? | Per-request _meta |
| Negotiated capabilities | What can this client and server do together? | Per-request _meta plus server/discover |
| Application state | What is this workflow in the middle of? | Server-minted handles in tool arguments, or server-owned storage |
Connection identity and capability negotiation become per-request data — cheap to carry, expensive to forget. Application state becomes either an explicit handle threaded through tool arguments (a basket_id, a browser_id) or storage the server owns and addresses itself.
The consequence is blunt: "stateless protocol" is not "stateless application." A server that needs cross-call continuity still needs a store. It just no longer needs the protocol to hide that store behind transport metadata.
The design bet worth naming is that state visible to the model can be composed, reasoned about, and handed between tools in ways transport metadata never allowed. The model can call create_basket, receive a basket_id, and pass that same identifier back as an argument to add_item. That is not a workaround for missing session state. In practice it is often a more powerful substitute, because the handle is now part of the reasoning surface rather than an invisible connection detail.
Knowledge check
Check your understanding
Answer this question before you continue.
Scalability: What Round-Robin Actually Buys You
Convert the headline into an infrastructure delta you can verify in your own deployment.
Before: any instance serving any request required sticky routing plus a shared session store. Gateways often needed to inspect request bodies to route or throttle, because the operation name was buried in the payload.
After: a plain round-robin load balancer works. Routing decisions can key off Mcp-Method and Mcp-Name headers. Scale-to-zero and serverless runtimes become viable for remote servers, because there is no session to lose when an instance disappears.
Cacheable list results change the client-side cost model too. Clients learn freshness from ttlMs and sharing safety from cacheScope instead of holding an SSE stream open just to detect that the tool list changed. That is a real reduction in idle connections and a real reduction in the "is this list stale?" problem.
What does not get cheaper: downstream state stores, authorization checks, and any per-user data the server must still read on each call. If your tool queries a database per invocation, statelessness does nothing for that query. It only removes the protocol-level session as a scaling constraint.
Decision boundary: statelessness pays off most for horizontally scaled remote servers with many short calls. It buys little for a single-instance local server where the session was never the bottleneck.
Knowledge check
Check your understanding
Answer this question before you continue.
Context and Capability Ownership After the Handshake
Removing the handshake redistributes who knows what, and that redistribution creates new obligations.
Capability negotiation moves from a one-time exchange to a per-request assertion. Clients declare capabilities in _meta. Servers declare identity in each result's _meta. Mismatches return UnsupportedProtocolVersionError. The practical consequence is that the server can no longer assume a stable client profile across calls. Capability drift between calls is now a real condition to handle, not a theoretical one.
The extensions field on ClientCapabilities and ServerCapabilities makes optional behavior explicit rather than implied by version. That shifts ownership of feature detection onto both sides: you cannot infer support from a version number alone, so you check.
Server-to-client requests are restructured in two ways. First, server-initiated requests may only be issued while the server is actively processing a client request (SEP-2260). Earlier spec versions recommended this; it is now required. A user is never prompted out of nowhere. Second, elicitation uses Multi Round-Trip Requests returning InputRequiredResult instead of holding an SSE stream open (SEP-2322). The client learns the outcome of an out-of-band interaction by retrying the original request.
That last change has a sharp edge. Correlation that the protocol used to provide — matching an out-of-band interaction to its originating call — is now the server's job. Servers needing to correlate an elicitation across retries encode their own identifier in requestState. If you skip that, you will eventually match the wrong retry to the wrong prompt.
Tasks moved out of core into a versioned extension (io.modelcontextprotocol/tasks) with polling via tasks/get and tasks/update (SEP-2663). The redesigned extension replaces the blocking tasks/result method, removes tasks/list, and allows servers to return task handles unsolicited without per-request opt-in. Long-running-work ownership is now an opt-in surface rather than a core protocol guarantee.
Knowledge check
Check your understanding
Answer this question before you continue.
Recovery and Failure Paths in a Sessionless World
Statelessness introduces or exposes several failure modes. Design for them before production traffic finds them.
Retry semantics change. With no session to resume, a retried request is a fresh request. Idempotency must come from the tool contract or from handles the server can recognize, not from transport continuity. If your tool is not idempotent and the client retries after a timeout, you will double-execute.
Handle lifecycle becomes an application concern. Expiry, revocation, cross-user leakage, and what happens when the model drops or mangles a handle mid-workflow are all yours now. The protocol will not catch a stale basket_id. Your server must.
Multi Round-Trip Requests mean a client may need to retry the original request to learn an out-of-band outcome. Servers must make that retry safe and must encode their own correlation identifier in requestState.
Auth is not solved by statelessness. Reported connector behavior shows token refresh and session churn on every tool call, plus repeated re-auth loops against stateless Streamable HTTP transports. In one documented pattern, a connector unconditionally refreshes the OAuth token and creates a new MCP session before every tool call, even when the access token is still valid. Server-side logs showed valid tokens and successful RPC calls while the client still triggered fresh auth popups. Treat this as a class of risk to test rather than a universal guarantee — connector-side behavior varies by client and version.
Observability is the recovery tool. Pinned trace-context propagation in _meta is what lets a single tool call appear as one trace across client, server, and downstream systems in any OpenTelemetry-compatible backend. Without it, a sessionless architecture turns every failure into archaeology.
When Stateless MCP Is the Wrong Choice
Do not adopt the redesign as a default. The migration cost is real, and it buys nothing in some deployments.
If your server is single-instance, local, or stdio-based, the protocol session was never your scaling constraint. The migration cost buys you little.
If your workflow depends on long-lived server-initiated interaction, the restructured elicitation and Tasks-extension paths add client-side work you must implement deliberately. That is not free.
If your state is genuinely per-connection and short-lived, an explicit handle may be more machinery than the problem deserves.
Migration reality: this is a breaking release. Clients and servers must move together, and any code reading Mcp-Session-Id or relying on the handshake needs to change.
The rule I would use: adopt stateless MCP when horizontal scaling or serverless deployment is the actual bottleneck. Otherwise migrate on your own schedule and spend the effort on handle design and observability first.
The Relocated-State Model as a Decision Rule
The reusable rule is this: when a protocol removes state, ask where that state went. If it moved into per-request metadata, you gained routing flexibility and lost the ability to assume a stable client profile. If it moved into explicit handles, you gained composability and lost the protocol's help with lifecycle. If it moved into server-owned storage, you gained control and lost the excuse that the protocol was managing it for you.
Here is the next action I would take. Pick one existing tool that currently relies on session state. Replace that reliance with an explicit server-minted handle threaded through the tool arguments. Then run the same workflow twice against two different server instances and confirm the handle — not the connection — is carrying continuity. Watch three things while you do it: whether the handle expires mid-workflow, whether a retry double-executes, and whether the client re-authenticates on every call. Those three signals will tell you more about your readiness for stateless MCP than any spec reading will.
Knowledge check
Final check
Finish the article by checking the ideas you just learned.
References
Research updated Sep 11, 2026


