Skip to main content
Glama
ghidriff-mcp

ghidriff-mcp

by waterofriver

ghidriff-mcp

An MCP server that exposes ghidriff — the Ghidra binary diffing engine — as tools an AI agent can call.

Point it at an old and a new build of a binary and it drives Ghidra headlessly through ghidriff, then hands back structured results: match statistics, added/deleted/modified functions, per-function code diffs and the generated Markdown report — as JSON, not as a screenful of console noise.

CI License: MIT Python 3.10+ MCP

English | 简体中文


Contents

Related MCP server: GhidraMCP

Why this exists

ghidriff is excellent but it is a batch CLI: you invoke it, wait while Ghidra imports and analyses every input, and you get a Markdown report plus a large pdiff JSON on disk. That shape does not fit an agent loop, where you want to inspect a change, follow up on one function, and keep the reasoning in context.

This server closes that gap:

  • Jobs instead of blocking calls. Every diff runs in the background with an id, a log file and a persisted job.json, so a crash or restart does not lose the result.

  • Structured output. The pdiff JSON ghidriff writes is the source of truth; the server turns it into compact summaries and per-function details instead of dumping megabytes into the model's context.

  • Late-bound environment. The server does not need ghidriff or Ghidra in its own interpreter — it launches whichever interpreter or command you configure.

  • Honest errors. Ghidra's failure modes are translated into actionable messages: missing GHIDRA_INSTALL_DIR, a project path Ghidra refuses, a JVM out of memory, a missing pyghidra.

  • A boundary you control. The agent decides what to diff; you decide how the engine runs. See Security and trust model.

Requirements

Component

Version

Notes

Python

3.10+

for the MCP server itself

ghidriff

1.x

pip install ghidriff

Ghidra

11.x / 12.x

set GHIDRA_INSTALL_DIR

JDK

21+

Ghidra 12 requires a modern JDK

Ghidra and ghidriff do not have to live in the same interpreter as this server. If your MCP client runs the server from an isolated virtualenv, point GHIDRIFF_MCP_PYTHON at the interpreter that has ghidriff installed.

Tested against ghidriff 1.0.0, Ghidra 12.1.3 and JDK 21 on Windows, and in CI on Ubuntu, macOS and Windows with Python 3.10 and 3.13.

Install

pip install ghidriff-mcp

Or from source:

git clone https://github.com/waterofriver/ghidriff-mcp
cd ghidriff-mcp
pip install -e .

Verify the tool chain with the built-in self check:

ghidriff-mcp --check

It prints a JSON diagnosis of Ghidra, Java and ghidriff, with next steps for anything missing, and exits non-zero when the setup is not usable yet:

// ghidriff-mcp --check (trimmed for length)
{
  "ready": true,
  "checks": {
    "ghidra": { "ok": true, "detail": "Ghidra 12.1.3 at C:\\ghidra\\ghidra_12.1.3_PUBLIC (found via GHIDRA_INSTALL_DIR)" },
    "java": { "ok": true, "detail": "Java 21.0.12.1 (C:\\Program Files\\Microsoft\\jdk-21...\\bin\\java.exe)" },
    "ghidriff": { "ok": true, "detail": "ghidriff 1.0.0 importable by C:\\Python313\\python.exe" }
  },
  "problems": [],
  "next_steps": []
}

The same report is available to agents through the ghidriff_environment tool.

Configuration

Everything is environment driven.

Variable

Default

Purpose

GHIDRIFF_MCP_HOME

~/ghidriff-mcp-work

Root for run directories, Ghidra projects and diff output.

GHIDRA_INSTALL_DIR

auto-discovery

Path to the extracted Ghidra folder.

GHIDRIFF_MCP_PYTHON

the server's own interpreter

Interpreter that has ghidriff installed.

GHIDRIFF_MCP_COMMAND

—

Explicit ghidriff command line, e.g. ghidriff --max-ram-percent 40. Takes precedence over GHIDRIFF_MCP_PYTHON.

GHIDRIFF_MCP_TIMEOUT

3600

Default per-run timeout in seconds.

GHIDRIFF_MCP_MAX_JOBS

2

Maximum concurrent diffs (each one boots a JVM).

GHIDRIFF_MCP_EXTRA_ARGS

