Summary
The MCP server parses JSON-RPC request ids as int64_t only. When a client sends a string id — which is explicitly permitted by
JSON-RPC 2.0 §4, and is used by several MCP clients (including Claude Desktop) for
the initialize request — the server silently coerces it to an integer via strtol, which returns 0 for non-numeric strings.
The response then carries "id": 0 instead of echoing the original string, so the client cannot correlate the response with its request
and the handshake hangs or errors out.
Root cause
cbm_jsonrpc_request_t.id is declared as int64_t in src/mcp/mcp.h:24, and cbm_jsonrpc_parse in
src/mcp/mcp.c:116 handles a string id like this:
if (v_id) {
out->has_id = true;
if (yyjson_is_int(v_id)) {
out->id = yyjson_get_int(v_id);
} else if (yyjson_is_str(v_id)) {
out->id = strtol(yyjson_get_str(v_id), NULL, CBM_DECIMAL_BASE);
}
}
For a request like:
{"jsonrpc":"2.0","id":"init-1","method":"initialize", ...}
…strtol("init-1", ...) yields 0, and that 0 is what the server writes back in the response.
▎ Per JSON-RPC 2.0 §4, the response id MUST equal the request id and preserve its type. A string id must be echoed back as a string.
---
Impact
- initialize (and any other request) fails silently for clients that use string ids — the server replies, but the client drops the
response as uncorrelated.
- Numeric ids greater than INT64_MAX, or fractional ids, would also be mishandled (though these are rarer in practice).
---
Scope of the fix
The int64_t id type leaks beyond the parser, so a fix is not local:
┌──────────────────┬─────────────────────────────────────────────────────────────────┐
│ Location │ Symbol │
├──────────────────┼─────────────────────────────────────────────────────────────────┤
│ src/mcp/mcp.h:24 │ cbm_jsonrpc_request_t.id │
├──────────────────┼─────────────────────────────────────────────────────────────────┤
│ src/mcp/mcp.h:31 │ cbm_jsonrpc_response_t.id │
├──────────────────┼─────────────────────────────────────────────────────────────────┤
│ src/mcp/mcp.h:47 │ cbm_jsonrpc_format_error(int64_t id, ...) │
├──────────────────┼─────────────────────────────────────────────────────────────────┤
│ src/mcp/mcp.c │ cbm_jsonrpc_format_response(...) (consumes the response struct) │
└──────────────────┴─────────────────────────────────────────────────────────────────┘
A proper fix needs to store the id as a raw JSON value (or a tagged union of int / string / null) end-to-end, and write it back verbatim
in both success and error responses. Patching only the parser is not sufficient.
---
Reproduction
Send the server the following line on stdin:
{"jsonrpc":"2.0","id":"init-1","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test
","version":"0.0.1"}}}
- Expected: response contains "id":"init-1"
- Actual: response contains "id":0
---
Suggested test
Add a jsonrpc_parse_string_id_roundtrip test in tests/test_mcp.c that asserts a parsed string id survives a parse → format-response
round-trip unchanged.
▎ There is already a jsonrpc_parse_string_id test at tests/test_mcp.c:1315, but it likely only verifies the current (lossy) behavior and
▎ should be updated alongside the fix.
Summary
The MCP server parses JSON-RPC request ids as
int64_tonly. When a client sends a string id — which is explicitly permitted byJSON-RPC 2.0 §4, and is used by several MCP clients (including Claude Desktop) for
the
initializerequest — the server silently coerces it to an integer viastrtol, which returns0for non-numeric strings.The response then carries
"id": 0instead of echoing the original string, so the client cannot correlate the response with its requestand the handshake hangs or errors out.
Root cause
cbm_jsonrpc_request_t.idis declared asint64_tinsrc/mcp/mcp.h:24, andcbm_jsonrpc_parseinsrc/mcp/mcp.c:116handles a string id like this: