Est.

MCP Server Versioning and Breaking Change Management

Backwards-compatible changes don't bump the version date, making protocol evolution harder to track.

Staff Writer · · 12 min read
Cover illustration for “MCP Server Versioning and Breaking Change Management”
MCP Architecture · August 30, 2026 · 12 min read · 2,708 words

MCP versioning runs on dates, not counters. A version string like 2026-07-28 marks the last day the spec broke backwards compatibility, and that's it, nothing more. It's not a build number, and it doesn't tick up every time something changes. That one design choice is behind most of the confusion I see teams run into when they try to figure out whether their server is current, and it's the thread running through everything below.

Here's the part that trips people up: two servers can share the same date-version and behave completely differently. If a change is backwards-compatible, the date doesn't move. So a server built in August against 2026-07-28 and one built in October against that same string might have picked up different non-breaking additions along the way, and there's no patch number anywhere to tell them apart. An update either becomes a new publication with its own version, or it happens quietly in place. Nothing in between.

Individual features make this messier still. Each one carries its own status, Draft, Current, or Deprecated, and that status can shift without the top-level version date moving at all. So the spec is really two layers stacked on each other: a date that tracks breaking changes, and a set of feature lifecycles moving independently underneath it.

Five revisions have shipped since launch: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, and 2026-07-28. Call it a quarterly-to-biannual rhythm. Anthropic open-sourced the protocol in late 2024, and by 2026 both OpenAI and Google had shipped support for it. That matters, because every time the spec shifts, the surface area affected gets wider. This stopped being a niche protocol with a handful of implementers a while ago.

What each major revision actually changed, and where the instability has come from

The first release, 2024-11-05, ran on stateful SSE: server-to-client streaming over Server-Sent Events, client-to-server over HTTP POST. Two separate channels, both required.

2025-03-26 tore that up. Chunked HTTP streaming replaced SSE, and the spec added JSON-RPC batching, letting clients bundle multiple calls into one request.

Then 2025-06-18 removed batching. Added in March, gone by June, one release later. Maintainers said there was no compelling use case for it; not everyone building against the spec agreed, and that's the real lesson here. Protocol decisions land on server authors with no warning and no vote. That same release also required OAuth Resource Server classification, required Resource Indicators under RFC 8707, and required an MCP-Protocol-Version header on every subsequent HTTP request. It added structured tool output, elicitation, resource links inside tool results, and a dedicated security best-practices page, all in one date.

2025-11-25 was quiet by comparison, just a few optional fields added to the clientInfo shape (title, description, icons, websiteUrl). Contained, low-risk, easy to absorb.

Then 2026-07-28 landed as the biggest revision since day one: a stateless core, response caching, an extensions framework, MCP Apps, a redesigned Tasks extension, and the first formal deprecation policy. Sit with the pattern across these five releases for a second: the spec has added and removed features back to back, sometimes inside a single release cycle. Teams that built early against a draft feature ate the cost when that feature reversed course months later. That's not a hypothetical. It happened, with batching, in public, and I'd bet more than a few server authors remember exactly where they were when they read that changelog.

The stateless core in 2026-07-28 and why it required a clean architectural break

Before 2026-07-28, connecting to an MCP server over HTTP meant going through a stateful handshake. The server replied to your initialize call with an Mcp-Session-Id header, and every request after that had to carry that same ID. That pins a client to one specific container or pod, the one holding that session in memory.

Scaling that horizontally is a genuine headache. Teams either stood up shared Redis stores to hold session state across instances, or built gateway-level logic to inspect packets and route them to the right pod. Both options add latency and operational weight that work against how cloud-native load balancing is supposed to function: spread traffic anywhere, no memory, no stickiness.

2026-07-28 removes the handshake outright. The initialize/initialized exchange (SEP-2575) and the Mcp-Session-Id header (SEP-2567) are both gone. In their place, protocol version, client info, and client capabilities travel inline, in a _meta field on every request. Each request describes itself completely now and stands on its own; nothing about it depends on what came before.

The payoff is concrete. A remote MCP server can sit behind a plain round-robin load balancer with zero session affinity, and clients can cache tools/list responses for as long as the server's ttlMs value says they're good for.

This wasn't a surface fix. The stateful handshake was load-bearing, wired straight into the transport layer, so there was no incremental path off it. The break had to happen all at once. With it done, the spec's authors are betting that extensions and formal deprecation windows, not another transport rewrite, are what future evolution runs on. Anyone building against 2026-07-28 shouldn't need to touch lifecycle or transport code again for a good while.

