At 16:47 UTC on July 28, the MCP revision I had been following became the stable 2026-07-28 specification.
That label settled the wire contract. It did not show whether SDKs agreed at the edges, whether the ecosystem had absorbed the revision, or whether its deployment claims had independent performance and security evidence behind them.
The useful claim is narrower: MCP cut the protocol session cord. Requests can now describe the context needed to process them instead of relying on an earlier handshake. That improves deployment. It also makes the remaining state harder to pretend is somebody else's problem.
Before: protocol context lived across a connection
Under the 2025-11-25 lifecycle, a client began by sending initialize with its protocol version, capabilities, and identity. The server selected a version and returned its own capabilities and identity. The client then sent notifications/initialized before normal operations could begin.
Streamable HTTP could add an Mcp-Session-Id that had to follow later requests. SSE could carry server-to-client traffic, with event IDs and Last-Event-ID supporting resumability. The connection established shared context and later calls depended on it.
That dependency leaked into deployment topology. If the next request reached a different server instance, the new instance needed the negotiated state. Teams dealt with that using sticky routing, a shared session store, or an implementation that quietly assumed one process would stay alive long enough.

None of those choices are inherently stupid. The problem is that protocol state was easy to mistake for application state and easy to leave hidden in the connection.
After: requests carry their own protocol context
The 2026-07-28 core removes initialize, notifications/initialized, and the protocol-level Mcp-Session-Id.
Every request now carries its protocol version and client capabilities in _meta. Client identity is optional but recommended. HTTP requests also expose protocol version, MCP method, and, where applicable, the tool or resource name in headers. Servers must support server/discover, though clients can call an RPC directly and handle an unsupported-version error instead.
Server identity can ride on each result. Both client and server identities are self-reported display information. The specification is explicit that they must not be used for security decisions, which is the sort of sentence I expect somebody to violate within a week if it is not nailed into the authorization layer.
Server-to-client work changes shape too. Multi Round-Trip Requests return resultType: input_required; the client then retries the original operation with the requested input. The request remains the unit the server can interpret.

Modern MCP repeats enough context for one operation to be routed and validated independently. Some facts now appear in both headers and the body; HeaderMismatch exists because they can disagree.
Why teams wanted this
Once the application dependencies behind an ordinary request are durable and reachable, any instance can handle it. Because version and capability context travel with each request, a restart does not need to recover negotiated protocol state. It can still lose in-flight work and any application state that was not made durable.
Because MCP exposes method and, where applicable, name headers, infrastructure configured to inspect them can route, meter, or audit requests without parsing a JSON body. Those headers improve routing and observability; they do not authorize anything by themselves. Deterministic list ordering plus ttlMs and cacheScope let implementations build scoped catalogue caches. Any effect on prompt-cache stability depends on the client, gateway, and upstream cache.
GitHub reported removing Redis session state from its MCP implementation. The report is useful implementation evidence. It says nothing about independent performance, general security, or whether another architecture can delete Redis and go home early.
The headers also create new operational questions. What lands in gateway logs? Does Mcp-Name expose a sensitive tool or resource name? Can a cache entry escape the scope declared by the result? Infrastructure visibility is useful right up until it becomes accidental telemetry leakage.
State did not disappear
Cross-call state now belongs in an explicit application or extension handle. Long-running tasks have durable IDs. Subscriptions are scoped streams. Authentication, tenant authorization, rate limits, business workflow state, and audit correlation remain application responsibilities.

An explicit handle is better than invisible connection state because it can be routed, inspected, expired, and reasoned about. It also needs an owner. Somebody has to bind it to a tenant, authorize every use, choose where it lives, expire it, delete it, enforce quotas, and connect it to an audit record.
The API version of "no sessions" often looks like this:
create_counter() -> counter_id
increase_counter(counter_id)
get_counter(counter_id)
An explicit ID is clearer than a counter trapped inside one connection. It is still state. The caller, model, and application now have to propagate the handle correctly through the API.
"Stateless" is a transport and deployment property here. It does not provide authorization, durability, or security by itself.
A broken stream is now a retry problem
The final changelog gives this a sharp failure rule: if a response stream breaks, the in-flight request is lost. The client retries with a new request ID.
That means the old connection cannot be the safety mechanism. The unit that needs idempotency is the business operation: deploy, charge, write, create. A fresh JSON-RPC ID tells the protocol this is a new request. It does not tell the application whether the previous effect happened before the response disappeared.

The useful pattern is an application-level idempotency key, durable operation record, or effect receipt. On retry, the new request can discover that the effect already completed instead of performing it twice. Retry semantics belong in the migration itself whenever a tool has side effects.
The stateless core does not make every interaction short-lived or freely movable. subscriptions/listen is a long-lived, request- and connection-scoped stream. It is best effort and non-resumable. A disconnect means re-subscribing and restoring freshness through re-listing, polling, TTLs, or another application strategy. Use notifications as change signals. Durable freshness needs another mechanism.
The compatibility bridge is the migration
Modern and legacy peers are not symmetrically wire-compatible. There is no magic handshake that makes the split disappear. A real rollout needs safe detection, bounded dual-era support or an adapter, and evidence for when the fallback can be deleted.

The work reaches beyond one SDK upgrade. Microsoft's Agent Governance Toolkit migration RFC described scanners, bundled servers, documentation, and tests that had encoded initialization and session assumptions.
TypeScript server beta.4 expected serverInfo in the server/discover body. The final shape put it in result _meta and made it optional. A conforming modern server could be rejected, misclassified as legacy, and sent into an initialize fallback that it did not support. Beta.5 documented the correction.
In the Go SDK, issue #1025 reported a stateful HTTP server accepting a 2026-07-28 discovery request while omitting that version from supportedVersions. A maintainer reopened it as potentially nonconformant. It was still open when I checked on July 29. These two cases give us migration tests; they cannot describe ecosystem-wide prevalence.
Three tests before rollout
I would not ship this migration on a happy-path modern-client-to-modern-server test.
- Inventory implicit state. Find session-bound auth, connection-bound capabilities, in-memory handles, task state, subscriptions, catalogue caches, and assumptions about what happens after a response disappears.
- Run the era and transport matrix. Exercise modern, legacy, and the exact dual-era fallback over every transport you ship. Include unsupported versions, omitted optional identity, header/body mismatch, and the official conformance suite where it applies.
- Fault-inject the operation. Load-balance consecutive calls across instances. Restart between calls. Break response and subscription streams. Retry side-effecting work. Assert one effect, an auditable receipt, and restored catalogue freshness.
If those assertions do not exist, the migration has moved state without designing it.
Fewer hidden transport assumptions make ordinary request handling easier to deploy and the remaining responsibilities harder to ignore. Every handle, task, retry, cache, and subscription now needs an explicit owner and lifecycle.
MCP removed protocol-session state. The state design is still yours.
Source trail
Normative protocol claims above come from the final 2026-07-28 overview, versioning and compatibility guide, server/discover, and changelog. The older connection model comes from the 2025-11-25 lifecycle and transport documents. Vendor posts and SDK releases provide implementation evidence. Issue trackers provide edge signals. Independent performance and security validation remains absent.