—

Raw ghidriff arguments appended to every run. Operator-only: this is where --no-symbols or --jvm-args belong, not in tool calls.

GHIDRIFF_MCP_LOG_LEVEL

INFO

ghidriff log level (DEBUG is very verbose).

Ghidra path rule. Ghidra refuses a project location containing a path element that starts with . (Path element starting with '.' is not permitted). Keep GHIDRIFF_MCP_HOME dot-free; if it is not, the server relocates the Ghidra project to the system temp directory and reports a warning instead of failing after a multi-minute import.

MCP client configuration

{
  "mcpServers": {
    "ghidriff": {
      "command": "C:\\path\\to\\venv\\Scripts\\python.exe",
      "args": ["-m", "ghidriff_mcp"],
      "env": {
        "GHIDRA_INSTALL_DIR": "C:\\ghidra\\ghidra_12.1.3_PUBLIC",
        "GHIDRIFF_MCP_PYTHON": "C:\\Python313\\python.exe",
        "GHIDRIFF_MCP_HOME": "C:\\ghidriff-work"
      }
    }
  }
}

HTTP transports are available for clients that prefer them:

ghidriff-mcp --transport streamable-http --host 127.0.0.1 --port 8765

Command-line flags: --check (self check), --workspace PATH and --ghidra-install-dir PATH (override the matching environment variables for this process), --transport, --host, --port, --version.

Tools

Thirteen tools, all prefixed ghidriff_ so an agent can tell them apart from other Ghidra-related servers.

Tool

What it does

ghidriff_environment

Verifies Ghidra, Java and ghidriff, and explains how to fix anything missing.

ghidriff_start_diff

Starts a diff in the background, returns a job_id immediately.

ghidriff_run_diff

Blocking convenience wrapper around the above.

ghidriff_job_status

Status, timing and log tail of one job.

ghidriff_job_result

Match statistics, function counts, names, artefacts.

ghidriff_job_log

The raw ghidriff log, tailed or paged.

ghidriff_job_cancel

Kills a running diff.

ghidriff_jobs

Job history (persisted in job.json, survives restarts).

ghidriff_list_runs

Diff artefacts on disk, workspace-wide or in one directory.

ghidriff_read_report

Page through the generated Markdown report.

ghidriff_function_detail

One changed function: metadata, code, and the code diff.

ghidriff_search_functions

Search changed functions by name.

ghidriff_settings

Effective configuration and environment variables.

ghidriff_start_diff accepts old_binary, one or more new_binaries, the engine (VersionTrackingDiff, SimpleDiff, StructualGraphDiff), output_dir, project_dir, summary, side_by_side, force_analysis, force_diff, bsim, bsim_full, min_func_len, max_section_funcs, base_address, no_symbols and timeout_s. Raw engine pass-through is deliberately not on this list — that lives in GHIDRIFF_MCP_EXTRA_ARGS.

Pass several newer binaries to get chained diffs (old → v2 → v3), and set summary=true to also diff old → newest. Use side_by_side=true if you want the per-function HTML diffs on disk.

How it works

MCP client ──stdio──▶ ghidriff-mcp ──subprocess──▶ ghidriff ──PyGhidra──▶ Ghidra (JVM)
                           │                            │
                           │                            └── writes <name>.ghidriff.md
                           │                                       json/<name>.ghidriff.json
                           └── job.json + ghidriff.log              json/<name>.ghidriff.matches.json
                               (structured summaries, paged reads)
  • The server never imports ghidriff, so a JVM crash cannot take it down, and cancelling a job is a single kill.

  • One run directory per job: <workspace>/runs/<job_id>/ containing job.json, ghidriff.log, ghidriff/ (output) and ghidra_projects/.

  • Child output is redirected into the log file through an inherited handle rather than a pipe, which keeps it streamable and works in environments where pipes are restricted.

  • pdiff JSON files are parsed lazily, capped in size and cached by mtime.

  • Relative paths in tool calls resolve against the workspace, so an agent never has to know the absolute layout of the machine.

Security and trust model

