Every explanation of the Model Context Protocol written before August 2026 opens the same way. A client connects. It sends initialize. The server replies with its capabilities. The client sends notifications/initialized. From then on there is a session, identified by an Mcp-Session-Id header, and the two ends hold state about each other.

That sequence is no longer in the specification. The revision dated 28 July 2026 removed it, in a change whose own summary line is blunt about the intent: make MCP stateless.

What actually went

The handshake is gone protocol-wide. Instead of negotiating once and remembering, every request now carries its own protocol version and client capabilities, in _meta, under the keys io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities.

The session identifier went with it. Mcp-Session-Id is removed from the Streamable HTTP transport — that being the transport the header lived in — and the list endpoints (tools/list, resources/list, prompts/list) no longer vary per connection. A server that needs state across calls is now expected to mint an explicit handle and pass it as ordinary data, rather than lean on the transport to remember who is asking.

In its place, servers must implement a new server/discover call. Clients may use it for up-front version selection, or as a backward-compatibility probe on stdio.

Several things were deleted outright rather than replaced: ping, logging/setLevel, and notifications/roots/list_changed. The entire category of server-to-client requests is gone — the server can no longer call back to ask the client something mid-request. What replaces them is a retry pattern: the server returns a result marked input_required, and the client re-sends the original request with the answers attached.

⚠️ Stream resumability is also gone. Last-Event-ID and event IDs are removed, so a broken SSE stream no longer picks up where it left off — the in-flight request is lost and the client must re-issue it under a new request ID. If your error handling assumed a dropped connection could be resumed, it now needs to assume the opposite.

And three capabilities are formally deprecated with named migrations: Roots, Sampling and Logging. The suggested replacements are server configuration instead of Roots, talking to an LLM provider's API directly instead of Sampling, and stderr or OpenTelemetry instead of Logging.

The change nobody leads with is the one that matters most

Buried under the governance heading, a maintainer will find the most important change: this revision is the one that gave MCP a deprecation policy at all.

Features now have three states — Active, Deprecated, Removed — with a minimum twelve-month window between a feature being deprecated and the earliest it may be removed, and a registry of deprecated features to look them up in.

Before this, there was no such promise. A feature could simply be absent from the next revision. That is what happened to ping and to the handshake in this release. The awkward, honest reading is that the revision removing the most also created the rule that would have slowed the removal down.

For anyone deciding whether to build on MCP, that governance change is worth more than any individual endpoint. An unversioned protocol with no deprecation policy is a maintenance liability regardless of how good its design is. A twelve-month floor is a different proposition.

The error codes moved, and it is the kind of change that breaks quietly

Three error codes introduced earlier in the same development cycle were renumbered before release:

ErrorDraft codeFinal code
HeaderMismatch−32001−32020
MissingRequiredClientCapability−32003−32021
UnsupportedProtocolVersion−32004−32022

The reason is a new partition of the JSON-RPC server-error range, leaving −32000 to −32019 implementation-defined and grandfathered while reserving −32020 to −32099 for the specification. The draft numbers sat in the range the spec was about to hand back to implementers.

If you tracked the draft and hardcoded any of those three, your error path is now wrong in the worst way: it will run, match nothing, and fall through to a generic handler. There is no type error to catch it.

What this means for what you have already built

Nothing breaks today. An old client and an old server still speak to each other perfectly well, and no end-of-life date has been announced for the previous revision — the release post sets none and the changelog sets none. Anyone telling you there is a migration deadline is inventing it.

What has changed is the cost of writing new tutorials or clients against the old protocol. The tier-1 SDKs — TypeScript, Python, Go and C# — supported the new revision at release, with Rust in beta. So a tutorial written today against initialize is teaching something the current SDKs no longer require and the current spec no longer contains.

⚠️ That includes one of ours. Our guide How to write an MCP server is pinned to the June 2025 revision and walks through the handshake and the sampling callback. Its shape is still a fair description of how a great many deployed servers work — which is why it has not been withdrawn — but it is a description of the older protocol, and this paragraph is here so that nobody finds that out the hard way. Read it as history until it is updated.

We are not going to tell you what share of published servers has moved. We did not count, and a number invented for a sentence like that is worse than the absence of one.

If you are writing a server now

Assume statelessness rather than working around it. The migration that bites hardest is not a single removed endpoint, but the structural change for code that held per-connection state and must now mint explicit handles. That is a design change, not a rename.

Assume a request can be lost. With resumability gone, a dropped stream means re-issuing under a new request ID, so idempotency stops being good hygiene and starts being load-bearing.

And stop calling back to the client. If a tool needs more input, return input_required and let the client come back — the round trip is now the client's to drive.

If you are wiring a local server into a desktop client, our MCP server config builder generates the configuration entry, and it is unaffected by all of this: it produces a stdio launch definition, and the transport-level changes above are Streamable HTTP concerns. For reading the payloads themselves, our JSON formatter handles JSON-RPC envelopes, and it is worth remembering that JSON-RPC error codes are a separate namespace from HTTP status codes — a distinction the new −32020 range makes sharper, not softer.

Where this comes from, and what will date it

Every change above was read from the specification's own changelog for revision 2026-07-28, and the SDK support position from the release announcement. Nothing here rests on secondary coverage, which for this topic is mostly still describing the previous revision.

Two things we checked and cannot tell you: there is no announced sunset for the 2025-11-25 revision, and we did not measure adoption. One thing we verified specifically because the whole premise rested on it — the handshake removal is protocol-level, not scoped to a single transport. The changelog's own wording is "make MCP stateless".

This guide dates on the next revision, and now — unlike before July — a deprecated feature has to be given twelve months' notice before it can go. That is the first time anything in this paragraph has been predictable.