MCP Went Stateless: A Migration Checklist for Servers
If you run a Model Context Protocol server over HTTP, the 2026-07-28 revision of the spec changes your deployment more than your tool code. The protocol no longer has sessions, and it no longer has an initialize handshake. Everything a server needs to answer a request now arrives with the request.
This post walks through the changes that touch a server, in the order I would tackle them, and says which ones can wait. It is based on the official changelog, so check it against your own SDK version before you plan work.
What changed, in the parts that reach your server
Sessions are gone. The spec removes protocol-level sessions and the Mcp-Session-Id header from the Streamable HTTP transport. List endpoints (tools/list, resources/list, prompts/list) no longer vary per connection. A server that needs state across calls hands out its own handle and takes it back as an ordinary tool argument.
There is no handshake. Each request carries the protocol version and the client’s capabilities in _meta. Servers must implement a new server/discover call that advertises supported versions, capabilities and identity.
Streams do not resume. The spec drops SSE resumability and message redelivery. If a response stream breaks, the in-flight request is lost, and the client must send it again as a new request with a new ID.
Servers stop asking questions mid-request. Server-initiated requests such as sampling/createMessage or elicitation/create are replaced by a multi round-trip pattern. The server returns a result of type input_required listing what it needs, and the client retries the original request with the answers attached.
Why operations teams should care
A server that holds no per-connection state can be answered by any instance. In practice that means no sticky sessions at the load balancer, simpler autoscaling, easier rolling deploys, and a cleaner fit for serverless platforms. The protocol change moves the hard part, state, into your code and your datastore, where you can see it and test it.
The checklist
1. Find the state you were hiding in the session
Search your server for anything keyed on a session ID: in-memory maps, per-connection caches, “current document” variables. Each one needs to become an explicit handle.
// Before: state lives in the connection
sessions.get(ctx.sessionId).draft = draft;
// After: the handle travels with the conversation
const draftHandle = await store.put({ owner: caller.id, draft }); // Redis, Postgres, anywhere shared
return { content: [{ type: "text", text: JSON.stringify({ draftHandle }) }] };
// A later tool call takes it back
// edit_draft({ draftHandle, changes })
Make handles unguessable and check the owner on every read. A handle is now the main thing standing between one caller and another caller’s data.
2. Sort tools/list
The spec says servers should return tools in a deterministic order so clients can cache the list and improve LLM prompt cache hit rates. The second reason is worth understanding. In most LLM APIs the tool definitions are part of the prefix at the start of the prompt, so if two requests list the same tools in a different order, the cached prefix no longer matches and you pay full price for tokens you already paid for. Sorting by name is a one-line fix.
3. Add ttlMs and cacheScope
List and read results now require both fields. ttlMs is a freshness hint in milliseconds. cacheScope is public or private and controls whether shared intermediaries may cache the response. If a tool list differs by user or by plan, use private. If it is identical for everyone, public plus a sensible TTL cuts polling.
4. Implement server/discover
Stop relying on initialize. Return your supported protocol versions, capabilities and identity from server/discover, and make sure a request with an unsupported version returns the new UnsupportedProtocolVersionError instead of failing in some other way.
5. Make tools safe to retry
Because broken streams are not resumed, clients will re-issue lost requests as new ones. A tool that creates an invoice, sends an email or charges a card needs an idempotency key, either from the caller or derived from the arguments. Say you have a create_ticket tool. If the stream dies after the ticket is created but before the response arrives, a plain retry makes a second ticket.
6. Move off the deprecated pieces on your own schedule
The revision deprecates Roots, Sampling and Logging. They keep working during the deprecation window, and new code should not add them. The spec suggests passing directories or files as tool parameters or resource URIs instead of Roots, calling your model provider directly instead of Sampling, and using stderr or OpenTelemetry instead of Logging. The HTTP+SSE transport, already deprecated since 2025-03-26, is formally reclassified as deprecated, so Streamable HTTP is the target.
7. Check your authorization setup
Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents. Authorization servers should include the iss parameter in authorization responses, and clients must validate it when present. Clients must also key stored credentials by issuer and re-register when the authorization server changes. If you run your own authorization server, read the authorization section of the changelog line by line.
What can wait
The spec adopts a feature lifecycle policy with a minimum twelve-month deprecation window. Under that policy nothing deprecated is removed in under a year, so Roots, Sampling, Logging and Dynamic Client Registration can be scheduled rather than rushed. The items that deserve attention first are session state and retry safety, because they affect correctness, and tool ordering, because it is cheap and saves money.
A note on sources
Summaries of spec releases drift quickly, and download or adoption numbers quoted in blog posts often disagree with each other. When you plan migration work, read the changelog and the linked proposals themselves, and test against the SDK version you actually run.
If you are planning an MCP migration or deciding which tools your agents should be given at all, get in touch for a free consultation.