The Model Context Protocol specification published on 28 July 2026 removes three things a working server probably depends on: the initialize and notifications/initialized handshake, the Mcp-Session-Id header on the Streamable HTTP transport, and stream resumability. Every request now carries its own protocol version and client capabilities in _meta, and every server must implement a new server/discover method. The protocol blog states the aim plainly: MCP "is transforming from a bidirectional stateful protocol into a request/response stateless protocol", which lets a server sit "behind a plain round-robin load balancer without needing shared storage". If your server keeps per-connection state in process memory, that is the first thing to move.
The key changes document runs to nine major items, a dozen minor ones and four deprecations. Below is the part that touches code you have already shipped, and the order to work through it.
Removed, and what took its place
| Gone in 2026-07-28 | What you use instead |
|---|---|
initialize and notifications/initialized | Per-request _meta keys: io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientCapabilities and io.modelcontextprotocol/clientInfo. Servers put io.modelcontextprotocol/serverInfo in each result. |
Mcp-Session-Id header, and list results that varied per connection | Server-minted handles passed as ordinary tool arguments. tools/list, resources/list and prompts/list must now be the same for everyone. |
The HTTP GET endpoint, resources/subscribe and resources/unsubscribe | subscriptions/listen: one long-lived POST-response stream, with clients opting in to toolsListChanged, promptsListChanged, resourcesListChanged or resourceSubscriptions. |
ping, logging/setLevel, notifications/roots/list_changed | Log level set per request through io.modelcontextprotocol/logLevel in _meta. Servers must not emit notifications/message for requests that omitted it. |
Server-initiated roots/list, sampling/createMessage, elicitation/create | Multi Round-Trip Requests: return resultType: "input_required" with an inputRequests field; the client retries the original call carrying inputResponses. |
Last-Event-ID and SSE event IDs | Nothing. A broken response stream loses the in-flight request and the client must re-issue it with a new request ID. |
| Experimental tasks in the core protocol | The io.modelcontextprotocol/tasks extension, with polling through tasks/get and a new tasks/update. tasks/list is gone. |
The audit, in order
- Find every piece of per-connection state. Search your codebase for the session identifier and for anything keyed by connection: cached client capabilities, per-session auth context, in-progress conversation state, a tool list filtered by who is asking. Each one now needs either a handle you mint and hand back as a tool argument, or a row in a real store. A single Postgres table covers most of it, and it goes further than people expect before a second system earns its keep.
- Implement
server/discover. Servers must implement it; clients may call it first for version selection, or use it as a backwards-compatibility probe over stdio. It advertises supported protocol versions, capabilities and identity. - Read version and capabilities from
_metaon every request rather than once at connect time, and returnUnsupportedProtocolVersionErroron a mismatch. - Stamp
resultTypeon every result. It is now required:"complete"for ordinary results,"input_required"for interim ones. Clients must read a missing field from older servers as"complete", which is what keeps existing deployments alive during the transition. - Rewrite anything that called back into the client. If your tool asked for roots, sampling or elicitation mid-call, it now returns early with an input-required result and expects a retry. That changes your handler shape from "await the client" to "return, then resume from what the retry carries", so any state the retry needs has to be encoded in
requestStateor your own store. - Fix hardcoded error codes. Resource-not-found moves from
-32002to-32602. The specification now reserves-32020to-32099for itself and leaves-32000to-32019implementation-defined, which renumbered three draft codes:HeaderMismatchto-32020,MissingRequiredClientCapabilityto-32021andUnsupportedProtocolVersionto-32022. - Add the required headers and check what sits in front of you. Streamable HTTP POST requests must now carry
Mcp-MethodandMcp-Name, so a gateway can route and authorise without parsing the JSON body. If you run a reverse proxy or an API gateway, this is where per-tool authorisation belongs, and it is worth running the same pass over the endpoints you expose while you are in there. - Tighten authorization. Clients must validate the
issparameter from RFC 9207 against the recorded issuer before redeeming an authorization code, must key persisted credentials by issuer, and must re-register when the authorization server changes. Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents, though it stays available for servers that do not support them.
What you can leave alone for a while
The revision also adopted a feature lifecycle policy with a minimum twelve-month deprecation window, and immediately used it on Roots, Sampling and Logging. All three still work. The suggested replacements are worth reading now even if you act later: pass directories and files as tool parameters, resource URIs or server configuration instead of Roots; call your model provider's API directly instead of Sampling; log to stderr over stdio or emit OpenTelemetry instead of the Logging feature. The old HTTP+SSE transport, deprecated since the 2025-03-26 revision, is now formally in the deprecated state as well.
The window matters for planning. Nothing on that list breaks in this release, so the sequence that wastes the least time is: fix the removals, ship, then retire the deprecated features on your own schedule inside the twelve months.
Two changes that pay for themselves
Caching hints are now required on list results. tools/list, prompts/list, resources/list, resources/read and resources/templates/list must return ttlMs and a cacheScope of "public" or "private". Setting a realistic TTL is the cheapest way to stop clients polling you, and cacheScope is what tells a shared gateway whether it may hold the response for other callers.
Deterministic tool ordering. The specification says servers should return tools from tools/list in a stable order, because a list that reshuffles busts the client's prompt cache. If your handler iterates a hash map, sort it. That is a one-line change that shows up on someone else's inference bill.
Trace context is worth wiring at the same time: the revision documents OpenTelemetry conventions for the traceparent, tracestate and baggage keys in _meta, which is how you will follow a request across a stateless fleet once the session identifier is no longer there to group things for you.
Deciding whether this is a this-week job
Two questions settle it. Do you serve remote clients over Streamable HTTP, and do you hold anything in memory between calls? Both yes means the migration is load-bearing and the payoff is real, because the point of a stateless protocol is that you can add a second instance without shared storage. A local stdio server with no per-connection state gets off lightly: implement server/discover, stamp resultType, fix the error codes, and you are current.
One practical brake on moving fast: the blog names updated SDKs for TypeScript, Python, Go and C#, with Rust still in beta, and clients adopt new revisions on their own timetable. Keeping the older revision working while you serve the new one is the difference between a migration and an outage, and the resultType rule for older servers is a hint that the specification expects exactly that overlap. Treat it the way you would treat any provider change you do not control, which is to say with a second path already tested before you need it.
Discussion
Sign in with Google or just a name. No email link, no password to remember.