When no shared protocol version exists, the MCP client should abort gracefully at initialize time and report the mismatch, never proceed and hope runtime errors catch it.
Picture two people meeting who each speak a few languages. Before chatting, they swap lists and pick one both know. If the lists overlap, great, they talk in that shared language. If there is zero overlap, the sensible move is to stop and say 'we cannot understand each other' right away. The reckless move is to just start talking in your own language and hope the other person guesses. MCP works the same way at connection time. The client sends the newest version it knows. The server either agrees or names one it prefers. If nothing matches, the honest move is to close the connection and report it, not to barrel ahead and discover the breakage halfway through a real task.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Concept explanation~2 min read
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
MCP connections begin with a handshake, and the first thing the two sides settle is which protocol version they will speak. The protocol uses date-based version strings, for example 2025-06-18, rather than semantic version numbers. Each published revision of the spec gets a date, and a client or server simply carries the set of dates it knows how to speak. This question targets the edge case: what happens when the client and server share no common version at all?
The short answer is graceful abort. The client should close the connection and report an incompatibility error rather than proceed. The interesting part is why that is correct, why the other plausible-sounding options are wrong, and how version negotiation relates to the separate mechanism of capability negotiation that rides in the same handshake.
The topic matters in interviews because it sits at the intersection of protocol design and operational reliability. A weak answer recites 'abort' without explaining the failure mode. A strong answer names capability decay, ties the choice to the fail-fast principle, and separates the version axis from the capability axis. This deep dive walks the initialize lifecycle, explains the failure mode the spec is protecting against, separates version gating from capability gating, and gives you a clean mental model for forward and backward compatibility across an evolving server fleet.
The initialize handshake and where version lives
Every MCP session opens with the client sending an initialize request. That request carries a protocolVersion field set to the newest version the client supports, plus the client's declared capabilities and a clientInfo block with name and version. The initialize call is special: it is the only request allowed before negotiation completes, and the client must not send any other request until it succeeds.
The server responds to initialize with its own protocolVersion, its capabilities, and serverInfo. If it supports the version the client proposed, it echoes that exact string back. If it does not, it replies with a version it does support, effectively a counter-proposal. The client then inspects the server's chosen version and decides whether it appears in its own supported set.
If the versions agree, the client sends an initialized notification and normal operation begins. If they do not, the client is responsible for terminating the connection. This responsibility split is worth stressing: the server's job is to name a version it can speak, and the client's job is to accept it or abort. The server never has to reason about the client's full supported set.
Crucially, all of this happens before a single tool is called or a single resource is read. The negotiation is a gate at the front door, not a check sprinkled through later requests. That ordering is the entire reason the abort behavior is safe and cheap: no state has been mutated, no tool has run, so closing the transport leaves nothing half-done to clean up.
Situations where this technique stops working.
2–4 min · Everything important, quickly.
| Aspect | Protocol version | Capabilities |
|---|---|---|
| What it selects | The wire contract and method set | Which optional features are live |
| Format | Date based string (2025-06-18) | Named flags (tools, resources, prompts) |
| Negotiated where | initialize request and response | Same handshake, capabilities field |
| No agreement means | Abort the connection | Feature is simply unavailable |
| Failure mode if ignored | Wrong method set, broken contract | Calling an unadvertised feature |
Real products, models, and research that use this idea.
- Claude Code and Claude Desktop send their supported MCP version in the initialize request and refuse to operate against a server with no shared version.
- The official MCP TypeScript and Python SDKs implement the initialize handshake and reject connections when the negotiated version is unsupported.
What an interviewer would ask next. Try answering before peeking at the approach.
QHow does capability negotiation in the same handshake differ from protocol version negotiation?
Version picks the wire contract and method set; capabilities are named flags that gate optional features like tools, resources, prompts, and sampling within that contract.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Red flags & common mistakes
The phrases that signal junior thinking. Click to expand.
Assuming the client should silently fall back to an old version. The spec says abort and report when no shared version exists, not guess.
60 second bullets to scan on the way to the call.
Where the protocol version is exchanged in the lifecycle
The format of MCP version strings
Primary sources. Browse if you want the original framing.
Same topic, related formats. Practice these next.