Ghidra BizHawk MCP
A unified MCP (Model Context Protocol) server bridging Ghidra's headless static analysis with BizHawk's live emulation — switch between decompiling a ROM and running it on real hardware in the same session.
GBA ROMs: If analyzing Game Boy Advance ROMs, install pudii/gba-ghidra-loader in your Ghidra installation for proper ROM header parsing, mirrored memory regions, and I/O register maps. The loader repository has pre-built
.gpafiles for Ghidra 11.x.
Prerequisites
| Dependency | Version | Required | Notes |
|---|---|---|---|
| Python | >= 3.10 | Yes | Runtime for the MCP server |
| Ghidra | 11.x or 12.x | Yes | Headless or GUI install; GHIDRA_INSTALL_DIR must point here |
| Java (JDK) | >= 17 | Yes | Bundled with Ghidra; needed for JVM bridge |
| pyghidra | >= 3.0 | Yes | Python-to-Ghidra bridge; installed automatically |
| BizHawk (EmuHawk) | Latest stable | No | Only needed for live emulation tools; BIZHAWK_EXE_PATH optional |
| Docker | Latest | No | Only needed for containerized deployment |
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ MCP Client (Claude / Cursor) │
│ sends JSON-RPC over stdin/stdout │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ ghidra-bizhawk-mcp │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ MCP Server (server.py) — tool registry, stdio dispatch ││
│ └──────────────────────────┬──────────────────────────────────┘│
│ │ │
│ ┌──────────────┴──────────────┐ │
│ ▼ ▼ │
│ ┌────────────────────┐ ┌──────────────────────────────┐ │
│ │ GhidraSession │ │ BizhawkBridge │ │
│ │ pyghidra → JVM │ │ TCP client → localhost:8766│ │
│ │ decompile, etc. │ └──────────────┬───────────────┘ │
│ └────────────────────┘ │ │
└───────────────────────────────────────────┼─────────────────────┘
│ TCP (newline-delimited JSON)
▼
┌──────────────────────────────┐
│ BizHawk (EmuHawk.exe) │
│ built-in socket server │
│ ┌────────────────────────┐ │
│ │ bridge.lua │ │
│ │ memory read/write │ │
│ │ joypad, savestate │ │
│ │ frame advance │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
Security Model
The MCP server communicates with the MCP client exclusively over stdin/stdout — no HTTP or network listener. The only local TCP socket is a loopback-only connection (127.0.0.1:8766) between the server and BizHawk's built-in Lua socket server. This is used solely for live-emulation features and is not exposed to the network.
Hardware & Retro Ecosystem Integration
ghidra-bizhawk-mcp includes native out-of-the-box support for retro-reversing automation pipelines via Ghidra's static analysis, plus live emulation via BizHawk's multi-system emulator. The server bundles:
- Nintendo Entertainment System (NES) via
GhidraNes - Super Nintendo Entertainment System (SNES) via native 65816 memory maps
- Game Boy Advance (GBA) via
gba-ghidra-loader - Nintendo DS (NDS) via
NTRGhidra - Nintendo Switch via
ghidra-switch-loader - PlayStation 1 (PSX) via
ghidra_psx_ldr - Sega Genesis / Mega Drive via native 68000 memory maps
- Sega Master System / Game Gear via
Ghidra-SegaMasterSystem-Loader - Sega Dreamcast via native SuperH4 memory maps
Zero-Input Triage — Worked Example (GBA)
The primary entry point is triage_and_load_retro_rom. Call it with any ROM path and the server handles the rest:
# Auto-detect platform, map language, provision session
triage_and_load_retro_rom(rom_path="/data/game.gba")
# → platform: "Game Boy Advance (GBA)"
# → loader: "GBA ROM Loader"
# → arch: "ARM:LE:32:v4t"
# Decompile the main entry point on the same session
decompile_function(address="0x00001c2c")
# → decompiled C code for the GBA ROM entry routine
# Search for a known pattern (e.g. 32-bit ARM store-multiple)
search_bytes(pattern="09 08 00 01")
# → matching addresses labelled "gba_ram_start"
Execution Chaining Flow
Instead of forcing your AI agent to spend cycles manually identifying architecture maps, register layouts, or memory segments, chain the automated ingestion pipeline:
- Invoke
triage_and_load_retro_romwith a target file path. - The server headlessly parses the binary file structure (
NES\x1a,NTR,NSO0,GBA, SNES title vectors,PS-X EXE,SEGA,TMR SEGA,SEGA ENTERPRISES), binds the matching Ghidra language module (6502:LE:16,ARM:LE:32:v4t,AARCH64:LE:64,65816:LE:24,MIPS:LE:32,68000:BE:32,Z80:16,SuperH4:LE:32), loads standard address memory blocks, and links automated signature cache arrays. - Use the integrated
emulate_sliceoremulate_slice_with_tainttools to analyze localized console loops — no physical console hardware or open GDB networking ports needed.
Triage Tool
| Tool | Description |
|---|---|
triage_and_load_retro_rom |
Reads raw file magic bytes to detect NES, SNES, GBA, NDS, Switch, PSX, Genesis, SMS, or Dreamcast ROMs. Provisions a correctly-language-mapped Ghidra session and auto-restores cached function signatures. Returns platform, loader, architecture tag, and mapped memory blocks. |
Quick Start
1. Install
pip install ghidra-bizhawk-mcp
Or from source:
git clone https://github.com/getanirao/ghidra-bizhawk-mcp.git
cd ghidra-bizhawk-mcp
pip install -e .
2. Set environment
# Required: point to your Ghidra installation
export GHIDRA_INSTALL_DIR=/opt/ghidra_11.2 # Linux / macOS
set GHIDRA_INSTALL_DIR=C:\Program Files\ghidra_11.2 # Windows
# Optional: enable live BizHawk emulation
export BIZHAWK_EXE_PATH=/path/to/EmuHawk.exe
# Optional: run in mock mode (no Ghidra/BizHawk needed)
export MOCK_MODE=1
3. Run
ghidra-bizhawk-mcp
The server listens on stdin/stdout — pipe it to any MCP-compatible client.
Docker
docker build -t ghidra-bizhawk-mcp .
docker run -i --rm -v /path/to/binaries:/data ghidra-bizhawk-mcp
The container bundles JDK 17, Ghidra 11.2, and the server — no host dependencies beyond Docker.
Configuration
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
GHIDRA_INSTALL_DIR |
Yes | — | Path to Ghidra installation (e.g. /opt/ghidra_11.2) |
BIZHAWK_EXE_PATH |
No | — | Path to EmuHawk.exe for live emulation features |
MOCK_MODE |
No | 0 |
Set to 1 to run without Ghidra/BizHawk (for testing/CI) |
Claude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"ghidra-bizhawk": {
"command": "ghidra-bizhawk-mcp",
"env": {
"GHIDRA_INSTALL_DIR": "/opt/ghidra_11.2"
}
}
}
}
Cursor
Add to your Cursor MCP configuration:
{
"mcpServers": {
"ghidra-bizhawk": {
"command": "ghidra-bizhawk-mcp",
"env": {
"GHIDRA_INSTALL_DIR": "/opt/ghidra_11.2"
}
}
}
}
Tools
Session management
| Tool | Description |
|---|---|
analyze_binary |
Import + analyze a binary, returns a session_id. Reuses the ID if provided, otherwise auto-generates. |
list_sessions |
List all active workspaces with their session IDs, binary paths, and load times. |
close_session |
Close a session and free its Ghidra project resources. |
Most tools accept an optional session_id parameter — omit it to use the most recently loaded session.
Read / Analysis
| Tool | Description |
|---|---|
decompile_function |
Decompile a function by name or address. |
decompile_function_paginated |
Decompile with line_start, line_end, max_tokens (token-budget truncation), and summarize (strips boilerplate locals + collapsing blank lines). Prevents context-window exhaustion. |
get_data_types |
List all data types defined in the program. |
get_cross_references |
Cross-references to/from an address. |
get_call_graph |
Recursive call graph + callers for a function. |
analyze_and_decompile_entrypoints |
Composite — bulk decompile all entry points (program entry, exports, main, _start, etc.) in one call. |
generate_workspace_report |
Produce a Markdown summary of the active workspace — entry points, function count, custom symbols, recovered structures, renamed functions, comments. Replaces a GUI CodeBrowser window. |
Write / Mutation
| Tool | Description |
|---|---|
rename_symbol |
Rename a function or label. Stored in the Ghidra project DB. |
add_comment |
Attach a comment (plate, pre, post, eol, repeatable). |
create_struct |
Create a custom structured data type from a JSON member layout [{offset, name, type}, ...]. Offsets are optional. |
retype_variable |
Re-type a local variable or function parameter (e.g. undefined4* → MyStruct*). |
Assembly-level
| Tool | Description |
|---|---|
disassemble_range |
Disassemble N raw instructions at an address — returns mnemonic, operands, hex bytes, and length for precise lower-level inspection. |
get_listing_range |
Raw hex + ASCII dump for a byte range, equivalent to Ghidra's Listing panel. Complements disassemble_range for data regions. |
Byte-sequence search
| Tool | Description |
|---|---|
search_bytes |
Search the entire binary for a hex byte pattern (e.g. 09 08 00 01 or F86D0003). Returns matching addresses with context bytes and any string label at the hit. |
Binary diffing
| Tool | Description |
|---|---|
diff_binaries |
Compare two loaded sessions by function name and body size. Returns functions unique to each side and changed functions. |
Workspace Sessions
Each analyze_binary call creates a named session. Sessions keep their Ghidra project open independently, so multiple binaries can be loaded concurrently:
# Load two binaries into separate sessions
s1 = analyze_binary(binary_path="/bin/a.out") # auto session_id
s2 = analyze_binary(binary_path="/bin/b.out", session_id="my_session")
# Operate on a specific session
decompile_function(function_name="main", session_id=s1.session_id)
# Diff them
diff_binaries(session_a=s1.session_id, session_b="my_session")
Deployment
Docker (multi-user / CI)
docker build -t ghidra-bizhawk-mcp .
# Run as an MCP subprocess
docker run -i --rm \
-v /data/binaries:/data \
ghidra-bizhawk-mcp \
--ghidra-dir /opt/ghidra
The Dockerfile bundles Ghidra 11.2 and JDK 17 in a slim Python 3.11 image. Bind-mount your binaries directory at runtime.
MCP Bundle (MCPB — Claude Desktop / Smithery)
Package as a portable .mcpb bundle for one-click install in Claude Desktop or publishing on Smithery.
Prerequisites: Install the MCPB CLI:
npm install -g @anthropic-ai/mcpb
Build the bundle:
# From the repo root — also builds the Smithery publish variant
scripts/build-mcpb.ps1
Or manually with mcpb:
mcpb pack
The output ghidra-bizhawk-mcp.mcpb wraps the server with a manifest.json that prompts for GHIDRA_INSTALL_DIR (required) and optionally BIZHAWK_EXE_PATH at install time — no manual JSON editing.
Publishing to Smithery:
Smithery's publish API requires MCP-style tool schemas (inputSchema) and
object-form prompt arguments, which the MCPB schema forbids in manifest.json.
The build script therefore emits a second bundle whose manifest is patched from
mcpb/tool-schemas.json:
python scripts/build_smithery_bundle.py
smithery mcp publish ./dist/ghidra-bizhawk-mcp.smithery.mcpb -n getanirao/ghidra-bizhawk-mcp
P-code micro-emulation
| Tool | Description |
|---|---|
emulate_slice |
Headlessly execute N instructions. Seed register state and get a step-by-step trace of register mutations. |
emulate_slice_with_taint |
Same as emulate_slice but with automated taint tracking — specify a taint register (e.g. r0) and the tool flags exactly when its value is modified or propagates to other registers. |
emulate_slice_with_breakpoints |
Execute until a condition is met or the count expires. Condition syntax: R0==0, R1>0xFF, R2!=R3, PC==0x1234. Stops before or after the matching instruction. |
All run inside the pyhidra process via Ghidra's EmulatorHelper — no GDB/LLDB, no network ports, no debugger stubs. Works on ARM, x86, MIPS, and any Ghidra-supported architecture.
Worked example — breaking on a register condition
Suppose you're reversing a GBA ROM and want to find the first time r0 becomes zero inside a loop at 0x08000100:
# Step until r0 == 0, stop before the matching instruction
result = emulate_slice_with_breakpoints(
session_id="gba_v1",
start_address="0x08000100",
max_instructions=5000,
stop_condition="R0==0",
stop_mode="before"
)
# result.exit_reason → "R0==0"
# result.instructions_executed → 312
# result.trace → [step 311: r0 goes 4→2, step 312: r0 goes 2→0]
# Check if a specific address was reached after a branch
result = emulate_slice_with_breakpoints(
session_id="gba_v1",
start_address="0x08000100",
max_instructions=5000,
stop_condition="PC==0x08001234"
)
# result.exit_reason → "PC==0x08001234"
# Use inequalities to catch bounds checks
result = emulate_slice_with_breakpoints(
session_id="gba_v1",
start_address="0x08000100",
max_instructions=5000,
stop_condition="R1>0xFF"
)
# result.exit_reason → "R1>0xFF"
# result.last_step["r1"] → 0x100
This is especially powerful for identifying copy-loop bounds (R3 >= R4), null-pointer paths (R0==0), or switch-table targets (PC==0x).
Function fingerprinting / signature transfer
| Tool | Description |
|---|---|
calculate_function_fingerprint |
Generate a structural hash for a function (vars, params, body size, branches, called funcs, embedded strings, numeric constants). Survives compiler reordering. |
export_signature_map |
Build a complete {hash → name} map for every function in the current binary. Save this JSON to reuse across versions. |
apply_signature_map |
Pass a previously exported signature map; the server sweeps the binary and renames every matching function automatically. |
Persistent signature stash (server-side cache)
| Tool | Description |
|---|---|
save_active_binary_signature |
Fingerprint all functions and stash the map under a lineage_group_id (e.g. "my_firmware_v1"). Stored in ~/.ghidra_bizhawk_mcp/signatures/ — no JSON files to manage. |
auto_restore_signatures_from_stash |
Load a stashed map by lineage_group_id and auto-rename every matching function. |
auto_stash_current_binary |
Zero-input auto-stash — hashes the binary's first 4 KB, saves a map under that hash. Just analyze and call. |
auto_restore_current_binary |
Zero-input auto-restore — hashes the binary, looks up a previous stash, renames matches. No group ID needed. |
list_stashed_signature_groups |
List all stashed groups currently in the local cache. |
Workflow — fully automated persistence:
# Analyze v1 — stashes automatically under binary content hash
s1 = analyze_binary(binary_path="/bin/v1.bin")
auto_stash_current_binary(session_id=s1.session_id)
# Later, analyze v2 — restores automatically
s2 = analyze_binary(binary_path="/bin/v2.bin")
auto_restore_current_binary(session_id=s2.session_id)
# → 142 functions renamed, zero manual JSON handling
Try these prompts
After configuring your MCP client (see Configuration), ask your AI agent:
- "Load this GBA ROM and decompile the entry point."
- "What functions call 0x8001234 in this NDS binary?"
- "Triage this PSX EXE and trace r0 through the first 20 instructions."
- "Diff the two sessions I have open and show me changed functions."
Project Structure
ghidra-bizhawk-mcp/
├── Dockerfile
├── pyproject.toml
├── README.md
└── src/ghidra_bizhawk_mcp/
├── __init__.py
├── server.py # MCP server, tool registry, stdio transport
├── ghidra_bridge.py # GhidraSession — pyghidra wrapper, all Ghidra logic
├── lua/
│ └── bridge.lua # BizHawk-side Lua bridge for live emulation
└── tools/
├── __init__.py
├── bizhawk_bridge.py # TCP client connecting MCP ↔ BizHawk
└── ...
How it works
pyhidra.start()boots Ghidra's JVM once at server startup- Each
analyze_binarycall opens a new Ghidra project in its own named session - Read/write tools route to the requested session via
session_id(or the active default) - Write tools apply changes directly to the Ghidra program database
- Sessions persist until explicitly closed — enabling multi-binary workflows and diffing
Metadata
Release files for ghidra-bizhawk-mcp 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ghidra_bizhawk_mcp-1.1.0.tar.gz | 61.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ghidra_bizhawk_mcp-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 112.7 kB
Release files / ghidra_bizhawk_mcp-1.1.0.tar.gz
| Download URL | ghidra_bizhawk_mcp-1.1.0.tar.gz |
|---|---|
| Size | 61.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
87a2f9a02887b32e2d79b872aa2df483cc00528f52711a318ee1975b567e531f
|
|
BLAKE2b-256 checksum How to use checksums |
c3ebf79f6a5b81dfbf1fccc3e7fca4eba9a681d8157759c18e4d4a72d510176a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|
Release files / ghidra_bizhawk_mcp-1.1.0-py3-none-any.whl
| Download URL | ghidra_bizhawk_mcp-1.1.0-py3-none-any.whl |
|---|---|
| Size | 51.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
26270a47d9e9b4454515b63368d2260eebc3f6befa30667a96a00482c3d9304f
|
|
BLAKE2b-256 checksum How to use checksums |
0d134c1d774bee7d2df324328907ba443d6c7cd477e83ca2183d953bd322796f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.3
|