Skip to main content
Glama
README.md
# Ghidra MCP Server

A [Ghidra](https://ghidra-sre.org/) headless server exposed via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), designed for binary analysis and malware reverse engineering with AI assistants. Runs locally or in Docker.

## Features

- **32 MCP tools** for binary analysis: decompilation, function listing, string search, cross-references, byte pattern search, malware-focused analysis, advanced RE tools (CFG, call graphs, instruction search), and emulation
- **3 emulation tools** — emulate functions with automatic calling convention handling, single-step through code, read registers and memory
- **5 MCP resources** for browsing binary metadata, functions, strings, and imports
- **Multi-binary support** — analyze multiple binaries simultaneously in a single Ghidra project
- **Malware analysis tools** — entropy analysis (packing detection), suspicious API categorization, section anomaly detection
- **Code Mode** — token-saving operating mode that exposes only 2 tools (`search` + `execute`) instead of all 32, for LLM-efficient usage
- **Ghidra Server** — connect to a shared Ghidra server for collaborative reverse engineering with checkout/checkin workflow
- **SSE transport** — run as an HTTP server with `--transport sse` for network-accessible deployments
- **Docker or local** — runs in an isolated container or directly on your machine via stdio transport
- **PyGhidra 3.0** — direct Ghidra Java API access via in-process JVM (no Ghidra scripts needed)

## Quick Start

### Docker (recommended)

```bash
# Build the image
docker compose build

# Run in full mode (all 32 tools)
docker compose run --rm -i ghidra-mcp

# Run in code mode (2 tools: search + execute, saves tokens)
docker compose run --rm -i ghidra-mcp --mode code
```

See [Client Configuration](#client-configuration) for setup with Claude Desktop, Claude Code, OpenCode, and Continue.dev.

#### Apple Silicon

The Docker image builds natively on arm64. Since Ghidra releases don't include pre-built `linux_arm_64` decompiler binaries, the Dockerfile automatically builds the decompiler from source during `docker compose build` (adds ~2 min to build time, requires no extra configuration). If you encounter issues, you can force x86_64 emulation by uncommenting `platform: linux/amd64` in `docker-compose.yml` (slower due to Rosetta/QEMU).

### Local (no Docker)

#### Prerequisites

- **Python 3.11+** — [python.org/downloads](https://www.python.org/downloads/). On Windows, check "Add Python to PATH" during installation.
- **Java 21+** (e.g., OpenJDK 21) — `JAVA_HOME` must be set and `java` must be on your PATH.
  - **Linux/macOS:** `export JAVA_HOME=/path/to/jdk-21` (add to `~/.bashrc` or `~/.zshrc`)
  - **Windows:** Set `JAVA_HOME` via System Properties > Environment Variables (e.g., `C:\Program Files\Eclipse Adoptium\jdk-21`)
- **[Ghidra 12.0.4](https://github.com/NationalSecurityAgency/ghidra/releases)** — download and extract (no installer needed).
- **`GHIDRA_INSTALL_DIR`** environment variable pointing to your Ghidra installation.
  - **Linux/macOS:** `export GHIDRA_INSTALL_DIR=/path/to/ghidra_12.0.4_PUBLIC` (add to `~/.bashrc` or `~/.zshrc`)
  - **Windows:** Set via System Properties > Environment Variables, or `set GHIDRA_INSTALL_DIR=C:\ghidra\ghidra_12.0.4_PUBLIC` in Command Prompt

#### Installation

**Quick install** (checks prerequisites, creates venv, installs):

```bash
# Linux/macOS
./scripts/install.sh

# Windows (PowerShell)
.\scripts\install.ps1
```

**Manual install:**

```bash
python -m venv venv

# Linux/macOS:
source venv/bin/activate

# Windows (Command Prompt):
venv\Scripts\activate

# Windows (PowerShell):
venv\Scripts\Activate.ps1

pip install -e .
```

#### Usage

```bash
# Activate the virtual environment first (see Installation for Windows commands)
source venv/bin/activate

# Full mode (default) — all 32 tools
ghidra-mcp

# Code mode — 2 meta-tools (search + execute)
ghidra-mcp --mode code

# Custom project directory and name
ghidra-mcp --project-dir ~/my-projects --project-name my_project
# Windows: ghidra-mcp --project-dir C:\Users\you\my-projects --project-name my_project

# SSE transport (HTTP server on localhost:8080)
pip install -e ".[sse]"
ghidra-mcp --transport sse

# SSE with custom host and port
ghidra-mcp --transport sse --host 0.0.0.0 --port 3000
```

See [Client Configuration](#client-configuration) for setup with Claude Desktop, Claude Code, OpenCode, and Continue.dev.

## Client Configuration

> All examples show **full mode**. For **code mode**, append `--mode code` (Docker: add `"--mode", "code"` to the args array; Local: add `"--mode", "code"` to args or the command).

### Claude Desktop

Config file: `claude_desktop_config.json`

**Docker:**

```json
{
  "mcpServers": {
    "ghidra": {
      "command": "docker",
      "args": ["compose", "-f", "/path/to/docker-compose.yml", "run", "--rm", "-i", "ghidra-mcp"]
    }
  }
}
```

**Local:**

```json
{
  "mcpServers": {
    "ghidra": {
      "command": "/path/to/venv/bin/ghidra-mcp",
      "env": {
        "GHIDRA_INSTALL_DIR": "/path/to/ghidra_12.0.4_PUBLIC"
      }
    }
  }
}
```

> **Note:** Use the full path to `ghidra-mcp` inside your virtual environment. If `GHIDRA_INSTALL_DIR` is already set in your shell profile, you can omit the `env` block. **Windows:** Use `C:\path\to\venv\Scripts\ghidra-mcp.exe` as the command and Windows-style paths for `GHIDRA_INSTALL_DIR`.

### Claude Code

**Docker — via CLI:**

```bash
claude mcp add ghidra -- docker compose -f /path/to/docker-compose.yml run --rm -i ghidra-mcp
```

**Docker — via project config** (`.mcp.json` in project root):

```json
{
  "mcpServers": {
    "ghidra": {
      "command": "docker",
      "args": ["compose", "-f", "/path/to/docker-compose.yml", "run", "--rm", "-i", "ghidra-mcp"]
    }
  }
}
```

**Local — via CLI:**

```bash
claude mcp add ghidra --env GHIDRA_INSTALL_DIR=/path/to/ghidra_12.0.4_PUBLIC -- /path/to/venv/bin/ghidra-mcp
```

**Local — via project config** (`.mcp.json`):

```json
{
  "mcpServers": {
    "ghidra": {
      "command": "/path/to/venv/bin/ghidra-mcp",
      "env": {
        "GHIDRA_INSTALL_DIR": "/path/to/ghidra_12.0.4_PUBLIC"
      }
    }
  }
}
```

> **Note:** Use the full path to `ghidra-mcp` inside your virtual environment. If `GHIDRA_INSTALL_DIR` is already set in your shell profile, you can omit the `env` block and `--env` flag. **Windows:** Use `C:\path\to\venv\Scripts\ghidra-mcp.exe` as the command and Windows-style paths for `GHIDRA_INSTALL_DIR`.

### OpenCode

Config file: `opencode.json` (project root or `~/.config/opencode/opencode.json`)

**Docker:**

```json
{
  "mcp": {
    "ghidra": {
      "type": "local",
      "command": ["docker", "compose", "-f", "/path/to/docker-compose.yml", "run", "--rm", "-i", "ghidra-mcp"],
      "enabled": true
    }
  }
}
```

**Local:**

```json
{
  "mcp": {
    "ghidra": {
      "type": "local",
      "command": ["/path/to/venv/bin/ghidra-mcp"],
      "environment": {
        "GHIDRA_INSTALL_DIR": "/path/to/ghidra_12.0.4_PUBLIC"
      },
      "enabled": true
    }
  }
}
```

> **Note:** Use the full path to `ghidra-mcp` inside your virtual environment. If `GHIDRA_INSTALL_DIR` is already set in your shell profile, you can omit the `"environment"` block. **Windows:** Use `C:\path\to\venv\Scripts\ghidra-mcp.exe` in the command array and Windows-style paths for `GHIDRA_INSTALL_DIR`.

### Continue.dev

Config file: `.continue/mcpServers/ghidra.json`

> **Note:** MCP tools are only available in Continue's **Agent mode**, not Chat mode.

**Docker:**

```json
{
  "mcpServers": [
    {
      "name": "Ghidra",
      "type": "stdio",
      "command": "docker",
      "args": ["compose", "-f", "/path/to/docker-compose.yml", "run", "--rm", "-i", "ghidra-mcp"]
    }
  ]
}
```

**Local:**

```json
{
  "mcpServers": [
    {
      "name": "Ghidra",
      "type": "stdio",
      "command": "/path/to/venv/bin/ghidra-mcp",
      "env": {
        "GHIDRA_INSTALL_DIR": "/path/to/ghidra_12.0.4_PUBLIC"
      }
    }
  ]
}
```

> **Note:** Use the full path to `ghidra-mcp` inside your virtual environment. If `GHIDRA_INSTALL_DIR` is already set in your shell profile, you can omit the `env` block. **Windows:** Use `C:\path\to\venv\Scripts\ghidra-mcp.exe` as the command and Windows-style paths for `GHIDRA_INSTALL_DIR`.

## Server Modes

The server supports three operating modes, selectable via the `--mode` flag:

| Mode | Flag | Tools Registered | Use Case |
|------|------|-----------------|----------|
| **Full** | `--mode full` (default) | All 32 tools + 5 resources | Direct tool access, best for exploration and interactive use |
| **Code** | `--mode code` | 2 tools (`search` + `execute`) | Token-efficient, best for automated pipelines and cost-sensitive usage |
| **Script** | `--mode script` | 6 tools (`search_api` + `get_class_info` + `execute_script` + binary management) | Direct Ghidra Java API access via live reflection and Python code execution |

Full and Code modes provide identical analytical capabilities — Code Mode routes calls through a dynamic dispatcher instead of registering each tool individually. Script Mode goes further by exposing the raw Ghidra Java API for custom analysis not covered by the built-in tools.

## MCP Tools (Full Mode)

### Project Management

| Tool | Description |
|------|-------------|
| `import_binary` | Import a binary file for analysis |
| `upload_binary` | Upload a binary via base64-encoded data |
| `list_binaries` | List all imported binaries |
| `delete_binary` | Remove a binary from the project |

### Analysis

| Tool | Description |
|------|-------------|
| `list_functions` | List functions with pagination and name filtering |
| `decompile_function` | Decompile a function to C pseudocode |
| `rename_function` | Rename a function |
| `rename_variable` | Rename a variable (parameter or local) within a function |
| `rename_label` | Rename a symbol/label in the program |
| `list_strings` | List defined strings |
| `search_strings` | Search strings by substring or regex |
| `list_imports` | List imported symbols |
| `list_exports` | List exported symbols |
| `get_xrefs` | Get cross-references to/from an address |
| `search_bytes` | Search for hex byte patterns with wildcards |
| `get_memory_bytes` | Read raw bytes from an address |
| `search_instructions` | Regex search over disassembly mnemonics/operands |

### Malware Analysis

| Tool | Description |
|------|-------------|
| `get_entropy` | Per-section Shannon entropy, packing detection |
| `detect_suspicious_apis` | Categorized suspicious imports (injection, persistence, crypto, network, anti-debug) |
| `get_sections` | Sections with permissions, entropy, and anomaly flags (W+X, unusual names) |

### Advanced Analysis

| Tool | Description |
|------|-------------|
| `get_function_summary` | Rich function metadata (params, callees, callers, strings, complexity) without decompilation |
| `get_basic_blocks` | Control-flow graph basic blocks with instructions and edges |
| `get_call_graph` | Function call graph with BFS depth control (callees/callers/both) |

### Emulation

Emulate functions using Ghidra's `EmulatorHelper` API. The emulator automatically handles calling conventions — arguments are placed in the correct registers or stack locations based on the function's parameter metadata, and return values are extracted from the appropriate return register.

| Tool | Description |
|------|-------------|
| `emulate_function` | Emulate a function with optional integer arguments, get return value |
| `emulate_step` | Single-step an existing emulator session, read registers and memory |
| `emulate_session_destroy` | Destroy an emulator session and free its resources |

### Server Connectivity

Connect to a shared Ghidra server for collaborative reverse engineering. Programs opened from the server are available to all analysis tools. Supports checkout/checkin workflow with exclusive locking.

| Tool | Description |
|------|-------------|
| `connect_server` | Connect to a Ghidra server (host, port, username, optional password) |
| `disconnect_server` | Disconnect from server, release checkouts, clean up |
| `list_repositories` | List available repositories on the connected server |
| `list_server_files` | List files and subfolders in a server repository |
| `open_from_server` | Open a program from the server for analysis (with optional checkout) |
| `checkin_file` | Check in changes back to the Ghidra server |

#### Server Workflow

```
# 1. Connect to the Ghidra server
connect_server(host="ghidra.example.com", port=13100, username="analyst")

# 2. Browse available repositories and files
list_repositories()
list_server_files(repository_name="malware-lab")

# 3. Open a program (checkout for editing)
open_from_server(repository_name="malware-lab", file_path="/samples/trojan.exe", checkout=True)

# 4. Analyze with any tool — decompile, rename functions, etc.
decompile_function(binary_name="trojan.exe", name_or_addr="main")
rename_function(binary_name="trojan.exe", old_name="FUN_00401000", new_name="decrypt_payload")

# 5. Save changes back to the server
checkin_file(binary_name="trojan.exe", comment="Identified decryption routine")

# 6. Disconnect when done
disconnect_server()
```

#### Emulation Workflow

The typical emulation workflow is:

1. **Start emulation** with `emulate_function` — sets up the emulator, places arguments, runs until the function returns or the step limit is reached, and returns the result including the return value.

2. **Inspect interactively** (optional) with `emulate_step` — after `emulate_function` creates a session, you can single-step through the remaining execution, reading specific registers and memory regions at each step.

3. **Clean up** with `emulate_session_destroy` — disposes the emulator and frees resources. Sessions are also cleaned up automatically when a binary is deleted or the server shuts down.

#### Emulation Example

```
# Step 1: Emulate a function with arguments
emulate_function(binary_name="malware.exe", name_or_addr="decrypt_string", args=[0x00402000, 16])
# Returns: {
#   "session_key": "malware.exe:decrypt_string",
#   "return_value": 4198400,
#   "steps_executed": 847,
#   "hit_breakpoint": true,
#   ...
# }

# Step 2: Inspect registers and memory after execution
emulate_step(binary_name="malware.exe", name_or_addr="decrypt_string",
             count=0, read_registers=["RAX", "RCX"],
             read_memory=[{"address": "0x00402000", "size": 32}])
# Returns: {
#   "registers": {"RAX": "0x401000", "RCX": "0x0"},
#   "memory": [{"address": "0x00402000", "hex": "48656c6c6f..."}],
#   ...
# }

# Step 3: Clean up
emulate_session_destroy(binary_name="malware.exe", name_or_addr="decrypt_string")
```

#### Emulation Limitations

- **External calls**: If the emulated function calls imported/external functions (e.g., `printf`, `malloc`), emulation will stop or produce undefined behavior. Only self-contained functions emulate correctly without additional stubbing.
- **Architecture support**: x86/x86-64 (push sentinel return address to stack) and ARM/AARCH64 (set LR register) are supported. Other architectures will raise an error.
- **Integer arguments only**: The current implementation handles integer arguments via register/stack writes. Floating-point and struct arguments are not supported.
- **Step limit**: A `max_steps` parameter (default: 10,000) prevents runaway emulation.

## MCP Resources

| URI | Description |
|-----|-------------|
| `ghidra://binaries` | All binaries in the project |
| `ghidra://binary/{name}/info` | Binary metadata (arch, format, hashes, entry point) |
| `ghidra://binary/{name}/functions` | Full function list |
| `ghidra://binary/{name}/strings` | All defined strings |
| `ghidra://binary/{name}/imports` | All imported symbols |

## Code Mode

Code Mode is a token-efficient operating mode that replaces all 32 individual tool registrations with just 2 meta-tools: `search` and `execute`. This dramatically reduces the number of tool schemas sent to the LLM on every request, saving tokens and cost while preserving full analytical capability.

### Activation

```bash
# Docker
docker compose run --rm -i ghidra-mcp --mode code

# Local
ghidra-mcp --mode code
```

### Tools

#### `search(query?)`

Search the tool catalog. Returns tool names, descriptions, and full parameter signatures.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | No | Substring to filter tool names and descriptions. Returns all 32 tools if omitted. |

**Example — find emulation tools:**
```
search(query="emulate")
# Returns:
# [
#   {"tool": "emulate_function", "description": "Emulate a function with optional arguments...",
#    "parameters": [{"name": "binary_name", "type": "string", "required": true}, ...]},
#   {"tool": "emulate_step", ...},
#   {"tool": "emulate_session_destroy", ...}
# ]
```

**Example — find tools related to entropy:**
```
search(query="entropy")
# Returns tools whose name or description contains "entropy"
```

#### `execute(method, params?)`

Execute any Ghidra analysis tool by name with the given parameters.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `method` | string | Yes | Tool name from the catalog (use `search` to discover available tools). |
| `params` | object | No | Keyword arguments as a dictionary. Omit for tools with no required params. |

**Example — list functions:**
```
execute(method="list_functions", params={"binary_name": "test.elf", "filter": "main", "limit": 10})
```

**Example — decompile a function:**
```
execute(method="decompile_function", params={"binary_name": "test.elf", "name_or_addr": "main"})
```

**Example — emulate a function:**
```
execute(method="emulate_function", params={"binary_name": "test.elf", "name_or_addr": "decrypt", "args": [1, 2]})
```

**Example — list all binaries (no params needed):**
```
execute(method="list_binaries")
```

### Error Handling

- **Unknown method**: Raises `ValueError` with the list of all available method names.
- **Missing required parameter**: Raises `TypeError` from the underlying Python method call.
- **Bridge errors**: `KeyError` (binary not found), `RuntimeError` (decompilation failure), etc. propagate directly with descriptive messages.

### When to Use Code Mode

| Scenario | Recommended Mode |
|----------|-----------------|
| Interactive exploration with Claude Desktop | Full |
| Automated analysis pipelines | Code |
| Cost-sensitive / high-volume usage | Code |
| First time using the server | Full |
| LLM with small context window | Code |
| Custom analysis beyond built-in tools | Script |
| Exploring Ghidra API for scripting | Script |

## Script Mode

Script Mode provides direct access to the Ghidra Java API via live reflection and Python code execution. Instead of using pre-built tools, the LLM can search API classes, inspect method signatures, and execute arbitrary Python code with full Ghidra API access — like a programmable Ghidra scripting environment.

### Activation

```bash
# Docker
docker compose run --rm -i ghidra-mcp --mode script

# Local
ghidra-mcp --mode script
```

### Tools

#### `search_api(query, package?)`

Search Ghidra Java API classes and methods by keyword using live Java reflection.

```
search_api(query="FunctionManager")
# Returns class info with all method signatures

search_api(query="getParameters", package="ghidra.program.model.listing")
# Returns classes in that package with matching methods
```

#### `get_class_info(class_name)`

Get full reflection info for a specific class — methods, parameter types, return types, interfaces.

```
get_class_info(class_name="ghidra.program.model.listing.Function")
# Returns: {"class": "...", "methods": [{"name": "getName", "params": [], "returns": "String"}, ...]}

get_class_info(class_name="Function")  # Short name also works
```

#### `execute_script(code, binary_name?)`

Execute a Python code snippet with full Ghidra Java API access.

```python
execute_script(
    binary_name="malware.exe",
    code="""
fm = program.getFunctionManager()
funcs = list(fm.getFunctions(True))
result = []
for f in funcs[:5]:
    result.append({
        'name': f.getName(),
        'addr': str(f.getEntryPoint()),
        'cc': f.getCallingConventionName(),
    })
return result
"""
)
```

Pre-defined variables in script scope:
- `bridge` — GhidraBridge instance
- `program` / `currentProgram` — Program object (if `binary_name` provided)
- `monitor` — ConsoleTaskMonitor
- All `ghidra.*` packages importable via normal Python imports

## Configuration

### CLI Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--mode` | `full` | Server mode: `full`, `code`, or `script` |
| `--transport` | `stdio` | Transport protocol: `stdio` or `sse` |
| `--host` | `localhost` | Host to bind for SSE transport |
| `--port` | `8080` | Port to bind for SSE transport |
| `--project-dir` | `./ghidra-projects` | Directory for Ghidra projects |
| `--project-name` | `mcp_project` | Ghidra project name |

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `GHIDRA_INSTALL_DIR` | *(auto-detect)* | Path to Ghidra installation directory (required if PyGhidra auto-detection fails) |
| `GHIDRA_ANALYSIS_TIMEOUT_SECONDS` | `300` | Analysis timeout per binary |
| `GHIDRA_MAX_HEAP` | `2g` | JVM max heap size |

## Docker Volumes

| Path | Purpose |
|------|---------|
| `/home/ghidra/binaries` | Input binaries (mounted read-only) |
| `/home/ghidra/projects` | Persistent Ghidra project data |

## Development

```bash
# Install dev dependencies
pip install -e ".[dev]"

# Run tests (uses mocked GhidraBridge, no Ghidra needed)
pytest tests/ -v

# 192 tests covering full mode, emulation, server, code mode, script mode, CLI, and validation
```

## Architecture

```
MCP Client (Claude Desktop / Claude Code / OpenCode / Continue.dev / ...)
  ↕ stdio or SSE (HTTP)
FastMCP Server (server.py)
  ├── Full Mode: 32 @mcp.tool() + 5 @mcp.resource()
  ├── Code Mode: search + execute → _dispatch()
  └── Script Mode: search_api + get_class_info + execute_script
        ↕
GhidraBridge (ghidra_bridge.py)
  ├── Programs cache     (dict[str, Program])
  ├── Decompilers cache  (dict[str, DecompInterface])
  └── Emulators cache    (dict[str, EmulatorHelper])
        ↕
PyGhidra / JPype / JVM
        ↕
Ghidra Java API
```

- **FastMCP** handles MCP protocol over stdio or SSE
- **GhidraBridge** manages the JVM lifecycle, Ghidra project, cached program handles, decompiler instances, and emulator sessions
- **PyGhidra** provides in-process access to Ghidra's Java API via JPype (no separate Ghidra process needed)
- **Code Mode dispatcher** (`_dispatch`) translates `execute(method, params)` calls into the appropriate bridge method invocations, handling parameter renaming and response wrapping

## License

MIT