Serving two MCP protocol eras: deprecations and a migration checklist MCP servers must serve both the initialize-handshake era (2025-11-25 and earlier) and the stateless era (2026-07-28) from one codebase, with the protocol era treated as a property of each request rather than a server-wide mode, according to a migration guide published in October 2026. Deprecated features keep working for at least twelve months, with two shorter exceptions, and even then only become eligible for removal, while much of the handshake era — including the initialize handshake, Mcp-Session-Id and server-initiated requests — is already gone rather than deprecated. The guide reports that the Ruby SDK serves both eras with no configuration, the C# SDK v2 supports versions from 2024-11-05 through 2026-07-28 but rejects initialize when pinned to 2026-07-28, TypeScript SDK 2.0 adds the 2026-07-28 revision, and Python's mcp 2.3.0 MCPServer serves both eras unconfigured. Serving two MCP protocol eras: deprecations and a migration checklist Moving a server to the 2026-07-28 revision of the Model Context Protocol MCP doesn’t mean you can leave older clients behind. For the foreseeable future, real servers will meet clients from two protocol eras: the initialize-handshake era 2025-11-25 and earlier and the stateless era 2026-07-28 . What surprised me in the deprecation rules is that features don’t all run on the same clock, and that no clock removes anything by itself. This is part 22 of my series on what an MCP server does under the 2026-07-28 specification, after the threat model in part 21 https://pournasserian.com/writing/mcp-2026-threat-model . It turns the changes of part 2 https://pournasserian.com/writing/mcp-2026-what-changed into a plan for your own server. The facts are as I read them in October 2026. In brief 1. Your server will meet clients from both protocol eras, so serve both from one codebase. 2. Treat the era as a property of each request, not a server-wide mode. 3. A deprecated feature keeps working for at least twelve months, with two shorter exceptions, and even after that it only becomes eligible for removal. 4. Much is already gone, not merely deprecated, from the handshake and Mcp-Session-Id to server-initiated requests. 5. My advice: drop the legacy path when your traffic says you can, not when the specification does. One server, two eras A clean approach, and the one several software development kits SDKs take, is to decide the era per request, not for the whole server: server/discover needs no handshake; part 3 https://pournasserian.com/writing/mcp-2026-server-discover shows how clients use it to pick a version or detect an older server. A request whose meta carries the protocol version gets the stateless lifecycle, its envelope validated every time part 5 https://pournasserian.com/writing/mcp-2026-stateless-requests . What the SDKs do Examples, as of October 2026: | SDK | Serving both eras | |---|---| | Ruby SDK https://ruby.sdk.modelcontextprotocol.io/server/discovery/ | Its bundled transports serve server/discover and the per-request envelope alongside the initialize handshake, with no configuration. Its client probes server/discover and falls back to the handshake. | | C SDK https://csharp.sdk.modelcontextprotocol.io/v2/api/ModelContextProtocol.Server.McpServerOptions.html v2 | Supports the versions from 2024-11-05 through 2026-07-28. Pinned to 2026-07-28, it rejects initialize ; pinned to an earlier version, it rejects the per-request envelope. Its sampling application programming interfaces APIs are marked obsolete. | | TypeScript SDK 2.0 | Adds the 2026-07-28 revision. The v2 MCP runtime of Claude Code https://code.claude.com/docs/en/mcp is built on it, and uses the newer revision with servers that support it. | | Python SDK https://py.sdk.modelcontextprotocol.io/protocol-versions/ , mcp 2.3.0 | MCPServer serves both eras with no configuration, as the sample below shows. | One Python server, both eras One MCPServer , with nothing configured for either era, has a tool that reports the protocol version its request arrived on. Two in-memory clients call it: the default mode probes server/discover , and mode="legacy" uses the initialize handshake. Part 3 used the same two modes to read discovery; here the point is the server. python import anyio from mcp import Client from mcp.server import MCPServer from mcp.server.mcpserver import Context mcp = MCPServer "acme-docs" @mcp.tool def which era ctx: Context - str: """Report the protocol version this request arrived on.""" return ctx.protocol version async def main : for client in Client mcp , Client mcp, mode="legacy" : async with client: how = "discover" if client.session.discover result else "initialize" result = await client.call tool "which era", {} print f"{client.mode}: {how}, {client.protocol version}, " f"tool saw {result.content 0 .text}" anyio.run main It prints: auto: discover, 2026-07-28, tool saw 2026-07-28 legacy: initialize, 2025-11-25, tool saw 2025-11-25 The same tool answers both clients without a line for either era; ctx.protocol version is there for the rare handler that must tell them apart. From the package’s source and short probes: - Routing. Over Streamable HTTP, mcp 2.3.0 routes each request by its MCP-Protocol-Version header. On standard input and output stdio , the first request settles the era for the whole connection, so there the decision is per connection rather than per request: a later initialize on a modern connection gets -32022 UnsupportedProtocolVersion , and a request carrying the 2026-07-28 meta envelope on a legacy connection gets -32600 Invalid Request . - Asking the user. A Resolve question part 13 https://pournasserian.com/writing/mcp-2026-multi-round-trip-requests works in both eras; the push-style ctx.elicit raises NoBackChannelError on a 2026-07-28 request. - Logging. ctx.info and its siblings run in both eras, with an MCPDeprecationWarning , but on a 2026-07-28 request the message is sent only when the request carries io.modelcontextprotocol/logLevel in meta . What is deprecated, and what is already gone The 2026-07-28 revision adopted a formal feature lifecycle, SEP-2596 a Specification Enhancement Proposal, or SEP , with a registry of deprecated features https://modelcontextprotocol.io/specification/2026-07-28/deprecated . When a SEP marks a feature deprecated, it stays fully functional, but new implementations should not adopt it. It stays for at least twelve months, or at least 90 days when it presents an active security risk, and even then it only becomes eligible for removal: taking it out is a decision for the core maintainers. Deprecated, still working Part 2 https://pournasserian.com/writing/mcp-2026-what-changed gave the rule in brief; here is each feature with its earliest removal. One of them gets less than twelve months: the HTTP with Server-Sent Events SSE transport, HTTP+SSE. | Feature | Deprecated | Use instead | Earliest removal | |---|---|---|---| | Roots | 2026-07-28 | Pass directories or files as tool parameters, as resource Uniform Resource Identifiers URIs or in server configuration | First revision on or after 2027-07-28 | | Sampling | 2026-07-28 | Call your own model provider from the server | First revision on or after 2027-07-28 | | Logging notifications/message | 2026-07-28 | stderr on stdio, or OpenTelemetry part 20 https://pournasserian.com/writing/mcp-2026-opentelemetry | First revision on or after 2027-07-28 | | Dynamic Client Registration | 2026-07-28 | Client ID Metadata Documents; registration stays for older authorization servers | First revision on or after 2027-07-28 | | HTTP+SSE transport | Soft-deprecated 2025-03-26, formally 2026-07-28 | Streamable HTTP | Three months after SEP-2596 reaches Final | | includeContext values thisServer and allServers | Soft-deprecated 2025-11-25 | Omit the field, or use "none" | Follows sampling’s schedule | Removed outright in 2026-07-28 - the initialize and notifications/initialized handshake; - the Mcp-Session-Id header; - ping and logging/setLevel ; - notifications/roots/list changed ; - the HTTP GET endpoint; - resources/subscribe and resources/unsubscribe ; - SSE resumability Last-Event-ID ; - server-initiated requests, replaced by Multi Round-Trip Requests MRTR ; - notifications/elicitation/complete and elicitationId ; - core experimental tasks, tasks/result and tasks/list included; tasks now live in the redesigned Tasks extension a later part , without those two methods. Migration checklist: 2025-11-25 to 2026-07-28 Most of the move swaps an old pattern for a new one; where an earlier part explains the new one, the row links to it. If you used to… now you… | Old pattern | New pattern | |---|---| | Stored per-session state keyed by Mcp-Session-Id | Return an opaque handle from a “create” tool, accept it as an argument, and keep the state behind it in a shared store part 5 https://pournasserian.com/writing/mcp-2026-stateless-requests | | Read the version and capabilities once at initialize | Read them from meta on every request part 5 https://pournasserian.com/writing/mcp-2026-stateless-requests | | Sent elicitation/create from inside a tool call | Return input required , let the client retry part 13 https://pournasserian.com/writing/mcp-2026-multi-round-trip-requests , and protect requestState part 14 https://pournasserian.com/writing/mcp-2026-request-state | | Called sampling/createMessage or roots/list | Call your own model provider, and take directories or files as tool parameters. Until both features are removed, an input required result can still ask the client for them. | | Pushed notifications over the GET stream, or after resources/subscribe | Deliver them on subscriptions/listen : acknowledge first, and tag every message with subscriptionId part 17 https://pournasserian.com/writing/mcp-2026-subscriptions-listen | | Relied on SSE Last-Event-ID to resume | Make tools idempotent, or move long work to Tasks | The checklist - Implement server/discover : versions, capabilities, instructions, ttlMs and cacheScope . - Make each change in the table above. - Add resultType to every result. - Add ttlMs and cacheScope to complete results of the list methods and resources/read part 6 https://pournasserian.com/writing/mcp-2026-caching-and-pagination ; an input required result carries none, per the caching page https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching , and sort lists deterministically. - Require and verify the Streamable HTTP headers part 7 https://pournasserian.com/writing/mcp-2026-transports : MCP-Protocol-Version and Mcp-Method on every request, Mcp-Name only on tools/call , prompts/get and resources/read the Streamable HTTP page https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http . Return -32020 when a header and the body disagree. - Change resource-not-found from -32002 to -32602 part 10 https://pournasserian.com/writing/mcp-2026-resources . - Move experimental tasks to the Tasks extension: tasks/get polling plus tasks/update , with no tasks/result or tasks/list . - Stop emitting logs unless the request sets logLevel , and move to OpenTelemetry. - Plan the removals of roots, sampling, logging and HTTP+SSE, on the schedule in the table of deprecated features above. - Move from Dynamic Client Registration to Client ID Metadata Documents part 18 https://pournasserian.com/writing/mcp-2026-oauth-resource-server . - Test against at least one modern and one legacy client. In my review of MCP testing tools https://pournasserian.com/writing/mcp-server-testing-tools-2026 , the official conformance suite “carries per-revision requirement sets for 2025-11-25 and 2026-07-28”, and the official MCP Inspector “speaks stdio, SSE and Streamable HTTP in both protocol eras”. When to drop the legacy path The deprecation windows say when a feature may leave the specification, not when your server should stop serving older clients. My advice: don’t rush. Hosts adopt new protocol revisions at very different speeds, and a server that only speaks 2026-07-28 will look broken in any host that hasn’t caught up. Track the protocol versions you see in your clientInfo and protocolVersion telemetry, and remove the legacy path when your traffic says you can, not when the specification does. Method and caveats - Built from two chapters of my guide, on deprecations and on what changed, and written against the 2026-07-28 specification. The SDKs’ and Claude Code’s behaviour is as I read it in October 2026. - The Python sample ran against mcp 2.3.0 on 9 October 2026 with the in-memory client; the output is what it printed, and the notes under it come from the package’s source and short probes. - Where the specification is more precise than my notes how long deprecated features last, that the move from Dynamic Client Registration belongs in the migration, which requests need Mcp-Name , which results carry caching hints , this article follows the specification’s registry of deprecated features, its Streamable HTTP page and its caching page, as checked on 8 October 2026.