Guide
Why Your MCP Server Fails the initialize Handshake
August 18, 2026 · 7 min read
The initialize handshake is the front door of every MCP session: the client sends its protocol version, capabilities, and client info; the server answers with its own. When it fails, nothing else works — and because most MCP clients surface handshake failures as a vague "failed to connect", the real cause hides in the HTTP layer. These are the five causes we see most often across monitored servers, roughly in order of frequency, with how to debug each.
1. Wrong or missing Accept header
Streamable HTTP requires the client to send both content types in its Accept header, because the server may answer a POST with either plain JSON or an SSE stream:
Accept: application/json, text/event-stream Spec-compliant servers reject requests without it — typically with 406 Not Acceptable. The confusing part is the asymmetry: your curl test with the right headers works, while a client library with a stripped-down default fails, or the reverse. Proxies and API gateways that rewrite or drop headers cause the same symptom. Debug it by replaying the exact request with curl and toggling only the Accept header; if behavior changes, you've found it. The same applies to Content-Type: application/json on the POST itself.
2. Protocol version mismatch
MCP versions are dates: 2024-11-05, 2025-03-26, 2025-06-18, and later revisions. The client proposes one in initialize; a server that can't support it should respond with the closest version it can, and the client decides whether to proceed. Two failure modes show up in practice:
- Strict servers that return a JSON-RPC error for any version they don't recognize — often after an SDK upgrade quietly changed which version the client proposes.
- Since
2025-06-18, clients must send anMCP-Protocol-Versionheader on subsequent HTTP requests. Servers that enforce this return400ontools/listeven thoughinitializesucceeded — a handshake that "passes" but a session that doesn't.
Debug by reading the server's protocolVersion in the initialize result and comparing it with what your client sends afterwards. Pin SDK versions on both sides when you upgrade.
3. Authentication: 401 and 403
Remote servers increasingly sit behind OAuth 2.1 or a static bearer token. A 401 on initialize means missing or expired credentials; 403 usually means valid credentials without the required scope. The failures worth monitoring for are the slow ones: tokens that expire after 30 or 90 days, rotated API keys that one environment missed, or an auth provider change that invalidates existing tokens. All of them look like "worked yesterday, 401 today". If your server uses OAuth discovery, also verify the protected resource metadata endpoint responds — clients fail the handshake when discovery itself is broken.
4. Session handling
Streamable HTTP servers may issue a session on the initialize response via the Mcp-Session-Id header. From then on the client must echo that header on every request; requests without it get 400, and requests with an expired or unknown session get 404, which the client should treat as "start a new session". Bugs cluster in three places:
- Servers behind load balancers that keep sessions in per-instance memory — the next request lands on a different instance and 404s intermittently.
- Clients that keep a session across a server restart and don't handle the 404-means-reinitialize rule.
- Proxies that strip the
Mcp-Session-Idheader in one direction.
Intermittent handshake failures under load are almost always this category. Use sticky sessions or shared session storage, and test the restart path deliberately.
5. Timeouts and buffering proxies
Serverless MCP servers add cold starts: the first initialize after idle can take several seconds, and clients with tight connect timeouts give up first — a failure that disappears the moment you test it manually, because your test warmed the function. The other classic is a reverse proxy that buffers responses: SSE streams need buffering off (proxy_buffering off in nginx, or an X-Accel-Buffering: no response header), otherwise the initialize response sits in a buffer until timeout. If curl with --no-buffer against the origin works but going through the proxy hangs, it's the proxy.
A debugging checklist
- Replay
initializewith curl, exact headers, straight at the origin — the walkthrough has the full commands. - Compare origin vs proxy behavior; diff the headers both directions.
- Read the response status precisely: 406 → Accept, 400 → version header or session, 401/403 → auth, 404 → session or route, hang → buffering or cold start.
- Check what protocol version each side actually sends, not what you think it sends.
And because most of these regress on deploys rather than appearing spontaneously, a scheduled handshake check — the core of MCP monitoring — turns each of them from a user report into an alert with the failing phase attached.