Skip to main content
KI-Agenten

Migrating MCP Servers from 2025-11-25 to the 2026-07-28 Spec: A Server Checklist

Migrate MCP servers from 2025-11-25 to 2026-07-28: per-request version checks, server/discover, required result fields, auth changes and a keep-or-pin rule.

10 min
Lesezeit
1,969
Wörter
Veröffentlicht
Engr Mejba Ahmed

Geschrieben von

Engr Mejba Ahmed

Artikel teilen

Migrating MCP Servers from 2025-11-25 to the 2026-07-28 Spec: A Server Checklist

If your agent's custom MCP servers were built against the 2025-11-25 revision, the 2026-07-28 MCP specification update changes how every request declares its version, which fields every result must carry, and which methods still exist. This guide gives you a server-side checklist, a dual-version gate you can adapt, and a keep, upgrade or pin decision for each server you run.

Why the 2026-07-28 MCP specification update needs a server migration plan

MCP uses date-based version identifiers in the format YYYY-MM-DD, and the date marks "the last date backwards incompatible changes were made", according to the versioning page. That page names 2026-07-28 as the current protocol version. The GitHub releases list shows the stable 2026-07-28 release published on 28 Jul 2026, after a release candidate.

A new date means at least one change breaks compatibility, and the 2026-07-28 changelog lists several that land directly on server code:

Area 2025-11-25 2026-07-28
Version negotiation Once, in the initialize handshake Per request, in _meta
Connection state Protocol-level sessions, Mcp-Session-Id Stateless; sessions and Mcp-Session-Id removed
Discovery Capabilities returned by initialize server/discover RPC, mandatory for servers
Liveness and logging ping, logging/setLevel Both removed; log level set per request
Server-to-client requests roots/list, sampling/createMessage, elicitation/create Multi Round-Trip Requests (input_required results)

The versioning page also points to a separate Backward Compatibility page for interoperating with handshake-based revisions (2025-11-25 and earlier). Read it before you ship any dual-version design, including the one below.

For background, see MCP Turned My AI From Chatbot to Real Agent.

From initialize handshake to per-request version checks

Under 2025-11-25, the lifecycle page fixes the version once. The client sends a version it supports in initialize; if the server supports it, the server "MUST respond with the same version", otherwise it responds with another version it supports. After a successful response the client sends:

{ "jsonrpc": "2.0", "method": "notifications/initialized" }

Two ordering rules matter for any legacy code path you keep. The client SHOULD NOT send requests other than pings before the server has answered initialize, and the server SHOULD NOT send requests other than pings and logging before receiving notifications/initialized. Over HTTP, the client MUST include MCP-Protocol-Version: <protocol-version> on all subsequent requests. Those later legacy requests carry the version only in the header.

The 2026-07-28 changelog removes the initialize/notifications/initialized handshake entirely. Every request now carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in _meta. Clients SHOULD send io.modelcontextprotocol/clientInfo, and servers SHOULD return io.modelcontextprotocol/serverInfo in each result's _meta. On Streamable HTTP, the versioning page says the same version value "is also carried in the MCP-Protocol-Version header", and the server "accepts or rejects each request independently."

An illustrative 2026-07-28 request (the _meta keys come from the changelog; the method, tool name and values are placeholders):

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "search_tickets",
    "arguments": { "query": "refund" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": { "name": "example-agent", "version": "1.0.0" }
    }
  }
}

If the server does not support the requested version, it returns an UnsupportedProtocolVersionError listing the versions it does support, and the client can retry with a mutually supported one. Servers MUST implement server/discover, which returns supported versions, capabilities and identity in one call. Clients MAY call it up front or skip it and handle a version error.

The changelog also renumbers the error codes introduced during the draft: HeaderMismatch moves from -32001 to -32020, MissingRequiredClientCapability from -32003 to -32021, and UnsupportedProtocolVersion from -32004 to -32022. If your SDK or test fixtures were written against a release candidate, grep for the old numbers.

The tradeoff is straightforward. Every request now pays for a version and capability check, but you no longer need sticky routing or session storage to remember what a client negotiated. Any replica can serve any request, which simplifies horizontal scaling behind a load balancer.

Left side shows one handshake key unlocking a single long session; right side shows each request packet carrying its own version tag routed to any server replica, illustrating the stateless 2026-07-28 model.

Server checklist part 1: a dual-version gate

This section is a design recommendation, not a documented API. Treat it as unverified until you compare it with the official Backward Compatibility rules; where they disagree, follow the spec.

