What the MCP 2026-07-28 revision is
The MCP 2026-07-28 revision is the latest specification of the Model Context Protocol, the common standard that connects AI models to external tools. The official changelog states explicitly that it lists changes made since the previous revision, 2025-11-25.
MCP is a shared contract for how an AI model reaches external tools and data. Build a server that follows it and any compatible AI tool can call it the same way. Service providers increasingly ship their own, as with the official MCP server X released.
The theme is removing state
One principle runs through this revision: take the machinery for holding state across connections out of the protocol itself.
MCP used to keep conversational state between client and server. You connected, exchanged capabilities via initialize, and everything after that ran on top of that shared assumption, with a session ID carried along.
The 2026-07-28 revision drops that. Every request now stands on its own.
What was removed
The list of removals is long.
Main removals in the 2026-07-28 revision (per the official changelog)
| Removed | Replacement |
|---|---|
Protocol-level sessions / Mcp-Session-Id header | Server-minted handles passed as ordinary tool arguments |
initialize / notifications/initialized handshake | Each request declares version and capabilities in _meta |
HTTP GET endpoint / resources/subscribe and friends | subscriptions/listen (a single long-lived POST-response stream) |
ping / logging/setLevel / notifications/roots/list_changed | Log level set per request via _meta |
Server-initiated requests (sampling/createMessage etc.) | The MRTR pattern (return a result, receive the answer on retry) |
SSE stream resumability / message redelivery (Last-Event-ID) | Re-issue with a new request ID |
All of these appear under "Major changes," and all of them touch existing implementations.
This document lists changes made to the Model Context Protocol (MCP) specification since the previous revision, 2025-11-25. / Remove 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. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments (SEP-2567). — From the statements on which revision this covers, and on the removal of sessions
What removing sessions actually changes
This is the part that hits implementations hardest.
State travels as a tool argument
Once sessions are gone, you need somewhere to put state that has to survive across calls. The specification gives an answer.
Servers that need cross-call state mint explicit handles and pass them as ordinary tool arguments. Instead of the protocol quietly carrying state behind the scenes, the reference to that state becomes a visible argument.
Fewer implicit assumptions make it easier to see what is going on. In exchange, defining and moving those state values is now the responsibility of whoever designs the server and its tools.
Checking the structure of the values you pass as arguments saves time while you build.
Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments (SEP-2567). — From the statement on passing cross-call state as handles in ordinary arguments
Lists no longer vary per connection
An easy one to miss: list behavior changed. tools/list, resources/list, and prompts/list no longer return different results depending on the connection.
That is a constraint and an advantage at the same time. If the same query returns the same answer, it can be cached. The specification leans into this: servers SHOULD return tools/list results in a deterministic order, and the stated reason is to enable client-side caching and improve LLM prompt cache hit rates.
On top of that, results from the list endpoints and resources/read now require two fields, ttlMs and cacheScope. ttlMs is a freshness hint in milliseconds so clients can cache responses and poll less. cacheScope is either public or private and controls whether shared intermediaries may store the response.
Servers SHOULD return tools from tools/list in a deterministic order to enable client-side caching and improve LLM prompt cache hit rates. / Require ttlMs and cacheScope fields on results returned by tools/list, prompts/list, resources/list, resources/read, and resources/templates/list via a new CacheableResult interface. ttlMs is a freshness hint (in milliseconds) allowing clients to cache responses and reduce polling; cacheScope ("public" or "private") controls whether shared intermediaries may cache the response. — From the statements on deterministic list ordering and the new caching fields
Version negotiation happens on every request
With initialize gone, version negotiation had to move somewhere.
Every request carries its protocol version and client capabilities in _meta. Clients SHOULD identify themselves on each request, and servers SHOULD identify themselves in each result's _meta. A mismatch returns UnsupportedProtocolVersionError.
A new call, server/discover, was added alongside it. Servers MUST implement it. It advertises supported protocol versions, capabilities, and identity. Clients may call it before anything else to pick a version up front, or use it as a backward-compatibility probe on STDIO (the transport that talks over standard input and output).
Make MCP stateless: remove the initialize/notifications/initialized handshake. Every request now carries its protocol version and client capabilities in _meta (io.modelcontextprotocol/protocolVersion, io.modelcontextprotocol/clientCapabilities). Clients SHOULD identify themselves on each request (io.modelcontextprotocol/clientInfo), and servers SHOULD identify themselves in each result's _meta (io.modelcontextprotocol/serverInfo). Version mismatches return UnsupportedProtocolVersionError (SEP-2575). / Add server/discover: servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity. Clients MAY call it before any other request for up-front version selection, or use it as a backward-compatibility probe on STDIO (SEP-2575). — From the statements on going stateless and on adding server/discover
How servers ask questions now
The other major design shift is the removal of server-initiated requests.
MRTR, the new shape of the exchange
Previously, when a server needed extra information mid-processing, it sent its own request back to the client: roots/list, sampling/createMessage, or elicitation/create. That is replaced by a pattern called MRTR (Multi Round-Trip Requests).
The new flow is different. The server returns a result. But that result's resultType is input_required, and its inputRequests field carries the requests for what it needs in order to continue. The client reads that and re-sends the original request with inputResponses attached.
Rather than the server seizing the conversation and interrupting, it hands back control and waits to be asked again with the missing pieces. The effect is that the mapping between request and response stays simple.
Asking for more information: the old way versus MRTR
| Stage | Previously (server-initiated) | MRTR |
|---|---|---|
| Trigger | Server pauses and sends a request | Server returns a result (resultType is input_required) |
| Where the ask lives | roots/list, sampling/createMessage, elicitation/create | The result's inputRequests field |
| Client's answer | Reply to the server's request | Re-send the original request with inputResponses |
| Mapping | Server-initiated round trips nest inside the call | Still one response per request |
To support this, every result now requires a resultType field. Ordinary results are complete; MRTR interim results are input_required. If a result comes back from an earlier-protocol server without the field, clients MUST treat it as complete.
Multi Round-Trip Requests (MRTR) pattern introduced which replaces the previous approach of sending server-initiated requests, such as roots/list, sampling/createMessage, or elicitation/create. Servers return an InputRequiredResult (resultType: "input_required") whose inputRequests field carries the requests for the additional information needed to process the request. Clients respond with inputResponses on a retry of the original request providing the requested information. (SEP-2322). / All results now carry a required resultType field: "complete" for ordinary results and "input_required" for multi round-trip request interim results. Clients MUST treat results from earlier-protocol servers that omit the field as "complete" (SEP-2322). — From the statements introducing MRTR and requiring the resultType field
Notifications were reorganized too
The mechanism for announcing changes was rebuilt. The HTTP GET endpoint and resources/subscribe / resources/unsubscribe were replaced by subscriptions/listen, a single long-lived POST-response stream.
Clients opt in to the types they want: tools list changed, prompts list changed, resources list changed, and resource subscriptions. The server acknowledges and tags notifications with a subscription ID.
One distinction matters: request-scoped notifications do not flow on that stream. Progress and message notifications continue to travel on the response stream of the request they belong to. Monitoring notifications and in-flight notifications now take separate paths.
Replace the HTTP GET endpoint and resources/subscribe/resources/unsubscribe with subscriptions/listen: a single long-lived POST-response stream for opted-in server-to-client change notifications. Clients opt in to specific types (toolsListChanged, promptsListChanged, resourcesListChanged, resourceSubscriptions); the server acknowledges and tags notifications with io.modelcontextprotocol/subscriptionId. Request-scoped notifications such as notifications/progress and notifications/message continue to flow on the response stream of the request they relate to, not the subscriptions/listen stream (SEP-2575). — From the statements on the new notification path and on request-scoped notifications
Long-running work moved to an official extension
Tasks, the experimental mechanism for long-running work, moved out of the core protocol and into an official extension.
The design was reworked as well. The blocking tasks/result method is gone, replaced by polling via tasks/get. A new tasks/update carries client-to-server input, tasks/list was removed, and servers may now return task handles unsolicited, without per-request opt-in.
Move experimental tasks out of the core protocol and into an official extension (io.modelcontextprotocol/tasks). The redesigned extension replaces the blocking tasks/result method with polling via tasks/get and a new tasks/update for client-to-server input, removes tasks/list, and allows servers to return task handles unsolicited without per-request opt-in (SEP-2663). — From the statement on moving tasks into an extension and redesigning it
What to check if you already run an implementation
If you have an MCP server or client in production, you need to scope the impact.
The parts most likely to break
Start with anything that depends on the session ID. A server that reads the Mcp-Session-Id header to look up state will not work as written. If you need state, rewrite it so the server mints a handle and the tool takes it as an argument.
Next, recovery from a dropped connection. SSE stream resumability was removed, so you cannot resume mid-stream with Last-Event-ID. A break loses the in-flight request, so you have to issue it again with a new request ID. Check whether your design assumes retries are possible.
Then the removed calls. ping, logging/setLevel, and notifications/roots/list_changed are all gone. Log level moved to a per-request _meta field, and servers MUST NOT emit message notifications for requests that did not include it.
Remove ping, logging/setLevel, and notifications/roots/list_changed. Log level is now set per-request via io.modelcontextprotocol/logLevel in _meta; servers MUST NOT emit notifications/message for requests that did not include this field (SEP-2575). / Remove SSE stream resumability and message redelivery (the Last-Event-ID header and SSE event IDs) from the Streamable HTTP transport. A broken response stream loses the in-flight request; clients MUST re-issue it as a new request with a new request ID (SEP-2575). — From the statements on removed calls and on dropping stream resumability
Smaller changes that still bite
Beyond the major changes, several adjustments affect implementations.
The error code for a missing resource changed from -32002 to -32602 (Invalid Params), aligning with the JSON-RPC specification (the JSON-based remote procedure call convention MCP is built on). An allocation policy for error codes was also defined: -32000 to -32019 stays implementation-defined, while -32020 to -32099 is reserved for the MCP specification.
Schema constraints were loosened, too. inputSchema and outputSchema now accept any JSON Schema 2020-12 keyword, and structuredContent accepts any JSON value.
When you are working through schemas and error payloads, it helps to check the structure as you go.
Change resource not found error code from -32002 to -32602 (Invalid Params) to align with JSON-RPC specification. / Define an error code allocation policy partitioning the JSON-RPC server-error range: -32000 to -32019 remains implementation-defined (existing SDK usage is grandfathered), -32020 to -32099 is reserved for the MCP specification. / Loosen inputSchema and outputSchema to allow any JSON Schema 2020-12 keywords, and structuredContent to allow any JSON value. — From the statements on the error code change, the allocation policy, and looser schema constraints
Wrapping up: complexity the protocol absorbed has moved to implementations
The MCP 2026-07-28 revision comes down to one sentence: the protocol stopped managing state. Sessions and the initialization handshake are gone, and every request now stands on its own.
The upside is easy to see. Independent requests are easier to cache and easier to load-balance. Lists that no longer vary per connection, a recommended deterministic order, and cache lifetimes carried in the results themselves all push in that direction.
The flip side is that the complexity the protocol quietly absorbed did not disappear — it moved into implementations. If you need state, design a handle and pass it as an argument. If a connection drops, send it again. Things you never had to think about are now part of your server and client design.
If you already run an implementation, the starting point is your dependence on session IDs and your recovery path after a dropped connection. The full list of removed calls lives in the official changelog, which is the reliable place to check.
The same theme — that the plumbing decides the outcome — runs through the case where harness settings tripled a benchmark score and how context engineering is approached. If you want to try an MCP server first-hand, the official MCP server X released is an approachable entry point.



