MCP went stateless: what to change in your server, and in what order
The 2026-07-28 spec revision removes sessions, the initialize handshake, and three whole features. If you maintain an MCP server, here is the audit that finds what breaks in your code, the fixes in dependency order, and the parts that can wait a year.
If you maintain an MCP server, the protocol contract underneath it changed on July 28. The 2026-07-28 revision of the Model Context Protocol is a final release, and it is a breaking one: the initialize handshake is gone, the Mcp-Session-Id header is gone, every result must now declare a resultType, and three features you may depend on (Roots, Sampling, Logging) entered a formal deprecation window.
Nothing is broken today. Clients that speak the older revisions keep working, and the deprecation policy guarantees a minimum of twelve months before removed features stop being served. But "nothing is broken today" is exactly the situation in which a migration gets postponed until a client update breaks it for you. The work splits cleanly into things worth doing this week, things to schedule, and things to leave alone, and the split is not obvious from the changelog. This post is the sorted version.
What the revision changes, in one pass
The one-sentence summary: MCP used to be a stateful, session-based protocol with a handshake, and it is now a stateless request/response protocol where every request carries its own context.
The specifics that matter for a server author, each verified against the spec changelog:
- Sessions are gone. The
Mcp-Session-Idheader is removed from the Streamable HTTP transport (SEP-2567). List endpoints such astools/listmay no longer vary per connection. If your server needs state across calls, the spec's replacement pattern is explicit: mint your own handle server-side and pass it back and forth as an ordinary tool argument. - The handshake is gone.
initialize/notifications/initializedno longer exist (SEP-2575). Each request instead carries the protocol version and client capabilities in_metaunderio.modelcontextprotocol/protocolVersionandio.modelcontextprotocol/clientCapabilities. A version mismatch returnsUnsupportedProtocolVersionError. server/discoveris mandatory. Servers MUST implement this new RPC so clients can learn supported versions and capabilities up front, and so older tooling has a compatibility probe.- Two new required headers. Streamable HTTP POST requests must carry
Mcp-MethodandMcp-Name(SEP-2243), so gateways can route and meter traffic without parsing the JSON body. - Every result declares
resultType."complete"for a normal result,"input_required"for the new Multi Round-Trip Request pattern, which replaces server-initiated requests likesampling/createMessageandroots/list: the server returns what it needs ininputRequests, and the client retries the original request withinputResponses. - List results must declare cacheability.
tools/list,prompts/list,resources/list,resources/read, andresources/templates/listnow requirettlMsandcacheScope("public"or"private") fields (SEP-2549). - Subscriptions consolidate. The HTTP GET endpoint and
resources/subscribe/unsubscribeare replaced by a singlesubscriptions/listenstream that clients opt in to. SSE resumability (Last-Event-ID) is removed: a broken stream means the client re-issues the request with a new ID. - Small but sharp edges.
pingandlogging/setLevelare removed. The resource-not-found error code moves from-32002to-32602. Spec-defined error codes move into a reserved range:HeaderMismatchis now-32020,UnsupportedProtocolVersionis-32022. - Deprecated with a twelve-month clock: Roots, Sampling, Logging (SEP-2577), plus the old HTTP+SSE transport and Dynamic Client Registration (superseded by Client ID Metadata Documents). Deprecated features keep working during the window; new implementations should not adopt them.
Step 1 — audit your server for the things that break
Run these against your server's source. Each hit maps to a specific fix.
# Session state: the biggest one. Any hit here needs the handle pattern.
grep -rn "Mcp-Session-Id\|sessionId\|session_id" src/
# Handshake assumptions: per-connection setup that must move to per-request.
grep -rn "initialize\b\|initialized" src/
# Server-initiated requests: these become Multi Round-Trip Requests.
grep -rn "sampling/createMessage\|roots/list\|elicitation/create" src/
# Removed methods and codes.
grep -rn "logging/setLevel\|resources/subscribe\|Last-Event-ID" src/
grep -rn -- "-32002" src/
Zero hits across the board is a real possibility. A server that exposes stateless tools, was generated from a recent SDK template, and never used Sampling or Roots mostly needs an SDK upgrade and the new required fields, which the SDK adds for you. In that case skip to step 3.
Step 2 — fix the session hits with the handle pattern
The common failure mode in existing servers: caching something at initialize time (a database connection, a working directory, a logged-in API client) keyed on the session ID, then reading that cache in every tool call. Under the new revision there is no session ID to key on, and two consecutive requests may arrive at two different replicas behind a load balancer, which is precisely the deployment the stateless design enables.
The spec's replacement is server-minted handles as tool arguments. Concretely:
- The tool that used to establish state (
connect_database,open_project,start_analysis) returns an opaque handle in its result:{"handle": "db-7f3a…"}. - Every dependent tool takes that handle as a declared parameter in its
inputSchema. The model passes it back on each call, the same way it passes any other argument. - Server-side, the handle resolves through shared storage (Redis, a database row, a signed token that encodes the state itself) rather than process memory, so any replica can serve any call.
This is more work than a session cache, and it is also a strictly better design: your server survives restarts and horizontal scaling, and the state a tool depends on is visible in the tool's schema instead of being invisible protocol plumbing.
Step 3 — upgrade the SDK, which does the mechanical parts
The required _meta fields, resultType, server/discover, the new headers, and ttlMs/cacheScope defaults are SDK-level mechanics. The current releases:
- Python: v2.0.0 is stable, and
pip install mcpnow installs the 2.x line. The release note worth acting on: a v2 server "serves every earlier revision from the same server". You can upgrade today, and clients still speaking 2025-11-25 or earlier keep working against the same process. - TypeScript: v2.0.0 is the first v2 release and is labelled a beta. The repo ships two migration guides,
docs/migration/upgrade-to-v2.mdfor the SDK API changes anddocs/migration/support-2026-07-28.mdfor the protocol revision. If your server is on the v1 line and in production, reading both before upgrading is the honest estimate of the work. - Go and C# SDKs support the revision; Rust support is in beta.
The backwards-serving behaviour decides your ordering. Anthropic's own announcement says 2026-07-28 support is rolling out across Claude products "soon", which means the clients calling your server today are still on older revisions. Upgrading the SDK now costs nothing for those clients and makes your server correct for the new ones as they arrive. Waiting, by contrast, has a real failure mode: a major client updates first, and your server is the thing that stopped working.
Step 4 — schedule the exits from Roots, Sampling, and Logging
These three features keep working for at least twelve months, so the fix belongs on the roadmap rather than in this week's sprint. The spec suggests a migration path for each, and they are all simplifications:
- Roots (the client telling the server which directories it may touch): pass directories or file paths as tool parameters or server configuration instead. Most servers using Roots were effectively treating them as a config value with extra steps.
- Sampling (the server asking the client's model to generate text): call an LLM provider API directly from the server. This also removes the least predictable dependency in the protocol, since sampling quality depended on whichever client happened to be connected.
- Logging (
notifications/messageto the client): log tostderrfor stdio servers, or emit OpenTelemetry for HTTP servers. Note the sharp edge in the new revision: a server must not send log notifications for a request that did not setio.modelcontextprotocol/logLevelin_meta, so client-directed logging is now opt-in per request even during the window.
If you built on the experimental Tasks API, that moved to a versioned extension (io.modelcontextprotocol/tasks) with polling via tasks/get replacing the blocking tasks/result. Extension APIs are versioned separately from the core spec, so pin the extension version you test against.
What to leave alone
Two things in the changelog look urgent and are not. The auth hardening (RFC 9207 issuer validation, credentials bound to their issuing authority, Client ID Metadata Documents) lands almost entirely on client and authorization-server implementers; a server that delegates auth to a standard OAuth library inherits the fixes with a dependency update. And the old HTTP+SSE transport's formal deprecation only matters if you never moved to Streamable HTTP, which has been the recommended transport since March 2025; if you are on it, there is nothing to do.
If MCP as a concept is new to you, start with MCP in plain English or the glossary entry instead of this post, and the walkthrough for standing up a first server is in Setting up your first MCP server. This post is for the people who already shipped one, because they are the ones the clock started for on July 28.
Get the next post when it ships
One email on Sunday with the new post and a short list of what shipped that week — new guides, tool updates, and a couple of links worth reading.