The gate below serves handshake-era clients and 2026-07-28 clients from one server. Checks run in this order:

  1. Any initialize request goes to your existing 2025-11-25 handshake code. On stdio, that connection is then pinned to the negotiated version, requests without _meta are accepted, and only ping is allowed before the initialize response has been sent.
  2. An HTTP request with no _meta version but a handshake-era MCP-Protocol-Version header is served as legacy. Leave legacy Mcp-Session-Id issuance and validation to your existing 2025-11-25 transport code.
  3. If _meta and the header are both present and differ, reject with -32020. The mismatch check applies only when both exist.
  4. An unsupported _meta version is rejected with -32022 and a list of the versions this server accepts in _meta (only 2026-07-28 here). Handshake-era versions are left out of that list so a client retrying from it is not rejected again; those clients must use initialize instead. The error data shape here is a placeholder; take the real one from the schema.
  5. A request with no version at all is rejected.
CURRENT = {"2026-07-28"}
LEGACY = {"2025-11-25"}

def gate(req, conn):
    if req.method == "initialize":
        return legacy_handshake(req, conn)          # pins stdio conn
    if conn.is_stdio and conn.pinned_version:
        if not conn.init_answered and req.method != "ping":
            return reject_out_of_order(req)
        return serve_legacy(req, conn.pinned_version)

    meta_v = req.meta.get("io.modelcontextprotocol/protocolVersion")
    header_v = req.headers.get("MCP-Protocol-Version")  # None on stdio

    if meta_v is None and header_v in LEGACY:
        return serve_legacy(req, header_v)          # legacy session code
    if meta_v and header_v and meta_v != header_v:
        return error(-32020, "HeaderMismatch")
    if meta_v and meta_v not in CURRENT:
        return error(-32022, "UnsupportedProtocolVersion",
                     data={"supported": sorted(CURRENT)})  # placeholder shape
    if meta_v is None:
        return reject_missing_version(req)
    return serve_current(req, meta_v)

Two failure cases to test explicitly. First, a legacy HTTP client that drops the header after initialize falls through to the missing-version rejection. That is this design's choice, based on the 2025-11-25 lifecycle rule that clients MUST send the header; confirm the expected server-side fallback for a missing header in the 2025-11-25 transport and Backward Compatibility docs before relying on it. Second, a 2026-07-28 client behind a proxy that rewrites headers will trip -32020; log both values so you can tell a misbehaving client from a misbehaving intermediary.

Implement server/discover alongside the gate. Its exact response schema belongs to the spec's schema files, so build it from there rather than from the field names in this article.

A sorting gate routes incoming requests into a legacy handshake lane or a 2026-07-28 lane, with reject chutes for header mismatches and unsupported versions, mirroring the ordered checks of the proposed dual-version gate.

Server checklist part 2: results, headers and auth

Once a request reaches serve_current, these changelog items apply to every 2026-07-28 response path:

Check Requirement
resultType Required on all results: "complete" for ordinary results, "input_required" for interim multi round-trip results
ttlMs, cacheScope Required on tools/list, prompts/list, resources/list, resources/read, resources/templates/list; cacheScope is "public" or "private"
Request headers Mcp-Method and Mcp-Name required on Streamable HTTP POST requests
Sessions Mcp-Session-Id removed; pass server-minted handles as ordinary tool arguments for cross-call state
Removed methods ping, logging/setLevel, notifications/roots/list_changed
Logging Level set per request via io.modelcontextprotocol/logLevel; servers MUST NOT emit notifications/message without it
Subscriptions HTTP GET endpoint and resources/subscribe/unsubscribe replaced by subscriptions/listen
Resumability Last-Event-ID and SSE event IDs removed; a broken stream loses the in-flight request
Errors Resource not found changes from -32002 to -32602
Ordering Servers SHOULD return tools/list in a deterministic order

The cacheScope field deserves a deliberate choice per endpoint. If your tool list differs by user or tenant, mark it "private" so shared intermediaries do not cache it. A short ttlMs keeps clients polling more often; a long one risks stale tool lists, although listChanged notifications still complement it.

For authorization, the 2026-07-28 authorization page keeps the core resource-server duties. Servers MUST validate that access tokens were issued specifically for them as the intended audience (RFC 8707 Section 2), MUST NOT accept or pass through other tokens, and return 401 for invalid or expired tokens, 403 for insufficient scopes and 400 for malformed requests. Authorization MUST be included in every HTTP request from client to server, and tokens never go in the query string.

The server-side items to audit:

  • Servers MUST account for scope hierarchies, where a broader scope implies narrower ones, when deciding whether a token is sufficient.
  • On insufficient scope, clients compute the union of previously requested scopes and the scopes in the current challenge, so your challenge can list what the current operation needs.
  • Protected resources SHOULD NOT include offline_access in WWW-Authenticate scope or in Protected Resource Metadata scopes_supported, because refresh tokens are not a resource requirement.

