Back to Blog
DevelopmentOct 4, 202611 min read

Support Both MCP Specs? A Q4 2026 Dual-Protocol Checklist

NT

Nikhil Tiwari

MCP Playground

πŸ“– TL;DR

  • Public MCP server? Keep serving both specs through Q4 2026. Old clients still send the 2025 initialize handshake.
  • A modern-only server rejects them with -32022 UnsupportedProtocolVersion. They simply can't connect.
  • New v2 SDK clients are fine either way. They try server/discover and fall back to the handshake.
  • In the v2 TypeScript SDK, dual support is the default: createMcpHandler(factory, { legacy: 'stateless' }).
  • Drop legacy when your logs say so, not on a date. Count requests per era first.
  • Deprecated features (Roots, Sampling, Logging) keep working until at least July 28, 2027.

The stateless MCP spec shipped on July 28, 2026. Two months later, I'm still getting the same question.

"Can I drop the old 2025-11-25 handshake yet?"

It's a fair question. Serving one spec is simpler. Dual code paths feel like debt you want gone.

But get it wrong and some of your users stop connecting overnight. They won't get a helpful message either. They'll get an error code and give up.

This post is my answer to MCP dual protocol support in Q4 2026. Who breaks if you drop the old spec, how detection works, and how to serve both from one URL.

There's also a checklist, and a simple rule for when it's safe to stop. It's based on your traffic, not the calendar.

If you haven't migrated at all yet, start with my 2026-07-28 migration guide. This post assumes you're past that.

MCP 2025-11-25 vs 2026-07-28: The Two Specs in One Table

The official v2 SDK splits MCP into two eras. Every revision up to 2025-11-25 is legacy. 2026-07-28 and later is modern.

Aspect Legacy (≀ 2025-11-25) Modern (2026-07-28)
Connection startinitialize handshakeNone. Optional server/discover
SessionsMcp-Session-Id headerRemoved
Protocol versionNegotiated onceIn _meta on every request
HTTP routing headersβ€”Mcp-Method, Mcp-Name required
Resource not found-32002-32602
ScalingSticky sessions or shared storeAny instance, any request

For the full list of changes, see what the stateless MCP spec changes.

Who Breaks If You Drop MCP 2025-11-25?

This is the part people get backwards. Dropping legacy support doesn't hurt new clients. It hurts old ones.

Client Dual server Modern-only server
New (v2 SDK)βœ… Modernβœ… Modern
Old (2025-era SDK)βœ… Legacy❌ -32022

A new client asks server/discover first. If the server is legacy, it falls back to the initialize handshake. So new clients cope with old servers.

The reverse isn't true. An old client only knows the handshake. A modern-only server answers it with -32022 UnsupportedProtocolVersion, and the connection ends.

Who still runs old clients? Pinned SDK versions in production agents. Internal tools nobody has touched since spring. Desktop apps whose users never update.

Should Your MCP Server Support Both Specs?

It depends who connects to you. Here's how I'd decide.

  • Public server, unknown clients: yes. Keep both through Q4 2026 at minimum.
  • Internal server, clients you control: upgrade the clients, then go modern-only.
  • Brand-new server: build for 2026-07-28. Leave the SDK's default fallback on, since it costs you nothing.
  • Sessionful legacy deployment: keep it running beside a new modern handler, and route between them.

The cost of keeping legacy is small in the v2 SDK. The cost of dropping it too early is lost users. That trade is easy.

How MCP Version Detection Works

A dual server has to sort each request into an era. The request itself tells you. No extra header or URL is needed.

  • Legacy signals: an initialize request body, or an Mcp-Session-Id header on later calls.
  • Modern signals: a protocol version in _meta, under the key io.modelcontextprotocol/protocolVersion.

On the client side, it's the mirror image. Call server/discover. If that fails, fall back to initialize. The v2 client SDK does this for you.

One more client-side detail. The newest revision the v2 client uses for its handshake is 2025-11-25, not 2026-07-28. That's the fallback version, not a bug.

How to Serve Both MCP Specs From One Endpoint

In the v2 TypeScript SDK, createMcpHandler serves both eras by default. You pass a factory that builds one server per request.

import { createMcpHandler } from '@modelcontextprotocol/server';

// One factory backs both eras, so they can't drift apart.
const handler = createMcpHandler(
  ({ era }) => {
    metrics.increment('mcp.requests', { era }); // 'legacy' | 'modern'
    return buildServer();
  },
  { legacy: 'stateless' } // the default; 'reject' = modern-only
);

export default { fetch: (req: Request) => handler.fetch(req) };

Two things to notice. The era argument is your migration metric. Count it, and you'll know exactly when legacy traffic dies.

And legacy: 'stateless' serves 2025 traffic without sessions. That's fine for most tool servers, because each call stands alone.

Already Running a Sessionful Legacy Server?

Don't rewrite it. Put a modern-only handler in front, and route legacy requests to your existing code with isLegacyRequest.

import { createMcpHandler, isLegacyRequest } from '@modelcontextprotocol/server';

const modern = createMcpHandler(factory, { legacy: 'reject' });

export default {
  async fetch(request: Request) {
    if (await isLegacyRequest(request)) {
      return existingLegacyHandler(request); // your current sessionful setup
    }
    return modern.fetch(request);
  },
};

This is the pattern the SDK docs recommend. Your proven legacy path stays untouched while new traffic moves to stateless.

