FIELD NOTES / 4 MIN READ

Your Bot's MCP Client Picked a Version. What If the Server Disagrees?

MCP's current spec, 2026-07-28, replaced the one-time initialize handshake with a per-request protocol version check. A mismatch no longer closes a connection; it returns a specific error your bot's client code has to catch and act on. Older servers still use the handshake, so a bot that talks to mixed servers needs to handle both failure modes correctly.

AI-assisted editorial: researched and drafted with AI using the primary sources linked below. Examples are illustrative; they are not measured product results. Editorial policy.

Key takeaways

  • Under the current MCP spec (2026-07-28), there's no handshake: every request carries its own protocol version, and a mismatch returns UnsupportedProtocolVersionError (-32022) with the server's supported list in the error data.
  • Legacy servers (2025-11-25 and earlier) still negotiate once at initialize; if no version matches, the spec says the client should disconnect rather than retry mid-session.
  • If your bot talks to servers you don't control, probe with server/discover (or catch the version error) and retry with a version from the server's own supported list — don't hardcode one version and assume it will always work.

Two negotiation models exist right now, and your bot may hit both

MCP has been through five dated revisions since November 2024. The current one is 2026-07-28, and it changed how version mismatches are detected and handled. Revisions 2025-11-25 and earlier used a handshake: the client's first message was an initialize request carrying the protocol version it wanted, and the server replied once, before any other work happened.

The 2026-07-28 revision removed that handshake entirely. There is no initialize call and no session. Instead, every single request declares its own protocol version in a _meta field (and on HTTP, also in an MCP-Protocol-Version header), and the server accepts or rejects each request independently.

If your bot only ever talks to one server you built yourself, you can pick whichever model that server speaks and move on. If it connects to third-party MCP servers you don't control — which is the common case for a small-team bot pulling in several tools — you need to know both failure paths, because you'll hit a mix of old and new servers for a long transition period.

Sources: Key Changes - Model Context Protocol, Versioning and Compatibility - Model Context Protocol.

Legacy servers: one shot at agreement, then disconnect

On a 2025-11-25-or-earlier server, version negotiation happens exactly once, in the initialize exchange. The client sends the version it supports — ideally its latest. If the server also supports that version, it must echo it back unchanged. If not, the server must respond with another version it does support, ideally its own latest.

The decision point then falls to the client: if it doesn't support the version the server offered, it should disconnect. There's no retry loop built into the handshake itself — your bot's client code has to decide, after seeing the server's countered version, whether to accept it or give up. A worked example: your bot pins protocol version 2025-06-18. It connects to an older server that only speaks 2024-11-05. The server counters with 2024-11-05 in its initialize response. If your client library's SUPPORTED_VERSIONS list doesn't include 2024-11-05, the right move is to close the connection and surface a clear 'incompatible server' error to whoever configured that tool — not to keep sending requests on a version the server never agreed to.

A common bug here is a client that accepts whatever the server sends back without checking it against its own supported list, which can lead to silently using features the client never actually implemented for that version.

Sources: Lifecycle - Model Context Protocol.

Current spec: every request is checked on its own

Under 2026-07-28, a mismatch doesn't happen at connection time — it happens on whichever request first declares a version the server won't serve. The server responds with a structured UnsupportedProtocolVersionError (code -32022) whose data field lists the versions it actually supports and the version that was requested.

The client's job is to read that list and retry with a mutually supported version, or surface an error to the user if nothing overlaps. This is strictly more informative than the legacy path, where a client had to infer compatibility from a countered version with no explicit list. Worked example: your bot sends a tools/call request tagged with protocol version 2027-01-01 (a version that doesn't exist yet) to a server that only supports 2026-07-28 and 2025-11-25. The server returns -32022 with supported: ["2026-07-28", "2025-11-25"]. Your bot's retry logic picks 2026-07-28, resends the exact same tool call with the corrected _meta, and proceeds — no reconnection needed, because there was never a session to rebuild.

Servers on this revision must also implement a server/discover RPC that reports their supported versions, capabilities, and identity up front. Calling it is optional for the client — a bot can skip straight to its preferred request and only consult the error data if that request bounces — but it's useful as a one-time probe when you're not sure which era a server speaks at all, since on stdio transports it's the documented way to detect a legacy server without stalling on a request it will never answer.

Sources: Versioning and Compatibility - Model Context Protocol, Key Changes - Model Context Protocol.

What this means for your bot's client code

Treat version handling as something your bot checks per server, not once for your whole fleet. Three concrete rules follow from the spec text above.

First, don't hardcode a single protocol version into your bot's MCP client and assume every server you connect to will accept it. Keep a short list of versions your client actually implements, in order of preference, and use whichever one a given server confirms it supports.

Second, treat a legacy initialize mismatch and a modern UnsupportedProtocolVersionError as two different code paths, not one. The legacy path ends in disconnect-and-report if nothing matches; the modern path gives you a supported list to retry against in the same request cycle, with no reconnection.

Third, if you're writing a client that has to reach unknown servers — the situation most small-team bots are actually in, since you rarely control every MCP server you connect to — probe with server/discover (or catch a non-modern error on the first request) before committing to an era, and cache that result per server rather than re-probing on every call. A server that never answers an unrecognized pre-handshake request will otherwise stall your bot for a full timeout window before it falls back to the legacy path.

  • Keep an ordered list of protocol versions your client implements; don't pin to exactly one.
  • Legacy mismatch (pre-2026-07-28): disconnect if the server's countered version isn't on your list.
  • Modern mismatch (2026-07-28+): read the supported list in the -32022 error data and retry in-place.
  • Cache the detected era per server so you're not re-probing on every tool call.

Sources: Versioning and Compatibility - Model Context Protocol, Lifecycle - Model Context Protocol.

Primary sources

Sources checked 2026-10-10. Standards and product documentation can change; follow the linked version when implementing.

  1. Versioning and Compatibility - Model Context Protocol — Model Context Protocol
  2. Key Changes - Model Context Protocol — Model Context Protocol
  3. Lifecycle - Model Context Protocol — Model Context Protocol

BotBento is in development. Suggest a correction.