Learn MCP - Versioning & Compatibility (Modern vs Legacy)
Series/Learning MCP/Episode 17
Episode 17 of 23

Learn MCP - Versioning & Compatibility (Modern vs Legacy)

This episode closes the operations phase with version negotiation across MCP spec eras: how modern and legacy clients interact, dual-era implementation strategies so one server serves both, and deprecation policy — features like Roots, Sampling, and Logging that carry a removal clock of about one year before being removed.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 16 you managed a fleet with versioning and rolling deployment. But there's one version more complicated than your application's version: the MCP protocol version itself. The spec changed dramatically in 2026-07-28 — stateless, server/discover, MRTR, deprecations — while the clients in the wild (IDE agents, CLI agents, older applications) still have some speaking the 2025-11-25 era or older. Episode 17 covers how you survive when those two worlds meet.

This episode's roadmap: the version negotiation mechanism, what changed between the modern and legacy eras, dual-era implementation strategies so one server serves both eras, deprecation policy with removal clocks, and a measured migration plan.

Version Negotiation in MCP

Every MCP session starts with the initialize handshake (from episode 3). One of its most important roles is version negotiation: the client declares the protocolVersion it supports, and the server replies with the version they'll use together. The two don't have to be exactly the same — they find the highest version both understand.

initialize - client menawar versi
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2026-07-28",
    "capabilities": { "tools": {}, "resources": {} },
    "clientInfo": { "name": "legacy-agent", "version": "1.4.0" }
  }
}

A server supporting multiple eras replies with the best-fit version. A modern server that's also compatible with old clients will respond protocolVersion: "2025-11-25" if the client only understands that era. This negotiation is the foundation of every compatibility strategy — without it, servers and clients from different eras would send each other messages neither understands.

Modern vs Legacy: What Changed

So you know what needs bridging, here's a summary of the most decisive differences between the modern era (2026-07-28+) and legacy (2025-11-25 and earlier):

AspectLegacy (≤2025-11-25)Modern (2026-07-28+)
StateSession state with Mcp-Session-IdStateless, identity per request
Handshakeinitialize then sessionserver/discover replaces the full handshake
Long notificationsRelies on the SSE streamMRTR with messageId/routingId
Additional featuresRoots, Sampling, Logging in coreDeprecated, replaced by OTel & extensions
MRTRNot availableAvailable

At the implementation level, this difference hurts most around state: legacy clients send Mcp-Session-Id and expect the server to remember it, while modern clients send everything per request. A server serving both must handle both patterns without mixing them.

Dual-Era Implementation

Dual-era means one server binary that can serve clients from both eras simultaneously. This is the most widely used strategy during the transition, and the pattern is consistent:

  1. Accept the request, detect the era from the protocolVersion value on initialize (or from server/discover for modern clients).
  2. Branch the behavior: legacy clients get the session flow (Mcp-Session-Id), modern clients get the stateless flow.
  3. For methods that changed, provide both paths — for example long notifications use SSE for legacy and MRTR for modern.
  4. Expose deprecated features only to legacy clients that actually ask for them.

Example of era detection logic on the server side:

era.ts - memilih jalur berdasarkan versi client
const MODERN = "2026-07-28";
const LEGACY = "2025-11-25";
 
function negotiate(clientVersion) {
  const supported = [MODERN, LEGACY];
  return supported.includes(clientVersion) ? clientVersion : LEGACY;
}
 
function handleInitialize(req, res) {
  const era = negotiate(req.params.protocolVersion);
  if (era === MODERN) {
    return res.json({ protocolVersion: MODERN, serverDiscover: true });
  }
  res.setHeader("Mcp-Session-Id", crypto.randomUUID());
  return res.json({ protocolVersion: LEGACY, capabilities: legacyCaps });
}

Dual-era does add code complexity, but it buys valuable transition time: you don't have to upgrade all clients at the same time as your server release. Stricter strategies — for example rejecting all legacy clients — are only worth it if you control every client and have a coordinated release schedule.

Warning

Dual-era is not a reason to neglect security. Legacy features — especially session state and Roots — carry risks that were mitigated in the modern era. Make sure served legacy clients still go through the gateway with unified auth (episode 16), and monitor the metrics for how much traffic still uses the old era; that's the key data for deciding when the legacy era can be turned off.

Deprecation Policy & Removal Clock

MCP enforces a disciplined deprecation policy: abandoned features aren't removed suddenly, but given a removal clock — a grace period of about one year from when the deprecation status is announced. The three names you'll most often encounter:

  • Roots — the list of root directories a server may access; replaced by an architecture that doesn't extend trust automatically.
  • Sampling — the server asking the model to have another model create a completion; replaced by more explicit tool-based patterns.
  • Logging — the built-in logging protocol; replaced by OpenTelemetry (episode 15).

These features are still usable while the removal clock runs — dual-era servers often provide them for old clients — but there will be no development, and support will be removed after the deadline. Your plan should be:

  • Inventory — audit whether your servers send or consume deprecated features.
  • Replacement — move to the replacements (OTel for logging, explicit tools for sampling).
  • Deadline — schedule support removal before the removal clock expires, don't wait for errors in production.

Migration Plan

Putting it all together, here's a measured migration plan for your team:

  1. Catalog: record the spec version of every server and client in the fleet (episode 16 gives them semantic versions).
  2. Dual-era: make the server modern with legacy compatibility the first step — not a direct jump.
  3. Order clients: upgrade the clients using the most-deprecated features first; use gateway era metrics to set priorities.
  4. Clean up: once all clients are modern era, turn off the legacy paths and features whose removal clock has expired.
  5. Test: run a test matrix — modern and legacy clients against new servers — before and after every release.

Throughout the whole process, keep returning to version negotiation: as long as server and client agree on a version at the handshake, two eras can coexist peacefully. The most common mistake isn't technical, it's managerial — ignoring version negotiation until a big release surprises everyone.

Conclusion

Episode 17 closed the operations phase with maturity: version negotiation becomes the bridge between different spec eras, modern and legacy differences (state, handshake, notifications, features) are mapped out to be worked around, the dual-era strategy buys transition time, the deprecation policy with its ~one-year removal clock makes removal planned, and the five-step migration plan keeps the journey measured.

Key takeaways:

  • Version negotiation happens at the handshake — the server replies with the highest version both understand, not your favorite version.
  • Modern (2026-07-28+) and legacy (2025-11-25-) differ fundamentally: stateless versus session, server/discover versus full handshake.
  • Dual-era is a transition strategy: one server serves both eras with version detection, without sacrificing gateway security.
  • Deprecation uses a ~one-year removal clock — Roots, Sampling, and Logging are in a grace period, with OTel as the logging replacement.
  • A five-step migration plan: catalog, dual-era, order clients, clean up legacy, and test the compatibility matrix.

In the next episode 18 we enter the advanced phase: Advanced SDK & Custom Transport — implementing custom transports like WebSocket and gRPC, JSON-RPC framing, notification batching, and error codes. See you there!

Learn MCP - Versioning & Compatibility (Modern vs Legacy) | Learning MCP