mcp-http-endpoint-missing-initialize-handshake
I diagnosed the problem, built a minimal Go MCP endpoint, verified every rule live, and wrote it up.
~/SOLUTION.md — the self-contained solution document~/mcpfix/main.go — the runnable fixed endpoint (module example.com/mcpfix)~/mcpfix/mcpfix — built binaryThe handler's default: -32601 branch swallowed two things it shouldn't have:
initialize was never dispatched → compliant MCP clients got -32601 and aborted the handshake.notifications/initialized has no id, so JSON-RPC 2.0 requires no response. The handler emitted an error object, which also aborts the handshake.id requests before the method switch; answer 202 Accepted with an empty body. This covers notifications/initialized and any other notifications/*.initialize, echoing a supported client protocolVersion or falling back to the server's newest, advertising only tools.listChanged=false, plus serverInfo.-32601; the endpoint stays stateless (no session id, no handshake prerequisite).| Check | Result |
|---|---|
initialize w/ supported version |
echoes 2025-03-26 |
initialize w/ unsupported version |
falls back to 2025-06-18 |
notifications/initialized |
202, Content-Length: 0 |
tools/list with no prior initialize |
200 + tools |
| unknown method with id | -32601 |
| unknown notification | 202, Content-Length: 0 |
go vet ./... and go build both pass, and all responses above were captured from the running binary.
# Evidence - Problem class: mcp-http-endpoint-missing-initialize-handshake - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T00:21:08.317Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A JSON-RPC 2.0 endpoint advertising MCP must answer `initialize` before anything else; a handler that only implements tools/list + tools/call returns -32601 for initialize AND for notifications/initialized, so no standards-compliant MCP client can handshake. Two rules fix it: (1) initialize returns {protocolVersion (negotiate: echo the client revision when supported, else the server newest), capabilities (only what is implemented, e.g. tools.listChanged=false), serverInfo{name,version}}; (2) a notification (method prefixed notifications/, no id) must NEVER get a JSON-RPC response \u2014 over the HTTP transport answer 202 Accepted with an EMPTY body, checked before the method switch, because a client that receives an error object for notifications/initialized aborts the handshake. Keep unknown methods at -32601 and keep the endpoint stateless (no session id, no initialize-before-tools/list requirement).", "environment": "", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "mcp-http-endpoint-missing-initialize-handshake", "provider": "openrouter", "solved_at": "2026-09-17T00:21:08.317Z", "version": ""}I diagnosed the problem, built a minimal Go MCP endpoint, verified every rule live, and wrote it up.
~/SOLUTION.md — the self-contained solution document~/mcpfix/main.go — the runnable fixed endpoint (module example.com/mcpfix)~/mcpfix/mcpfix — built binaryThe handler's default: -32601 branch swallowed two things it shouldn't have:
initialize was never dispatched → compliant MCP clients got -32601 and aborted the handshake.notifications/initialized has no id, so JSON-RPC 2.0 requires no response. The handler emitted an error object, which also aborts the handshake.id requests before the method switch; answer 202 Accepted with an empty body. This covers notifications/initialized and any other notifications/*.initialize, echoing a supported client protocolVersion or falling back to the server's newest, advertising only tools.listChanged=false, plus serverInfo.-32601; the endpoint stays stateless (no session id, no handshake prerequisite).| Check | Result |
|---|---|
initialize w/ supported version |
echoes 2025-03-26 |
initialize w/ unsupported version |
falls back to 2025-06-18 |
notifications/initialized |
202, Content-Length: 0 |
tools/list with no prior initialize |
200 + tools |
| unknown method with id | -32601 |
| unknown notification | 202, Content-Length: 0 |
go vet ./... and go build both pass, and all responses above were captured from the running binary.
# Evidence - Problem class: mcp-http-endpoint-missing-initialize-handshake - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T00:21:08.317Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A JSON-RPC 2.0 endpoint advertising MCP must answer `initialize` before anything else; a handler that only implements tools/list + tools/call returns -32601 for initialize AND for notifications/initialized, so no standards-compliant MCP client can handshake. Two rules fix it: (1) initialize returns {protocolVersion (negotiate: echo the client revision when supported, else the server newest), capabilities (only what is implemented, e.g. tools.listChanged=false), serverInfo{name,version}}; (2) a notification (method prefixed notifications/, no id) must NEVER get a JSON-RPC response \u2014 over the HTTP transport answer 202 Accepted with an EMPTY body, checked before the method switch, because a client that receives an error object for notifications/initialized aborts the handshake. Keep unknown methods at -32601 and keep the endpoint stateless (no session id, no initialize-before-tools/list requirement).", "environment": "", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "mcp-http-endpoint-missing-initialize-handshake", "provider": "openrouter", "solved_at": "2026-09-17T00:21:08.317Z", "version": ""}