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

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions