The 2026-07-28 revision of the Model Context Protocol is the biggest change since MCP launched. MCP is now a stateless protocol: there is no initialize handshake and no Mcp-Session-Id, every request carries its own protocol version and client capabilities in _meta, and servers no longer send requests to clients mid-call. They return an input_required result and the client retries instead. Sampling, roots and logging are deprecated, Tasks have moved to an extension, and Dynamic Client Registration gives way to Client ID Metadata Documents. This guide explains each change, how old and new clients coexist, and what to migrate first.
New to the protocol? Start with What Is MCP?. Everything below was checked against the 2026-07-28 specification, its changelog and the official SDK migration guides.
Why statelessness matters
Under 2025-11-25 and earlier, a client opened a session with initialize. Over Streamable HTTP the server could hand back an Mcp-Session-Id, and every later request had to reach something that knew that session. Servers could also push requests back to the client (sampling, elicitation, roots) on an open stream.
Consider an insurer that runs a claims-lookup MCP server as several pods behind a load balancer. With sessions, the team had three options. Sticky routing breaks when a pod is rescheduled. A shared Redis session store adds latency and one more thing to secure. A single replica can't scale. Serverless was worse still, because no function instance outlives the request.
The new spec states the rule outright: everything needed to process a request is in the request, and servers must not rely on earlier requests on the same connection for version, capabilities or client identity. The practical results:
- Load balancing. Any replica can serve any request, so you can drain pods freely and use plain round-robin.
- Serverless. A function that answers one POST and exits is now a valid MCP server design.
- Routing. Streamable HTTP now requires
Mcp-MethodandMcp-Nameheaders (SEP-2243), so gateways can route or block by tool name without parsing the body. - Caching. List and read results now carry
ttlMsandcacheScope(SEP-2549), andtools/listshould return tools in a deterministic order.
Before and after: the request flow
BEFORE (2025-11-25): stateful session client server pod A |-- initialize ---------------->| |<-- capabilities + session id -| |-- notifications/initialized ->| |-- tools/call (session id) --->| must reach pod A |<-- elicitation/create (push) -| or a shared store |-- elicitation response ------>| |<-- tool result ---------------| AFTER (2026-07-28): stateless requests client any pod |-- server/discover (optional)->| |-- tools/call + _meta -------->| pod A answers |<-- input_required + state ----| |-- tools/call + _meta | | + inputResponses + state -->| pod B is fine |<-- resultType: complete ------|
The major changes, one by one
1. The initialize handshake is gone; _meta travels on every request
SEP-2575 removes initialize and notifications/initialized. Every request now carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in params._meta. Clients should also send clientInfo, and servers should return serverInfo in each result's _meta. On HTTP the version is also sent in the MCP-Protocol-Version header, but the body is the source of truth. If the two disagree, the server returns a HeaderMismatch error.
Every result now carries a required resultType: "complete" or "input_required". Clients must treat a missing resultType from an older server as "complete". The spec's own error codes move to a reserved range. For example, UnsupportedProtocolVersion is now -32022, and "resource not found" becomes the standard -32602.
2. server/discover
Servers must implement server/discover. It returns supportedVersions, capabilities, optional instructions and the server's identity, and the result can be cached. Clients may call it first, but they don't have to. A client can send any request with its preferred version and handle UnsupportedProtocolVersionError. The spec warns that serverInfo is self-reported and should not drive security decisions.
3. Mcp-Session-Id removed; state lives in handles
SEP-2567 removes protocol-level sessions. A modern-only server ignores an Mcp-Session-Id header from an old client and answers HTTP GET or DELETE with 405. SSE resumability (Last-Event-ID) is gone too. If a stream breaks, the client re-sends the request with a new ID.
When a server really needs state across calls (a basket, a browser context, a database transaction), the tools page recommends explicit handles. A creation tool returns an opaque ID, and later calls pass it as an ordinary argument. The spec's design advice: check authorization against the handle on every call, keep handles opaque, state their lifetime in the tool description, and return a clear error when one has expired so the model can create a new one.
4. Multi Round-Trip Requests and input_required results
Servers no longer send elicitation/create, sampling/createMessage or roots/list as separate requests. Under the Multi Round-Trip Requests pattern (SEP-2322), a server that needs more input during tools/call, resources/read or prompts/get returns an InputRequiredResult. That result holds inputRequests and, optionally, an opaque requestState. The client gathers the answers and retries the original request with a new JSON-RPC ID, the inputResponses, and the unchanged requestState.
Because the retry carries everything the server needs, it can land on a different replica. The catch is that requestState passes through the client, so the spec says to treat it as attacker-controlled. If it affects authorization or business logic, protect its integrity with an HMAC or AEAD, and bind it to the user, a short expiry and the original request.
5. Subscriptions: one long-lived listen stream
subscriptions/listen replaces the HTTP GET stream and resources/subscribe. The client opts in to specific types: toolsListChanged, promptsListChanged, resourcesListChanged, or a list of resourceSubscriptions. The server first sends an acknowledgment listing what it will honour, then tags every notification with io.modelcontextprotocol/subscriptionId. Progress and log messages still travel on their own request's stream. Servers keep no subscription state across reconnects, so clients must re-subscribe.
6. Removals and deprecations
ping, logging/setLevel and notifications/roots/list_changed are removed. Log level is now set per request with io.modelcontextprotocol/logLevel in _meta, and servers must not emit logs for requests that didn't include it. SEP-2577 deprecates Roots, Sampling and Logging: they still work, but new implementations should not adopt them.
| Deprecated feature | Suggested migration | Earliest removal |
|---|---|---|
| Roots | Tool parameters, resource URIs or server configuration | First revision on or after 2027-07-28 |
| Sampling | Call your LLM provider's API directly | First revision on or after 2027-07-28 |
| Logging | stderr on stdio; OpenTelemetry | First revision on or after 2027-07-28 |
| Dynamic Client Registration | Client ID Metadata Documents | First revision on or after 2027-07-28 |
Elicitation is not deprecated. It now travels inside MRTR. The old HTTP+SSE transport, deprecated since 2025-03-26, is also formally listed as Deprecated. This revision adds a feature lifecycle policy (SEP-2596) with three states, Active, Deprecated and Removed. A deprecated feature normally stays at least twelve months, and a public registry tracks every deprecated feature. For enterprise teams, that predictability matters more than any single feature.
7. Tasks moved to an extension
Experimental Tasks have left the core protocol and become the official io.modelcontextprotocol/tasks extension (SEP-2663), which clients and servers negotiate through the new extensions field in capabilities. The redesign replaces the blocking tasks/result with polling through tasks/get, adds tasks/update, and removes tasks/list. Check SDK support before you depend on it. The Python SDK v2 migration guide says that SDK does not implement the extension yet.
8. Authorization updates
Authorization still covers HTTP transports only, built on OAuth 2.1, Protected Resource Metadata (RFC 9728) and Resource Indicators (RFC 8707). The verified changes:
- Client ID Metadata Documents (CIMD) are preferred. The client's
client_idis an HTTPS URL that serves a JSON document containing at leastclient_id,client_nameandredirect_uris. Authorization servers advertise support withclient_id_metadata_document_supported. The priority order is pre-registration, then CIMD, then DCR, then asking the user. - Dynamic Client Registration is deprecated (PR #2858). It remains as a fallback, and clients using it must send an appropriate
application_type(SEP-837). - Issuer validation (SEP-2468). Authorization servers should include
issin authorization responses (RFC 9207), and clients must check it against the recorded issuer before redeeming the code. - Issuer-bound credentials (SEP-2352). Clients key stored credentials by issuer and must re-register when the authorization server changes.
For delegated access and token exchange with an enterprise identity provider, see AI agent identity and access.
9. Tool annotations and tool definitions
The annotation hints are unchanged: title, readOnlyHint, destructiveHint, idempotentHint and openWorldHint. Clients must still treat them as untrusted unless they come from a trusted server. What did change: schemas now accept any JSON Schema 2020-12 keyword (SEP-2106), and the new x-mcp-header property copies a primitive parameter into an Mcp-Param-{name} HTTP header for routing. Never mark sensitive values this way, because intermediaries can see headers. Clients must drop tools whose x-mcp-header values are invalid.
Version negotiation and coexistence with older clients
The spec defines three eras. Legacy uses initialize (2025-11-25 and earlier). Modern uses per-request metadata (2026-07-28 onward). Dual-era means an implementation that supports both. There is no handshake. A server that doesn't support the requested version returns UnsupportedProtocolVersionError with a supported list, and the client retries.
| Client | Server | Outcome |
|---|---|---|
| Modern | Modern | Works; server/discover optional |
| Modern | Legacy | Fails; on stdio, send server/discover first so it fails clearly |
| Dual-era | Legacy | Works; falls back to initialize |
| Legacy | Modern | Fails; legacy clients cannot move up to the new protocol |
| Legacy | Dual-era | Works under legacy rules |
To detect a server's era over stdio, a dual-era client sends server/discover and falls back on any error that isn't a recognised modern one. Over HTTP it sends a modern request and, on a 400, reads the body first: a modern error means retry, anything else means fall back. It caches the result per server process or origin. A dual-era server decides per request from how the client opens, and can serve both eras on one endpoint.
The practical point: a modern-only server breaks every legacy client, so run dual-era servers during the transition. SDK defaults differ. The Python SDK v2, where FastMCP is now MCPServer, defaults Client to mode='auto', which probes and then falls back. The TypeScript SDK's guide to this revision says its v2 packages speak the 2025-era protocol unless you opt in, for example with versionNegotiation: { mode: 'auto' }.
Rolling out protocol changes like this inside a customer's environment, across their gateways, identity provider and ITSM, is routine work for Forward Deployed Engineers. The FDE PRO program practises it in its ServiceNow AI Agent via MCP project.
Migration checklist for server authors
- Upgrade the SDK and read its migration guide. In Python v2, imports move from
mcp.server.fastmcptomcp.server.mcpserver, and transport options move torun(). - Serve
server/discoverwith a realserverInfoname and version. - Find hidden session state: anything keyed by connection or session ID. Move it into handles or
requestState. - Port server-initiated calls to MRTR. In Python v2,
ctx.elicit()raisesNoBackChannelErroron a 2026-07-28 connection. The guide recommends resolver parameters (Elicit,Sample,ListRoots), which work in both eras. - Sign and expire
requestStatewhenever it affects access. - Publish change notifications on
subscriptions/listen. On 2026-era connections, the old session helpers' notifications are dropped. - Replace deprecated features. Roots become parameters, sampling becomes a direct LLM API call, and logging goes to stderr or OpenTelemetry (AI observability).
- Return the cache fields, then remove sticky sessions from the load balancer.
A server built from our MCP server in Python tutorial needs little change. The work is mostly dependency pins, imports and testing both eras.
Migration checklist for host and client authors
- Send the required
_metaon every request. Over HTTP, also sendMCP-Protocol-Version,Mcp-MethodandMcp-Name. - Implement era detection, and cache the result.
- Handle
input_required: collect the answers, retry with a new ID, and echorequestStateunchanged. - Treat a missing
resultTypeascomplete, and treat unknown values as invalid. - Open
subscriptions/listenwith explicit filters, and re-subscribe after a disconnect. - Re-issue broken requests, since streams can no longer be resumed.
- Move OAuth to CIMD, validate
iss, and key credentials by issuer.
Testing with the MCP Inspector
The MCP Inspector now has three clients: web (npx @modelcontextprotocol/inspector), CLI (--cli) and terminal UI (--tui). Each server has a protocol era setting. legacy is the default and does a plain initialize. auto probes server/discover and falls back. modern pins 2026-07-28 with no fallback. For a dual-era server:
- Connect in
legacymode and confirm existing hosts see no change. - Connect in
modernmode and check thatsupportedVersionsandserverInfoappear in Connection Info. - Call a tool that asks the user for input and confirm the
input_requiredround trip completes. - Set the per-request log level to Off and confirm no logs arrive. That silence is correct.
- Run the CLI client in CI so every pull request tests both eras.
Then repeat the same calls through several replicas behind your real load balancer.
Security implications
requestStateis client-held state. Without integrity protection, a client can edit it to skip checks. Anything that must be used only once needs a server-side record.- Handles are names, not permissions. Check authorization against the handle on every call.
- Headers are now policy inputs. Gateways should trust
Mcp-NameorMcp-Param-*headers only on versions that require the server to validate headers against the body. Never put personal data in them. - Self-reported identity. Don't build allow-lists on
serverInfoorclientInfo. - Annotations are advisory. A tool marked
readOnlyHintcan still write, so approvals belong in the host (human-in-the-loop AI). - Keep the old defences.
Originvalidation and binding local servers to localhost still protect against DNS rebinding.
Consider a GCC IT team in Hyderabad that exposes a ServiceNow incident server to an internal assistant. After migrating, it can scale the server horizontally, block Mcp-Name: delete_incident at the gateway, and sign the requestState that carries a half-filled change request. That is easier to audit than a hidden session cache. For how this compares with plain REST, read MCP vs API. For MCP alongside A2A and other agent protocols, see AI agent protocols explained.
Frequently asked questions
Is the MCP 2026-07-28 spec backward compatible with older clients?
Not by default. A legacy client that sends initialize to a modern-only server fails. Servers that must support older hosts should run dual-era, answering initialize under legacy rules and per-request metadata under 2026-07-28 rules.
Do clients have to call server/discover first?
No. Servers must implement it, but clients may send any request with their preferred version and retry on UnsupportedProtocolVersionError. On stdio it is the recommended probe for detecting legacy servers.
What replaces Mcp-Session-Id?
Nothing at the protocol level. State that must span calls uses explicit handles passed as tool arguments, or an opaque requestState inside a multi round-trip request.
Is elicitation deprecated in MCP 2026-07-28?
No. Roots, sampling and logging are deprecated under SEP-2577. Elicitation remains, but servers now deliver it inside an input_required result instead of sending a separate request.
When will sampling, roots and logging be removed?
The deprecated features registry gives the earliest removal as the first specification revision released on or after 2027-07-28. Until then they keep working.
What happened to MCP Tasks?
Tasks became the official io.modelcontextprotocol/tasks extension under SEP-2663, using polling with tasks/get instead of a blocking tasks/result. SDK support varies, so check your SDK's documentation first.
Should a new MCP client use CIMD or Dynamic Client Registration?
Use Client ID Metadata Documents when the authorization server advertises support. Dynamic Client Registration is deprecated and remains only as a fallback, and pre-registered credentials still take priority.
How do I test both protocol eras with the MCP Inspector?
Set the server's protocol era to legacy, auto or modern. Test in legacy and modern modes, and run the CLI client in CI.
Ready to take MCP servers from a working demo to a governed, observable production service? Cloudsoft's AI Forward Deployed Engineer course builds a ServiceNow AI Agent via MCP alongside Kubernetes, Entra ID and observability. Classes run in Ameerpet, beside Ameerpet Metro, or live online. Call +91 96660 19191 for a free demo.