Watch Your Gateway

If a proxy sits in front, check two things. It must forward Mcp-Method and Mcp-Name for modern traffic.

And it must keep sticky routing for sessionful legacy traffic. Mixing those rules up is the most common dual-stack bug I see. More in my MCP gateway guide.

What the MCP Deprecation Policy Actually Covers

I see the 12-month rule misread a lot. It's about deprecated features, not old spec versions.

The 2026-07-28 release deprecated three features:

  • Roots β†’ use tool parameters, resource URIs or server config
  • Sampling β†’ call your LLM provider's API directly
  • Logging β†’ use stderr or OpenTelemetry

The policy says these keep working in this release and in every spec version published within a year of it. So the earliest removal is July 28, 2027.

That gives you room, not a free pass. Start moving off Roots, Sampling and Logging now, while you're touching this code anyway.

The Q4 2026 MCP Dual-Protocol Checklist

Run through this before the end of the year.

  1. Serve both eras from one URL, unless every client is under your control.
  2. Log the era of every request. You need this number to ever drop legacy.
  3. Return era-correct error codes. Resource-not-found is -32002 for legacy and -32602 for modern.
  4. Forward Mcp-Method and Mcp-Name through every proxy and gateway.
  5. Remove session assumptions from tool code. State goes in explicit handles, like a basket_id argument.
  6. Add cache hints. Set ttlMs and cacheScope on list results so modern clients stop refetching.
  7. Plan off Roots, Sampling and Logging before July 2027.
  8. Test both eras on every release. A green modern test says nothing about legacy.

Item 3 trips people up. Here's what -32602 means and why the code moved.

How to Test Both MCP Versions Against Your Server

Point 8 is where most teams slip. They test with the newest client and assume old ones still work.

MCP Playground has an MCP version switch on the server tester for exactly this:

  • Auto detect tries server/discover, then falls back to the handshake, like a real v2 client.
  • Stateless 2026-07-28 forces the modern path.
  • Stateful 2025-11-25 forces the legacy handshake, like an old client.

After connecting, a badge shows what actually answered: the revision, stateless or stateful, and the session ID if there is one.

So the test is simple. Connect once on Stateless, once on Stateful. Both should list the same tools. Test any MCP server free β†’

Want to see a correct dual server first? Point the tester at the mock MCP servers. The stateless mock accepts ?rev=2026-07-28 or ?rev=2025-11-25 to pin one era, so you can watch the -32022 rejection happen.

Writing a client instead? Build against the 2026 spec and test it on the pinned mocks.

When Should You Drop MCP 2025-11-25?

Not on a date. Drop it when your era metric says legacy traffic is near zero, and has stayed there for a few weeks.

When you get there, do it in two steps:

  1. Announce it. Put a date in your docs and changelog. Give old clients time to upgrade.
  2. Flip to legacy: 'reject'. Keep watching your error logs for -32022 spikes.

For most public servers, I don't expect that before 2027. Until then, dual support is the cheap insurance.

How MCP Playground Can Help

The MCP Playground server tester connects to any remote MCP server in either spec, shows which era answered, and lets you call every tool. You can share a link that opens the tester with your server and MCP version already selected, so a teammate can reproduce a legacy-only failure in one click.

The Bottom Line

Keep serving both MCP specs through Q4 2026 if strangers connect to your server. New clients cope with old servers, but old clients can't reach new-only ones.

The v2 SDK makes dual support the default. Log the era, test both paths each release, and drop legacy when your data says so.

Does your server answer both specs?

Connect on Stateless, then on Stateful, and compare. Free, in the browser.

Test any MCP server free β†’ Try the mock servers β†’

Sources: MCP 2026-07-28 changelog Β· MCP release candidate notes Β· MCP TypeScript SDK Β· AAIF migration overview Β· W3C Trace Context

FAQ

Should my MCP server support both 2025-11-25 and 2026-07-28?+
If it is public, yes, at least through Q4 2026. Clients built on 2025-era SDKs only know the initialize handshake, and a modern-only server rejects them with -32022. Internal servers whose clients you control can go modern-only once those clients are upgraded.
Can a new MCP client connect to an old MCP server?+
Yes. The v2 client SDK calls server/discover first and falls back to the initialize handshake if the server is legacy. The reverse does not work: an old client cannot connect to a modern-only server.
How does a server tell which MCP version a request uses?+
From the request. An initialize body or an Mcp-Session-Id header means legacy. A protocol version in _meta under io.modelcontextprotocol/protocolVersion means modern. The v2 SDK exposes this as isLegacyRequest and passes the era to your server factory.
Does the 12-month deprecation policy mean 2025-11-25 is supported until 2027?+
Not exactly. The policy covers deprecated features (Roots, Sampling and Logging), which keep working in every spec version published within a year of 2026-07-28. Whether your server still accepts 2025-era clients is your decision, based on who connects to you.
How do I test my MCP server on both specs?+
Use the MCP version switch in the MCP Playground server tester. Connect once with Stateless 2026-07-28 and once with Stateful 2025-11-25, and check that both list the same tools and return the same results.
NT

Written by Nikhil Tiwari

15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.

Build, compare & ship MCP agents

Connect any MCP server, run evals on it, compare 60+ models side-by-side, deploy hosted servers, and save reusable agents you can export as an API β€” all in your browser.

Try for Free β†’
Support Both MCP Specs? A Q4 2026 Dual-Protocol Checklist