For related reading, see Best MCP Servers 2025: 6 Tools That Fixed My AI Coding Workflow (Finally).

FAQ

Must I drop 2025-11-25 support now?

No. The versioning page says clients and servers MAY support multiple protocol versions simultaneously. Deprecated features stay in the specification for at least twelve months, or at least ninety days under the expedited-removal exception. Roots, Sampling and Logging are deprecated in 2026-07-28, not removed, and the changelog says they remain fully functional during the deprecation window, though new implementations should not add them.

Must clients call server/discover?

No. Servers MUST implement it; clients MAY call it for up-front version selection or as a backward-compatibility probe on stdio.

Does the MCP-Protocol-Version header replace _meta?

No. On Streamable HTTP the version is carried in both. That duplication is why the -32020 mismatch check exists.

Did client registration change?

Yes, but mostly outside your MCP server. The changelog deprecates OAuth 2.0 Dynamic Client Registration (RFC 7591), requires clients to specify an application_type during registration, and requires clients to validate a present iss against the recorded issuer before redeeming the authorization code. Those duties fall on clients and authorization servers.

What replaces server-initiated elicitation and sampling requests?

The Multi Round-Trip Requests pattern. A server returns an InputRequiredResult whose inputRequests field describes what it needs, and the client retries the original request with inputResponses. Clients MUST treat results from earlier-protocol servers that omit resultType as "complete".

Decide keep, upgrade or pin for each MCP server

This decision rule is a design recommendation. Apply it per server, not per fleet, because each server has its own client mix and SDK:

def decide(server):
    if all(c.handshake_only for c in server.clients):
        return "keep"     # stay on 2025-11-25
    if server.sdk_lacks("resultType") or server.sdk_lacks("ttlMs"):
        return "pin"      # hold, watch the deprecated features registry
    return "upgrade"      # dual-version gate + server/discover

Keep 2025-11-25 when every client still uses the handshake; nothing on the wire forces a change yet. Pin when at least one client sends a _meta version but your SDK cannot yet emit the required result fields, since a half-migrated server that omits resultType or ttlMs is not compliant. Upgrade to the dual-version gate plus server/discover in every other case.

Concrete next steps:

  1. Inventory each server's supported versions and record which clients call it over stdio and which over Streamable HTTP.
  2. Add per-version tests: a stdio legacy handshake, a legacy HTTP request carrying only the header, a 2026-07-28 request with matching header and _meta, a mismatched pair expecting -32020, and an unsupported version expecting -32022.
  3. Check your gate against the Backward Compatibility page linked from the versioning page, and track the deprecated features registry for removal dates of Roots, Sampling and Logging.
  4. Audit tool and resource handlers for Mcp-Session-Id dependencies and replace them with explicit, server-minted handles.

For a deeper build walkthrough, see Master MCP Servers: Build AI-Powered Tools & Integrations from Scratch (2026).

Need a hand implementing this? See Claude AI agent development.

Coffee cup

Hat Ihnen dieser Artikel gefallen?

Ihre Unterstützung hilft mir, mehr tiefgehende technische Inhalte, Open-Source-Tools und kostenlose Ressourcen für die Entwickler-Community zu erstellen.

Verwandte Themen

Engr Mejba Ahmed

Engr Mejba Ahmed

Engr. Mejba Ahmed builds AI-powered applications and secure cloud systems for businesses worldwide. With 8+ years shipping production software in Laravel, Python, and AWS, he's helped companies automate workflows, reduce infrastructure costs, and scale without security headaches. He writes about practical AI integration, cloud architecture, and developer productivity.

Verwandte Artikel

Alle anzeigen

Comments

Leave a Comment

Comments are moderated before appearing.

Learning Resources

Expand Your Knowledge

Accelerate your growth with structured courses, verified certificates, interactive flashcards, and production-ready AI agent skills.

Sample Certificate of Completion

Sample certificate — complete any course to earn yours

Engr Mejba Ahmed

Engr Mejba Ahmed

AI assistant · trained on my work

👋

Hey there!

Quick Actions

WhatsApp Direct line to me

Chat on WhatsApp

+880 1723 741224 · Replies within the hour on working days

Popular Questions

Engr Mejba Ahmed is connected
Engr Mejba Ahmed is typing...
Engr Mejba Ahmed avatar

✉ Want me to follow up? Drop your email

Engr Mejba Ahmed avatar

📞 Connect Directly

Choose how you'd like to reach me

WhatsApp

+880 1723 741224

Email

mejba.13@gmail.com

✓ Details sent! I'll get back to you shortly.

Powered by OpenAI

335+

Blog Posts

25

AI Courses

63

Projects

Services & Expertise

Pricing & Process

Learning & Resources

Connect & Support