How does an MCP client tell a legacy initialize-era server from a modern one?
answer
- nothing announces the era up front
- ask, then read the failure
- one transport has no headers to read
- a missing mandatory method is the tell
- cache the verdict per server
basics
~20 sThe client probes. Over stdio it calls server/discover, which every modern server must implement, and reads a method-not-found error as the signal to fall back to the legacy initialize handshake. Over HTTP the JSON-RPC error body of a failed attempt identifies the era.
solid answer
~50 sThere is no handshake in MCP revision 2026-07-28 and no header that announces an era, so a client has to find out by asking. On **stdio** there is no status-code or header layer at all, so the probe is a call: `server/discover` is mandatory for modern servers, so a normal result means modern, while a method-not-found error (`-32601`) means the process does not implement it and the client falls back to the legacy `initialize` handshake. On **Streamable HTTP** the client learns the era from the error response body of a failed attempt — a JSON-RPC error such as `-32022` `UnsupportedProtocolVersionError`, whose `data.supported` names exactly which revisions the server serves, settles the question and hands you the remedy at the same time. Whatever the transport, determine the era **once per server**, cache it, and drive all subsequent traffic from that decision rather than re-probing on every call.
go deeper
Know that there is no handshake to ask, so the client finds out by calling something and seeing what comes back — a discovery call on stdio, and the error body of a failed attempt over HTTP.
Explain why each transport forces its own probe: stdio has no header or status layer, while HTTP returns structured JSON-RPC errors whose data can list the revisions a server serves.
Show the operational judgment — cache the era per server with an expiry, tell a protocol error apart from a dead subprocess, and read a sudden contradiction of the cached verdict as a partially rolled fleet rather than client flakiness.
Own the fleet-level rule that makes detection reliable at all: every node must present the same supported-version set before the new era is advertised, and rollouts should be sequenced so no client can observe two different answers to the same probe.
## Why a probe is needed at all Revision 2026-07-28 removed the `initialize` handshake, which was previously the one moment where a client and server exchanged identity and version before doing anything else. With that gone, nothing at connection setup tells a client what it is talking to. And era matters: a modern client against a legacy-only server fails outright, and there is no fall-forward in the other direction either. So a client that must survive the mixed 2026 fleet has to detect the server's era before it can trust anything else. The detection strategy differs by transport, because the two transports expose very different amounts of out-of-band signal. ## Stdio: probe with a call On stdio the client launches a subprocess and exchanges newline-delimited JSON-RPC over its stdin and stdout. There is no header layer and no status code — which is precisely why version and capabilities have to travel in the request metadata in the first place. That leaves exactly one detection channel: send something and interpret the answer. The designated probe is `server/discover`. Modern servers **MUST** implement it, and it exists partly for this purpose: it is the client's first call and its second job is the stdio backward-compatibility probe. - A well-formed discovery result means the server is modern (or at least dual-era) and, as a bonus, tells the client which revisions it serves and what it offers. - A method-not-found error, JSON-RPC `-32601`, means the process has never heard of `server/discover`. That is the legacy signal: the client falls back to the handshake model and opens with `initialize`. Two practical cautions. First, distinguish a protocol answer from a process failure: a subprocess that dies, writes garbage to stdout, or never responds is a broken server, not a legacy one, and treating a crash as "legacy" produces a confusing second failure. Note also that stdio servers use `stderr` for logging and a client should not treat stderr output as an error signal on its own. Second, remember that stdout must carry only MCP messages; a server that prints a banner will break framing in ways that look like an era problem but are not. ## Streamable HTTP: read the error body Over HTTP there is more signal available, because failures come back with status codes and structured JSON-RPC error bodies rather than silence. The era question is answered by attempting a modern request and reading what comes back: - A normal result means modern. - A `-32022` `UnsupportedProtocolVersionError` in the body is the most useful outcome of all: it not only says the server will not serve the revision you declared, it lists in `data.supported` exactly which revisions it will. If that list contains only handshake-era dates, you are talking to a legacy server; if it contains `2026-07-28` alongside older dates, you have found a dual-era server. - A method-not-found error for a modern-only method points the same way as it does on stdio. Because `data.supported` is a list rather than a boolean, HTTP detection tends to be more precise than a stdio probe: it distinguishes legacy-only from dual-era in one round trip, where a stdio discovery success only proves the modern side works. ## Cache the answer, and cache it per server Era is a property of the server, so the detection result is a per-server fact and should be stored as one — keyed by the server's endpoint or launch configuration, not by connection, since since 2026-07-28 a connection is not a session and carries no state of its own. Re-probing on every call wastes a round trip on every single request and, worse, invites inconsistent behaviour if a fleet is mid-rollout. Give the cache an expiry and a re-probe path anyway. Servers get upgraded underneath you, and a client that decided "legacy" at nine in the morning should not still be speaking the handshake model a week after the operator rolled the fleet forward. ## The load-balancer wrinkle Behind a load balancer, a partially rolled fleet can answer identical requests differently depending on which node you hit: one returns a modern result, another returns `-32022`. A client that caches the first answer will then see intermittent failures that look like flakiness. There is no client-side fix that is really correct here — the honest diagnosis is a non-uniform rollout, and the remedy belongs to the operator: make the supported-version set identical on every node before advertising the new era. On the client side, the defensive posture is to treat a surprise `-32022` as a signal to re-probe rather than as a transport failure. ## What a strong answer sounds like Name the two probes, tie each to why the transport forces it (no header layer on stdio, structured error bodies on HTTP), state that `server/discover` is mandatory for modern servers so its absence is meaningful, and finish with the operational points: cache per server, expire the cache, and read a mid-flight change of answer as a rollout problem rather than a bug in your client.
- Why does the stdio transport need a probe call rather than reading a header?Because stdio has no header layer at all — it is newline-delimited JSON-RPC over stdin and stdout, with no status codes and no metadata envelope. That is the same reason protocol version and capabilities travel in each request's metadata there. With no out-of-band channel, the only way to learn anything about the peer is to send a message and interpret the reply.
- How would you distinguish a legacy server from a crashed one during a stdio probe?A legacy server answers — with a well-formed JSON-RPC method-not-found error. A crashed or broken one does not: the process exits, times out, or emits non-JSON on stdout. Treat only a valid error response as an era signal, and remember stderr output alone is not an error indicator, since stdio servers use it for logging.
- When should a client re-probe a server whose era it has already cached?On cache expiry, and whenever a request unexpectedly contradicts the cached verdict — a `-32022` from a server you believed was modern, or a method-not-found for a method you had been calling successfully. Both usually mean the fleet is mid-rollout or was rolled back, and a fresh probe is cheaper than misdiagnosing it as a transport fault.
- What does a -32022 whose data.supported lists both old and current revisions tell you?That the server is dual-era: it implements both the handshake model and the per-request model, and it is telling you the whole set it will serve. That is more information than a stdio discovery success gives you, which only proves the modern path exists. Pick the newest mutually supported revision and cache that choice for the server.
saying these in an interview costs you the question
- Expects a header or handshake to announce the server's era on stdio
- Treats a crashed or silent subprocess as evidence of a legacy server
- Re-probes the era on every single request instead of caching per server
- Never re-probes, so a fleet upgrade leaves the client stuck on the old model
- Assumes one successful call proves which revisions the server supports