Skip to content
 
 

Repository files navigation

Ryuumonbuchi

Ryuumonbuchi

Maybe the headless Ghidra MCP you are looking for.

Python License Ghidra MCP

Overview

Ryuumonbuchi turns Ghidra into a 217-tool Model Context Protocol server. An LLM agent opens a binary and drives analysis through typed tool calls: decompilation, disassembly, type reconstruction, patching, symbol and memory edits, and project export. No GUI automation, no hand-written analyzeHeadless scripts.

The server builds on the low-level MCP SDK (mcp.server.lowlevel) with a registry generated from one authoritative catalog. The catalog declares 217 dotted tool names; 212 map one-to-one onto methods of a persistent PyGhidra backend. health.ping and mcp.response_format are server-native, headless.run and headless.start are the native launcher paths, and operation.batch is the batching dispatcher.

Behind the transport, one persistent worker child holds a live PyGhidra/JVM session for the whole MCP lifespan. Repeated calls reuse the warmed backend instead of paying JVM startup per request.

Architecture

 MCP client ── stdio or streamable HTTP ──► Ryuumonbuchi server
                                          (low-level SDK, schema/policy dispatch)
                                                     │
                                                     │ protocol-v2, 8-byte length-prefixed socket IPC
                                                     ▼
                                          persistent worker child
                                          (lazy PyGhidra/JVM, multiple program sessions)
                                                     │
                                  ┌──────────────────┴───────────────────┐
                                  ▼                                      ▼
                          backend (PyGhidra)              headless.run ── analyzeHeadless
                          program sessions,                (separate process group)
                          decompiler, analysis

Three dispatch paths:

  1. Worker tools (212): validated against the catalog schema, sent to the persistent child over protocol-v2 IPC, run against a live program session.
  2. headless.run: spawns support/analyzeHeadless directly with the caller's argv in its own process group. No shell, no rewriting of arguments.
  3. operation.batch: 1 to 32 worker tools in one call; read-only batches run without a transaction, mutating batches wrap in one undo transaction with rollback on error.

Prerequisites

  • Ghidra 12.0 or newer. Its bundled JDK supplies Java, so no separate Java install is needed.
  • Python 3.10 through 3.13. Python 3.14 is not yet supported: pyghidra pins Jpype1==1.5.2, which ships no Python 3.14 wheel.
  • uv to run the documented workflow.

Install

Run the published package from PyPI:

uvx ryuumonbuchi

uvx builds an isolated environment on first run and reuses it after.

To run the current main branch instead:

uvx --from git+https://github.com/elliottophellia/Ryuumonbuchi@main ryuumonbuchi

For a local checkout:

git clone https://github.com/elliottophellia/Ryuumonbuchi.git
cd ryuumonbuchi
uv sync --locked --all-groups
uv run ryuumonbuchi

Configuration

Precedence is CLI flag over environment variable over built-in default. Classpath, class-file, and VM-argument values from CLI and environment are combined rather than overridden.