The rollout itself moved with more patience than I expected: a ten-week release-candidate window ran before the July 28, 2026 publication date, giving SDK maintainers time to test the new model against real workloads instead of synthetic ones. All four Tier 1 SDKs shipped support by the day the spec went live.

How version negotiation works across the old handshake model and the new stateless model

Under the old model, negotiation was a single round-trip. The client sends an initialize request naming the protocol version it wants. The server answers with whatever version it's actually running, and if that doesn't match, the client finds out on that same exchange, no guessing required.

When it fails outright, the failure is specific: a -32602 error, labeled "Unsupported protocol version," with a data object listing what the server does support. Over Streamable HTTP, the MCP-Protocol-Version header (required since 2025-06-18) has to ride along on every request after the first. Leave it off, and the server quietly falls back to 2025-03-26 to stay backwards-compatible.

The stateless model, post-2026-07-28, drops the handshake entirely. Version and capability data travel inside each request's metadata, so there's no session left to negotiate in the first place. Clients that want to check compatibility ahead of time can call server/discover, an optional but recommended RPC that returns supported versions, capabilities, and identity before a single tool gets called. A client can also just skip that step and send a request; if the version's wrong, it gets back an UnsupportedProtocolVersionError with the server's supported list attached.

Clients and servers can both support more than one protocol version at once, and newer clients built for 2026-07-28 aren't stranded when they hit an older server. They fall back to the initialize handshake automatically, which keeps the generational boundary from turning into a hard wall.

None of this eliminates failure. What it does is make failure legible. A mismatch returns a clear, structured error with the information needed to recover, instead of a silent drop or an ambiguous timeout. That's the practical win here: teams can build automated fallback logic around a known, documented error shape instead of guessing at what went wrong.

The formal deprecation policy introduced in 2026-07-28 and the features it immediately affects

Before this release, there was no deprecation policy. None. Features got added and pulled across successive versions with no defined runway, no advance notice, no minimum window. The batching episode is the clearest proof of that: shipped in March, gone in June, no warning built into the process because there was no process to build one into.

2026-07-28 closes that gap. Anything marked for deprecation now has to stay functional for at least 12 months before it can be pulled, and every deprecated feature gets tracked in a public registry with a stated timeline.

Three features got the deprecation label in this release, with July 28, 2027 as the earliest possible removal date. Roots, Sampling, and Logging (SEP-2577) all stay in the spec through the window; the recommended paths off them are tool parameters, direct provider APIs, and stderr or OpenTelemetry, respectively. Separately, the original HTTP+SSE transport from 2024-11-05 (SEP-2596) got reclassified as Deprecated. Teams still running that first-generation transport now have an actual migration deadline instead of a surprise cutoff one day.

Tasks is worth watching closely as a case study. It shipped as an experimental core feature back in 2025-11-25, and real production use surfaced design problems fast enough that 2026-07-28 redesigned it as an extension instead of a core piece of the spec. Pulling a feature out of core is, by definition, a breaking change. But now that it lives in the extension model, future changes to Tasks can move through capability flags and settings-level versioning rather than forcing another spec-wide break.

SDK obligations follow the same calendar discipline. Tier 1 SDKs were expected to ship support inside the ten-week RC window, and all four did, by publication day. That's the actual value of the new policy. It's not a best-effort promise anymore; it's a date on a calendar that teams can plan a migration against.

How the MCP Registry handles server-level versioning and what it requires of publishers

The official MCP Registry went into preview in September 2025. Think of it as an app store for MCP servers, one authoritative place to find publicly available servers and know what you're actually getting.

Versioning rules there are strict, and they're documented in the official registry guidelines. Every server has to declare a version string in its server.json file, and that string has to be unique per publication. Once a version goes live, neither the string nor its metadata can change afterward. That's enforced at the registry level, not left to convention or good intentions.

Semantic versioning is the recommended format, but the registry doesn't require it; it accepts any string. It tries to parse each one as semver so it can sort versions and mark the "latest" correctly. If parsing fails, the registry just marks that version as latest by default, a quiet trap for anyone publishing loosely formatted version strings without realizing it. For local servers, the guidance is to align the server's version with the underlying package version, so the deployment artifact and the protocol behavior don't drift apart in someone's head six months later.

Enterprises running private catalogs have their own options too. MLflow's MCP Registry, for instance, gives organizations a centralized catalog for registering, versioning, and managing internal MCP servers, built on the open registry standard, with full version control and lifecycle tracking for teams that want a curated internal inventory rather than the public one.

