Skip to content

initialize handshake fails when client sends a string JSON-RPC id #253

@mydreamdoctor

Description

@mydreamdoctor

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 idsthe 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:
                                                                                                                                          
┌──────────────────┬─────────────────────────────────────────────────────────────────┐                                                    
│     LocationSymbol                              │                                                    
├──────────────────┼─────────────────────────────────────────────────────────────────┤                                                    
│ src/mcp/mcp.h:24cbm_jsonrpc_request_t.id                                        │
├──────────────────┼─────────────────────────────────────────────────────────────────┤
│ src/mcp/mcp.h:31cbm_jsonrpc_response_t.id                                       │                                                    
├──────────────────┼─────────────────────────────────────────────────────────────────┤
│ src/mcp/mcp.h:47cbm_jsonrpc_format_error(int64_t id, ...)                       │                                                    
├──────────────────┼─────────────────────────────────────────────────────────────────┤                                                    
│ src/mcp/mcp.ccbm_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 parseformat-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 andshould be updated alongside the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integration

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions