All posts

MCP went stateless. Your application did not

MCP 2026-07-28 removes protocol sessions and transport replay, moving durable state, retries, long-running work, and compatibility into explicit application contracts.

Blue request cards, each carrying a yellow state token, travel independently toward three red server blocks in a textured screen print

MCP 2026-07-28 removes sessions from the protocol core. It does not make the applications behind MCP stateless. The practical change is ownership: protocol version, capabilities, and request context now travel with every call, while workflow state must live in explicit handles and application storage rather than a transport session hidden behind Mcp-Session-Id.

That shift makes an MCP endpoint look much more like an ordinary HTTP workload. A request can land on any compatible instance behind a round-robin load balancer. A failed instance no longer takes its protocol session with it. Gateways can route and apply policy using standard headers. But the simplicity at the transport layer creates work elsewhere: callers must know when a request is safe to retry, servers must make state portable, long-running operations need durable task records, and mixed-version fleets need deliberate compatibility paths.

The protocol stopped owning continuity

Earlier Streamable HTTP revisions established continuity through an initialization handshake and could assign an Mcp-Session-Id. Deployments then had to preserve the relationship between that identifier and the server process or reconstruct the session from shared storage. Sticky routing, session draining, and failure recovery became part of operating an MCP server even when the underlying tool calls were otherwise independent.

The 2026-07-28 core removes the initialize/initialized handshake and protocol-level session IDs. Each request carries its protocol version and client capabilities in _meta; on HTTP, the protocol version is also mirrored in a header. server/discover lets a client fetch supported versions and capabilities in advance, but discovery is optional. A client may send its intended request immediately and handle an unsupported-version response.

The resulting boundary is clearer:

  • Protocol context belongs to the request. Version, identity, capabilities, and extension support are not inferred from a prior connection.
  • Application context belongs to the application. A server that needs continuity mints an explicit handle and expects the caller to return it as data.
  • Instance affinity is an implementation choice, not a protocol requirement. Any instance can serve a request if it can validate the request and resolve its explicit application state.
MCP session state moves from the transport into explicit application handles The left side shows three calls tied by a hidden session to one server instance. The right side shows self-contained calls routed to different instances, each carrying the same explicit application handle to durable application state. Earlier Streamable HTTP MCP 2026-07-28 Transport session creates instance affinity Request data makes application state portable call 1 · session S7 transport-held context call 2 · session S7 transport-held context call 3 · session S7 transport-held context server instance A owns session S7 instance B cannot help Sticky routing or shared session recovery self-contained call 1 handle: workflow-42 self-contained call 2 handle: workflow-42 self-contained call 3 handle: workflow-42 server instance A server instance B server instance C durable application state
Earlier revisions could bind continuity to a transport session and one server instance. In 2026-07-28, requests can reach different instances because the caller carries an explicit handle and the application resolves it against durable state.

An explicit handle is not automatically better state management. It is inspectable and portable, which helps routing and recovery, but it also becomes part of the application interface. Servers must scope handles to the right identity, prevent guessing or reuse across tenants, define expiry, and decide whether the model should see or manipulate them. Removing session storage from the protocol does not remove authorization or lifecycle design from the service.

A broken stream is a new request, not a replayed response

The transport change has a sharp failure semantic that deserves more attention than the word “stateless.” Earlier Streamable HTTP revisions supported SSE event IDs and Last-Event-ID for stream resumability. The new revision removes both. If a response stream breaks, the in-flight request is lost; the client must issue a new request with a new JSON-RPC request ID.

That is retry, not transport replay. A gateway cannot assume that reconnecting will continue from the last event, and a server cannot assume that a new request means the operation never started. The downstream side effect may have completed before the connection failed.

For read-only tools, reissuing may be harmless. For tools that create deployments, send messages, charge accounts, or update records, the application needs an explicit duplicate-suppression contract. An idempotency key should describe the intended operation, survive client retries, and map to a durable result. Operators also need logs that correlate the original request, the failed stream, the replacement request ID, and the application operation key.

Multi Round-Trip Requests solve a different problem. When a server needs input while handling a request, it can return resultType: "input_required" carrying input requests, an opaque requestState, or both; the two fields are individually optional, and at least one must be present. The client collects the input and reissues the original method with the responses and the same state, when supplied. Any instance can continue because the necessary protocol context is in the payload. MRTR makes interaction resumable across requests; it does not restore byte-level stream replay or make side effects idempotent.

Tasks separate long-running work from connection lifetime

Holding an HTTP response open is a poor durability strategy for a job that may outlive a client, proxy timeout, or deployment. The Tasks extension gives long-running work an explicit lifecycle instead.

A client advertises io.modelcontextprotocol/tasks. A server may then answer a supported request such as tools/call with a durable task handle rather than a completed tool result. The client uses tasks/get to poll, tasks/update to provide requested input, and tasks/cancel to request cancellation. The server, not the client, decides whether a particular call becomes a task. Task IDs must be persisted by the client if work should survive its own restart.

