Skip to content

Bound peer JSON nesting and request size (Fixes #63) - #65

Merged
hkr04 merged 1 commit into
mainfrom
cursor/peer-json-limits-3d5a
Oct 21, 2026
Merged

hkr04 merged 1 commit into
mainfrom
cursor/peer-json-limits-3d5a

Conversation

@hkr04

@hkr04 hkr04 commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Fixes #63

Peer json::parse of stdio lines and HTTP bodies had no nesting or size bound. nlohmann ordered_json copies nested values recursively when an object grows, so a message nested tens of thousands of levels deep overflows the stack (SIGSEGV / ASan stack-overflow). Streamable HTTP parsed the body before the session lookup, so one unauthenticated POST /mcp could kill the process.

This follows the official Ruby MCP SDK transport limits. RFC 8259 §9 allows an implementation to cap nesting; MCP itself does not mandate a depth. Ruby and the TypeScript SDK both default to a 4 MiB body, and Ruby caps nesting at 64.

Alignment with the Ruby SDK

  • MAX_JSON_NESTING = 64
  • DEFAULT_MAX_REQUEST_BYTES = 4 * 1024 * 1024 (stdio max_line_bytes is the same 4 MiB)

Source of truth:

What changed

parse_peer_json rejects an input larger than 4 MiB before parsing, and aborts from nlohmann's parser callback once another array or object would make the document deeper than 64. The callback runs before that container is built, so a deep DOM is never constructed.

Call sites:

  • server::start_stdio — bounded frame read (discards the rest of an over-long line so the next frame stays aligned) and parse_peer_json. Oversize → JSON-RPC -32600; bad JSON or over-depth → -32700. The server keeps reading.
  • server::handle_jsonrpc — parse the body before session lookup. HTTP 400 / -32700 for bad or too-deep JSON; HTTP 413 / -32600 for an oversized body.
  • server::handle_mcp_post — same, still before the Mcp-Session-Id check. A declared Content-Length above 4 MiB is rejected by cpp-httplib (set_payload_max_length) before the route handler, with a JSON-RPC 413 body.
  • tools/call when arguments is a JSON string — parse_peer_json; failure is JSON-RPC -32602.
  • Client defence in depth (Ruby hardened both sides for size): stdio client frames and SSE / HTTP response parses go through parse_peer_json, and the stdio client drops a frame that grows past 4 MiB instead of buffering it forever.

params is std::moved out of the parsed value on the server paths above. That only removes the extra deep copy; the depth cap is what stops the overflow inside json::parse.

Test plan

  • cmake -S . -B build -DMCP_BUILD_TESTS=ON && cmake --build build -j --target mcp_tests && ./build/test/mcp_tests — 30 tests passed
  • Nesting of 64 (arrays and empty objects) parses; nesting of 65 throws before a deep DOM is built
  • A JSON value of exactly 4 MiB parses; 4 MiB + 1 byte is rejected
  • stdio: a depth-64 ping succeeds; a depth-65 line returns -32700 and a following ping still succeeds
  • stdio: a line over 4 MiB returns -32600 and a following ping still succeeds
  • stdio: a tool-argument string nested past 64 returns -32602; a shallow string argument and a later ping still work
  • POST /mcp with depth 64 initialize returns 200; depth 65 returns 400 / -32700 (not the missing-session error) before session lookup; a body over 4 MiB returns 413 / -32600; a later request is still served
  • POST /message with depth 65 returns 400 / -32700, then a ping returns 202
Open in Web Open in Cursor 

Peer json::parse had no depth or size limit, so a deeply nested stdio line
or unauthenticated POST could overflow the stack. Match the Ruby MCP SDK:
nesting depth 64 and a 4 MiB frame/body cap, and keep serving with a
JSON-RPC error instead of crashing.

Fixes #63

Co-authored-by: Zihong Zhang <hkr04@users.noreply.github.com>
@hkr04
hkr04 marked this pull request as ready for review October 21, 2026 03:36
@hkr04
hkr04 merged commit 30ecf72 into main Oct 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.

Deeply nested JSON overflows the stack: one stdio line, or one unauthenticated HTTP POST, crashes a cpp-mcp server

2 participants