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 initialize handshake and the Mcp-Session-Id header. Read the protocol version from _meta per request.
  • Implement server/discover — it is mandatory for servers.
  • Add resultType to every result, and ttlMs plus cacheScope to every list and read result.
  • Replace sampling/createMessage, roots/list and elicitation/create callbacks with Multi Round-Trip Requests.
  • Replace resources/subscribe and the HTTP GET endpoint with subscriptions/listen.
  • Renumber four error codes, starting with -32002 to -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

ChangeWhat it doesSEP
Sessions removedDrops Mcp-Session-Id; list endpoints no longer vary per connection.SEP-2567
Handshake removedNo initialize / notifications/initialized; version and capabilities move to per-request _meta.SEP-2575
server/discover addedServers MUST implement it to advertise versions, capabilities and identity.SEP-2575
subscriptions/listenReplaces the HTTP GET endpoint and resources/subscribe / unsubscribe with one long-lived POST-response stream.SEP-2575
Methods removedping, logging/setLevel and notifications/roots/list_changed are gone.SEP-2575
Tasks became an extensionMoves to io.modelcontextprotocol/tasks; tasks/list removed, tasks/result replaced by polling.SEP-2663
MRTR patternReplaces server-initiated requests with InputRequiredResult plus a client retry.SEP-2322
resultType requiredEvery result carries "complete" or "input_required".SEP-2322
SSE resumability removedNo 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:

ErrorWasNow
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:

FeatureMigration pathEarliest removal
RootsPass directories via tool parameters, resource URIs, or server configOn or after 2027-07-28
SamplingIntegrate directly with LLM provider APIsOn or after 2027-07-28
Loggingstderr for stdio; OpenTelemetry for observabilityOn or after 2027-07-28
Dynamic Client RegistrationClient ID Metadata DocumentsOn or after 2027-07-28
includeContext: thisServer / allServersOmit the field or use "none"Follows Sampling
HTTP+SSE transportStreamable HTTPThree 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/read and resources/templates/list now require ttlMs (a freshness hint in milliseconds) and cacheScope ("public" or "private", controlling whether shared intermediaries may cache) via the new CacheableResult interface. These complement, not replace, the existing listChanged notifications. (SEP-2549)
  • New required headers. Streamable HTTP POST requests must carry Mcp-Method and Mcp-Name. Custom headers can be driven from tool parameters via x-mcp-header. (SEP-2243)
  • Full JSON Schema 2020-12. inputSchema and outputSchema accept any 2020-12 keywords — composition, conditionals, $ref — and structuredContent accepts any JSON value. This one is an opportunity rather than a break. (SEP-2106)
  • Deterministic tool order. Servers should return tools from tools/list in a stable order, which lets clients cache and improves LLM prompt-cache hit rates.
  • Authorization hardening. Authorization servers should send the iss parameter per RFC 9207 and clients MUST validate it before redeeming the code (SEP-2468). Clients must declare application_type during 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, tracestate and baggage keys are now documented for _meta. (SEP-414)
  • Extensions field. ClientCapabilities and ServerCapabilities gain an extensions field — this is how you declare things like the tasks extension.

A migration order that works

  1. 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.
  2. Renumber the four error codes. Also mechanical, and easy to miss because the old values still look like valid JSON-RPC codes.
  3. Add what is now required. server/discover, resultType on results, ttlMs and cacheScope on list and read results, Mcp-Method and Mcp-Name on POSTs.
  4. 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.
  5. 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.