Skip to content

Match MCP JSON-RPC responses that echo string ids - #343

Merged
tylerreckart merged 3 commits into
mainfrom
fix/mcp-string-jsonrpc-ids
Sep 21, 2026
Merged

tylerreckart merged 3 commits into
mainfrom
fix/mcp-string-jsonrpc-ids

Conversation

@cursor

@cursor cursor Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

parse_response only copied JSON-RPC id when the member was a number. JSON-RPC 2.0 allows String | Number | Null, and JS MCP stacks commonly echo the integer we sent as "id":"1".

Client::rpc matches resp.id == req.id (req.id starts at 1). A string id left Response::id at 0 — the same sentinel used for notifications — so a valid reply was skipped. The loop then sat until init_timeout / call_timeout and killed the MCP subprocess. Playwright and other stdio MCP servers that stringify ids hung on every initialize / tools/call.

Fix

Parse decimal-string ids with stoll (full-string match, base 10) into the same int64_t we already store for numeric ids. Null, empty, non-numeric, and fractional ("1.0") ids stay 0 so notifications and unmatched traffic still skip. Numeric ids are unchanged.

Tests

unit_mcp — string success/error ids, non-numeric / empty / null / "1.0" skip, omitted id (notification). 4 parse_response* cases locally; suite 48/48 with the pre-existing Subprocess: terminate SIGTERM flake excluded. ASan/UBSan driver over parse_response passed.

Independently mergeable against main (0456350). Code hunks (mcp/types.{h,cpp}, test_mcp.cpp inserted before the malformed-envelope case) auto-merge with #337 (null members, later in parse_response). CHANGELOG [Unreleased] will need a rebase after whichever sibling merges first.

Open in Web View Automation 

Note

Low Risk
Localized MCP JSON-RPC parsing change with tests; no auth or data-path impact, though incorrect id handling could still affect MCP subprocess RPC behavior.

Overview
Fixes MCP stdio hangs when JavaScript servers echo JSON-RPC request ids as strings ("id":"3") instead of numbers.

parse_response now normalizes decimal-string ids into the same int64_t used for numeric ids via a new parse_jsonrpc_id helper (stoll, full-string match). That lets Client::rpc match replies again instead of treating them as notifications (id == 0) and waiting until init/call timeout kills the subprocess. Empty, null, non-numeric, and fractional string ids still map to 0 so the notification skip path is unchanged. Unit tests cover success/error string ids and skip cases; the unreleased changelog documents the fix.

Reviewed by Cursor Bugbot for commit edc6427. Bugbot is set up for automated code reviews on this repo. Configure here.

JSON-RPC 2.0 allows String | Number ids. parse_response only copied
numeric ids, so a server that echoed "id":"1" left Response::id at 0.
Client::rpc then treated a valid reply as a notification and hung until
timeout, killing the MCP subprocess.

Co-authored-by: Tyler Reckart <tylerreckart@users.noreply.github.com>
@tylerreckart
tylerreckart marked this pull request as ready for review September 21, 2026 12:59
@tylerreckart
tylerreckart enabled auto-merge (squash) September 21, 2026 12:59
@tylerreckart
tylerreckart merged commit bdf6542 into main Sep 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants