MCP server that gives reverse-engineering agents binary diffing: export binaries to BinExport v2 and compare two builds with BinDiff.
IDA is driven through ida-nexus: every
export runs in a headless idalib worker that is released again when the export
finishes. Its IDA database (.i64) is kept in the output folder next to the
export, never beside the binary, so the binary's folder can be read-only or open
in another IDA session. Exports and diffs are read at function
level only, using the schema of
python-binexport and the
algorithm names of python-bindiff;
basic block and instruction matches are never loaded.
- IDA Pro 9.x with idalib enabled (
idapyswitch),IDADIRpointing at the install - The BinExport IDA plugin (
binexport12_ida.so) in$IDAUSR/plugins - The BinDiff differ (
bindiff) inPATH, in$BINDIFF_PATH, default at/opt/zynamics/BinDiff/bin libmagic(used by python-binexport through python-magic)
uv pip install .bindiff-mcp stdio # stdio transport (default)
bindiff-mcp http --host 127.0.0.1 --port 8745 # Streamable HTTP at /mcpRegister with Claude Code:
claude mcp add bindiff -- bindiff-mcp stdio
claude mcp add bindiff --transport http http://127.0.0.1:8745/mcpIDA can spend tens of minutes analyzing a large binary, longer than MCP clients
wait for a response. binexport and bindiff_compare therefore run as jobs: a
call waits up to wait_seconds (default 20, at most 60) and returns either the
finished job or how far it has come:
{
"job_id": "5f0c2a9e41d7b3c8",
"tool": "bindiff_compare",
"status": "running",
"elapsed_seconds": 20.0,
"stage": "exporting",
"exports": { "primary": "autoanalysis", "secondary": "exporting" },
"next": "call bindiff_job with job_id \"5f0c2a9e41d7b3c8\" until the status is succeeded"
}Poll bindiff_job until status is succeeded; the tool's output is then in
result. A failed job comes back as a tool error that names its stage.
Repeating a request for the same binaries (by SHA-256) and output directory
joins the job still running instead of exporting them again; one binary is
never exported by two jobs at once. At most BINDIFF_MCP_MAX_JOBS jobs run at
a time, and BINDIFF_MCP_JOB_TIMEOUT bounds every stage of a job. IDA cannot
interrupt an analysis, so an idalib worker still busy when its stage runs out
of time is killed and its unsaved database files are dropped; the next export
starts again from the binary or from the last saved .i64.
Exports binary to <output_results_dir>/<binary name>.BinExport. An export
made from the same bytes (it records their SHA-256) is reused instead of
re-analyzing the binary; file dates are not trusted. IDA's database is kept
beside it as <binary name>.i64 and reopened by the next export; one built from
another file of the same name is discarded. The job's result:
{
"binary": "/work/httpd",
"binexport": "/work/out/httpd.BinExport",
"reused": false,
"name": "httpd",
"architecture": "x86-64",
"sha256": "...",
"functions": 2317
}Exports both binaries in parallel, runs the differ, and writes everything into a
folder of its own inside output_results_dir, named
<primary>_vs_<secondary>-<hash>: the names come from the binaries (reduced to
A-Z a-z 0-9 . _ -) and the hash from their bytes. Comparing other builds into
the same directory therefore never overwrites earlier results, and repeating a
comparison reuses its exports. The folder holds primary/ and secondary/
(each export with its IDA database), the .BinDiff database and four JSON files:
| File | Content |
|---|---|
matched_similar.json |
matched functions with similarity 1.0 (identical) |
matched_different.json |
matched functions that changed, most divergent first |
primary_only.json |
functions only in the primary binary |
secondary_only.json |
functions only in the secondary binary |
Match entries carry both addresses and names, the similarity and confidence
scores and the BinDiff algorithm that produced the match; unmatched entries
carry address, name and function type. The job's result holds the result
folder (results_dir), the overall similarity and confidence, the .BinDiff
database path, per-list counts and the path of every file written.
Waits up to wait_seconds for a job and returns it as above. Job records are
kept under BINDIFF_MCP_STATE_DIR, so a finished job can still be fetched after
a server restart; a job that was running when the server stopped is reported as
failed and has to be submitted again.
| Variable | Default | Meaning |
|---|---|---|
BINDIFF_MCP_OPEN_TIMEOUT |
600 |
Seconds to wait for IDA to open a database |
BINDIFF_MCP_ANALYSIS_TIMEOUT |
7200 |
Seconds for the initial autoanalysis of one binary |
BINDIFF_MCP_EXPORT_TIMEOUT |
1800 |
Seconds for the BinExport step after autoanalysis |
BINDIFF_MCP_JOB_TIMEOUT |
14400 |
Seconds for a whole job once it starts; caps each stage |
BINDIFF_MCP_MAX_JOBS |
2 |
Jobs run at once; each uses up to two idalib workers |
BINDIFF_MCP_STATE_DIR |
~/.local/state/bindiff-mcp |
Job records; keep it on a persistent volume |
BINDIFF_PATH |
— | Directory containing the bindiff differ |