The MCP spec revision is a rewrite wearing a date
Protocol revisions are usually additive. You get some new fields, a deprecation notice with a year to act on it, and a changelog you skim.
The MCP revision dated 2026-07-28 removed the initialize handshake. Not deprecated it. Removed it.
Once you see that, the rest of the changelog stops looking like a list and starts looking like one decision applied everywhere.
The decision
MCP is stateless now.
- Protocol-level sessions are gone, and the
Mcp-Session-Idheader with them.tools/list,resources/listandprompts/listno longer vary per connection. A server that needs state across calls mints an explicit handle and takes it back as an ordinary tool argument. - The
initialize/notifications/initializedhandshake is gone. Every request now carries its own protocol version and client capabilities in_meta. server/discoverarrives to replace what the handshake used to do, and servers must implement it.- Stream resumability is gone. No
Last-Event-ID, no SSE event IDs. A broken response stream loses the in-flight request and the client re-issues it as a new one with a new id. - Server-initiated requests —
roots/list,sampling/createMessage,elicitation/create— are replaced by a retry pattern. The server returns a result saying "I need input", the client retries the original request carrying the answer.
Every one of those is the same move. Nothing is allowed to live between two requests.
There's a second, quieter list: ping, logging/setLevel and notifications/roots/list_changed removed outright, and Roots, Sampling and Logging all deprecated with a twelve-month window. The suggested migrations are blunt — pass files as tool parameters instead of Roots, call the model provider directly instead of Sampling, write to stderr or OpenTelemetry instead of Logging.
What it cost me, which was nothing
I run an MCP connector called Perch. When I went to check the damage, there wasn't any: it's on version 2 of the official SDK, and the handler is configured with legacy: "reject".
const handler = createMcpHandler(() => createServer({ userId: claims.sub }), { legacy: "reject" });
The entire transport migration happened inside a dependency bump I'd already done for an unrelated reason. That's the argument for tracking a protocol through the official SDK rather than a bridge package — the SDK absorbs a rewrite, a bridge package becomes a version conflict.
What it did cost me
Authorization. The same revision deprecates the OAuth 2.0 Dynamic Client Registration Protocol in favour of Client ID Metadata Documents.
Perch registers clients dynamically, on purpose, because MCP clients turn up with no credentials and something has to bootstrap them. That mechanism is now on a clock. It stays available for backwards compatibility with authorization servers that don't do CIMD, and twelve months is a real window, but I built on it eight weeks after it was deprecated and only found out because I went reading the changelog for something else.
There are smaller auth changes with teeth too. Authorization servers should now return the iss parameter, and clients must validate it against the recorded issuer before redeeming a code. Client credentials are explicitly bound to the authorization server that issued them — key them by issuer, never reuse them across servers, re-register when the server changes.
The part worth generalising
Two things.
The first: when a protocol removes a handshake, it isn't tidying up. A handshake exists to establish shared state, so deleting one is a statement that there is no shared state to establish. Read the removals before the additions; they tell you what the maintainers decided.
The second is the boring one. I'm only writing this because I opened a changelog I had no reason to open. The transport rewrite cost me nothing because a library absorbed it. The auth deprecation will cost me real work because no library can absorb a decision about which registration mechanism you chose. Dependencies protect you from the changes you can't see, and not at all from the ones you made yourself.