Support Both MCP Specs? A Q4 2026 Dual-Protocol Checklist
Nikhil Tiwari
MCP Playground
π TL;DR
- Public MCP server? Keep serving both specs through Q4 2026. Old clients still send the 2025
initializehandshake. - 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/discoverand 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 start | initialize handshake | None. Optional server/discover |
| Sessions | Mcp-Session-Id header | Removed |
| Protocol version | Negotiated once | In _meta on every request |
| HTTP routing headers | β | Mcp-Method, Mcp-Name required |
| Resource not found | -32002 | -32602 |
| Scaling | Sticky sessions or shared store | Any 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
initializerequest body, or anMcp-Session-Idheader on later calls. - Modern signals: a protocol version in
_meta, under the keyio.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.
- Serve both eras from one URL, unless every client is under your control.
- Log the era of every request. You need this number to ever drop legacy.
- Return era-correct error codes. Resource-not-found is
-32002for legacy and-32602for modern. - Forward
Mcp-MethodandMcp-Namethrough every proxy and gateway. - Remove session assumptions from tool code. State goes in explicit handles, like a
basket_idargument. - Add cache hints. Set
ttlMsandcacheScopeon list results so modern clients stop refetching. - Plan off Roots, Sampling and Logging before July 2027.
- 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:
- Announce it. Put a date in your docs and changelog. Give old clients time to upgrade.
- Flip to
legacy: 'reject'. Keep watching your error logs for-32022spikes.
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?+
Can a new MCP client connect to an old MCP server?+
How does a server tell which MCP version a request uses?+
Does the 12-month deprecation policy mean 2025-11-25 is supported until 2027?+
How do I test my MCP server on both specs?+
Written by Nikhil Tiwari
15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.
Free MCP Tools (no install)
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.