No Handshake, No Problem: MCP Goes Fully Stateless
Here’s a scenario anyone running MCP servers behind a load balancer will recognize immediately: you scale from one instance to three, and within minutes you start seeing 400 session not found errors. Nothing is actually broken. The request just landed on an instance that never heard of the session the client was carrying.
That single design flaw, a session ID that pins a client to one specific server instance, is what MCP’s newest spec update removes entirely. The protocol is now stateless by default. No handshake, no session header, no sticky routing. It also ships a formal extensions framework, real authorization hardening, and enough breaking changes that “just bump the package version” isn’t going to cut it.
If you need a refresher on what MCP actually is before diving into what changed, our earlier post covers the fundamentals.
A Session Only One Server Remembers
If you’ve ever debugged why users kept getting logged out at random after a second app server got added to the pool, you already know this story. It’s the oldest scaling bug in web development: someone stores session state in the memory of the server process that created it. It works perfectly in staging, on one instance. It falls apart the moment traffic gets spread across more than one, because the next request can land on a server that has never heard of the session the first one just created.
MCP’s old initialize flow had exactly this shape. The client sent an initialize request, the server minted a session ID, and every subsequent call had to carry that ID along with it, trusting that whichever instance received the next request would be the same one that handled the first.
That worked fine in a demo, one client, one server process, nothing to route between. It fell apart the moment you introduced anything resembling real infrastructure. A standard round-robin load balancer has no idea that request four needs to land on the same instance as request one, so routing it elsewhere means a session that instance has never seen. If the instance holding a session went down or got recycled during a deploy, every request after that failed until the client re-initialized. The usual fixes, sticky sessions, a shared Redis instance to look up session state across instances, are the same fixes teams have reached for since the earliest days of clustered web servers. They add latency, cost, and operational surface area for a problem that shouldn’t need to exist at the protocol layer in the first place.
None of this is a new class of bug. It’s the same in-memory session trap that’s caught out backend teams for two decades, just relocated from your application code into the protocol itself, where you didn’t get a say in fixing it until now.
What Actually Changed
The new spec, dated 2026-07-28 (MCP versions are dates, not semver), ships two core proposals. SEP 2575 removes the initialize and initialized handshake entirely. SEP 2567 removes the MCP session ID header and the protocol-level session tied to it. The net effect: every request is now completely self-contained. There’s no setup call before the real call, and no header the server needs to have seen before to make sense of what’s coming in.
A few other changes ride along with this shift, and each one is clearly modeled on things HTTP already solved decades ago. Two new headers, MCP-Method and MCP-Name, now sit in the HTTP header instead of being buried in the JSON body, so a gateway, rate limiter, or firewall can make routing and throttling decisions by reading headers alone, without parsing the payload. Capability negotiation moved into a _meta field on the request itself, and a new optional server/discover method lets a client learn what a server supports ahead of time. TTL and cache scope hints are now attached to tool, prompt, and resource list responses, again modeled directly on HTTP caching semantics.
The change worth sitting with is elicitation. In the old model, if a server needed to ask “are you sure?” mid-call, it needed an open stream to push that question down to the client, a pattern where a user could get prompted out of nowhere for something they never explicitly asked for. That’s both a bad experience and a real security smell. The new model turns this into an explicit request/response cycle: call a tool that needs confirmation, and the server returns an input-required result with a request state payload attached, essentially the full context of that call, serialized. The client surfaces the question, the user answers, and the client reissues the original call with the answer attached, echoing the request state back. Because that state is fully serialized and passed back explicitly, any instance behind your load balancer can pick up the resumed call. It doesn’t have to be the instance that asked the question in the first place.
Long-running work graduated to an official extension too. Tasks moved out of experimental status and now lives under the new extensions framework (io.modelcontextprotocol/tasks). A server writes the task state to a database, kicks off the async job, and immediately returns a response telling the client it’s running. The client polls with tasks/get, or listens on a new subscriptions/listen stream it opts into per notification type, instead of holding a connection open and blocking the conversation.
One more change is easy to skim past and annoying to find later: “resource not found” used to return a custom MCP error code, -32002. It now returns the standard JSON-RPC -32602 (“Invalid Params”) instead. If anything in your client or test suite pattern-matches on the literal -32002 value, it will silently stop matching. Worth a dedicated grep across your codebase before you assume the migration is done.
A Real Extensions Framework, Not Just Tasks
This release also formalizes something MCP didn’t really have before: a proper, versioned way to add capabilities without changing the core protocol. Extensions are identified by reverse-DNS style IDs, negotiated explicitly between client and server, and allowed to version independently of the spec itself. New capabilities can stabilize in their own lane instead of forcing a core protocol bump every time someone wants to ship something new.
MCP Apps lets a server ship an actual interactive UI, HTML rendered inside a sandboxed iframe on the client side, rather than just returning text or structured data for the model to describe. Tools declare their UI templates upfront, so a host application can prefetch, cache, and security-review them before anything actually runs. Every action a user takes inside that UI still goes through the same JSON-RPC audit and consent path as a direct tool call, so the security model doesn’t get any looser just because there’s now a rendered surface involved.
Tasks, covered above, is the other extension graduating out of the core spec in this release, alongside long-running work handling. A third extension, Enterprise Managed Authorization, is named in the spec alongside these two, aimed at organizations that need to govern access across MCP servers they don’t directly control.
What If You Actually Need State?
This is the question every engineer asks the moment “stateless” gets mentioned, and the answer is refreshingly boring: you handle it the same way HTTP APIs have handled it for thirty years. If a tool needs to track something across calls, say a shopping basket, it mints an explicit handle: a basket ID, a session token, whatever makes sense for the domain. That handle gets passed back as an ordinary argument on later tool calls.
State management moves from being the protocol’s job to being your application’s job, which is exactly where most of us would want that decision to live anyway. You get to choose the storage, the expiry policy, and the consistency model instead of inheriting whatever the protocol assumed on your behalf.
Authorization Gets Genuinely Enterprise-Ready
Authorization wasn’t just left alone here, it got real hardening, and it’s arguably the change with the biggest long-term impact for teams running MCP behind corporate identity providers.
The headline is a formal move away from Dynamic Client Registration (DCR) toward Client ID Metadata Documents (CIMD). Under the old model, DCR expected a client to register itself with an authorization server programmatically, an approach that never mapped cleanly onto how enterprise identity providers like Entra or Okta actually expect clients to be provisioned. CIMD flips that: a client publishes its identity as a metadata document the authorization server can fetch, which lines up far more closely with how OAuth 2.0 and OpenID Connect already work in production enterprise environments. DCR isn’t gone, it’s retained for backward compatibility with authorization servers that haven’t adopted CIMD yet, but it’s formally deprecated and on the same twelve-month clock as everything else marked for removal.
Alongside that, issuer validation per RFC 9207 is now required, closing off a category of token confusion attacks where a client could be tricked into trusting a token from the wrong authorization server. If you’re running MCP servers that authenticate against enterprise identity providers, this is the part of the release worth reading line by line, not just skimming the summary.
The Deprecation Policy Is Now a Real Policy
Previous MCP releases handled deprecation informally. This one formalizes it: every feature now moves through an explicit Active → Deprecated → Removed lifecycle, with a minimum twelve-month window at each stage before anything can actually disappear.
Three core primitives get marked deprecated in this release under that new policy: Roots, Sampling, and Logging. They keep working, they’ll keep working for at least a year, and new implementations simply shouldn’t build on them going forward. The legacy HTTP+SSE transport is on the same kind of dated offramp. If any of your servers still lean on these, there’s no need to panic, but it’s worth adding them to your backlog rather than assuming they’re permanent.
What This Update Actually Fixes
For anyone who has actually operated MCP servers rather than just building against them locally, the upside here is substantial. Standard load balancing just works again: round robin, least connections, whatever your infrastructure already does for every other stateless HTTP service now works for MCP too, with no sticky sessions and no shared state store just to hold session lookups.
Failure recovery gets simpler, too. If an instance crashes or gets recycled during a deploy, the load balancer routes the next request to a healthy instance and the client never notices, because there was never any per-instance state to lose. Serverless deployment becomes genuinely viable: on something like Cloudflare Workers or Cloud Run, a server no longer needs to sit up 24/7 holding a connection open, and can scale to zero when nobody’s using it. Cloudflare’s own writeup on this update notes that MCP no longer requires durable objects to speak the protocol at all, a meaningful simplification if you were previously reaching for that just to satisfy the session model.
The bigger story is that the infrastructure gets boring, in the good way. MCP now behaves like a normal HTTP API. Your existing gateways, rate limiters, observability stack, and load balancers already know how to deal with it. You’re not maintaining a special case for one protocol anymore.
What This Update Actually Costs
None of this comes free, and it’s worth being honest about the cost side before you schedule the upgrade. The handshake is gone. The session header is gone. If your servers or clients depend on either, this isn’t a drop-in update, expect real code changes, not a version bump.
State becomes your responsibility, fully. You now own the storage, the expiry, and the lookup logic for anything that used to ride along on the session. For teams that leaned on the protocol’s session as a convenient place to stash context, that convenience is gone. Moving to the request state and reissue model is a real design change to how your tools handle confirmation and follow-up questions.
Most SDKs have also taken a major version bump to support this. If you’re on TypeScript, the monolithic SDK package has been split into separate client and server libraries, alongside a codemod for the standard API renames, useful, but still a migration project, not a config change. And auth migration isn’t optional if you’re on enterprise identity: moving from DCR to CIMD, and adding issuer validation, is real work if your MCP servers sit behind Entra, Okta, or similar. It’s not a breaking change in the sense of “your server stops working today,” but it is work you’ll eventually need to do to stay off a deprecated path.
The one piece of good news on timing: deprecated features get a minimum twelve-month window before removal. There’s no reason to treat this as a fire drill.
How to Sequence the Upgrade
If you’re running MCP servers as part of a production stack, here’s a reasonably sane way to sequence this rather than reacting to it all at once. Start by auditing what you’re actually running: which servers rely on session state today, and what are they using it for? Separate “genuinely needs cross-call state” from “was just convenient because the session was there.” A lot of usage will fall into the second bucket, and that’s the easy part of the migration.
For anything that genuinely needs state across calls, decide now what the handle looks like and where it lives, the same design decision you’d make for any stateless API. If you built sticky sessions or a shared Redis lookup specifically to work around MCP’s old session model, this update is your chance to remove that operational overhead entirely and go back to standard routing. Rebuild your elicitation flows deliberately rather than as a mechanical find-and-replace, since the shift from a pushed prompt to a request state and reissue cycle changes where in your code that logic naturally lives.
Check what your caching hints actually enable, too. If you’re running MCP servers behind anything like a dispatcher or edge cache layer, similar in spirit to how an AEM instance already thinks about exposing structured content to agents, the new TTL and cache scope hints on tool and resource lists give you a real, protocol-level signal to build caching policy around, instead of guessing at freshness.
Grep for the error code before you assume you’re done, a hardcoded -32002 is the kind of change that passes CI cleanly and then breaks quietly in production weeks later. Start the CIMD conversation with whoever owns your identity provider now, even if you don’t migrate immediately. DCR still works today, but you don’t want to be the team scrambling in month eleven of a twelve-month deprecation window. And budget real migration time for the SDK changes: even with codemods available, treat this like any other major version SDK migration, run it through your normal review and testing process rather than merging a generated diff. With a twelve-month deprecation window, there’s no operational reason to treat any of this as urgent.
What’s Next
This update is a good reminder that a lot of what looks like “AI protocol design” is really just distributed systems design wearing a new name. Session affinity, stateless request handling, async task polling, none of this is new territory if you’ve built scalable backend systems before. MCP just had to relearn it in public.
If you’re running MCP servers as part of an AEM-driven content or commerce stack, this is worth pairing with how you’re already thinking about exposing structured content to agents. The same discipline that makes an AEM instance conversational over MCP is exactly what makes this migration straightforward rather than painful.
What’s your MCP server footprint looking like right now, mostly experimental, or already carrying real production traffic? Connect with me on LinkedIn if you want to compare notes or be notified when the next post goes live.