---
title: MCP 2026-07-28: The Stateless Rewrite Explained
date: 2026-07-30
topics: protocols-standards
tags: mcp, protocols, api-management, architecture
author: markus-muller
reading_time: 11 minutes
word_count: 2190
url: https://agentic-academy.ai/posts/mcp-2026-07-28-stateless-rewrite/
---


## Summary

The newest MCP specification removes sessions, the initialize handshake, and SSE resumability. What breaks, what improves, and what enterprises should do now.



On 28 July 2026, the Model Context Protocol published its most consequential revision since the protocol became an industry default. The headline is easy to state and hard to overstate: **MCP is now stateless.**

Protocol-level sessions are gone. The `initialize` handshake is gone. The `Mcp-Session-Id` header is gone. SSE stream resumability is gone. Three core features — Roots, Sampling, and Logging — are formally deprecated. Server-initiated requests have been replaced by an entirely different interaction pattern.

If you have MCP servers in production, this is not a version bump you absorb with a dependency update. If you run an API gateway in front of those servers, however, it is the release you have been waiting for.

{{< interactive src="/visualizations/mcp-stateless-rewrite.html" title="MCP 2026-07-28: The Stateless Rewrite" caption="Switch between the 2025-11-25 and 2026-07-28 revisions to compare how a single tool call travels through each, then review everything removed, added, and deprecated." height="auto" fullwidth="true" >}}

## What actually changed

The changes cluster into four themes. It is worth separating them, because they have very different blast radii.

### 1. Statelessness: the connection no longer carries meaning

Previously, an MCP interaction opened with `initialize`, received `notifications/initialized`, and established a session identified by `Mcp-Session-Id`. Everything after that was interpreted in the context of that session. List endpoints could legitimately return different results per connection.

That model is gone. Every request now carries its own context in `_meta`:

- `io.modelcontextprotocol/protocolVersion` — the version this request speaks
- `io.modelcontextprotocol/clientCapabilities` — what the client supports
- `io.modelcontextprotocol/clientInfo` — who the client is (SHOULD be sent on every request)

Servers reciprocate by identifying themselves in each result's `_meta` via `io.modelcontextprotocol/serverInfo`. A version mismatch returns `UnsupportedProtocolVersionError`.

Critically, `tools/list`, `resources/list`, and `prompts/list` **no longer vary per connection**. A server that needs cross-call state must now mint an explicit handle and pass it back as an ordinary tool argument. State becomes application data, not protocol plumbing.

A new RPC, `server/discover`, replaces the handshake's discovery role. Servers **MUST** implement it to advertise supported protocol versions, capabilities, and identity. Clients **MAY** call it before anything else for up-front version negotiation, or use it as a backward-compatibility probe over STDIO.

### 2. Notifications and streaming were restructured

