Before an MCP client can list a server’s tools, read a resource, or call anything, the two sides run a short handshake: an initialize request, a response, and an initialized notification. Nothing else is supposed to happen until that finishes. Almost no one thinks about it, because clients run it automatically the moment you add a server — but it’s where the protocol version gets settled and where each side declares what it actually supports, and it’s usually the first place to look when a server won’t connect at all.
The three messages that open every connection
The client sends initialize first, carrying the protocol version it speaks, a capabilities object describing which optional features it supports, and a small clientInfo block with its own name and version. The server answers with the same shape back: its own protocol version, its own capabilities, a serverInfo block, and — optionally — a free-text instructions field. The client then sends initialized, a one-way notification with no reply expected, and only after that do normal requests like tools/list start flowing. Three messages, one direction each, before anything real happens.
The protocol version is negotiated, not assumed
The client proposes the version it wants to speak. If the server doesn’t support that exact version, it’s expected to respond with a version it does support instead of just failing — and it’s then the client’s job to decide whether it can work with what the server offered or whether to end the connection. This is the one part of the handshake that isn’t optional for either side, unlike, say, the bare liveness check covered in MCP ping — every connection goes through version negotiation, whether the two ends end up agreeing or not.
Capabilities say what’s possible, not what’s useful
The capabilities object each side sends is a declaration of which parts of the spec they implement — not a promise that the implementation is any good. A server lists whether it offers tools, resources, prompts, logging, and completions, down to sub-flags like whether its resources support subscriptions. A client lists whether it can serve roots, sampling, or elicitation requests back. If a capability isn’t declared here, the other side isn’t supposed to assume it exists later in the session — which is why, for instance, tools, resources, and prompts each have their own flag instead of one blanket “this server does things” bit.
The instructions field is a quiet extra
A server’s initialize response can include an optional instructions string — free text meant to give the model guidance about the server as a whole, separate from any individual tool’s own description. It’s the server-level counterpart to what we covered in how to write MCP tool descriptions, and it’s easy to miss: not every client surfaces it to the model, and a server that leaves it blank hasn’t done anything wrong, since it’s optional by design.
Why almost nothing works until this finishes
The spec keeps the window before the handshake completes deliberately narrow: the client isn’t meant to send real requests before the server has answered initialize, and the server isn’t meant to send its own requests before the client’s initialized notification arrives. That ordering is what makes the rest of the session predictable — by the time a tools/list call goes out, both sides already agree on the protocol version and already know which optional features are even worth trying.
Where it actually shows up
Most of the time this handshake is invisible. It stops being invisible the moment a server won’t connect at all. An endpoint that accepts the connection and then closes it mid-handshake, or that answers with something that isn’t valid JSON-RPC, usually means the URL isn’t a real MCP endpoint in the first place — we cover the specific error codes that show up here in MCP error -32000 and friends. The MCP Inspector is the fastest way to see the raw exchange instead of guessing from a client’s generic “couldn’t connect” message.
initialize request against a server before it’s ever added to gate’s directory — a listing with a README isn’t the same as an endpoint that actually answers the handshake. You can run the same check yourself against any URL with gate’s connection tester before you wire a client up to it, and if you’re building a server rather than connecting to one, gate’s MCP Builder and a pass through the free security scanner both exercise the same handshake your real users’ clients will.The bottom line
Three messages, sent once, settle more than they look like they do: which protocol version the rest of the session speaks, and which optional features either side can rely on from the other. When a server behaves strangely from the first call, it’s worth checking whether the strangeness actually started here — in a capability neither side actually agreed to, or a version mismatch nobody noticed — before assuming the problem is in the tool call itself.