CLI flag Environment variable Default Description
--ghidra-install-dir PATH GHIDRA_INSTALL_DIR /usr/share/ghidra (/opt/homebrew/share/ghidra on macOS) Ghidra installation root
--max-heap-mb MIB RYUUMONBUCHI_MAX_HEAP_MB 1024 Worker JVM max heap, 256 to 8192
--max-cpu COUNT RYUUMONBUCHI_MAX_CPU 2 Worker CPU affinity count, 1 to os.cpu_count()
--operation-timeout-seconds SECONDS RYUUMONBUCHI_OPERATION_TIMEOUT_SECONDS 900 Per-operation wall-clock deadline, 30 to 86400
--max-import-bytes BYTES RYUUMONBUCHI_MAX_IMPORT_BYTES 67108864 Cap on program.open_bytes payloads
--max-response-bytes BYTES RYUUMONBUCHI_MAX_RESPONSE_BYTES 4194304 Inline response cap before spill-to-file
--max-log-tail-bytes BYTES RYUUMONBUCHI_MAX_LOG_TAIL_BYTES 65536 Worker log tail returned on failure
--classpath PATH (repeatable) RYUUMONBUCHI_CLASSPATH (path-separated) empty Extra Java classpath entries
--class-file PATH (repeatable) RYUUMONBUCHI_CLASS_FILES (path-separated) empty Extra Java class files to load
--vmarg ARG (repeatable) RYUUMONBUCHI_VMARGS (shlex) empty Extra JVM arguments
--allow-export RYUUMONBUCHI_ALLOW_EXPORT disabled Enable export and save tools
--allow-import-bytes RYUUMONBUCHI_ALLOW_IMPORT_BYTES disabled Enable program.open_bytes
--transport {stdio,http} RYUUMONBUCHI_TRANSPORT stdio MCP transport to serve
--http-host HOST RYUUMONBUCHI_HTTP_HOST 127.0.0.1 Bind address for --transport http
--http-port PORT RYUUMONBUCHI_HTTP_PORT 8765 Bind port for --transport http, 1 to 65535
--http-path PATH RYUUMONBUCHI_HTTP_PATH /mcp Streamable HTTP mount path

Important

program.export_binary, program.export_packed, program.save, program.save_as, and project.export require RYUUMONBUCHI_ALLOW_EXPORT=1 or --allow-export. program.open_bytes requires RYUUMONBUCHI_ALLOW_IMPORT_BYTES=1 or --allow-import-bytes, and obeys the byte cap. Both gates default to deny.

Tool surface

All 217 tools use dotted names and take a JSON object. Backend tools generally accept a session_id (returned by program.open or program.open_bytes) and are batch-eligible. A representative slice, drawn from the catalog:

Category Example tools
Program & session program.open, program.open_bytes, program.close, program.summary, program.report, program.mode.get/set, program.image_base.set, program.save, program.save_as, program.export_binary, program.export_packed
Analysis & tasks analysis.update, analysis.update_and_wait, analysis.status, analysis.options.*, analysis.analyzers.*, analysis.clear_cache, task.analysis_update, task.status, task.result, task.cancel
Listing, decompilation, p-code listing.disassemble.*, listing.code_units.list, listing.data.*, listing.clear, decomp.function, decomp.tokens, decomp.ast, decomp.writeback.*, pcode.function, pcode.block, pcode.op.at
Functions, symbols, types, layouts function.*, symbol.*, namespace.create, class.create, type.*, layout.struct.*, layout.enum.*, layout.union.*, variable.*, parameter.*
References, search, graphs reference.*, search.*, graph.basic_blocks, graph.cfg.edges, graph.call_paths
Memory memory.blocks.list, memory.read, memory.write, memory.block.*
Comments, bookmarks, tags comment.*, bookmark.*, tag.*
Transactions & patches transaction.*, patch.assemble, patch.nop, patch.branch_invert
Projects & metadata project.*, metadata.query, metadata.store
External, source, relocations external.*, source.file.*, source.map.*, relocation.*, equate.*
Open world ghidra.call, ghidra.eval, ghidra.script
Server-native health.ping, mcp.response_format, headless.run, headless.start, operation.batch

Usage

A first-analysis sequence:

  1. health.ping: confirm the server responds; this never starts the JVM.
  2. program.open with path, read_only: true, update_analysis: false. Both read_only and update_analysis default to true when omitted, so set update_analysis: false when analysis options must be changed first.
  3. analysis.update_and_wait to run auto-analysis to completion.
  4. function.list to enumerate recovered functions.
  5. decomp.function on a function start address.
  6. program.close.

Function addresses and decompiler views

Function tools accept exact function entries and addresses contained within a function. An unresolved address remains an error rather than selecting a nearby function; the error reports the normalized address plus the nearest previous and next function entries.

decomp.function defaults to view: "raw", the complete Ghidra C output. Use view: "compact" only for initial triage of declaration-heavy functions:

{
  "session_id": "<session_id>",
  "function_start": 1053104,
  "view": "compact"
}