The HTTP GET endpoint and the `resources/subscribe`/`resources/unsubscribe` pair are replaced by a single method: `subscriptions/listen`. It opens one long-lived POST-response stream, and clients opt in explicitly to the notification types they want — `toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, `resourceSubscriptions`. The server acknowledges and tags each notification with `io.modelcontextprotocol/subscriptionId`.

Request-scoped notifications behave differently and this distinction matters in implementation: `notifications/progress` and `notifications/message` continue to flow on the response stream of the request they belong to, *not* on the `subscriptions/listen` stream.

SSE resumability is also gone. The `Last-Event-ID` header and SSE event IDs have been removed from Streamable HTTP. When a response stream breaks, the in-flight request is lost, and the client **MUST** re-issue it as a new request with a new request ID. There is no message redelivery.

Three methods were removed outright: `ping`, `logging/setLevel`, and `notifications/roots/list_changed`. Log level is now set per-request via `io.modelcontextprotocol/logLevel` in `_meta`, and servers **MUST NOT** emit `notifications/message` for requests that did not include that field.

### 3. Multi Round-Trip Requests replace server-initiated calls

This is the subtlest change and the one most likely to break mental models.

Previously, a server that needed something from the client mid-request — filesystem roots, an LLM completion via sampling, a user decision via elicitation — issued a server-initiated request back down the connection. That pattern is gone, because in a stateless protocol there is no connection to send it down.

In its place is the **Multi Round-Trip Requests (MRTR)** pattern. When a server needs more information, it returns an `InputRequiredResult` with `resultType: "input_required"`, and its `inputRequests` field carries what it needs. The client then **retries the original request**, supplying `inputResponses`.

Supporting this, all results now carry a required `resultType` field: `"complete"` for ordinary results, `"input_required"` for MRTR interim results. Clients **MUST** treat results from earlier-protocol servers that omit the field as `"complete"`.

MRTR also explains a removal that would otherwise look arbitrary: `notifications/elicitation/complete` and the `elicitationId` field are gone. Under MRTR, the client learns the outcome of an out-of-band interaction by retrying, so a server-initiated completion signal no longer fits the protocol. Servers that need to correlate an elicitation across retries encode their own identifier in `requestState`.

### 4. Extensions, caching, and a real deprecation policy

Tasks — experimental in the previous revision — moved out of the core protocol into an official extension, `io.modelcontextprotocol/tasks`. The redesign replaces the blocking `tasks/result` method with polling via `tasks/get`, adds `tasks/update` for client-to-server input, removes `tasks/list`, and lets servers return task handles unsolicited without per-request opt-in.

The `extensions` field now appears on both `ClientCapabilities` and `ServerCapabilities`, giving optional capabilities a first-class home outside the core spec.

For caching, a new `CacheableResult` interface requires `ttlMs` and `cacheScope` on results from `tools/list`, `prompts/list`, `resources/list`, `resources/read`, and `resources/templates/list`. `ttlMs` is a freshness hint in milliseconds; `cacheScope` (`"public"` or `"private"`) controls whether shared intermediaries may cache the response. Servers **SHOULD** also return tools from `tools/list` in a deterministic order — explicitly to improve client-side caching and LLM prompt cache hit rates.

Streamable HTTP POST requests now require the standard `Mcp-Method` and `Mcp-Name` headers, and custom headers can be sourced from tool parameters via `x-mcp-header`.

Finally, the project adopted a formal **feature lifecycle and deprecation policy** defining Active, Deprecated, and Removed states, with a minimum twelve-month deprecation window and a public registry of deprecated features.

## What is deprecated, and what to do instead

Deprecated features remain functional during the window, but new implementations should not adopt them.

| Deprecated | Suggested migration |
|---|---|
| **Roots** | Pass directories or files via tool parameters, resource URIs, or server configuration |
| **Sampling** | Integrate directly with LLM provider APIs |
| **Logging** | Log to `stderr` (stdio), or use OpenTelemetry |
| **HTTP+SSE transport** | Migrate to Streamable HTTP |
| `includeContext` values `"thisServer"` / `"allServers"` | Omit the field or use `"none"` |
| **OAuth 2.0 Dynamic Client Registration** (RFC 7591) | Client ID Metadata Documents |

The Roots, Sampling, and Logging deprecations deserve attention because they signal a philosophical position: MCP is narrowing to tool access and getting out of the business of being an application framework. Sampling in particular was always awkward — it asked the tool server to borrow the client's model. Removing it clarifies the boundary. As I have [argued before](/posts/mcp-context-window-client-problem), the client is the right place for a great deal of this responsibility, and this revision moves decisively in that direction.

## The authorization changes are quieter but material

Four changes tighten the authorization story, and enterprises should read them as a set:

1. Authorization servers **SHOULD** include the `iss` parameter in authorization responses per RFC 9207, and clients **MUST** validate a present `iss` against the recorded issuer before redeeming the authorization code. This closes a mix-up attack vector.
2. Clients **MUST** specify an appropriate `application_type` during Dynamic Client Registration, avoiding OpenID Connect redirect URI conflicts.
3. Client credentials are explicitly bound to the issuing authorization server. Clients **MUST** key persisted credentials by issuer identifier, **MUST NOT** reuse them across authorization servers, and **MUST** re-register when the authorization server changes.
4. Dynamic Client Registration itself is deprecated in favour of Client ID Metadata Documents, remaining available only for backwards compatibility.

Point 4 is the one to plan around. DCR was the pragmatic answer to a real problem — an agent discovering a tool server at runtime cannot have pre-registered with it. Client ID Metadata Documents solve the same problem by letting the client publish its metadata at a URL that serves as its identifier. If your architecture assumed DCR, this is a migration, not a tweak. It is also the direction [AAuth](/posts/aauth-agent-authentication-standard) has been pointing, and it reinforces why [agent identity](/posts/agent-identity-authentication) cannot be an afterthought.

Two smaller items: the resource-not-found error code moved from `-32002` to `-32602` (Invalid Params) for JSON-RPC alignment, and a new error code allocation policy partitions the server-error range — `-32000` to `-32019` stays implementation-defined with existing SDK usage grandfathered, `-32020` to `-32099` is reserved for the specification. Draft codes were renumbered accordingly: `HeaderMismatch` `-32001` → `-32020`, `MissingRequiredClientCapability` `-32003` → `-32021`, `UnsupportedProtocolVersion` `-32004` → `-32022`.

## Why this is good news if you run an API gateway

For anyone who has spent a career in API management, the statelessness change reads less like a rewrite and more like a correction.

Stateful protocols are hostile to the infrastructure enterprises already own. A session identifier in a header means your load balancer needs affinity. It means horizontal scaling requires either sticky routing or a shared session store. It means a server restart drops live conversations. It means your cache cannot safely serve a `tools/list` response to a second client, because the first client's answer might have been different. And it means the [gateway patterns](/posts/api-gateway-patterns-agentic-workloads) you would normally reach for — rate limiting per consumer, response caching, blue/green deploys, canary routing — all need special-casing.

Statelessness removes those exceptions. `tools/list` becomes cacheable across clients, and `ttlMs` plus `cacheScope` tell your intermediary exactly how to treat it. Any replica can serve any request. Restarts stop being conversation-ending events. The required `Mcp-Method` and `Mcp-Name` headers give a gateway something to route and meter on without parsing the JSON-RPC body — a genuinely thoughtful addition for anyone enforcing policy at the edge.

The documented OpenTelemetry trace context conventions for `_meta` (`traceparent`, `tracestate`, `baggage`) matter for the same reason. Distributed tracing across agent-to-tool boundaries stops being something each vendor invents, which makes [observability for agentic systems](/posts/observability-for-agentic-systems) meaningfully more tractable.

The cost of all this is paid elsewhere. Per-request `_meta` means every call re-sends its protocol version, capabilities, and client identity. That is real token and bandwidth overhead, and on high-frequency tool calls it is not nothing. The specification has traded a small, constant per-request cost for the removal of an entire class of infrastructure problem. For enterprise deployments, that is close to unambiguously the right trade — but it is a trade, and the [economics](/posts/economics-and-operations-of-ai) belong in your model.

Losing stream resumability is the more painful loss. Long-running tool calls over unreliable networks now fail completely rather than resuming, and the client must retry from scratch. For expensive operations this pushes work into the application layer: idempotency keys, checkpointing, and the `io.modelcontextprotocol/tasks` extension for anything genuinely long-running. This is exactly the territory covered in [error handling and recovery in agentic systems](/posts/error-handling-agentic-systems), and it just became more important rather than less.

## What this does not solve

MCP still does not specify authentication mechanisms, service discovery beyond `server/discover`, rate limiting, tenancy, or operational governance. The authorization changes harden how OAuth is used; they do not make MCP an identity system. The [MCP Registry](/posts/mcp-registry-deep-dive) remains a separate concern, and [AuthZEN](/posts/authzen-authorization-api) remains the answer for fine-grained authorization decisions rather than anything in this spec.

Nor does statelessness make agents reliable. Removing sessions eliminates a class of infrastructure fragility; it does nothing about the [reasoning limits](/posts/planning-reasoning-limits-of-ai-judgment) that make agents fail in the first place, and nothing about [where you draw autonomy borders](/posts/autonomy-borders).

## What to do now

**If you operate MCP servers.** Inventory your dependence on the removed features first — sessions, `initialize`, `ping`, `logging/setLevel`, SSE resumability, and any per-connection variation in your list endpoints. That last one is the sleeper: a server that quietly returned different tools to different clients was previously legal and is now not. Implement `server/discover`, since it is a MUST. Then plan the MRTR migration for anything using server-initiated requests.

**If you build MCP clients.** The `resultType` handling is mandatory and has a compatibility rule worth encoding carefully: absent means `"complete"`. Build the MRTR retry loop deliberately — it is a genuine control-flow change, not a wrapper. Start keying persisted OAuth credentials by issuer if you do not already.

**If you run the infrastructure.** This release is an invitation to treat MCP traffic like ordinary HTTP traffic. Revisit affinity rules, enable response caching guided by `ttlMs` and `cacheScope`, meter on `Mcp-Method` and `Mcp-Name`, and wire the OpenTelemetry `_meta` keys into your existing tracing backend.

**Everyone should note the twelve-month clock.** The formal deprecation policy means Roots, Sampling, and Logging have a defined runway rather than an indefinite one. Twelve months is the minimum window, not a promise of more.

## Key takeaways

The 2026-07-28 revision converts MCP from a stateful, session-oriented protocol into a stateless request-response protocol that behaves like the rest of the HTTP ecosystem — removing sessions, the initialize handshake, `ping`, `logging/setLevel`, and SSE resumability, and requiring every request to carry its own protocol version, capabilities, and identity in `_meta`. Server-initiated requests are replaced by the Multi Round-Trip Requests pattern, in which servers return `resultType: "input_required"` and clients retry with `inputResponses`; Roots, Sampling, and Logging are deprecated on a minimum twelve-month clock alongside HTTP+SSE and OAuth Dynamic Client Registration. For enterprises the trade is favourable but not free: statelessness makes MCP traffic cacheable, horizontally scalable, and governable with infrastructure you already own, at the cost of per-request metadata overhead and the loss of stream resumability that now has to be handled in the application layer. The formalisation of an extensions framework, a `CacheableResult` interface, standard request headers, OpenTelemetry conventions, and a real deprecation policy signals a protocol maturing into something enterprises can plan around — which is a more useful property than feature richness.

{{< source title="Key Changes — Model Context Protocol Specification 2026-07-28" authors="Model Context Protocol" url="https://modelcontextprotocol.io/specification/2026-07-28/changelog" >}}


---

*This content is from Agentic Academy (https://agentic-academy.ai/)*
*Published: July 30, 2026*
