The MCP 2026-07-28 Migration Guide
The 2026-07-28 revision is the largest change to the Model Context Protocol since launch, and most of it follows from one decision: the protocol is now stateless. That removes the initialize handshake and the Mcp-Session-Id header, replaces server-initiated requests with a retry-based pattern, collapses subscriptions into a single stream, and forces a new required field onto every result. This guide works through all nine major changes, all twelve minor ones, and the six newly-deprecated features, with the migration path and the SEP behind each. Check your own code against the list with the MCP spec migration checker.
TL;DR
MCP 2026-07-28 makes the protocol stateless and everything else follows. The short version of what you must change:
- Delete the
initializehandshake and theMcp-Session-Idheader. Read the protocol version from_metaper request. - Implement
server/discover— it is mandatory for servers. - Add
resultTypeto every result, andttlMspluscacheScopeto every list and read result. - Replace
sampling/createMessage,roots/listandelicitation/createcallbacks with Multi Round-Trip Requests. - Replace
resources/subscribeand the HTTP GET endpoint withsubscriptions/listen. - Renumber four error codes, starting with
-32002to-32602. - Stop adopting Roots, Sampling, Logging and Dynamic Client Registration — all now on a twelve-month clock.
Paste your source into the MCP spec migration checker to see which of these are actually in your code.
Why the protocol went stateless
Under 2025-11-25, an MCP session was a real thing in the transport. A client opened a connection, ran initialize, received an Mcp-Session-Id, and every later request rode that session. The server could therefore keep per-connection state, and the list endpoints — tools/list, resources/list, prompts/list — were allowed to vary per connection.
That design does not survive contact with ordinary HTTP infrastructure. Any horizontally-scaled deployment needed sticky routing or a shared session store just to keep the protocol coherent, and nothing could be cached at an intermediary because the response depended on who was asking. SEP-2567 removes protocol-level sessions and the session header; SEP-2575 removes the handshake that created them.
What replaces them is explicitness. Every request now carries its own protocol version and client capabilities in _meta, under io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities. Clients should identify themselves per request via io.modelcontextprotocol/clientInfo, and servers should identify themselves in each result's _meta via io.modelcontextprotocol/serverInfo. A server that genuinely needs cross-call state mints an explicit handle and passes it back as an ordinary tool argument — the state becomes visible in the protocol instead of hidden in the connection.
The nine major changes
| Change | What it does | SEP |
|---|---|---|
| Sessions removed | Drops Mcp-Session-Id; list endpoints no longer vary per connection. | SEP-2567 |
| Handshake removed | No initialize / notifications/initialized; version and capabilities move to per-request _meta. | SEP-2575 |
server/discover added | Servers MUST implement it to advertise versions, capabilities and identity. | SEP-2575 |
subscriptions/listen | Replaces the HTTP GET endpoint and resources/subscribe / unsubscribe with one long-lived POST-response stream. | SEP-2575 |
| Methods removed | ping, logging/setLevel and notifications/roots/list_changed are gone. | SEP-2575 |
| Tasks became an extension | Moves to io.modelcontextprotocol/tasks; tasks/list removed, tasks/result replaced by polling. | SEP-2663 |
| MRTR pattern | Replaces server-initiated requests with InputRequiredResult plus a client retry. | SEP-2322 |
resultType required | Every result carries "complete" or "input_required". | SEP-2322 |
| SSE resumability removed | No Last-Event-ID, no event IDs; a broken stream means re-issuing the request. | SEP-2575 |
Multi Round-Trip Requests, in practice
This is the change most likely to require real design work. Previously a server could interrupt its own request handling to ask the client for something: roots/list for permitted directories, sampling/createMessage to borrow the client's model, elicitation/create to ask the user a question. All three are server-initiated requests, and all three assume a live connection the server can address — exactly what statelessness removes.
Under MRTR, the server does not call back. It returns an interim result:
{
"resultType": "input_required",
"inputRequests": [
{ "type": "elicitation", "message": "Which project should I deploy?" }
],
"requestState": "deploy-flow-7c21"
}
The client collects the answers and retries the original request, this time carrying inputResponses. Because the outcome is learned by retrying rather than by notification, notifications/elicitation/complete and the elicitationId field — both added only in 2025-11-25 — are removed. A server that needs to correlate an out-of-band interaction across retries encodes its own identifier in requestState, as above.
The knock-on effect is the new required resultType field on all results, since a client must be able to tell an interim result from a final one. For backwards compatibility, clients MUST treat a result from an earlier-protocol server that omits the field as "complete".
Error codes: four renumbered, one policy added
The revision defines an allocation policy for the JSON-RPC server-error range. -32000 to -32019 remains implementation-defined, and existing SDK usage there is explicitly grandfathered; -32020 to -32099 is now reserved for the specification itself. Four codes move as a result:
| Error | Was | Now |
|---|---|---|
| Resource not found | -32002 | -32602 |
HeaderMismatch | -32001 | -32020 |
MissingRequiredClientCapability | -32003 | -32021 |
UnsupportedProtocolVersion | -32004 | -32022 |
Resource-not-found is the one most likely to be hard-coded in client error handling, and it moves furthest — to the standard JSON-RPC Invalid Params code rather than into the new MCP block. HeaderMismatchError is also added to the schema proper, having previously existed only in transport prose.
The first formal deprecation window
SEP-2596 gives MCP a feature lifecycle policy for the first time: features are Active, Deprecated or Removed, with a minimum twelve-month deprecation window and a public registry. Six things enter the Deprecated state:
| Feature | Migration path | Earliest removal |
|---|---|---|
| Roots | Pass directories via tool parameters, resource URIs, or server config | On or after 2027-07-28 |
| Sampling | Integrate directly with LLM provider APIs | On or after 2027-07-28 |
| Logging | stderr for stdio; OpenTelemetry for observability | On or after 2027-07-28 |
| Dynamic Client Registration | Client ID Metadata Documents | On or after 2027-07-28 |
includeContext: thisServer / allServers | Omit the field or use "none" | Follows Sampling |
| HTTP+SSE transport | Streamable HTTP | Three months after SEP-2596 is Final |
Note that "earliest removal" marks eligibility, not a scheduled date — actual removal is a Core Maintainer decision taken during release preparation and may land later. Nothing here stops working on 28 July 2026.
The minor changes worth acting on
- Caching becomes first-class.
tools/list,prompts/list,resources/list,resources/readandresources/templates/listnow requirettlMs(a freshness hint in milliseconds) andcacheScope("public"or"private", controlling whether shared intermediaries may cache) via the newCacheableResultinterface. These complement, not replace, the existinglistChangednotifications. (SEP-2549) - New required headers. Streamable HTTP POST requests must carry
Mcp-MethodandMcp-Name. Custom headers can be driven from tool parameters viax-mcp-header. (SEP-2243) - Full JSON Schema 2020-12.
inputSchemaandoutputSchemaaccept any 2020-12 keywords — composition, conditionals,$ref— andstructuredContentaccepts any JSON value. This one is an opportunity rather than a break. (SEP-2106) - Deterministic tool order. Servers should return tools from
tools/listin a stable order, which lets clients cache and improves LLM prompt-cache hit rates. - Authorization hardening. Authorization servers should send the
issparameter per RFC 9207 and clients MUST validate it before redeeming the code (SEP-2468). Clients must declareapplication_typeduring registration to avoid OIDC redirect conflicts (SEP-837). Credentials must be keyed by issuer, never reused across authorization servers, and re-registered when the authorization server changes (SEP-2352). - Tracing conventions. OpenTelemetry
traceparent,tracestateandbaggagekeys are now documented for_meta. (SEP-414) - Extensions field.
ClientCapabilitiesandServerCapabilitiesgain anextensionsfield — this is how you declare things like the tasks extension.
A migration order that works
- Scan for removed identifiers first.
Mcp-Session-Id,initialize,ping,logging/setLevel,tasks/list,tasks/result,resources/subscribe,Last-Event-ID,elicitationId. These are mechanical and safe to do in one pass. - Renumber the four error codes. Also mechanical, and easy to miss because the old values still look like valid JSON-RPC codes.
- Add what is now required.
server/discover,resultTypeon results,ttlMsandcacheScopeon list and read results,Mcp-MethodandMcp-Nameon POSTs. - Then do the design work. Any place you held state across calls, and any server-initiated request, needs rethinking — explicit handles for the former, MRTR for the latter. This is the part that is not find-and-replace.
- Leave deprecations for last. Roots, Sampling, Logging, DCR and HTTP+SSE all keep working for at least twelve months. Migrating them now is good hygiene, not urgency.
The MCP spec migration checker automates steps one through three against pasted source, and flags the step-four candidates it can detect textually.
Frequently Asked Questions
When does the MCP 2026-07-28 specification take effect?
The final specification publishes on 28 July 2026, superseding 2025-11-25. The release candidate was locked on 21 May 2026, which opened a ten-week validation window for SDK maintainers and client implementers; Tier 1 SDKs are expected to ship support within it. Because the protocol version now travels per-request in _meta rather than being negotiated once, servers can support both revisions simultaneously during the transition — a mismatch returns UnsupportedProtocolVersionError.
What is the single biggest change in MCP 2026-07-28?
The protocol became stateless. Protocol-level sessions and the Mcp-Session-Id header are removed (SEP-2567), and the initialize / notifications/initialized handshake is removed entirely (SEP-2575). Every request now carries its own protocol version and client capabilities in _meta, so any request can be routed to any server instance without sticky sessions or a shared session store. Nearly every other change in the revision — Multi Round-Trip Requests, the required resultType field, the mandatory server/discover RPC, the collapse of subscriptions into one stream — follows from that decision.
Do I have to rewrite my MCP server for 2026-07-28?
If it is a stdio server that only exposes tools, the change is modest: drop the initialize handler, implement server/discover, add resultType to results, and add ttlMs and cacheScope to tools/list. If it is an HTTP server that relies on sessions, resumable SSE streams, or server-initiated requests like sampling/createMessage, the change is substantial — session state has to become explicit server-minted handles passed as tool arguments, and callbacks have to become Multi Round-Trip Requests. Most of the mechanical work is find-and-replace on identifiers the spec renamed; the hard part is any place where you stored state in the transport.
Are Roots, Sampling and Logging removed in 2026-07-28?
No — they are deprecated, not removed. The revision also introduces MCP's first formal feature lifecycle policy, which guarantees a minimum twelve-month deprecation window. Roots, Sampling, Logging and Dynamic Client Registration all become Deprecated on 28 July 2026, with the earliest possible removal being the first revision released on or after 28 July 2027, and actual removal a separate maintainer decision. They keep working in the meantime; new implementations should simply not adopt them.
Which MCP error codes were renumbered?
Four. Resource-not-found moves from -32002 to -32602 (Invalid Params) to align with JSON-RPC. Then a new allocation policy partitions the server-error range — -32000 to -32019 stays implementation-defined with existing SDK usage grandfathered, and -32020 to -32099 is reserved for the specification — which moves HeaderMismatch from -32001 to -32020, MissingRequiredClientCapability from -32003 to -32021, and UnsupportedProtocolVersion from -32004 to -32022.
What replaced sampling/createMessage and elicitation/create?
The Multi Round-Trip Requests (MRTR) pattern, introduced by SEP-2322. Server-initiated requests assume a live identified connection, which a stateless protocol cannot guarantee, so instead of calling back to the client the server returns an InputRequiredResult with resultType: "input_required", whose inputRequests field carries what it needs. The client then retries the original request with inputResponses attached. This affects roots/list, sampling/createMessage and elicitation/create alike, and it is why notifications/elicitation/complete and the elicitationId field were both deleted — the client now learns the outcome by retrying, so there is nothing to notify.