Compact output conservatively elides Ghidra-generated local declarations, is not compilable, and reports the omission count. Return to raw C, decomp.tokens, decomp.ast, or p-code when exact structure matters.

Typed tools come first. Sessions open read-only by default; switch program.mode.set to read_only: false only before intended mutations. Use operation.batch for 1 to 32 atomic program-bound calls. Treat ghidra.call, ghidra.eval, ghidra.script, headless.run, and headless.start as open-world execution. Inspect the second TextContent block for the full JSON result; the first is a compact summary. Close sessions with program.close when done.

Long operations

analysis.update_and_wait and headless.run block until they finish. When the client attaches a progress token to the call, both stream notifications/progress while they run:

  • analysis.update_and_wait runs as a worker task and polls it every 2 seconds. progress is elapsed seconds, total is operation_timeout_seconds, and message is Ghidra's current analyzer phase (for example Disassembled 14 K). Ghidra's task monitor rescopes its own counters per analyzer, so elapsed time is the only monotonic series available. Without a progress token the call keeps its original blocking behavior.
  • headless.run reports elapsed seconds against its timeout_seconds once per second.

headless.start takes the same arguments as headless.run minus terminal, returns immediately with {"task_id": "native-<hex>", "status": "running"}, and is polled with the regular task.status, task.result, and task.cancel tools. task.cancel terminates the launcher's whole process group. Background runs have no PTY, so terminal capture stays exclusive to headless.run.

Claude Code

Add the server under mcpServers in .mcp.json or ~/.claude.json:

{
  "mcpServers": {
    "ryuumonbuchi": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "ryuumonbuchi",
        "--ghidra-install-dir",
        "/usr/share/ghidra"
      ],
      "env": {
        "RYUUMONBUCHI_MAX_CPU": "4",
        "RYUUMONBUCHI_MAX_HEAP_MB": "2048"
      }
    }
  }
}

Codex

Add the server to ~/.codex/config.toml:

[mcp_servers.ryuumonbuchi]
command = "uvx"
args = ["ryuumonbuchi", "--ghidra-install-dir", "/usr/share/ghidra"]
[mcp_servers.ryuumonbuchi.env]
RYUUMONBUCHI_MAX_CPU = "4"
RYUUMONBUCHI_MAX_HEAP_MB = "2048"

Streamable HTTP

The default transport is stdio. --transport http serves the same tool surface over MCP streamable HTTP:

uv run ryuumonbuchi --transport http --http-port 8765

Clients connect to http://127.0.0.1:8765/mcp:

{
  "mcpServers": {
    "ryuumonbuchi": {
      "type": "http",
      "url": "http://127.0.0.1:8765/mcp"
    }
  }
}

One process serves one persistent worker and one JVM. Concurrent HTTP sessions share it and serialize on the worker lock.

Warning

The HTTP transport is unauthenticated. It binds 127.0.0.1 by default, where DNS-rebinding protection restricts Host and Origin to loopback. Binding beyond loopback (--http-host 0.0.0.0) exposes every tool, including headless.run, ghidra.eval, and ghidra.script, to anyone who can reach the port. Put it behind an authenticating reverse proxy or a private network boundary.

Development

uv sync --locked --all-groups     # install runtime and dev dependencies
uv run ryuumonbuchi --version     # print version and exit
uv run ryuumonbuchi --help        # list every CLI flag

Test tiers:

uv run pytest tests/test_mcp_client_smoke.py tests/test_worker_lifecycle.py -q
uv run pytest -m "not live and not live_server" --cov=ryuumonbuchi --cov-branch --cov-report=term-missing --cov-fail-under=100

The live matrix requires a real Ghidra install plus Java 21 and a C compiler, and is skipped by default:

RYUUMONBUCHI_REQUIRE_LIVE=1 GHIDRA_INSTALL_DIR=/usr/share/ghidra uv run pytest -m live tests/test_live_workflow.py -q

Static checks run with ruff check, ruff format --check, and strict pyright.

About

Maybe the headless Ghidra MCP you are looking for.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages