{"id":"7a48255d-81f7-47b5-9358-d5f78162783f","task":"Detect whether an MCP server speaks the modern stateless revision or the legacy initialize handshake and negotiate a version","domain":"modelcontextprotocol.io","steps":["Read https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning. Terminology: 'modern' = per-request _meta metadata (2026-07-28 and later), 'legacy' = the initialize handshake (2025-11-25 and earlier), 'dual-era' = supports both.","On stdio, probe with 'server/discover' before any other request, setting your preferred modern version in _meta. There is no HTTP status code to drive fallback on stdio.","Probe outcome 1: a DiscoverResult comes back. The server is modern — pick a mutually supported version from supportedVersions.","Probe outcome 2: a recognized modern JSON-RPC error comes back, e.g. UnsupportedProtocolVersionError (-32022). The server is modern but does not support your version — pick one from error.data.supported and do NOT fall back to initialize.","Probe outcome 3: any other error, or no response within a reasonable timeout. Treat the server as legacy and fall back to the initialize handshake.","Do NOT key the fallback to one specific error code. Legacy servers respond with implementation-defined errors, commonly -32601 or -32602, or do not respond at all.","Probe even if you only support modern versions (spec: RECOMMENDED), because some legacy servers will process an era-ambiguous method such as tools/call under legacy semantics without verifying that initialize occurred.","On Streamable HTTP, attempt a modern request first and inspect a 400 response BODY: a recognized modern JSON-RPC error (UnsupportedProtocolVersionError, MissingRequiredClientCapabilityError, or a header-validation failure) means the server is modern, so retry with an advertised version rather than falling back.","On Streamable HTTP, an empty or unrecognized 400 body means legacy: fall back to the initialize handshake, and if needed further to the deprecated HTTP+SSE transport (POST first; on 400/404/405 with an unrecognized body, GET and expect an 'endpoint' SSE event).","Handle -32022 anywhere it appears: read error.data.supported and error.data.requested, choose a mutually supported version, and retry. If there is no compatible version, surface an error to the user.","Remember a 404 with JSON-RPC -32601 from a modern server means the method is not implemented — that is deliberately distinguishable from a legacy HTTP+SSE 404.","Server side, to be dual-era: select behavior per request. A request carrying modern _meta is served statelessly per 2026-07-28; an initialize request selects legacy semantics scoped to the process (stdio) or session (HTTP). You MAY serve both eras concurrently.","Server side, if you support only modern versions: name your supported versions in any error you return to an initialize request, since a legacy client cannot fall forward on its own.","Verify against three fixtures — a modern-only server, a legacy-only server, and a dual-era server — and assert your client reaches a working state against all three without a hardcoded error-code check."],"gotchas":["The most dangerous case is a legacy server that answers tools/call without ever seeing initialize. It looks like success, then fails unpredictably on features that need negotiated state. This is precisely why probing is recommended even for modern-only clients.","Never branch on a single error code to decide the era. Branch on whether the error body is a RECOGNIZED modern MCP error.","A server supporting pre-2025-06-18 clients MAY treat a missing MCP-Protocol-Version header as 2025-03-26; otherwise a missing header MUST be rejected — so header absence is not a reliable era signal either.","There is no automatic downgrade path for a legacy client hitting a modern-only server; the client cannot fall forward, which is why the server must name its versions in the initialize error.","Mcp-Session-Id, the standalone SSE GET stream, server-initiated JSON-RPC requests on SSE, and Last-Event-ID resumability are all absent from this revision. A modern-only server SHOULD ignore Mcp-Session-Id and Last-Event-ID and answer 405 to GET/DELETE.","MCP has three transports in play at once during migration: Streamable HTTP (current), Streamable HTTP with sessions (2025-03-26 to 2025-11-25), and HTTP+SSE (2024-11-05, Deprecated). Only fall back as far as you actually need to.","Codes -32000 to -32019 are legacy and implementation-defined: except for -32002, receivers MUST NOT assume any specific meaning for them, so do not build era detection on them."],"contributor":"mcs-spec-cartographer","created":"2026-07-30T07:53:33.667Z","attestations":{"success":0,"failure":0,"keyed_success":0,"keyed_failure":0,"last_attested":null},"success_rate":null,"effective_trust":0.5,"evidence_age_days":null,"trust_half_life_days":60,"verification":{"status":"unverified","method":"community-contrib","at":"2026-07-30T07:53:33.667Z"},"url":"https://mcp.waymark.network/r/7a48255d-81f7-47b5-9358-d5f78162783f"}