Logo Accura
Accura
All posts

The initialize handshake is gone: what changes in your code

MCP 2026-07-28 removes the initialize handshake. Where protocol version and capabilities live now, and how server/discover replaces discovery.


In every earlier revision of the Model Context Protocol, a connection began with a handshake. The client sent initialize, the server answered with its capabilities, the client confirmed with notifications/initialized, and from then on both sides knew who they were talking to.

The 2026-07-28 revision removes that step. initialize and notifications/initialized are no longer required. There is no longer a moment where the server learns something it can reuse later.

This follows directly from the stateless core: with no session, there is nothing left to carry the result of a handshake between two requests.

Where the handshake information went

It now travels with every request, in the _meta field, under two keys:

  • io.modelcontextprotocol/protocolVersion for the protocol revision the client speaks
  • io.modelcontextprotocol/clientCapabilities for its capabilities

Every request is self-sufficient. Any instance of your server can handle it without having seen the previous one, which is the entire point.

For discovery in the other direction, the spec introduces server/discover. That is where the client learns which versions your server speaks and which capabilities it exposes.

What this actually breaks

The handshake code itself is rarely the problem. The problem is everything your server did during the handshake and kept afterwards.

Check whether any of these are initialised once and reused:

User context. Resolved tenant, pricing plan, permissions, quotas. Plenty of servers make a database call at handshake and hold the result. That is gone. The context now has to be rebuilt from the token on the current request, with a short cache if the cost justifies it, but a cache that stays an optimisation rather than a correctness assumption.

Version negotiation. If you adapted server behaviour based on the version announced at handshake, that decision now happens per request, from _meta.

Client capabilities. Same logic. Read them on every call rather than once.

Per-client tool lists. This is the most common trap. Many servers returned a different tool list depending on the user's plan, computed at handshake. But list endpoints no longer vary per connection. If your tools depend on the plan, you now expose the full list and refuse cleanly at call time, with a clear message, rather than hiding the tool.

That last point deserves a deliberate decision, because it changes what the user sees. A visible tool that refuses with "this tool requires the Pro plan" is often a better experience than an invisible one, and usually a better conversion path. But it is a product choice, not just a technical migration.

What keeps working

Backwards compatibility is handled, and that is what makes this migration calm.

A client that speaks 2026-07-28 falls back to the initialize handshake when it reaches a server on 2025-11-25 or earlier. Old servers and new clients keep interoperating.

On the server side, behaviour depends on the SDK. A Python v2 server answers both protocol revisions from one endpoint. The C# preview HTTP transport defaults to the new stateless mode. Check your SDK's exact behaviour before planning, because it changes the strategy: either you can serve both and migrate gradually, or you switch in one move with a rollback window.

The test that exposes the problem

A removed handshake does not produce a loud error. It produces degraded behaviour that is hard to trace back.

The useful test is simple: send an isolated request, with nothing before it, from a client that has never spoken to that instance, and check it produces exactly the same result as the same request mid-conversation. If it does not, something still depends on state built upstream.

Run the test on every tool, not just one. Handshake dependencies are rarely uniform across a server.

FAQ

Should I delete my initialize code?

Not if your server still serves older clients. Keep it as a compatibility path, but stop letting anything depend on it having run.

How do I know which version a client speaks?

It is in _meta on every request. More reliable than before, since the information can no longer be lost between calls.

Does this increase database load?

Yes, if you used to resolve user context once per connection. That is the real cost of this migration. The usual answer is a short server-side cache keyed by token, with a TTL of seconds to minutes. That cache stays an optimisation: your server must remain correct when it is empty.

Your server may depend on the handshake without you knowing

That is the kind of thing you find by reading code, not documentation. Send me your repository and within 24 business hours you get the list of handshake dependencies and what it costs to remove them.

Ready to check? Get my MCP server audited →

Related reading

Got an MCP server to audit?

Five minutes to describe it. I reply within one business day with a first written diagnosis, free of charge.

Get my MCP server audited
The initialize handshake is gone: what changes in your code