MCP Was Wrong From The Start (They Just Fixed It)

BBetter Stack
Computing/SoftwareInternet Technology

Transcript

00:00:00MCP just shipped its biggest change since it launched, and the headline is the entire
00:00:03protocol is now stateless, as it probably should have been from the start. There's no more
00:00:07handshakes, no more session IDs, everything just got way simpler, except for upgrading,
00:00:12because there's been a lot of deprecations and breaking changes in here,
00:00:14so let's jump in and see what's changed.
00:00:21I'll start by giving you some context on the old version of MCP, the stateful version,
00:00:25and why this caused so much pain. The old flow went like this,
00:00:28the client posts initialized to your MCP endpoint, and your server would then mint a session ID
00:00:33and send that back. Every follow-up request would then have to carry that session ID,
00:00:38and that meant the session ID pins the client to whichever server instance issued it,
00:00:42and that is the part that caused all of the issues. Say you had a load balancer,
00:00:45and you scaled your MCP server up to three instances, the next request that comes in
00:00:49gets routed to a different instance than the one that ran initialize, and that instance had never
00:00:54heard of the session before, you would end up with a 400 session not found error.
00:00:57The same thing could happen if one of those pods went down, the session state would simply be lost,
00:01:01and every request after would fail. Now obviously there were workarounds for this,
00:01:05you could use sticky sessions so the client always hits the same instance,
00:01:08or you could have a shared Reddit instance so every session can be looked up. But those just added
00:01:12unnecessary complexity and also latency and costs. Something that doesn't cost you anything is
00:01:17subscribing if you want to stay up to date with developer and AI news, it really helps us out.
00:01:21So those were the problems with stateful MCP, and this is what the new spec has fixed. The spec is called
00:01:252026-07-28, because MCP versions are actually dates, and it has two key proposals in it.
00:01:31SCP-2575 removes the initialize and initialized handshake, and SCP-2567 removes the MCP session ID
00:01:38header and the protocol level session that came with it. This means that the handshake is gone,
00:01:42and from a protocol level, every single request is completely independent now.
00:01:46These changes make the code for an MCP tool call go from something like this,
00:01:50with initialize and session headers, to this, a single self-contained request. It is much,
00:01:55much simpler. And it also solves all of our earlier pain points. We can now use standard round-robin
00:01:59routing, because any container can now handle any request, so we can use a completely traditional
00:02:04load balancer, and we don't need a Reddit instance sat there holding our sessions. So if a
00:02:08server does crash or gets restarted, the load balancer can just send the request to the next
00:02:12working instance, and the client never notices. But the best advantage for me is in deployment.
00:02:17On something like Cloudflare Workers or Google Cloud Run, the server doesn't need to be up 24-7
00:02:21anymore, since there's no connections to hold open. So it can scale down to zero when no one's using it,
00:02:27which should save you a bit of money, and also just give you way more deployment options.
00:02:30On Cloudflare's own post about this, they say that MCP no longer requires durable objects
00:02:34to speak the protocol. So everything is getting way simpler, and the new spec is basically just MCP
00:02:39built on top of normal HTTP infrastructure, which everyone already knows how to deal with.
00:02:44In fact, talking about HTTP, there's also two other changes that have been inspired by it.
00:02:48Previously, all of the MCP information lived inside the JSON body of a request,
00:02:52but now there's two new HTTP headers, MCP method and MCP name, and this means that a gateway,
00:02:58rate limiter, or firewall can make decisions using those headers without having to pass the JSON,
00:03:03so it should help reduce some latency. They also added time to live and cache scope hints to tool,
00:03:07prompt, and resource list calls, again modelled on HTTP caching, so now a client should know exactly how
00:03:12long a tool list is fresh for, and whether it's safe to share across users. It doesn't need a
00:03:17persistent connection to stay up to date anymore. Now you might be wondering where all of that
00:03:21handshake information actually went, as it wasn't useless information. The server might still need to
00:03:26know what protocol version you're on, and what your client can do, but this information now just
00:03:30rides along on a meta field within the JSON of a single request. So the server can simply read that
00:03:35to understand what the client can do, and if a client wants to know what a server can do,
00:03:39there is now a new optional method called server discover that returns that information.
00:03:43So far then, we've seen a lot of simplification, but you still might have two questions. First,
00:03:48what have we actually wanted state? And second, if there's no persistent connection,
00:03:52how does the server ask the client something, like a follow-up question? Well I'll start with
00:03:56question one, what have you actually wanted some state? And guess what? You just do this the same
00:04:00way that HTTP APIs have done it for as long as they've existed. You could have a tool mint an
00:04:05explicit handle, something like basket ID or browser ID, and then the model just passes this
00:04:09back as an ordinary argument on the later tool calls. So now it's completely up to you how you
00:04:14manage the state and not the protocol, which is a way more flexible approach. And it's worth
00:04:18knowing authorization still works, and they've actually done a lot of work hardening it in this
00:04:22update as well. As for the second question, what if the server wants to send a follow-up question,
00:04:26like, are you sure? Well, the old way needed a constant open stream to send that message down
00:04:31to the client. This actually meant the user could be prompted out of nowhere without having asked for
00:04:35anything, which is a pretty bad experience and also a possible security problem. So in this new spec,
00:04:40they've completely rebuilt the workflow as a multi-round trip request. So say I had a tool that
00:04:44deleted a file on some cloud provider and I called it. Instead of pushing a follow-up question to me,
00:04:49the server actually returns an input required result, and attached to that is a request state payload,
00:04:53which is all of the context for that call serialized. This input required result can
00:04:58actually be something as simple as are you sure, typed as a boolean, and this will prompt the client
00:05:02to ask me, and when I answer, the client then reissues that original call, but this time with my input
00:05:07responses attached, and it also echoes that request state back. That request state actually contains
00:05:11all of the context needed to resume this work, which means that any instance on my load balancer can
00:05:16actually pick this up and retry it, and it doesn't have to be the one that initially requested the answer.
00:05:19But what about a tool call that takes a long time? You don't want it to be holding that connection open
00:05:23and blocking the entire conversation. Well, this is why they've graduated tasks from experimental to
00:05:28an official extension. Let's say we have a tool for processing a refund, and this is a task that takes
00:05:32a while. You'd simply write that task state into a database somewhere as working, and kick off the
00:05:37async job, and then immediately return a response for that tool call, telling the agent and the user
00:05:42that it's running. From here, the client can then pull that tool call with tasks slash get,
00:05:45or subscribe with subscriptions slash listen to track its progress and pull in the final result
00:05:50when it's done. So those are the key changes that make MCP stateless now, and as I mentioned in the
00:05:54intro, this comes with a lot of breaking changes and deprecations. They have said though that all of
00:05:58these features will have a minimum of 12 months in deprecation before they're officially removed,
00:06:03so there is still some time to update your servers. And talking of updating, all of the SDKs now
00:06:07support this spec, with most of them having a major version bump up to version 2, and if you use
00:06:12TypeScript like me, they've actually replaced the monolithic SDK package with modular libraries,
00:06:17one for the server and one for the client, and they also made a code mod that handles the standard
00:06:20API renames for you. Obviously though, we have seen a lot of changes in this update, so it's not going
00:06:25to be a simple update the package and leave, but all of these changes are genuinely worth making,
00:06:30as it feels like a great reset for MCP, doing things the way they should have been done from the
00:06:33start, and I'm pretty excited to see how much further MCP goes. What do you think? Let me know in the
00:06:38comments down below, while you're there subscribe, and as always, see you in the next one.

