What Changed in MCP 2026-07-28
The handshake is gone, sampling and roots are deprecated, and HTTP+SSE is no longer a transport. A test for whether the MCP guide you're reading describes a protocol that still exists.
If you learned MCP any time before mid-2026, a surprising amount of what you know is now wrong. Not subtly wrong — the handshake is gone, two of the three client primitives are deprecated, and one of the three transports no longer exists as a transport.
This is not a complaint about churn. It is a warning about your sources. Search for MCP sampling
or MCP roots
today and you will find dozens of confident explanations of features the specification has marked for removal. The same failure mode we wrote about in why every AI model comparison is out of date, arriving in a new place.
Here is what actually changed in protocol version 2026-07-28.
MCP is stateless now
The largest change, and the one everything else follows from.
Earlier revisions opened with an initialize handshake that established a connection-scoped session. Capabilities were negotiated once, and both sides remembered. That is gone.
MCP is a stateless protocol. Every request carries the protocol version and the capabilities relevant to that request in its
_metafield, so the server can process each request on its own.
Every request now carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in _meta, and clients should identify themselves there too unless configured not to. A server infers nothing from what came before.
There is a new server/discover request — mandatory for servers to implement, optional for clients to call — returning supported versions, capabilities, server identity, and caching hints. It is a convenience for fetching everything in one round trip, not a handshake. A client may skip it entirely, send whatever request it wants, and handle a version error if one comes back.
Practically: any remote MCP server instance can serve any request. No session affinity, no sticky load balancing, no session lost on reconnect. It is the change that makes serverless MCP hosting sane.
Sessions became state handles, and a new attack
Statelessness relocated a problem rather than removing it. Servers that genuinely need state across requests now mint an explicit handle — a cart ID, a workflow ID — returned in a tool result and handed back as an ordinary tool argument.
Which means anybody holding one can present it. The spec names this state handle hijacking and is direct about the fix:
MCP servers MUST NOT treat possession of a state handle as authentication.
Use non-deterministic handles, expire them, and bind them server-side to the authenticated user — key storage as <user_id>:<handle>, with the user ID derived from the verified token rather than supplied by the client. The old session-hijacking guidance now lives in the 2025-11-25 archive.
Sampling is deprecated
Sampling let a server ask the client for a model completion, so server authors could use an LLM without shipping an SDK or paying for inference. It was the most conceptually interesting thing in MCP and it is now deprecated and scheduled for removal.
The replacement guidance is one sentence: new implementations should integrate directly with LLM provider APIs instead.
If you have a server that depends on sampling, plan the migration. If you are reading a tutorial that presents sampling/createMessage as a design to build on, the tutorial predates this.
Roots are deprecated
Roots let a client tell a server which directories to focus on — file:// URIs marking workspace boundaries. Also deprecated, also scheduled for removal.
The replacement: pass directories or files via tool parameters, resource URIs, or server configuration.
The deprecation makes sense on the spec's own account of what roots were, which is worth quoting because it was widely misread:
Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers
SHOULD respect root boundaries,and not that theyMUST enforcethem, because servers run code the client cannot control.
Roots were advisory. Plenty of people treated them as a sandbox. If that was you, the actual boundary was always OS file permissions and sandboxing — see MCP security.
Logging is deprecated
Servers sending log messages to clients for debugging is out. Log to stderr on stdio, or use OpenTelemetry.
For stdio servers this was always the correct advice anyway — stdout carries JSON-RPC, and anything else written there corrupts the stream.
Elicitation survived, and grew a second mode
One client primitive remains: elicitation, where a server asks the user for information mid-request.
It now has two modes, and the distinction is a security control rather than a convenience.
Form mode — the server sends a JSON Schema, the client builds an input form, validates the response, and returns the data.
URL mode — the server supplies a URL for the user to open. The interaction happens out of band, and its data never passes through the client. The client only learns whether the user consented.
The rule attached to those modes matters:
Servers must not use form mode to request sensitive information such as passwords, API keys, access tokens, or payment credentials. Those interactions belong in URL mode, which keeps the data out of band so it never passes through the client or the LLM context.
That is the real point. Anything typed into a form mode field passes through the client and can land in a model's context window. Credentials must not. Clients must show the full URL and get explicit consent before opening it, and never fetch it automatically.
Elicitation uses a new request pattern
Elicitation no longer works by the server sending a request mid-conversation, because servers no longer initiate JSON-RPC requests at all. A binding MUST deliver client requests and notifications to the server and server responses and notifications to the client — no other message direction exists.
Instead there is Multi Round-Trip Requests (MRTR). When a server needs input while handling a tools/call, it returns an InputRequiredResult whose inputRequests field carries the elicitation/create request. The client collects the input and retries the original call, attaching inputResponses and echoing back any requestState the server included.
Same user-visible behaviour, entirely different mechanics, and fully compatible with statelessness — the server carries what it needs in requestState rather than remembering.
HTTP+SSE is no longer a transport
Two standard transports: stdio, and Streamable HTTP. The old two-endpoint HTTP+SSE design is gone. SSE still exists, as a reply mode — a single POST can be answered with a JSON body or a request-scoped SSE stream.
Covered properly in MCP transports.
Notifications are opt-in
A server no longer pushes change notifications at whoever is listening. The client opens a long-lived stream with subscriptions/listen, naming the notification types it wants. The server acknowledges the subset it will honour — dropping types it does not support — and tags every notification with the subscription's ID.
The spec is unusually candid about the guarantees:
There are no guarantees that every notification will be sent or received, particularly across transport reconnects. Clients should also rely on polling to preserve freshness of results.
Build the polling path. Treat notifications as an optimisation.
Responses carry cache hints
tools/list and friends now return ttlMs — a freshness window in milliseconds — and cacheScope, saying who may reuse the response. A five-minute tool-list TTL is the difference between one listing per session and one per turn.
Dynamic Client Registration is deprecated
On the authorization side, Client ID Metadata Documents are now the SHOULD, and Dynamic Client Registration is deprecated, kept only for authorization servers that don't support CIMD. Your client ID becomes an HTTPS URL serving your metadata rather than something you register for.
See MCP authorization and OAuth.
What this means for what you read
Deprecated as of 2026-07-28: sampling, roots, logging, dynamic client registration. Removed: the initialize handshake, connection-scoped sessions, HTTP+SSE as a transport, servers initiating requests.
So a quick test for any MCP material you find. If it describes an initialize handshake, lists three client primitives as current, tells you to configure "type": "sse", or presents roots as a security boundary — it is describing a protocol that no longer exists in that form. It may still be useful for concepts. Do not build from its code.
Interoperability is handled: implementations detect their counterpart's era and fall back, and the spec publishes a compatibility matrix. So old servers keep working. That is exactly why the stale tutorials are dangerous rather than merely obsolete — following one produces something that runs, which is not the same as something you should ship.
The durable parts are unchanged. Tools, resources and prompts are still the three server primitives, still divided by who controls them. The directory of servers still works the way it did. What moved is the plumbing underneath — and the plumbing is where most of the guides live.