Skip to content
Wiki

MCP's initialize handshake negotiates version and capabilities, and 2026-07-28 replaces it

DrFritzi · Reviewed · Updated 28 Sept 2026 · Markdown

Answer

In MCP revisions up to 2025-11-25, every connection starts with a three-step handshake. The client sends an initialize request with its protocolVersion, capabilities and clientInfo. The server replies with the version it will use, its own capabilities and serverInfo. The client then sends notifications/initialized. The latest revision, 2026-07-28, removes this handshake. Each request now carries its version and capabilities in _meta, and servers must answer server/discover. This page describes the 2025-11-25 handshake and notes what 2026-07-28 changed.

Details

The three messages (2025-11-25)

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
  "protocolVersion":"2025-11-25",
  "capabilities":{"elicitation":{}},
  "clientInfo":{"name":"ExampleClient","version":"1.0.0"}}}
{"jsonrpc":"2.0","id":1,"result":{
  "protocolVersion":"2025-11-25",
  "capabilities":{"tools":{"listChanged":true}},
  "serverInfo":{"name":"ExampleServer","version":"1.0.0"},
  "instructions":"Optional instructions for the client"}}
{"jsonrpc":"2.0","method":"notifications/initialized"}

clientInfo and serverInfo also accept optional fields such as title, description, icons and websiteUrl.

Version negotiation

  • The client MUST send a version it supports, and SHOULD send its latest one.
  • If the server supports that version, it MUST return the same one. If not, it returns another version it supports, and SHOULD pick its latest.
  • If the client cannot use the version the server returns, it SHOULD disconnect.
  • Over HTTP, the client then sends MCP-Protocol-Version: <version> on every later request.

Capabilities

Capabilities decide which optional features the session can use. Neither side may use a feature that was not negotiated.

Side Examples
Client roots, sampling, elicitation, tasks, experimental
Server tools, resources, prompts, logging, completions, tasks, experimental

Sub-flags refine these. listChanged means the side sends list-change notifications. subscribe (resources only) allows subscribing to single items.

What may be sent before initialization completes

  • Before the server has answered initialize, the client SHOULD NOT send requests other than ping.
  • Before the server receives notifications/initialized, it SHOULD NOT send requests other than ping and logging.

A common pitfall is a hand-written client that sends tools/list right after initialize to save a round trip. That breaks the first rule above. Wait for the initialize response, send notifications/initialized, and then list tools.

What 2026-07-28 changed

2025-11-25 (legacy) 2026-07-28 (modern)
initialize + notifications/initialized once per connection No handshake. Every request carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in _meta
clientInfo in initialize io.modelcontextprotocol/clientInfo on each request (SHOULD)
serverInfo in InitializeResult io.modelcontextprotocol/serverInfo in each result's _meta (SHOULD)
Server returns a different version Server returns UnsupportedProtocolVersionError (-32022) with a supported list
— Servers MUST implement server/discover, which returns supportedVersions, capabilities and optional instructions

A dual-era server answers initialize with legacy behavior and answers requests that carry modern _meta statelessly. On stdio, a client that supports both eras SHOULD send server/discover first. If it gets any error that is not a recognized modern error, or no reply, it falls back to initialize.

See also

Sources