This server drives ghidriff locally with your own privileges on files you point it at. Before wiring it to an agent, know what it does and does not constrain:

  • No shell. Every command is an argv list built by the server and passed to exec-style process creation. Nothing is interpolated into a shell string, so file names and option values cannot become shell syntax.

  • The agent picks the target, you pick the engine. Tool arguments select which binaries to diff, which supported engine to use, and how much output to render. They cannot select the executable, its JVM arguments, or arbitrary extra CLI switches. That matters, because --jvm-args -javaagent:... would otherwise be a code-execution primitive one model decision away.

  • Option-like file names are neutralised. Binaries are passed after a -- separator, so a file named --force-analysis stays a file.

  • Binaries are untrusted input. Strings, symbol names, decompiled code and report text from a sample end up in the model's context. Treat all of it as data, never as instructions: a crafted binary can try to talk your agent into something. The same holds for anything else you feed the model.

  • Symbol lookups reach the network by default. ghidriff lets Ghidra resolve PDBs, and for PE files with PDB metadata that can mean a query to a symbol server (Microsoft's among them), which leaks a little about what you are analysing. Pass no_symbols=true, or set GHIDRIFF_MCP_EXTRA_ARGS=--no-symbols, for offline work.

  • File access is not confined. The server reads and writes wherever you point it, because binaries normally live outside the workspace. Use a dedicated workspace and do not hand an agent paths to files you would not want in a transcript.

  • Transports. stdio by default. The HTTP transports bind to 127.0.0.1 unless you pass --host, and they carry no authentication — treat a non-loopback bind as exposing the server to that network.

  • External tooling is the real attack surface. Ghidra parsing a hostile file is a much bigger risk than this wrapper. Keep ghidriff and Ghidra current, and run samples in a VM you can throw away.

Everything a run produces lives under GHIDRIFF_MCP_HOME, so that directory is the thing to review — and delete — when you are done.

Troubleshooting

Symptom

Cause and fix

Path element starting with '.' is not permitted

Ghidra rejected the project path. Set GHIDRIFF_MCP_HOME to a directory without dot-prefixed elements.

GHIDRA_INSTALL_DIR missing / Ghidra not found

Set it to the extracted Ghidra folder (the one containing support/).

No module named ghidriff

The launched interpreter lacks ghidriff: set GHIDRIFF_MCP_PYTHON or GHIDRIFF_MCP_COMMAND.

No module named 'pyghidra'

Install ghidriff with its dependencies (pip install ghidriff), not just the CLI.

First diff takes minutes

Expected: Ghidra imports and analyses every binary once. Later diffs reuse the Ghidra project unless force_analysis=true.

Symbols are disabled, but the symbol is already downloaded

You passed no_symbols=true for a binary whose PDB is already in the symbol store. Delete it or drop the flag.

OutOfMemoryError

Lower max_ram_percent, or diff smaller binaries.

Job stuck in queued

Another job holds a slot; GHIDRIFF_MCP_MAX_JOBS limits concurrent JVMs.

ghidriff exited 0 but no diff artefacts

The output directory was overridden, e.g. by GHIDRIFF_MCP_EXTRA_ARGS; check ghidriff_job_log.

above the 512 MB parse limit

The pdiff is too large to parse safely. Page through the Markdown report with ghidriff_read_report instead.

ghidriff_environment is slow the first time

It starts the configured interpreter next to a possibly running JVM; the probe has a 120s budget and reports a timeout rather than hanging.

Compared with GhidraMCP

GhidraMCP bridges a running Ghidra GUI to an agent: interactive decompilation, renaming, commenting. This server does the opposite job: batch, headless diffing between two builds, with no GUI and no open project. They complement each other, and the ghidriff_ prefix means an agent can use both at once.

Project layout

src/ghidriff_mcp/
  server.py      FastMCP tool surface and CLI entry point
  runner.py      ghidriff command construction and subprocess execution
  jobs.py        background job state machine, job.json persistence
  reports.py     pdiff / Markdown parsing, paging, size limits
  ghidra_env.py  Ghidra, Java and ghidriff discovery and diagnosis
  paths.py       workspace path resolution and Ghidra path rules
  config.py      environment-driven settings
tests/           unit, protocol-level and opt-in integration tests

Development

pip install -e ".[dev]"
pytest                       # 110 tests, no Ghidra needed
ruff check src tests
ruff format --check src tests

The Ghidra-dependent test is opt-in:

GHIDRIFF_MCP_INTEGRATION=1 GHIDRA_INSTALL_DIR=/path/to/ghidra pytest -m integration

It generates a pair of PE files (a pristine copy of a system binary and one whose entry point is patched) and asserts that at least one function is reported as modified.

License

MIT — see LICENSE.

This project contains no ghidriff source code and does not link against it. It invokes ghidriff (GPL-3.0) and Ghidra (Apache-2.0) as separate processes that you install yourself; those programs keep their own licenses.

Available Tools

13 tools
ghidriff_environmentA

Check that Ghidra, Java and ghidriff are installed and reachable.

Args: probe: also launch the ghidriff interpreter to confirm it can import ghidriff (adds up to a minute on a cold start).

Inputs: probe. Returns: object

ghidriff_function_detailA

Explain one changed function: metadata, code, and the code diff if modified.

Inputs: name (required), job_id, json_path. Returns: object

ghidriff_job_cancelA

Cancel a running diff job and kill its ghidriff process.

Inputs: job_id (required). Returns: object

ghidriff_job_logA

Read the ghidriff console log of a job, tailed or paged.

Inputs: job_id (required), max_lines, start_line, tail_lines. Returns: object

ghidriff_job_resultA

Return the structured result of a finished (or running) diff job.

Inputs: job_id (required), max_functions, include_metadata_diff. Returns: object

ghidriff_jobsB

List diff jobs known to this server, newest first (survives restarts).

Inputs: limit, status. Returns: object

ghidriff_job_statusA

Report the state of one diff job, including a short log tail.

Inputs: job_id (required), log_tail. Returns: object

ghidriff_list_runsB

List diff outputs on disk, either from the run history or a given directory.

Inputs: limit, directory. Returns: object

ghidriff_read_reportC

Read a page of the generated Markdown diff report.

Inputs: path, job_id, max_lines, start_line. Returns: object

ghidriff_run_diffA

Run a diff and wait for it to finish (blocking convenience wrapper).

Prefer ghidriff_start_diff for large binaries: this call occupies the tool call for the whole analysis.

Inputs: old_binary (required), new_binaries (required), engine, summary, +5 more. Returns: object

ghidriff_search_functionsC

Search the changed functions of a diff by name.

Inputs: query (required), kinds, limit, job_id, +1 more. Returns: object

ghidriff_settingsA

Show the server configuration (workspace, interpreter, limits).

Inputs: no named parameters. Returns: object

ghidriff_start_diffA

Start a binary diff in the background and return a job id immediately.

Raw engine pass-through arguments are deliberately not accepted here: the agent decides what to diff, the operator decides how the engine runs, through GHIDRIFF_MCP_EXTRA_ARGS.

Inputs: old_binary (required), new_binaries (required), bsim, engine, +12 more. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 13 tools

    Disambiguation4/5

    Most tools have clearly distinct purposes: running diffs, managing jobs, and reading reports are separated well. However, ghidriff_start_diff vs ghidriff_run_diff and ghidriff_list_runs vs ghidriff_jobs could cause minor selection hesitation without careful reading.

    Naming Consistency4/5

    All tool names share the ghidriff_ prefix and use consistent snake_case, which makes the set feel cohesive. The pattern is mostly verb_noun, but ghidriff_settings, ghidriff_environment, and ghidriff_jobs are noun-only names, so it is not perfectly uniform.

    Tool Count5/5

    13 tools is well within the ideal range for this domain. The count covers environment checks, diff execution, job lifecycle management, and report exploration without feeling bloated.

    Completeness5/5

    The tool surface covers the full workflow: verifying prerequisites, configuring via settings, starting diffs asynchronously or synchronously, monitoring and canceling jobs, and inspecting generated reports and changed functions. There are no obvious dead ends or missing operations for the server's stated purpose.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables LLMs to autonomously reverse engineer binaries using Ghidra's capabilities including decompilation, function analysis, automatic renaming, and BSim integration for function similarity matching.
      1
      AGPL 3.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables reverse-engineering agents to export binaries to BinExport format and compare two builds, returning matched, changed, and build-unique functions with addresses, names, similarity, and confidence scores. Diff results are written as JSON files with overall similarity and confidence summaries.
      56 PyPI
      1
      MIT