I got tired of switching windows. Disassemble in one terminal, decompile in another, grep xrefs somewhere else, then shell out for strings or checksec. Every tool has its own CLI, its own output format, its own annoyances.

So I built reverse-mcp. One Docker container. Ghidra, radare2, angr, and a shell. All exposed as MCP servers behind a single HTTP endpoint. Claude Code connects, and every RE tool is one function call away.

How it works

┌──────────────┐     HTTP      ┌──────────────┐
│  Claude Code │ ────────────→ │   Gateway    │
│  (MCP client)│               │   :3100      │
└──────────────┘               └──┬───┬───┬──┘
                                  │   │   │
                    ┌─────────────┘   │   └─────────────┐
                    ▼                 ▼                 ▼
              ┌──────────┐   ┌──────────────┐   ┌──────────┐
              │  r2mcp   │   │  Ghidra      │   │  shell   │
              │  (r2pm)  │   │  headless    │   │  mcp.py  │
              └──────────┘   │  :8089       │   └──────────┘
                             └──────────────┘

The gateway

gateway.py, port 3100. Spawns each MCP back-end as a child process, composites their tools into one namespace. Each back-end gets a prefix:

Prefix Back-end What you get
r2__ radare2 (r2mcp) disassemble, decompile (r2ghidra), hexdump, xrefs, emulation
ghidra__ Ghidra headless :8089 project management, import, auto-analysis, 200+ tools: decompiler, patcher, BinDiff, debugger
shell__ shell-mcp anything: strings, checksec, ent, gdb, lldb, apktool, whatever
angr__ angr MCP CFG, symbolic execution, VFG, dependency analysis

If a back-end dies, the gateway logs it and keeps running. Tools from that back-end just don’t appear. No cascading failure.

The Ghidra situation

Ghidra tools are split in two. Static tools (import_file, list_instances, create_project) are always available. Instance tools (decompile, rename, xrefs, patch) only show up after you load a program.

Flow: import file → auto-connect → gateway fetches the instance schema → 200+ tools unlock. A bit clunky but it holds together.

Getting started

git clone https://github.com/mitsuakki/reverse-mcp
cd reverse-mcp
docker compose build
docker compose up -d

Drop binaries in ./workspace/. They show up at /workspace inside the container.

MCP client config:

{
  "mcpServers": {
    "reverse-mcp": {
      "type": "http",
      "url": "http://localhost:3100/mcp"
    }
  }
}

Agents

Five Claude Code agents in .claude/agents/. Each does one thing:

Agent Model Role
binary-triage Haiku Fast surface recon: radare2 + shell
ghidra-importer Sonnet Import into Ghidra, auto-analysis
ghidra-analyst Sonnet Static RE: decompile, xrefs, rename
ghidra-debugger Sonnet Dynamic: attach, breakpoints, trace
re-orchestrator Opus Runs the whole pipeline, triage → report

The orchestrator chains them. Triage first for a quick surface look. If the binary seems interesting, it hands off to Ghidra. Sometimes you just want a fast answer and don’t need the heavyweight pipeline.

What’s inside

Everything in the container. No GUI, no VNC. Terminal or MCP only.

radare2 + r2ghidra decompiler. Ghidra headless + analyzeHeadless + a helper script. BinDiff CLI. angr, pwntools, ropper, ROPgadget, capstone, unicorn, keystone, z3, LIEF, r2pipe. Fuzzing: AFL++ with QEMU mode, honggfuzz. Android: apktool, jadx, Frida, objection. Standard: gdb, lldb, strace, ltrace, nasm, objdump, patchelf, gcc, clang.

Basically everything I use day to day, containerized so I don’t have to think about dependencies.

Security

No auth on the gateway. Local dev tool, don’t expose it. Run untrusted binaries in a VM. The compose file adds SYS_PTRACE and drops seccomp for gdb/AFL to work.

CI

GitHub Actions on push and PR. Tagged versions push to GHCR. MIT license.

Next

Binary Ninja MCP bridge using BN headless mode. Frida bridge for mobile RE. Import Ghidra CFGs into angr for hybrid analysis.

I’ll write up how to build a custom MCP server for an RE tool. The pattern is simple: subprocess the tool, parse output, expose as typed MCP tools with JSON schemas. Under 200 lines of Python for a basic disassembler bridge.