Large Language ModelsGenerate imagesGenerate videos
Model Context Protocol Specification Changes: What's New and What Breaks
The 2026-07-28 revision of the Model Context Protocol makes MCP stateless: no initialize handshake, no Mcp-Session-Id, new routing headers, cacheable lists, and a retry pattern for elicitation. This lists every change, what breaks, and the order to migrate.
If your MCP server remembers anything between two requests, the newest revision of the protocol just turned that habit into a bug. The 2026-07-28 specification removes the initialize handshake, deletes the Mcp-Session-Id header, and rebuilds MCP as a plain request and response protocol where every message carries what the server needs to answer it. Some changes are small: a header here, a renumbered error code there. Others will break a server that worked fine last week.
This article sorts the changes by how much they hurt, using the official changelog and the announcement post as the source of truth. You get exact field names, SEP numbers, a table of what is removed versus merely deprecated, and a migration order you can finish in a single sprint.
💡 Short version: sessions are gone, server-initiated requests now use a retry pattern, list results are cacheable, authorization is stricter, and tasks live in an extension. Roots, Sampling, and Logging still work, but only for twelve months at minimum.
Why This Revision Is Different
Earlier revisions added features. This one removes assumptions. The move from a stateful, bidirectional connection to a stateless exchange touches every transport, every SDK, and every gateway sitting in front of a server.
Five Revisions, One Direction
Version
Headline change
2024-11-05
Client-server architecture, JSON-RPC 2.0, tools, resources, prompts, stdio and HTTP with SSE
Stateless core, no sessions, Multi Round-Trip Requests, routing headers, cacheable lists, extensions framework
Read down the right column and the direction is obvious. Each revision moves MCP away from a long-lived, chat-like socket and toward something a load balancer, a CDN, and a serverless runtime can handle without special treatment.
The release itself is already supported where it matters. All four Tier 1 SDKs (TypeScript, Python, Go, and C#) speak 2026-07-28, and the Rust SDK supports it in beta. The maintainers admit there will be "some migration cost, especially for developers that did depend on session identifiers," then add that early testing feedback made the process easier.
The Stateless Core
Two proposals do most of the damage: SEP-2567 removes sessions, and SEP-2575 removes the handshake and reshapes how notifications flow.
No Handshake, No Session ID
The initialize request and notifications/initialized are gone, and so is Mcp-Session-Id. List endpoints (tools/list, resources/list, prompts/list) can no longer vary per connection, because there is no connection identity left to vary on. When a tool needs state across calls, the server mints an explicit handle, such as a cart ID or a workspace ID, and the model passes it back as an ordinary tool argument.
In place of the handshake, every request carries its own context in _meta. This sketch shows the shape, not a copy from the spec:
A version mismatch returns UnsupportedProtocolVersionError, and servers identify themselves in each result through io.modelcontextprotocol/serverInfo.
⚠️ Hidden state is the real risk. In-memory maps indexed by session ID, per-connection log levels, and per-connection subscription lists all stop working. They are easy to miss in a code search because they rarely contain the word "session."
Advertising versions and capabilities. Servers must now implement a new RPC in the server/ namespace that reports their supported protocol versions, capabilities, and identity. Clients may call it before anything else to pick a version up front, and on STDIO it works as a backward-compatibility probe. The changelog lists it directly under the handshake removal, so check the schema page for the exact method name and response shape before you wire it in.
What Replaces the GET Stream
The HTTP GET endpoint, resources/subscribe, and resources/unsubscribe are replaced by a single call: subscriptions/listen. It opens one long-lived POST-response stream, and clients opt in to the change types they care about:
toolsListChanged
promptsListChanged
resourcesListChanged
resourceSubscriptions
The server acknowledges and tags each notification with io.modelcontextprotocol/subscriptionId. Request-scoped messages such as notifications/progress and notifications/message stay on the response stream of the request they belong to.
Three more removals ride along: ping, logging/setLevel, and notifications/roots/list_changed. Log level is now set per request through io.modelcontextprotocol/logLevel, and a server must not emit notifications/message for a request that lacks it.
SSE resumability is gone too. There is no Last-Event-ID header and no event IDs, so a broken response stream loses the in-flight request, and the client must send it again with a new request ID. A tool call that runs for ninety seconds over a flaky link now needs the tasks extension, not luck.
Multi Round-Trip Requests Explained
Why Server Requests Had to Go
Before this revision, a server could send elicitation/create, sampling/createMessage, or roots/list in the middle of a call over an open stream. That pinned the client to one server instance and demanded sticky load balancing or shared storage.
Multi Round-Trip Requests (SEP-2322) replace that design. The spec is blunt about it: servers must send these requests through the MRTR pattern, the old pattern is no longer supported, and this is a breaking change. Every result also now carries a required resultType field. One value is input_required, the other marks an ordinary finished result, and clients treat a missing field from an older server as the ordinary kind.
How the Retry Loop Works
The flow is four steps:
The client sends a normal request, for example tools/call.
The server cannot finish, so it returns an InputRequiredResult listing what it needs.
The client gathers the answers from the user or another source.
The client retries the original request with inputResponses attached, using a new JSON-RPC ID.
Only three client requests may receive this result: prompts/get, resources/read, and tools/call. Every InputRequiredResult needs at least one of inputRequests or requestState, and a server must not ask for a capability the client never declared. Because the retry tells the client how things ended, the elicitation completion notification and the elicitationId field from 2025-11-25 are removed.
Guard requestState Like User Input
The requestState string travels through the client, so the spec says to treat it as attacker-controlled. If it influences authorization, resource access, or business logic, protect its integrity with an HMAC or AEAD and reject anything that fails verification.
For replay protection, put three things inside the protected payload and verify each on receipt:
the authenticated principal
a short expiry
an identifier for the originating request, such as the method name and a digest of its main parameters
These measures bound replay but do not guarantee single use. A one-time redemption must be enforced on the server.
Headers, Caching, and Error Codes
Routing Headers for Gateways
SEP-2243 requires Mcp-Method and Mcp-Name on every Streamable HTTP POST request. The point is operational: gateways and WAFs can now route, meter, and rate-limit MCP traffic without parsing a JSON body. Custom headers can also be derived from tool parameters through x-mcp-header, and a mismatch between header and body surfaces as a HeaderMismatch error, which now exists in the schema.
💡 If you run an API gateway in front of an MCP server, this is the change that pays off first. Write rules on the two headers instead of regexes on request bodies.
Cache Hints and Error Codes
SEP-2549 adds a CacheableResult interface. Results from tools/list, prompts/list, resources/list, resources/read, and resources/templates/list must now include:
ttlMs: a freshness hint in milliseconds, so clients can cache instead of polling
cacheScope: "public" or "private", which tells shared intermediaries whether they may store the response
Both complement the existing listChanged notifications. Servers should also return tools in a deterministic order, which helps client caches and raises prompt cache hit rates on the model side.
Error codes moved as well, under a new allocation policy: -32000 to -32019 stays implementation-defined, and -32020 to -32099 is reserved for the specification.
Error
Old code
New code
Resource not found
-32002
-32602 (Invalid Params)
HeaderMismatch
-32001
-32020
MissingRequiredClientCapability
-32003
-32021
UnsupportedProtocolVersion
-32004
-32022
If a client branches on the old numbers, it will misread the new errors.
Authorization Gets Stricter
Issuer Checks and Bound Credentials
Three changes tighten the OAuth flow:
SEP-2468: authorization servers should include the iss parameter from RFC 9207, and clients must validate a present iss against the recorded issuer before redeeming the authorization code.
SEP-2352: client credentials are bound to the authorization server that issued them. Persist them by issuer identifier, never reuse them with a different server, and register again when the server changes.
SEP-837: clients must send a suitable application_type during Dynamic Client Registration, which prevents OpenID Connect redirect URI conflicts on localhost.
Dynamic Registration Is on the Way Out
The OAuth 2.0 Dynamic Client Registration protocol (RFC 7591) is deprecated in favor of Client ID Metadata Documents. It keeps working for authorization servers that lack the newer option, but the announcement says it will be removed in a later version of the spec. Plan the switch now rather than during an incident.
Tasks, Schemas, and Extensions
Tasks Move to an Extension
Experimental tasks left the core protocol and became the official io.modelcontextprotocol/tasks extension (SEP-2663). The redesign swaps the blocking tasks/result method for polling with tasks/get, adds tasks/update so a client can send input to a running task, and drops tasks/list. Servers may now return a task handle without the client asking for one.
That last point matters for long jobs. A slow report generator no longer has to hold a response stream open; it returns a handle, the client polls, and a dropped connection costs nothing.
Looser Schemas and New Extension Slots
Smaller additions worth a line each:
inputSchema and outputSchema may use any JSON Schema 2020-12 construct, and structuredContent may be any JSON value (SEP-2106), with new rules for $ref resolution and resource bounds on composition constructs.
ClientCapabilities and ServerCapabilities gain an extensions field for optional features beyond the core.
OpenTelemetry trace context travels in _meta through traceparent, tracestate, and baggage (SEP-414).
Cloudflare's write-up on the release notes that its /mcp endpoint accepts both the new stateless requests and 2025-era clients, which is a sensible pattern for anyone running a public server.
What Breaks and How to Fix It
Removed Features
These will fail outright against a 2026-07-28 peer:
Removed
Replacement
initialize and notifications/initialized
Per-request _meta fields
Mcp-Session-Id header
Server-minted handles in tool arguments
HTTP GET endpoint, resources/subscribe, resources/unsubscribe
Deprecated is not removed. These still work for at least twelve months under the new feature lifecycle policy (SEP-2596), which defines Active, Deprecated, and Removed states and a public registry:
Deprecated
Suggested move
Roots (SEP-2577)
Tool parameters, resource URIs, or server configuration
Sampling (SEP-2577)
Call the LLM provider API directly
Logging (SEP-2577)
Write to stderr, or use OpenTelemetry
HTTP+SSE transport
Streamable HTTP
includeContext values "thisServer" and "allServers"
"none" or omit the field
Dynamic Client Registration
Client ID Metadata Documents
Some write-ups lump Roots, Sampling, and Logging in with the removals. The spec text says deprecated, so treat them as a calendar item, not an outage. Only ping and logging/setLevel are actually gone.
A Migration Order That Works
Upgrade the SDK first. Move to a Tier 1 release that supports 2026-07-28 and read its migration notes before touching your own code.
Hunt for session state. Search for Mcp-Session-Id and for any map indexed by connection. Replace each with an explicit handle passed as a tool argument.
Accept both generations. Serve old and new clients from the same endpoint during the transition, as Cloudflare does.
Rewrite server-initiated calls. Turn every elicitation, sampling, and roots call into an InputRequiredResult with a signed requestState.
Add cache fields. Return ttlMs and cacheScope on list and read results, and sort tool lists deterministically.
Update gateway rules. Route on Mcp-Method and Mcp-Name, then retire body-parsing rules.
Fix authorization. Validate iss, store credentials per issuer, and schedule the move to Client ID Metadata Documents.
Then test the ugly cases: kill a response stream mid-request, replay a stale requestState, and run two server instances with no sticky routing. If all three behave, the migration holds.
💡 Speed tip: paste your server's session-handling code and the changelog section above into Claude Sonnet 5 on Picasso IA and ask for a list of every place state leaks across requests. Review the output yourself, since a model can miss a map hidden behind a helper function.
Make Your Own Visuals
Technical posts like this one live or die on their diagrams and header images, and you do not need a design team to produce them. Picasso IA puts dozens of image models in one place, so you can test the same prompt across several and keep the best result.
Try Seedream 5 Pro for photorealistic scenes, GPT Image 2 for precise prompt following, or Ideogram v4 Quality when your image needs readable text. Describe the scene, pick a 16:9 ratio, and generate a few variations before you commit.
Open Picasso IA, choose a model, and make the first image for your next MCP write-up today.