This is the right shape for batch imports, CI pipelines, approval gates, and wrappers around APIs that already expose job IDs. It also transfers operational responsibility to the task store. A production implementation must define retention, tenant scoping, result size, polling intervals, cancellation semantics, and what happens when the underlying worker finishes but the task record cannot be updated. Cancellation is cooperative, so an acknowledged request does not guarantee a cancelled terminal state.

Tasks are an extension, not part of the stateless core. Both parties must opt in, and host support varies. A gateway that forwards tools but does not understand task-shaped results cannot safely pretend the call is ordinary and synchronous.

MCP Apps add a UI distribution path, not just richer tool output

MCP Apps is another negotiated extension. A server can associate tools with interactive HTML interfaces that the host renders inside the conversation. The release candidate describes templates being declared ahead of execution so hosts can prefetch, cache, and review them. Current extension documentation places the interface in a sandboxed iframe and routes communication through a JSON-RPC dialect over postMessage.

For a platform, this adds a rendering and policy surface alongside tool transport. The host has to isolate the frame, enforce a content security policy, mediate tool calls, control link opening and other host capabilities, and make consent visible. A server-provided dashboard or form is executable third-party content even when its associated tool is read-only.

The benefit is material: structured results can arrive with the interface needed to inspect or act on them, without every host rebuilding the same bespoke UI. The cost is also material: accessibility, sandbox policy, template caching, UI provenance, extension negotiation, and audit now belong in the host contract. “Supports MCP tools” and “supports MCP Apps” are different capability claims.

Headers make gateways useful, but only when they are verified

Streamable HTTP now mirrors routing-relevant values into required headers. MCP-Protocol-Version identifies the protocol revision; Mcp-Method carries the JSON-RPC method; Mcp-Name identifies the named primitive for methods such as tools/call. This lets a gateway route, meter, rate-limit, or authorize without parsing every JSON body.

The body remains the source of truth. Servers must reject missing, malformed, or mismatched required headers, and intermediaries that act on mirrored headers should reject older or ambiguous traffic rather than trust values whose correspondence to the body is not guaranteed. Otherwise an attacker can present an allowed tool in a header while sending a different method or name in the payload.

List and read results also gain explicit cache hints. Results such as tools/list, prompts/list, resources/list, and resources/read carry ttlMs and cacheScope; deterministic tool ordering helps clients reuse catalogs and can keep upstream prompt caches stable. cacheScope: "private" and cacheScope: "public" are policy inputs, not decorative metadata. A shared gateway cache must include identity and capability context in its key whenever a result is not safe to share.

These changes remove reasons to terminate MCP deep inside a state-aware proxy, but they do not turn a generic HTTP cache into a correct MCP cache. Version, authorization, capability set, extension support, tenant, and the server's freshness hints still determine whether reuse is valid.

OAuth and OIDC details are now deployment requirements

The authorization changes align MCP more closely with production OAuth and OpenID Connect behavior. Clients validate an iss value when the authorization response supplies one, bind persisted credentials to the authorization server that issued them, and declare the appropriate OpenID Connect application_type during Dynamic Client Registration. Scope challenges support step-up authorization without discarding previously granted scopes.

The registration direction also changed. The release moves new implementations toward pre-registered clients or Client ID Metadata Documents, while Dynamic Client Registration remains for backward compatibility and is deprecated. Resource indicators bind authorization and token requests to the canonical MCP server URI, and resource servers must validate that tokens were issued for their audience.

For gateway operators, “we forward a bearer token” is not a complete authorization design. The gateway or client needs to know which issuer owns the credentials, which resource the token targets, how redirect URIs differ for desktop and web clients, how scope upgrades are bounded, and how credentials are invalidated when a resource changes authorization servers.

Stateless infrastructure has honest costs

The new core removes substantial coordination from the transport, but it does not eliminate coordination from the system.

  • Round-robin becomes viable, not mandatory. Applications with coordinated state still need a database, queue, object store, or stateful service. Cloudflare makes this distinction directly: ordinary Workers can serve stateless MCP, while Durable Objects remain appropriate when the application itself needs coordinated state.
  • Requests carry more context. Repeated version and capability metadata trade connection-local memory for self-description. Gateways should bound and validate that metadata rather than accept arbitrary capability payloads.
  • Polling replaces some held-open work. Tasks survive disconnections, but aggressive polling can create load. Servers publish a polling interval; clients need to respect it.
  • Extensions fragment capability support by design. Tasks and MCP Apps can evolve without destabilizing the core, but every deployment needs a fallback or a clear rejection path when one side lacks support.
  • Dual-era support adds temporary complexity. Modern and initialization-based clients are not directly compatible. A dual-era endpoint can support both, but protocol detection, routing, metrics, and tests must cover both behaviors until legacy traffic is retired.

