Two eras: legacy and modern

Most tutorials describe a session that opens with initialize. Revision 2026-07-28 removed it. Knowing both eras, and why the change happened, is the key to reading any MCP traffic you meet.

By the end of this lesson you can

  • name the two eras and the revisions they cover;
  • list what 2026-07-28 removed and what replaced each piece;
  • read the _meta block every modern request carries;
  • explain server/discover and error -32022;
  • say why all of it exists: any replica can answer any request.

Legacy, modern, dual-era

The specification itself names the eras. Revisions up to and including 2025-11-25 are legacy; revision 2026-07-28 and later are modern. A client or server that speaks both is dual-era.

The same deploy, two eras

  1. Legacy opens with a handshake: initialize, its result (with a session id on HTTP), then notifications/initialized. The server now keeps a session in memory.
  2. Later calls carry no version or capabilities: the session implies who this is. Another replica would not know.
  3. Mid-call, the legacy server sends its own request to the client, and keeps the half-finished call in one process's memory until the answer comes back.
  4. Modern has no handshake. Every request carries its version and capabilities in _meta.
  5. When the server needs input, it does not send a request. It ends the call with input_required and hands its context to the client as a signed requestState.
  6. The client sends the call again with a new id and the answers. Nothing was kept in between, so any replica can answer any request.

What changed, row by row

Legacy, up to 2025-11-25Modern, 2026-07-28
Openinginitialize then notifications/initializedNone. server/discover is optional
Version and capabilitiesNegotiated once, held by the sessionIn _meta on every request
SessionsMcp-Session-Id on HTTP, ended with DELETENone
Server needs inputServer sends its own request mid-callServer returns input_required; client retries
Change notificationsStandalone GET streamsubscriptions/listen request
Version mismatchNegotiated down in the handshakeError -32022 listing supported versions

Put another way, the modern protocol has no sessions, no Mcp-Session-Id, no GET stream and no SSE resumability, and servers MUST NOT initiate JSON-RPC requests. Change notifications such as tools/list_changed and resources/updated now arrive on a subscriptions/listen request the client opens, instead of a standalone GET stream.

Common misconception

“Every MCP connection starts with initialize.” Only in the legacy era. A modern client can send tools/call as its very first message. If a tutorial opens with a handshake, check which revision it targets.

What every modern request carries

Because there is no session, each request describes itself in _meta:

{ "jsonrpc": "2.0", "id": 7, "method": "tools/list",
  "params": { "_meta": {
    "io.modelcontextprotocol/protocolVersion": "2026-07-28",
    "io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
    "io.modelcontextprotocol/clientCapabilities": { "elicitation": {} }
  } } }

server/discover

server/discover returns the server's supported versions, capabilities and instructions. Servers MUST implement it; clients MAY call it. The result is cacheable through ttlMs and cacheScope, and it does not open a session: the server does not remember the call happened.

Error -32022

There is no negotiation round. If a request names a version the server does not support, the server answers with -32022 (UnsupportedProtocolVersionError) and lists what it does support. The client picks a mutually supported version and sends the request again.

{ "jsonrpc": "2.0", "id": 1,
  "error": { "code": -32022, "message": "Unsupported protocol version",
             "data": { "supported": ["2026-07-28", "2025-11-25"], "requested": "1900-01-01" } } }

Key idea

The point of all of it: any replica can answer any request, with nothing to remember in between. That means horizontal scaling with no sticky load balancing, which lesson 11 shows working with a simulator.

One call, two eras

The same tools/call, built the legacy way and the modern way. Switch era and transport and watch what appears and disappears.

On the wire

What to notice

    Quick check

    Which method MUST a modern server implement?

    • initialize
    • server/discover
    • ping only
    Servers MUST implement server/discover; clients MAY call it. initialize is the legacy handshake.

    A modern request arrives with protocol version 1900-01-01. What does the server do?

    • Negotiates down to its newest version and continues
    • Returns error -32022 listing the versions it supports
    • Closes the session
    No negotiation round exists. The error tells the client what to retry with, and that this server is modern.

    In the modern era, where does the server learn the client's capabilities?

    • From the session created at initialize
    • From server/discover
    • From _meta on every request
    Each request carries protocolVersion, clientInfo and clientCapabilities in _meta. server/discover tells the client about the server, not the other way round.

    Which of these did 2026-07-28 remove?

    • Tools, resources and prompts
    • Sessions and server-initiated requests
    • The stdio transport
    Sessions, Mcp-Session-Id, the GET stream and server-initiated requests went. Primitives and both transports remain.

    Recap