WFDASH /api/mcp previously only handled tools/list and tools/call,
which is fine for our internal curl-based testing but broke every
spec-compliant MCP client (Hermes, Claude Desktop, Cursor, mcp-cli).
The MCP protocol requires a three-step handshake before tools/list:
1. client → server: initialize
2. server → client: result with protocolVersion, capabilities, serverInfo
3. client → server: notifications/initialized (notification, no response)
Before this fix, step 1 got HTTP 404 with error.code -32601, which
MCP clients interpret as 'this isn't an MCP server' and terminate
the session. Symptom: configured servers show 'CONFIGURED' in the
Hermes WebUI but '0 tools', with the runtime logging
'unhandled errors in a TaskGroup ... Session terminated'.
Three new handlers, each with the right response shape:
initialize → 200 + plain result {protocolVersion, capabilities, serverInfo}
notifications/initialized → 204 + empty body (notification, no envelope)
ping → 200 + result {}
Critical: the initialize response uses a PLAIN object for result,
not the content/text envelope that our jsonRpcOk helper wraps
tools/list and tools/call results in. Using jsonRpcOk here would
have left the handshake broken in a subtler way.
Four new Vitest cases:
- initialize returns server info (regression: 2026-08-04)
- initialize echoes client-provided protocolVersion (forward compat)
- notifications/initialized returns 204
- ping returns {}
Bug report with full timeline + lessons: see
docs/bugs/2026-08-04-mcp-initialize-handshake-missing.md
(same commit series; pushed separately as the second commit).
All 46 tests pass.
Co-authored-by: Orca <help@stably.ai>