The release introduces a formal lifecycle to make that retirement plan less arbitrary. Features move through Active, Deprecated, and Removed states, with a minimum twelve-month deprecation window unless an active security risk justifies an expedited process. Roots, Sampling, Logging, and Dynamic Client Registration appear in the deprecated registry with migration paths. The legacy HTTP+SSE transport is an explicit exception to the twelve-month minimum: its transitional window is three months after SEP-2596 reaches Final. Deprecated features keep working during the transition: new implementations should choose the replacement, and existing ones should plan their migration.

What gateways and operators should re-evaluate

A migration review should start at failure boundaries rather than feature checkboxes.

  1. Inventory hidden session dependencies. Find state stored in an MCP server process, keyed only by Mcp-Session-Id, or assumed to exist after initialize. Replace required continuity with scoped handles and durable application storage.
  2. Classify every retryable operation. Define idempotency keys and duplicate-result behavior for side-effecting tools. Test a disconnect after the side effect commits but before the response arrives.
  3. Separate MRTR, Tasks, and streams. Use MRTR for bounded input exchanges, Tasks for durable long-running work, and request-scoped SSE only for progress associated with the current request. Do not treat one as a transparent substitute for another.
  4. Make the gateway version-aware. Validate required headers against the body, route modern and legacy traffic deliberately, and record the negotiated era in traces and metrics.
  5. Rebuild cache keys. Honor ttlMs and cacheScope, preserve deterministic order, and include every identity, version, and capability dimension that can change a result.
  6. Test extension fallback. Verify what users see when a client lacks Tasks or MCP Apps. Silent shape conversion is worse than a clear unsupported-capability error.
  7. Audit OAuth metadata and credential binding. Confirm issuer validation, resource audience, registration mechanism, redirect classification, scope accumulation, and credential replacement when an authorization server changes.
  8. Plan deprecation with traffic evidence. Measure legacy handshakes, session IDs, HTTP GET streams, deprecated feature calls, and old error handling before setting a removal date.

A clean stateless endpoint is the end of this work, not the beginning. The migration is complete only when any instance can serve a modern request and the application can explain what happens after every timeout, retry, restart, and version mismatch.

What this means for AIVAX gateways today

AIVAX's public documentation describes a concrete MCP surface: an AI Gateway accepts external Streamable HTTP sources, reads their tool catalogs, exposes the resulting functions to the selected model, and sends the remote call when the model chooses one. Source configuration accepts custom authentication headers, and a configurable cacheDuration controls discovery caching with a documented default of 600 seconds.

AIVAX also forwards application metadata in each tools/call under _meta, including the external user ID, call source, conversation token, timestamp, nonce, and custom gateway metadata. An MCP server can use those values for authorization, correlation, or application state lookup. They should not be confused with the removed protocol session: _aiv_conversation_token identifies conversation context supplied by the application, while the server remains responsible for validating identity and resolving any state it chooses to associate with that context.

The current AIVAX documentation does not publish which MCP protocol revisions its client negotiates, nor does it claim support for 2026-07-28 MRTR, Tasks, MCP Apps, the new mirrored headers, or the new cache hints. The safe operating rule is therefore to test those capabilities against the configured gateway before depending on them. In particular, do not infer revision support from “Streamable HTTP”: the 2025-03-26 through 2025-11-25 revisions used that transport with different session, GET stream, and replay behavior.

For an AIVAX operator evaluating a modern server, the immediate questions are concrete: Does discovery negotiate the intended version? Does a failed stream cause a safe new request? Are task-shaped and input-required results understood? Does the gateway preserve the metadata and headers the server uses for routing and authorization? How does AIVAX's configured discovery cache interact with the server's catalog freshness policy? Until those flows are verified, keep a compatible server route or treat the source as a synchronous tools-only integration.

The new boundary is explicit state

MCP 2026-07-28 removes protocol machinery that forced ordinary tool servers to behave like session hosts. That is a meaningful infrastructure improvement: requests become independently routable, gateway policy can use validated headers, catalogs become deliberately cacheable, and durable work can use explicit task handles instead of connection lifetime.

The architecture is only simpler if the displaced responsibilities are designed rather than ignored. Put workflow state behind scoped handles. Persist Tasks independently of workers and clients. Treat MCP Apps as sandboxed application code. Bind OAuth credentials to issuers and resources. Run modern and legacy behavior as named compatibility modes, not accidental fallbacks.

The protocol is stateless now. Reliability depends on making the remaining state visible.

A companion post, An MCP server is a trust boundary, not just a tool catalog, covers the other half of the problem: governing what becomes visible, who may call, what results mean, and what you can reconstruct once requests cross into your systems.

Primary references