Migrate from 2025-11-25 to 2026-07-28
- Spec
2025-11-25 → 2026-07-28- Evidence
- E2Reproducedarm64
- TypeScript SDK (server)
@modelcontextprotocol/server@2.4.0- TypeScript SDK (client)
@modelcontextprotocol/client@2.4.0- Python SDK
mcp@2.3.0- Java SDK
io.modelcontextprotocol.sdk:mcp@2.1.0- Go SDK
github.com/modelcontextprotocol/go-sdk@v1.8.0- Verified
Most of the work is done by the SDKs. Upgrading the package is not always enough, though: in our tests, common setups kept speaking 2025-11-25 after an upgrade. This page lists what to check.
If you have not read it yet, How 2026-07-28 works explains the concepts used here.
Who can talk to whom
Section titled “Who can talk to whom”The specification calls a client or server that only speaks 2025 versions legacy, one that only speaks 2026-07-28 modern, and one that speaks both dual-era:
| Client | Server | Result |
|---|---|---|
| Dual-era | Modern or dual-era | Works on 2026-07-28 |
| Dual-era | Legacy | Works: the client falls back to initialize |
| Legacy | Dual-era | Works on the old protocol |
| Legacy | Modern only | Fails. Old clients have no way forward |
| Modern only | Legacy | Fails |
So the safe path is to make servers dual-era first, then move clients. The TypeScript, Python and Go SDKs we tested serve both generations from the same server code, as long as you use the right entry point. The Java SDK 2.1.0 only serves 2025-11-25.
What we measured
Section titled “What we measured”Each example server was called by the TypeScript client SDK 2.4.0 three ways:
| Server | Default client | versionNegotiation: auto |
Pinned to 2026-07-28 |
|---|---|---|---|
TypeScript 2.4.0, serveStdio() |
2025-11-25 | 2026-07-28 | 2026-07-28 |
TypeScript 2.4.0, server.connect(new StdioServerTransport()) |
2025-11-25 | 2025-11-25 | — |
Python 2.3.0 |
2025-11-25 | 2026-07-28 | 2026-07-28 |
Go v1.8.0 |
2025-11-25 | 2026-07-28 | 2026-07-28 |
Java 2.1.0 |
2025-11-25 | 2025-11-25 | fails: Version negotiation failed |
Two things stand out. The default TypeScript client always used 2025-11-25. And the TypeScript server only spoke 2026-07-28 when started with serveStdio().
Server checklist
Section titled “Server checklist”- Upgrade the SDK to a version that implements 2026-07-28. Of the official SDKs we tested, the Java SDK 2.1.0 does not yet; keep it on 2025-11-25.
- TypeScript: change the entry point. A v2 server that still calls
server.connect(new StdioServerTransport()), or hands requests to a…StreamableHTTPServerTransport, serves 2025 clients only. UseserveStdio(() => createServer())for stdio andcreateMcpHandler(() => createServer())for HTTP. The official upgrade guide says the same. - Remove per-connection state. Anything you kept in a session (a logged-in user, an open cart) must become a handle passed as a tool argument, or come from the auth token.
- Move server-to-client requests to multi round-trip requests. In TypeScript, return
inputRequired(...). In Python, replaceawait ctx.elicit(...)with aResolve(...)parameter:ctx.elicit()failed on 2026-07-28 in our test. - Set cache hints for lists that rarely change (
cacheHintsin TypeScript,cache_hintsin Python). Without them the SDKs sendttlMs: 0. - Stop relying on removed methods.
pingandlogging/setLevelreturn-32601. The log level now arrives per request in_metaasio.modelcontextprotocol/logLevel; without it, the server must not send log notifications for that request. - Resource not found is now
-32602(Invalid params) instead of-32002. Update clients or tests that match the old code. - HTTP servers: expect
MCP-Protocol-Version,Mcp-MethodandMcp-Nameheaders, return405forGET, and ignoreMcp-Session-Idfrom new clients.
Client checklist
Section titled “Client checklist”- Turn negotiation on. With the TypeScript client SDK v2, set
versionNegotiation: { mode: 'auto' }. The default is'legacy', the plain 2025 handshake. - Send
_metaon every request: protocol version and capabilities. The SDKs do this once negotiation picked 2026-07-28. - Declare capabilities you can fulfil. A server that wants a confirmation returns
-32021if your request does not declareelicitation. - Handle
input_required: ask the user, then retry with a new id, the same arguments,inputResponsesand the unchangedrequestState. The TypeScript client does this automatically through the handlers you register withsetRequestHandler. - Treat a missing
resultTypeas"complete": older servers do not send it. - Use
subscriptions/listenfor change notifications instead of theGETstream orresources/subscribe. - On stdio, probe with
server/discoverbefore falling back toinitialize. - On a broken HTTP stream, resend the request with a new id. Streams can no longer be resumed with
Last-Event-ID.
Check the result
Section titled “Check the result”Run the Inspector against your server once per protocol era:
npx -y @modelcontextprotocol/inspector@2.12.0 --cli <your command or URL> --protocol-era modern --method tools/listnpx -y @modelcontextprotocol/inspector@2.12.0 --cli <your command or URL> --protocol-era legacy --method tools/listBoth should list your tools. If modern fails with Version negotiation failed, the server still speaks only 2025-11-25: check the SDK version and, for TypeScript, the entry point.
Related
Section titled “Related”Sources
MCPDev is community documentation. It is not affiliated with the Model Context Protocol project or with the vendors of the clients it covers.