What this adds up to: immutability at the registry level forces deliberate versioning. You can't patch a published version quietly; a breaking change means a new publication, full stop. That's a good forcing function, and worth saying plainly: any organization running MCP servers without registry entries has no authoritative record of what version any given agent is actually talking to. That's a real structural gap in how the whole thing gets governed, not a paperwork issue.

The tool-level versioning gap and what it means for production workflows

Here's where the spec still has a hole in it, and a wide one. There's no formal versioning system for individual tools. A server can update a tool's behavior, its schema, its return shape, anything, at any time, and nothing in the protocol requires a version bump or a notice to anyone.

That opens the door to silent breakage. A server ships a change to a tool's signature, an agent workflow depending on that exact shape breaks somewhere downstream, and no one gets told, because there was never a channel built for that notification to travel through in the first place. This is what makes MCP tool versioning harder than API versioning ever was. Traditional API consumers are developers; you can version an endpoint, publish a changelog, add a deprecation header, and reach them through channels that already exist. MCP tool consumers are often agents acting on behalf of end users who have no way to subscribe to a change event at the tool level, and often don't even know the tool exists as a discrete, versioned thing to begin with.

The spec's Draft/Current/Deprecated lifecycle operates at the protocol level. It has nothing to say about an individual tool's schema or behavior changing underneath an agent that's been calling it the same way for months.

So what can teams running servers in production actually do about this today, given that no standard exists yet? A few things I'd treat as non-negotiable rather than nice-to-have:

  • Treat any tool schema change as a breaking API change: ship a new tool name or a versioned namespace instead of mutating the old one in place.
  • Keep an internal changelog tied to each server publication in the registry, even where the spec doesn't require one.
  • Watch tool call patterns with observability tooling; a shift in argument shapes or return structures showing up unannounced in logs is usually the first sign something broke quietly.

Nothing here is exotic. It's the same discipline API teams have used for years, just applied to a corner the spec hasn't caught up to yet.

A deprecation and rollout strategy for teams running MCP servers in production

Good deprecation looks deliberate, not reactive. Mark a feature or tool version as deprecated in the server's metadata well before you remove it, and give a real removal date, not a vague intention to "eventually" retire it. Use the spec's own 12-month minimum as a floor for internal policy too, even where nothing forces you to; matching that commitment tells everyone downstream your server is predictable to build against. Run old and new versions in parallel through the whole deprecation window instead of pushing everyone to migrate the day you announce the change.

Version negotiation itself becomes a rollout lever. A server that supports multiple protocol versions at once can gate new behavior behind that negotiation, so clients still on an older version keep getting the old behavior until they choose to move on their own schedule. The server/discover RPC introduced in 2026-07-28 gives clients a clean way to check what's available before committing to anything; expose that endpoint, and keep its answer accurate as things change underneath it.

Controlled rollout follows the logic the registry already enforces. Treat every new server publication as immutable once it ships, the same rule the public registry holds itself to, and apply it even inside a private one. Run old and new versions side by side behind a version-aware gateway instead of upgrading in place and hoping nothing breaks. Lean on capability flags and the extensions model that came with 2026-07-28 to introduce new behavior in pieces, rather than shipping an all-or-nothing spec update and finding out what broke after the fact.

Observability has to sit underneath all of this, and it can't just be a log you check after something's already gone wrong. An audit trail read during a postmortem tells you what happened; it doesn't stop it from happening again. Real-time visibility into what protocol version an agent is negotiating, which tools it's calling, and what it's actually getting back, is the only way to catch a silent break while it's still small. None of that works without centralized access control and a real server registry sitting behind it. You can't catch version drift in a server you don't know is running.

Catching problems early means paying attention before release day, not after. Follow SEP activity and RC announcements as they happen; the RC window, now stretched to ten weeks for major releases, exists so you can validate against your own workloads, not so you can start planning once the spec's already final. Keep a central registry of every MCP server in your environment, tagged with its current version, which protocol versions it supports, and its deprecation status. An MCP server nobody registered is a version nobody can account for. And treat the public, calendar-anchored deprecation registry as an input to your own migration calendar, something you check on a schedule, not something you stumble on the week a feature disappears.

Sources

  1. modelcontextprotocol.io
  2. thenewstack.io
  3. medium.com
  4. hidekazu-konishi.com
  5. modelcontextprotocol.io
  6. developers.googleblog.com
Filed underMCP Architecture

More in MCP Architecture