The Stateful MCP Mistake Nobody Admitted to Making
When Anthropic launched the Model Context Protocol in November 2024, the design made a choice that seemed reasonable at the time: sessions. Every MCP interaction started with an initialization handshake, minted a session identifier, and then required that session ID to be passed on every subsequent request. It was the obvious choice if you were thinking in terms of traditional API design—stateful sessions are how you track context, right?
Wrong. Or at least, wrong for a protocol whose primary job is to expose discrete tools to LLM agents.
The MCP 2026-07-28 specification—what people are calling stateless MCP or MCP 2.0—dropped on July 28, 2026, and it is the most significant protocol change since the original launch. It eliminates the session requirement entirely. One HTTP request, one tool call, done. No session initialization, no session ID, no server-side state to maintain across calls.
I have been building on top of MCP since early 2025, and I have watched teams hit the same infrastructure wall over and over: they build a clever MCP server, then discover that scaling it requires sticky sessions or a shared session store, which means either a stateful load balancer, Redis, or both. The stateless spec does not just clean up the protocol; it fundamentally changes what kind of infrastructure you need to run MCP in production. That is worth taking seriously.
What Actually Changed in the Wire Format
The difference is concrete and visible in the HTTP requests themselves. Legacy MCP required two sequential round-trips before you could call a single tool. First, an initialization request:
POST /mcp HTTP/1.1
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {
"name": "my-app",
"version": "1.0"
}
}
}
Then a second request using the returned session ID:
POST /mcp HTTP/1.1
Mcp-Session-Id: 1868a90c-3a3f-4f5b
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search",
"arguments": { "q": "otters" }
}
}
Stateless MCP collapses this to a single request:
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": { "q": "otters" },
"_meta": {
"io.modelcontextprotocol/clientInfo": {
"name": "my-app",
"version": "1.0"
}
}
}
}
The client info moves from the initialization handshake into the request metadata. The session concept disappears. The protocol version moves to a header instead of a body field. It looks almost trivially simple, and that simplicity is the point.
Why Sessions Were the Wrong Mental Model
The session-based design borrowed intuitions from a different class of protocol. Database connections are stateful because they maintain transaction isolation, cursor state, and prepared statement caches that are genuinely expensive to recreate. WebSockets are stateful because they model a persistent channel where either side can push messages. SSH is stateful because the security context depends on a negotiated cipher and authentication state.
MCP tools are none of these things. A well-designed MCP tool is a pure function: given inputs, produce outputs. The search tool does not care whether this is the same “session” as the last call. The read_file tool does not need to remember what files you looked at before. Even tools that do have side effects—writing to a database, sending a notification—do not need a session to do their work correctly.
The session requirement in legacy MCP added real complexity for no architectural benefit in the common case. It forced every MCP server to answer a question—”how do I store and look up session state?”—that most tools did not actually need to answer. And it forced every infrastructure team deploying MCP servers to answer a much harder question: “how do I route requests from the same session to the same backend instance, or synchronize that session state across instances?”
I have personally debugged three separate incidents in 2025 where MCP tool calls failed intermittently in production because a load balancer without sticky session support was distributing requests across nodes, only one of which had the initialized session. The session model was, in practice, a footgun wrapped in a reasonable-sounding abstraction.
The analogy that helped me explain this to an infrastructure team that was skeptical the session removal was actually beneficial: think about REST APIs from the late 2000s that required a login call before each API session. Some APIs designed this way because their creators were thinking in terms of web browser sessions, where login made architectural sense. But those APIs were miserable to use from code. Every client had to manage token acquisition, token storage, token expiry, and the retry logic when a token expired mid-workflow. REST APIs that moved to long-lived API keys with per-request authentication were uniformly easier to build against, even though the per-request authentication added a marginal cryptographic overhead. Stateless authentication won because the reduction in client complexity was worth far more than the per-request overhead. Stateless MCP is the same argument made again, in a new context.
The Infrastructure Implications Are Not Subtle
Here is a comparison of what a production MCP deployment looked like under each specification:
| Concern | Legacy (Stateful) MCP | Stateless MCP 2026-07-28 |
|---|---|---|
| Load balancer requirement | Sticky sessions or shared session store | Standard round-robin, any backend |
| Horizontal scaling | Complex: session affinity or Redis | Trivial: add instances, done |
| Serverless / edge deployment | Requires workarounds or external state | Works natively, each invocation is independent |
| Session leak / cleanup | Requires TTL logic, GC, monitoring | Not applicable |
| Cold start penalty per tool call | One extra round-trip (init handshake) | None |
| Debugging failed calls | Two log entries to correlate | Single self-contained log entry |
| Client implementation complexity | Session lifecycle management required | HTTP POST and parse response |
The serverless deployment case deserves special emphasis. Lambda functions, Cloudflare Workers, and Fly.io Machines are all genuinely excellent runtime environments for MCP tools—they scale to zero, scale out automatically, and have global edge presence. Legacy MCP was architecturally hostile to all of them because any invocation might land on a different instance than the initialization request. Stateless MCP makes serverless a first-class deployment target, and I expect to see a wave of MCP servers built on edge runtimes over the next six months.
What “Stateless” Does Not Mean
There is a predictable confusion worth addressing: stateless MCP does not mean your MCP tools cannot have state. Your tools can absolutely read from and write to databases, maintain conversation history, access user preferences, or do anything else that involves persistent data. “Stateless” refers to the protocol transport layer, not to the application semantics of your tools.
The distinction matters because the common objection I have already seen is “but my tool needs to maintain context across calls.” Yes, it might. That context lives in your database, your vector store, your Redis cluster—wherever it belongs architecturally. What stateless MCP removes is the requirement to maintain an additional layer of protocol-level session state that your infrastructure has to route around. Your application state is still your problem. The protocol’s state is no longer your problem.
This is the same design principle that made HTTP itself successful. HTTP is stateless; cookies and server-side sessions are application-layer additions that exist when applications need them, not protocol requirements that every HTTP interaction must satisfy. MCP has made the same move, belatedly.
The Security Story Gets Simpler Too
Simon Willison noted in his July 31st post that one of his renewed interests in MCP comes from a security angle: MCP tools are easier to audit and control than giving an agent arbitrary shell access and curl. That framing is correct, and the stateless spec improves the security posture further in a less obvious way.
Session tokens are credentials. Legacy MCP session IDs were, in practice, bearer tokens: anyone who obtained your Mcp-Session-Id header could call tools as you, for the duration of that session. They were typically short-lived and transmitted over TLS, but they were additional secrets to protect. Stateless MCP eliminates this attack surface. There is no session token to steal, replay, or forget to rotate.
The authorization model shifts cleanly to request-level authentication—the same OAuth and API key patterns used for every other modern API. This is not a revolutionary security improvement, but it is a meaningful simplification. Security models that are simpler are models that are easier to audit, and auditing is where most real security value comes from.
Prompt injection remains a genuine risk for any MCP deployment regardless of protocol version—the threat model Willison outlined in his 2025 post on MCP security problems is unchanged by the wire format. But the session-level attack surface is gone, and that is worth noting.
The Skills Detour and Why MCP Is Back
Willison’s post references an interesting 2025 dynamic: MCP fell out of fashion for a period when general-purpose agent harnesses with terminal and curl access became more common. The reasoning was that a sufficiently capable model with a shell could do everything an MCP tool could do, plus arbitrary things MCP could not do, so why bother constraining yourself to the protocol?
The answer, which is now becoming clearer, has two parts. First, the “give the agent a shell” approach does not degrade gracefully to smaller models. A 70B model with a shell can accomplish a lot. A 7B model with a shell mostly creates log entries and occasionally deletes things it should not. MCP tools, because they are typed function calls with defined schemas, are far more accessible to smaller models that cannot reliably construct complex shell pipelines from natural language.
Second, the blast radius of a misbehaving agent is dramatically different between shell access and MCP access. An agent with a shell and network access can exfiltrate data, make outbound HTTP requests to arbitrary destinations, modify files outside its intended scope, install software, and do a hundred other things that would be difficult or impossible to prevent after the fact. An MCP server exposes exactly the tools you define and nothing else. The attack surface is bounded by construction.
I expect the industry to land on a model where coding agents and research agents get shell access—because that is genuinely what they need to do their jobs—while specialized integrations to databases, APIs, communication tools, and internal systems go through MCP. This is already roughly the pattern in Claude’s own tool architecture, and it makes sense for the same reasons the microservices argument for bounded scope made sense before people overfit it to everything.
The practical divide is organizational as much as technical. Giving a product team’s internal automation agent shell access on a production server requires a security review that most companies are not willing to fast-track. Giving that same agent access to a specific MCP tool that queries read-only analytics data is a much smaller security footprint that can be approved more quickly and audited more easily after the fact. The constraint is a feature, and stateless MCP makes that constraint easier to implement correctly.
Practical Migration: What You Actually Need to Do
If you are running an MCP server today under the legacy protocol, here is what the migration path looks like in practice.
The server side is the larger change. You need to stop maintaining session state and stop requiring the initialization handshake. Most of the session state that legacy MCP servers maintained was actually redundant with the client info that now travels in the _meta field. The state that was not redundant—accumulated context, temporary credentials, rate limit counters—needs to be either eliminated, moved to proper application-layer storage, or passed explicitly in each tool call’s arguments.
The client side is simpler. You drop the initialization request, move client info to the _meta field in each tool call, and add the MCP-Protocol-Version: 2026-07-28 header. Most of the client-side session management code becomes dead code that can be deleted.
The infrastructure side is often where the actual work is. If your legacy MCP servers are deployed with sticky sessions or a Redis-backed session store, you can simplify that significantly. Sticky sessions on your load balancer can be disabled. The Redis session store can be decommissioned if it was being used only for MCP—though if it was shared with other applications, obviously leave it in place.
There is one significant caveat: if your MCP tools genuinely need multi-turn conversational context that cannot be reconstructed from each request’s inputs, stateless MCP requires you to pass that context explicitly on each call or store it in application state that the tool can look up. This is not a regression from legacy MCP—it is just making visible something that was previously papered over by the session abstraction. The right answer is almost always explicit context passing or application-layer state, not protocol-layer sessions.
One pattern I have found useful during migrations: before removing session state from your MCP server, enumerate every piece of data stored in session objects and classify each as either “this was always application data” or “this was genuinely protocol-level state.” In my experience, the first category accounts for over ninety percent of what session objects contain in most real MCP servers. Once you have made that classification explicit, the migration path for each piece of data is usually obvious—move it to a database, pass it in request metadata, or discover that you were storing it unnecessarily and simply delete it.
The Serverless MCP Server You Can Build in 50 Lines
One of the things that excites me most about stateless MCP is that the implementation floor has dropped dramatically. Simon Willison noted he built three MCP-related projects in a single week under the new spec. Under legacy MCP, at least one of those projects would have been a session management library to abstract over the complexity.
A minimal stateless MCP server is genuinely just an HTTP endpoint that inspects the Mcp-Method header, dispatches to the appropriate tool handler, and returns a JSON response. There is no session lifecycle to manage, no state machine to implement, no session store to integrate. This is close to what web framework routing has always been.
This matters for the ecosystem beyond just developer ergonomics. Simpler servers mean more servers. Lower implementation cost means more tools built and maintained. The proliferation of MCP servers for every conceivable API and internal service becomes much more plausible when the implementation overhead is comparable to writing a REST endpoint rather than implementing a stateful protocol.
For teams already running internal tooling on FastAPI, Express, or Go’s net/http, building stateless MCP endpoints on top of existing infrastructure is now a realistic afternoon project. That was not true for the stateful spec, which required enough ceremony that most teams either reached for a framework or gave up and used the shell-plus-curl approach instead.
The concrete implication for platform teams: if you have been putting off building internal MCP tooling because the operational overhead seemed disproportionate to the benefit, the stateless spec changes that calculation. An MCP endpoint for your internal documentation search, your code review API, or your incident ticketing system is now closer in complexity to a webhook handler than to a session-management service. I have started recommending to teams I work with that they treat any internal API that they want to expose to agent workflows as a candidate for a thin stateless MCP wrapper—it costs almost nothing to add and makes the API dramatically more composable for LLM-driven workflows.
Watching the Ecosystem React
Several things will determine whether the stateless MCP specification achieves its potential or becomes another technically correct protocol that nobody actually adopts at scale.
First: toolchain support speed. The specification dropped July 28th. The major agent frameworks—LangChain, LlamaIndex, AutoGPT, and Anthropic’s own tooling—need to ship updated clients that speak the new protocol. Until they do, server authors face the choice of supporting both protocol versions or requiring clients to upgrade. The MCP specification does not provide explicit backward compatibility guidance here, and that ambiguity creates friction.
Second: the serverless runtime ecosystem. Cloudflare, AWS Lambda, and similar platforms would be wise to build first-party MCP server primitives on top of the stateless spec. The technical fit is excellent; the question is execution speed and quality. A well-designed first-party Lambda MCP runtime would significantly accelerate enterprise adoption because it removes the “but who maintains the MCP server infrastructure” objection that I have watched kill internal adoption at multiple companies.
Third: tool discovery and composition. The stateless spec makes individual tools cheaper to build. The harder unsolved problem is discovering and composing tools across servers without a central registry. The current answer is largely “configure tool URLs manually,” which does not scale to an ecosystem with thousands of MCP servers. Some form of MCP registry or discovery protocol—either standardized or through de facto dominant players—will be necessary for the ecosystem to mature beyond “developers configuring their own local tool sets.”
The Backward Compatibility Problem Nobody Has Solved
There is an uncomfortable question the stateless MCP specification does not fully answer: what happens to the existing ecosystem of legacy MCP servers during the transition period? The spec change is not backward compatible at the protocol level. A client speaking stateless MCP 2026-07-28 and a server expecting the legacy initialization handshake will fail immediately, before any tool call is even attempted.
The responsible approach for any MCP server maintainer right now is to support both protocol versions simultaneously for at least six to twelve months. This means detecting the protocol version from either the MCP-Protocol-Version header or the structure of the incoming request, and dispatching to the appropriate handler. It is not architecturally complex—it is closer to API versioning than to a real migration—but it is work that every server maintainer has to do separately.
The MCP working group has not published official migration guidance or a timeline for deprecating the legacy protocol. This is a gap that will create real problems. Enterprise deployments move slowly; if your company’s IT security team approved the legacy MCP deployment and needs to re-review the new spec before allowing stateless MCP in production, that review process could take months. During that window, you need the legacy server running in parallel with whatever new stateless tooling you want to adopt.
From the client side, the burden falls primarily on the major agent frameworks to ship protocol version negotiation. The cleanest solution would be for client libraries to probe for the server’s supported protocol version on first contact and then lock in the appropriate wire format for subsequent calls. This is approximately what HTTP content negotiation does, and it has worked reasonably well for HTTP versioning. Someone needs to write the spec for that negotiation, and ideally it ships as part of MCP’s official documentation rather than as five different framework-specific implementations that are subtly incompatible.
I expect this transition period to be the main source of friction in stateless MCP adoption over the next six months. The protocol itself is clearly better. The ecosystem path to getting there is underspecified, and underspecified migrations have a way of taking twice as long as anyone expects.
Where the Remaining Hard Problems Live
Stateless MCP solves the session complexity problem. It does not solve the other hard problems in building production MCP systems, and it is worth being clear-eyed about which problems remain.
Tool schema versioning is unsolved. When you update an MCP tool’s input or output schema, clients that have cached the old schema description will break silently or noisily depending on how they handle unexpected fields. REST APIs have decades of accumulated wisdom about how to version schemas—use versioned URLs, use additive-only changes, deprecate fields before removing them. MCP has none of this formalized yet. The stateless spec makes tools cheaper to deploy; it does not make them cheaper to evolve safely.
Rate limiting and quota management are also more complex in stateless MCP than they look. In legacy MCP, you could track usage per session and enforce quotas at the session level. In stateless MCP, every request is independent, so quota tracking has to happen at the authentication credential level or per-IP level. This is entirely solvable—it is what every HTTP API does—but if you are migrating from a legacy MCP server that relied on session-level rate limiting, you need to redesign that logic.
Streaming tool responses remain awkward. Some tools legitimately need to stream output—a code execution tool that produces incremental output, a search tool that ranks results in multiple passes, a database query that returns rows as they are generated. The stateless MCP spec handles streaming through server-sent events, which works but requires the HTTP connection to stay open for the duration of the stream. This is not stateful in the session sense, but it does mean you cannot use completely ephemeral serverless invocations for streaming tools without careful handling of the event stream lifecycle. Worth knowing before you commit to Cloudflare Workers for every tool.
The authorization model for multi-tenant MCP deployments also deserves more specification work than it has received. In a single-tenant deployment, you control who can call your MCP server, and the authorization story is simple: one API key, one user, all tools available. In a multi-tenant deployment—say, a company running an internal MCP server that exposes tools to different teams with different permission sets—you need per-tool authorization checks, and the stateless spec does not prescribe how those should work. Teams will implement their own solutions, and those solutions will be incompatible with each other until someone writes a standard.
The Design Lesson Worth Generalizing
Beyond the specific MCP case, the stateless specification revision is an interesting example of a protocol correcting an early design mistake under real-world pressure. The session-based design was not irrational in November 2024. It was the conservative choice made by engineers who had experience with stateful protocols and were uncertain about which direction MCP would evolve. Sessions buy you flexibility; they let you add context accumulation later without redesigning the wire format.
The problem is that flexibility costs compound across every implementation. Every MCP server author who implemented session handling wrote code that, it turns out, could have been avoided. Every infrastructure team that debugged sticky session routing issues spent time on a problem that the protocol created unnecessarily. Every failed adoption by a team that decided the operational complexity was not worth it represented real cost.
There is a broader lesson here about the cost of premature generality in protocol design. The session model was added to give MCP the capability to be stateful if applications needed it. But the cost of that capability was borne by every application, whether they needed it or not. REST made the same design choice that MCP 2.0 is now making: statelessness by default, with applications that need state adding it at the application layer where it belongs.
The pattern also shows up in gRPC versus REST debates, in GraphQL versus REST debates, in Kafka versus REST debates. Every time a protocol adds generality—multi-method calls, streaming, schema evolution, subscriptions—it imposes complexity on every user of the protocol, including the majority who do not need the complex features. The stateless MCP revision is a useful data point for the argument that protocols should start minimal and add complexity only when evidence demands it.
The MCP team made the right call. Stateless MCP is a meaningfully better protocol, and the breaking change cost is worth the long-term reduction in implementation and operational complexity. I would rather migrate now than spend another two years debugging sticky session failures at 2 AM. The lesson for anyone designing protocols or APIs today: default to stateless, default to minimal, and let empirical evidence from real deployments drive additions rather than hypothetical flexibility requirements.
Falsifiable Predictions
By Q1 2027, at least two major cloud providers will offer managed stateless MCP server runtime primitives—either as named products or as first-class deployment patterns with official SDKs. The legacy stateful MCP protocol will have effectively zero new server implementations by end of 2026; all new development will target the stateless spec. We will see at least one significant production incident caused by prompt injection through an MCP tool in a high-sensitivity context before this year ends—not because stateless MCP created the vulnerability, but because stateless MCP will accelerate adoption into environments where the stakes of a prompt injection attack are much higher than they were in early 2025. The backward compatibility transition period will take at least eight months longer than the MCP team currently estimates; enterprise security review cycles do not compress even when the protocol change is clearly an improvement. The protocol got simpler. The security problem did not. The organizational friction, as always, will be the actual bottleneck.




Discussion