Migrate an MCP server off Mcp-Session-Id: the stateless 2026-07-28 spec
MCP 2026-07-28 drops initialize and Mcp-Session-Id. Move state to explicit handles, make retries safe, and keep a legacy path for older clients.
MCP 2026-07-28, released on July 28, 2026, removes the initialize handshake and the Mcp-Session-Id header from the protocol. Every request now carries its protocol version and client capabilities in _meta, so a request can reach any instance that can authenticate the caller and resolve the application state it refers to. Your application is not stateless because of that: workflow state must live in explicit handles and application storage instead of a transport session. A server that supports only the new revision also rejects clients that still send initialize, so a migration needs a deliberate path for older clients.
The migration has three parts: remove the dependencies on the session, carry explicit handles in tool arguments, and keep a legacy path for as long as older clients connect. This guide covers each, and what the AIVAX gateway does today when you connect a server as a tool source.
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.
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 described templates being declared ahead of execution so hosts can prefetch, cache, and review them; confirm the current behavior in the MCP Apps extension documentation. That 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.
Structured results can arrive with the interface needed to inspect or act on them, without every host rebuilding the same bespoke UI. In exchange, 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. The SEP page now lists SEP-2596 as Final, but the registry gives no calendar date for the removal, so watch the registry instead of computing one. Deprecated features keep working during the transition: new implementations should choose the replacement, and existing ones should plan their migration.
What happens when an old client meets a new server?
The specification publishes a compatibility matrix. On Streamable HTTP, a client that supports both eras sends a modern request first and, on a 400 Bad Request, inspects the body: a recognized modern JSON-RPC error means the server is modern, and anything else means it is legacy, so the client falls back to initialize.
| Client | Server | Outcome |
|---|---|---|
| Modern | Modern | Works. server/discover is optional; a version mismatch returns UnsupportedProtocolVersionError and the client retries. |
| Modern | Legacy | Fails. The legacy server may reject, stay silent, or process an ambiguous method under legacy semantics. |
| Dual-era | Legacy | Works. The modern request is rejected without a recognized modern error body, and the client falls back to initialize. |
| Legacy | Modern | Fails. On HTTP the request lacks the required headers and gets 400 Bad Request. A legacy client has no fall-forward mechanism. |
| Legacy | Dual-era | Works. The server answers initialize and serves the negotiated legacy revision. |
A modern-only server also has rules for stray legacy traffic. It should answer an HTTP GET or DELETE to the MCP endpoint with 405 Method Not Allowed, ignore an Mcp-Session-Id header and never mint or echo one, and ignore Last-Event-ID. The practical migration is to run a dual-era endpoint, which selects behavior from how the client opens: a request with per-request _meta is served statelessly, and an initialize request selects legacy semantics for that session. Retire the legacy branch only after the traffic measurements below show it is unused.
The failure this fixes is a common one. A public MCP server behind a round-robin load balancer returned Session not found because sessions lived in each instance's memory (datagouv-mcp issue 25); the maintainers' answer was sticky sessions first, then stateless HTTP mode.
What gateways and operators should re-evaluate
A migration review should start at failure boundaries rather than feature checkboxes.
- Inventory hidden session dependencies. Find state stored in an MCP server process, keyed only by
Mcp-Session-Id, or assumed to exist afterinitialize. Replace required continuity with scoped handles and durable application storage. - 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.
- 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.
- 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.
- Rebuild cache keys. Honor
ttlMsandcacheScope, preserve deterministic order, and include every identity, version, and capability dimension that can change a result. - 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.
- 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.
- 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.
The migration is complete 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 AIVAX gateway looks like a legacy-era client today. The MCP documentation says that remote skill discovery requires the existing Streamable HTTP initialization flow and that servers requiring server/discover are not supported by that integration. In the source, the gateway builds its client with the ModelContextProtocol.Core package at version 1.4.0, and a code comment notes that this SDK version predates the server/discover flow. By the compatibility matrix above, a legacy client connecting to a modern-only server should expect a 400 Bad Request; I inferred that outcome and did not run it. The docs do not claim support for 2026-07-28 MRTR, Tasks, MCP Apps, the mirrored headers, or the cache hints, and a search of the gateway code found no use of ttlMs or cacheScope. 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.
The gateway also does not hold a protocol session across calls. It opens a new client to discover tools when the cache is cold, and a new client for each tools/call. A server therefore cannot rely on a transport session surviving between two tool calls from AIVAX; the application handle has to travel in the tool arguments or be resolved from the _meta values described above.
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 dual-era route for AIVAX and use only the capabilities you confirmed against its current client. This was read from the source and the maintained documentation as of October 13, 2026; it was not tested against a live modern-only server.
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 checklist above is the work that remains: state behind scoped handles, Tasks persisted independently of workers and clients, MCP Apps treated as sandboxed third-party code, OAuth credentials bound to issuers and resources, and modern and legacy behavior run as named compatibility modes.
Three related guides cover the neighboring problems: how to safely connect third-party MCP servers, how to make tool calls idempotent so a retry does not create a duplicate, and how to reduce MCP tool-definition tokens.
FAQ
Is Mcp-Session-Id removed from MCP? Yes, in revision 2026-07-28. The Streamable HTTP transport no longer uses it, and a server that supports only this revision should ignore the header and not mint one. Revisions 2025-03-26 through 2025-11-25 still define it.
Do I still need sticky sessions for an MCP server? Not for the protocol. Sticky routing is needed only if your own code keeps per-client state in instance memory. Move that state to a database or cache behind an explicit handle.
Will an older MCP client work against a server that supports only 2026-07-28? No. A legacy client sends initialize without the required per-request metadata and headers, and gets 400 Bad Request. Run a dual-era endpoint until legacy traffic is gone.
Does the AIVAX gateway work with a modern-only MCP server? Do not rely on it as of October 13, 2026. The docs say servers that require server/discover are not supported, and the gateway's SDK predates that flow. I did not test a modern-only server, so keep a dual-era route for AIVAX.
Do I need to upgrade my SDK? The official C# SDK 2.0 implements 2026-07-28 and keeps compatibility with peers that negotiate 2025-11-25 and earlier, so it uses initialize automatically against an older server. Check the equivalent guide for your language SDK before upgrading.
Primary references
- The 2026-07-28 MCP specification release
- The 2026-07-28 release candidate and migration rationale
- Cloudflare's MCP v2 infrastructure analysis
- Model Context Protocol specification, revision 2026-07-28
- Versioning and compatibility, including the era matrix
- Streamable HTTP transport
- Key changes in 2026-07-28
- Deprecated features registry
- MCP C# SDK versioning
- AIVAX MCP tool-source documentation
- AIVAX AI Gateway documentation