Key Takeaway

Specification 2026-07-28 eliminates MCP session IDs and handshakes, making the protocol stateless and compatible with standard HTTP infrastructure.

Highlights

  • The MCP protocol shifts from stateful to stateless under specification 2026-07-28.

  • Removal of initialization handshakes and session IDs eliminates 400 session not found errors during load balancing.

  • Servers scale down to zero on platforms like Cloudflare Workers and Google Cloud Run because persistent connections are no longer required.

  • Two new HTTP headers, MCP method and MCP name, allow gateways and firewalls to route requests without parsing JSON bodies.

  • Multi-round-trip request workflows replace server-pushed follow-up questions with input required results.

  • Official task extensions replace long-running connections with asynchronous jobs that clients poll via subscriptions.

Timeline

Stateful Protocol Limitations

  • Stateful architecture requires session IDs that pin clients to specific server instances.
  • Load balancers routing requests to different pods trigger 400 session not found errors.
  • Workarounds like sticky sessions and shared Redis instances add unnecessary complexity, latency, and cost.

The old MCP flow required a client to post an initialize request to an endpoint, which then minted a session ID. Every subsequent request carried this session ID to maintain state. Scaling servers across multiple instances or experiencing pod failures caused session state loss, as alternate instances had no record of the session. Mitigating this required sticky sessions or Redis lookups, introducing infrastructure overhead.

Stateless Protocol Architecture

  • Specification 2026-07-28 removes the initialize handshake and MCP session ID headers.
  • Standard round-robin routing handles requests because any container can process any call independently.
  • HTTP-inspired headers and caching hints reduce latency and eliminate the need for durable objects.

Proposals SCP-2575 and SCP-2567 remove protocol-level sessions and handshakes. Requests become independent, allowing standard round-robin load balancing without Redis instances. Server crashes no longer disrupt clients since requests route to working instances automatically. Furthermore, serverless platforms like Cloudflare Workers and Google Cloud Run can scale down to zero when idle, lowering costs. New HTTP headers for method names and caching scopes allow firewalls and gateways to inspect requests and determine freshness without parsing JSON.

State Management and Follow-Up Workflows

  • Explicit handles like basket IDs manage state at the application level rather than the protocol level.
  • Multi-round-trip request workflows replace constant open streams for server follow-up questions.
  • Official task extensions handle long-running operations asynchronously via database tracking and polling.

Applications manage state by passing explicit identifiers, such as basket IDs or browser IDs, as ordinary arguments in subsequent tool calls. For follow-up questions, servers return an input required result containing serialized request state instead of pushing messages down a constant open stream. The client reissues the call with user responses and request state attached. Operations taking extended time utilize the official task extension, writing working status to a database while clients poll progress through subscriptions.

Breaking Changes and SDK Updates

  • All deprecated features maintain a minimum 12-month deprecation period before removal.
  • SDKs update to major version 2 to support the stateless specification.
  • Modular TypeScript libraries and automated code mods simplify the migration process.

The transition introduces breaking changes and deprecations, though all removed features remain supported for at least 12 months. All major SDKs bump to version 2. TypeScript implementations transition from monolithic packages to modular client and server libraries, supported by automated code mods that handle standard API renames.

Community Posts

View all posts