64-bit values sent as bare JSON numbers crash BigInt-aware MCP clients
Summary
tools/call emits 64-bit integers as bare JSON numbers in structuredContent. Any value outside the JS safe-integer range (|v| > 2**53 - 1) becomes a BigInt in MCP clients that parse with a BigInt-preserving reviver, and the client then dies with TypeError: Do not know how to serialize a BigInt when it re-serializes the tool result.
In my case this killed the agent process mid-session, not just the one tool call.
Reproduction
Any binary works — reading 8 bytes of an ASCII string as u64 always exceeds 2**53 - 1, so tests/crackme03.elf from this repo is enough. No arm64 target needed.
1. Start the server:
uv run idalib-mcp --host 127.0.0.1 --port 8745 tests/crackme03.elfThis serves streamable HTTP at http://127.0.0.1:8745/mcp. Since the supervisor requires database=<session_id> on every tool call, the repro first looks the session up via idb_list.
2. Drive it with a Node-based MCP client:
copilot -p "Use the ida MCP tools: first call idb_list to find the open session_id for crackme03.elf, then call get_int with that database, addr='0x318', ty='u64'. Report the exact returned value." --allow-all --additional-mcp-config '{"mcpServers":{"ida":{"type":"http","url":"http://127.0.0.1:8745/mcp","tools":["*"]}}}'0x318 is the first ≥8-byte string in crackme03.elf; the server returns:
{"result": [{"addr": "0x318", "ty": "u64le", "value": 7795506888185572399}]}7795506888185572399 > 2**53 - 1, so a BigInt-preserving parser turns it into 7795506888185572399n, and the subsequent JSON.stringify throws TypeError: Do not know how to serialize a BigInt.
Affected tools
Not specific to get_int — anything that can emit a raw 64-bit Python int:
get_intwithty: u64/i64(easiest trigger, works on every binary)extract_function_constantsinutils.pyemits"decimal": op.valueuntyped; arm64o_immoperands routinely exceed the safe range. This was my original failure, viaanalyze_batchwithinclude_constants: true.- enum member values in
api_types.py
Expected
structuredContent should never contain a JSON number outside ±(2**53 - 1).
Prior art
bytebase/dbhub (Universal Database MCP Server) hit the same class of problem and solved it with a single choke point at the response-construction boundary:
bigIntReplacer— values inside the safe range stay numbers, values outside become strings.- Applied uniformly in
createToolSuccessResponse,createToolErrorResponse,createResourceSuccessResponseandcreateResourceErrorResponse. - Pinned down by
bigint-handling.test.ts, including the boundary cases (MAX_SAFE_INTEGERstays a number,MAX_SAFE_INTEGER + 1becomes a string) and a mixed-row serialization test. - Their MySQL connector enforces the same rule one layer down via
supportBigNumbers.
Their policy is value-driven (only values that actually don't fit get stringified), which is what I'm proposing here.
Suggested fix
A single choke point in the tools/call middleware in rpc.py: recursively stringify ints outside the safe range (leaving bool, small ints, and existing hex strings alone), and rebuild the content text so it stays consistent with structuredContent.
Two output types then need widening to keep the sanitized value valid against the advertised outputSchema:
api_memory.IntReadResult.value:int | None→int | str | Noneapi_types.EnumMemberUpsertResult.value:int→int | str
Untyped list[dict] returns (e.g. the constants extractor) need no schema change.
Environment
- ida-pro-mcp
main@ 0b5f7ae - IDA Pro 9.x via idalib, macOS
- Originally hit with GitHub Copilot CLI (Node client)
Source: mrexodia/ida-pro-mcp