The Stateless MCP · Part 1
MCP 2026-07-28 Is Final: The Protocol Goes Stateless and Its SDKs Enter a New Generation
The 2026-07-28 revision drops the initialize handshake, session IDs, and protocol-level stream resumption, and moves Tasks out into an extension. All four Tier 1 SDKs are stable, and the Python one ships without Tasks.
Marcin
Java since 2007, .NET since 2010 · Azure · lately deep in LLMs
Picture a typical hotel check-in: you hand over ID at the front desk, wait for a keycard, and for the rest of the stay the door reads the card, not you. The hotel remembers who you are because it wrote your details into a system the moment you checked in. Model Context Protocol used to work almost exactly like that. As of July 28, 2026, it doesn't anymore.
MCP's project maintainers have shipped the 2026-07-28 specification, and they call it the largest architectural revision since the protocol launched. Having read through it, I don't think that's an overstatement. The initialize handshake and the Mcp-Session-Id header are both gone, and so are resumable event streams. In their place: requests that declare their own version and capabilities every single time, and that are expected, though not strictly required, to identify themselves too.
What shipped
Every previous MCP revision could establish a logical session: you called initialize, the server handed back a session ID in the Mcp-Session-Id header, and every later request carried that same ID. The new spec throws that away. A modern request declares its own protocol version and capabilities right there in its metadata; over HTTP transport, the version also travels in its own dedicated header, instead of assuming the server remembers a handshake from three requests ago. Client identity is expected to travel with the request, and server identity is expected to come back with the result, though neither is strictly required: a server that skips it is simply anonymous, not broken. ping is gone too, along with logging/setLevel and the old resource subscribe and unsubscribe calls. Logging preferences move from persistent session config to something you set per request.
Every server that speaks the new spec must also expose server/discover now: one method that reports which protocol versions it speaks, which capabilities it has turned on, who it is, and how long that answer is safe to cache. That last part matters more than it sounds. The response carries its own ttlMs and cacheScope, so a client, or a proxy sitting in front of a whole fleet of servers, can reuse a discovery result instead of re-asking on every single call.
None of this means an MCP server has amnesia. It means the protocol itself stops requiring one server process to remember one client across a whole conversation. Your application can still keep a database, a cache, a task queue, or a partially filled-out form; the spec just stops treating any of that as the protocol's problem. That's the actual distinction worth holding onto: stateless protocol, not necessarily a stateless application.
Discovery, versioning, and the two eras of MCP
This splits MCP into two eras: modern (2026-07-28 and later) and legacy (2025-11-25 and earlier). A client calls server/discover, sees what a server speaks, and chooses accordingly; plenty of SDKs run both eras at once instead of forcing you to pick. A server that gets a request for a version it doesn't support now has an actual error code to hand back, -32022, instead of failing in some improvised way.
The old -32002 code for resource-not-found is gone too, replaced by the ordinary JSON-RPC invalid-params code, -32602. If your error handling still pattern-matches on the old numbers, this update may quietly break it.
When the protocol needs more from you
If you've ever shipped an agent, you've hit the annoying middle case: the tool call that is neither a clean success nor a clean failure; it just needs one more piece of information before it can finish. The new spec finally gives that state a name. A server can return an InputRequiredResult instead of forcing a fake failure; the client gathers whatever is missing and sends it back as inputResponses to continue the same operation.
Results also carry an explicit resultType now, so a client can tell a finished answer apart from one that is still waiting on you instead of guessing from the shape of the payload. The new spec simply treats older results with no resultType as complete, so nothing breaks retroactively. The protocol can tell you input is required. It has nothing to say about whether that should pop up as a dialog in an IDE, a prompt in a terminal, or a queued approval for someone on call at 2 a.m. That part is still entirely on you.
Tasks moves out, into an extension
Tasks, the mechanism for long-running or asynchronous work, didn't survive as a core feature. The spec pulls it out into its own namespaced extension, io.modelcontextprotocol/tasks: a client and server now have to explicitly agree to support it before either side can rely on it. That's a deliberate trade. The base protocol gets smaller and easier to implement correctly, and Tasks keeps evolving without dragging the whole spec along with it.
The cost shows up immediately in the SDKs. Python's brand new stable 2.0.0 release supports the modern core spec just fine, but doesn't include the Tasks extension at all. "Modern MCP" and "Tasks support" are no longer the same claim, so stop treating them as one.
The streaming model changes too
Using HTTP GET to open an event stream is gone, along with the old per-resource subscribe and unsubscribe calls, replaced by one unified method, subscriptions/listen. The bigger change is what is missing: there is no more protocol-level stream resumption. No Last-Event-ID, no redelivery. If your connection drops mid-stream, you don't resume it; you reissue the operation with a new request ID and let the application layer sort out what that means.
Which raises the obvious question: is the operation you're about to retry safe to run twice? The spec doesn't answer that for you. It just makes you ask it, which, given how many production incidents start with an unexamined retry, might be healthy.
Four SDKs, and C# catches up
TypeScript, Python, Go, and C# all hold the project's Tier 1 status, its highest support tier: broad protocol coverage, conformance testing, active maintenance, and a stable release backing it up. The official announcement leans hard on scale too: something like half a billion monthly downloads across the four SDKs, and more than a billion lifetime downloads each for the TypeScript and Python packages. Worth holding onto before you repeat that number in a meeting: those are package-download counts, not a census of developers or live deployments.
| SDK | Version supporting 2026-07-28 | Status |
|---|---|---|
| TypeScript | 2.0.0 | Stable |
| Python | 2.0.0 | Stable |
| Go | 1.7.0 | Stable |
| C# | 2.0.0 | Stable |
When the spec landed on July 28, three of those four were done, and C# was still sitting on a 2.0.0-rc.2 release candidate. That gap didn't last: C# shipped a stable 2.0.0 release later the same day, carrying forward the Tasks conformance, OAuth, and transport work its release candidate had already covered. All four Tier 1 SDKs now have a stable release behind the final spec, but check the current release yourself before you lean on it for a migration plan.
TypeScript's 2.0.0 reorganizes the SDK into separate client, server, core, Node.js, and framework-integration packages, ships a codemod for the mechanical parts of a v1-to-v2 migration, and needs Node 20 or later; the v1 line is expected to keep getting bug and security fixes for a limited window while you move. Python's 2.0.0 is a stable release too, and it's the default now: install the plain mcp package without pinning a version and you get the 2.x line, so anything that needs to stay on the old major has to pin below 2 explicitly. Its authorization story has its own gap: client-side DPoP proof binding and workload-identity JWT bearer support aren't there yet.
Go's 1.7.0 adds full support for the final spec while letting existing clients and servers keep operating under their previous configuration, though its stateless HTTP behavior on the server side is tied to a specific transport configuration, not something you get automatically from the version bump.
What this means if you run MCP today
If you're maintaining an MCP server or client right now, treat this as an architecture review, not a routine version bump. Start by finding every place your code assumes initialize already ran, reads a session ID, or expects a stream to resume itself. That's your actual migration surface. Upgrade to an SDK version that speaks both eras before you touch anything else, and confirm your existing integrations still work under it.
Then start calling server/discover and logging what comes back: which version got negotiated, which extensions the other side supports, how often you're hitting the new unsupported-version error. Only after that should you start redesigning retries around real idempotency guarantees instead of the resumption you used to get for free.
And don't rip out legacy support just because the spec is final. The project itself says July 28 is a publication milestone, not a shutoff date, and deprecated features like Roots, Sampling, Logging, and Dynamic Client Registration get a minimum twelve-month runway before the earliest possible removal, which lands no sooner than July 28, 2027.
The enterprise case, and what it doesn't solve
Put the pieces together and MCP looks a lot more like infrastructure than an integration convention. Requests can carry OpenTelemetry trace context now, so one MCP call can sit inside the same distributed trace as the model runtime, the transport, the server, and whatever database or queue it eventually touches. The OAuth changes tighten issuer validation and bind credentials to the specific issuer that created them, adding protections against credentials being used with the wrong issuer, and Dynamic Client Registration is on its way out in favor of Client ID Metadata Documents.
None of that, though, tells you which MCP servers your organization should trust, which tools an agent should be allowed to call unsupervised, or which data is allowed to leave your security boundary. The spec gives you sharper tools for observability and version control. It doesn't make the governance decisions for you, and I'd be suspicious of anyone who tells you it does.
So, what do you do with this
I keep coming back to the hotel comparison, because it's the part that changes how you build, more than how you talk about the release. A hotel that has you show your own paperwork at every door instead of trusting one shared keycard is more work to build, but it's also the version that survives a burned front desk or a shift change, because nobody has to remember who's supposed to be inside. That's the trade MCP just made on purpose, and it says nothing about who's allowed through which door. That part is still entirely yours to build.
I don't think this is finished. A Python SDK still missing the Tasks extension, and a retry model that leaves you to work out idempotency on your own instead of getting it for free, that's real unfinished business, not a rounding error. But it's the right direction, and it's rare to see a protocol admit its early design trusted too much and then go fix it instead of bolting on another workaround.
One part of this doesn't show up until you build on it. A tool call that needs an answer from a person has nowhere left to ask, so it has to end early and get called a second time with the answer. The same meeting room booking server, built that way in Python and then in C#, is Parts 2 and 3.