bethington/ghidra-mcp

▲ 242 stars today★ 4,279⑂ 181

Ghidra MCP Server — 200+ MCP tools for AI-powered reverse engineering. GUI plugin + headless server, lazy tool loading, convention enforcement, batch operations, Ghidra Server integration

About bethington/ghidra-mcp

bethington/ghidra-mcp is an open-source project on GitHub, mainly written in Java. Ghidra MCP Server — 200+ MCP tools for AI-powered reverse engineering. GUI plugin + headless server, lazy tool loading, convention enforcement It currently holds 4,279 stars and 181 forks with 58 open issues, and was last pushed on 2026-10-05 (repository created 2025-08-30).

Project Overview

Git Homed tracks it on the Today's Trending board, currently at rank #33 with 242 new stars today.

GitHub Repository Details

Repository bethington/ghidra-mcp · default branch dev · size 13321 KB · watchers 12 · source: GitHub REST API and repository README

README

Ghidra MCP Server

MCP Toplist

Tests Release License GitHub Sponsors

Python Java Ghidra MCP

Stars Last commit Discussions Issues OpenSSF Scorecard

If you find this useful, please ⭐ star the repo — it helps others discover it!
> If Ghidra MCP saves you time, consider sponsoring the project. One-time and recurring support both help fund compatibility updates, production hardening, docs, and new tooling.

A production-ready Model Context Protocol (MCP) server that bridges Ghidra's powerful reverse engineering capabilities with modern AI tools and automation frameworks. 253 MCP tools, battle-tested AI workflows, and the most comprehensive Ghidra-MCP integration available — now including P-code emulation, live debugger integration, and PCode-graph data flow analysis.

Why Ghidra MCP?

Most Ghidra MCP implementations give you a handful of read-only tools and call it a day. This project is different — it was built by a reverse engineer who uses it daily on real binaries, not as a demo.

Convention Enforcement

You've been there: six months into a project you find ProcessItem, process_items, handleItem, and ItemProc in the same codebase — four functions doing the same thing, named by four different sessions or engineers with no shared contract. Fixing it takes longer than it should, and the problem will happen again.

v5.0 moves conventions from "things to remember" into the tool layer, where they can actually be enforced.

| Tier | Behavior | Example | | ------ | ---------- | --------- | | Auto-fix | Applied silently | count field on a uint32 → auto-prefixed dwCount on save | | Warn | Change goes through, warning returned | processData → "name should be PascalCase with a verb: ProcessData" | | Reject | Change blocked with explanation | undefined → undefined type change → "no-op rejected, type unchanged" |

For AI agents, this means consistent output across every session, every model, every run — without pasting a style guide into every prompt. The tool knows the rules; the model just needs to make the call.

For teams, it eliminates the entire class of review comment that says "that's not our naming convention." Convention arbitration stays in the tool, not in code review.

For solo work at scale, analyze_function_completeness gives you a 0–100% score that measures honestly: structural deductions (unfixable compiler artifacts) are forgiven in your effective score, log-scaling prevents one bad category from burying everything else, and tiered plate comment quality means you know exactly what's missing and why.

🌟 Features

Core MCP Integration

Compatibility note: MCP tool names are normalized for GitHub Copilot CLI
and CAPI validation. Exposed tool names use lowercase letters, digits,
underscores, and hyphens only; nested HTTP paths such as /debugger/status
are advertised as names like debugger_status_2 when needed to avoid
collisions with static bridge tools.

Binary Analysis Capabilities

Dynamic Analysis (v5.4.0)

AI-Powered Reverse Engineering Workflows

Development & Automation

🚀 Quick Start

Prerequisites

Shared Ghidra Server users: Ghidra 12.1.3 clients require a Ghidra
Server at 12.1, 12.0.5, or a newer compatible version. Upgrade the
server before using this plugin from a 12.1 client.
> Ghidra 12.1.3 ships Jython as an optional extension. Java scripts work
by default, but .py scripts in ghidra_scripts/ require installing
the Jython extension from File > Install Extensions and restarting
Ghidra.

Installation

Recommended for all platforms: use python -m tools.setup directly.
> ensure-prereqs installs runtime Python requirements plus the Ghidra JARs needed in the local Maven repository.
deploy copies the build output, installs the user-profile extension, and patches Ghidra user config.

1. Clone the repository:

   git clone https://github.com/bethington/ghidra-mcp.git
   cd ghidra-mcp
   

2. Recommended: run environment preflight first:

   python -m tools.setup preflight --ghidra-path "F:\ghidra_12.1.3_PUBLIC"
   

3. Build and deploy to Ghidra:

   python -m tools.setup ensure-prereqs --ghidra-path "F:\ghidra_12.1.3_PUBLIC"
   python -m tools.setup build
   python -m tools.setup deploy --ghidra-path "F:\ghidra_12.1.3_PUBLIC"
   

deploy saves/closes an already-running matching Ghidra instance when needed, installs the extension, starts Ghidra, waits for MCP health, and runs schema smoke checks.

Prefer to click through Ghidra's own dialogs, or installing a release zip on a machine without the repo? Follow the illustrated manual GUI install guide.

4. Optional strict/manual mode (advanced):

   # Skip automatic prerequisite setup
   python -m tools.setup build
   python -m tools.setup deploy --ghidra-path "F:\ghidra_12.1.3_PUBLIC"
   

5. Show command help:

   python -m tools.setup --help
   

6. Optional build-only mode (advanced/troubleshooting):

   python -m tools.setup build
   

Two Java backends are supported. Gradle is the default for local work — it reads Ghidra's jars straight out of the installation, so there is no install-file step and nothing to install beyond a JDK. CI builds and gates with Maven, so Maven is a maintained peer rather than a fallback.

   # Gradle (default) -- the wrapper is committed, so no Gradle install is needed.
   # -PGHIDRA_INSTALL_DIR or the GHIDRA_INSTALL_DIR env var both work.
   # In Git Bash use forward slashes; a backslash path is mangled before Gradle sees it.
   ./gradlew buildExtension -PGHIDRA_INSTALL_DIR=/path/to/ghidra
   
   # Maven (peer backend; what CI uses). Needs Ghidra's jars in the local .m2 first:
   #   python -m tools.setup ensure-prereqs --ghidra-path /path/to/ghidra
   mvn clean package assembly:single -DskipTests
   

python -m tools.setup build routes to Maven by default; set TOOLS_SETUP_BACKEND=gradle to route it to Gradle instead.

Installation (Linux — Ubuntu/Debian)

1. Clone the repository:

   git clone https://github.com/bethington/ghidra-mcp.git
   cd ghidra-mcp
   

2. Install system prerequisites (if not already installed):

   sudo apt update && sudo apt install -y openjdk-21-jdk maven python3 python3-pip python3-venv curl jq unzip
   

> Debian/Kali/Ubuntu 23.04+ note (PEP 668): these distros mark the system > Python as externally managed, so a bare pip install fails with > error: externally-managed-environment. Don't work around it with > --break-system-packages — it can corrupt apt-managed tooling. Instead use > uv (recommended — it creates and manages a > project-local .venv automatically, and is what this repo's commands use): > >

   > curl -LsSf https://astral.sh/uv/install.sh | sh
   > uv run bridge-mcp-ghidra    # resolves deps into .venv and starts the bridge
   > 
> > or a classic virtual environment: > >
   > python3 -m venv .venv && source .venv/bin/activate
   > pip install -e .
   > bridge-mcp-ghidra
   > 

3. Run environment preflight:

   python -m tools.setup preflight --ghidra-path ~/ghidra_12.1.3_PUBLIC
   

4. Build and deploy to Ghidra (single command):

   python -m tools.setup ensure-prereqs --ghidra-path ~/ghidra_12.1.3_PUBLIC
   python -m tools.setup build
   python -m tools.setup deploy --ghidra-path ~/ghidra_12.1.3_PUBLIC
   

This will:

5. Optional: setup only Maven dependencies:

   python -m tools.setup install-ghidra-deps --ghidra-path ~/ghidra_12.1.3_PUBLIC
   

6. Show command help:

   python -m tools.setup --help
   
Linux paths: The extension is installed to $HOME/.config/ghidra/ghidra__PUBLIC/Extensions/GhidraMCP/.
Ghidra config files are in $HOME/.config/ghidra/ghidra__PUBLIC/.

Installation (macOS — Homebrew)

1. Install prerequisites:

   brew install openjdk@21 maven python ghidra
   

2. Clone the repository:

   git clone https://github.com/bethington/ghidra-mcp.git
   cd ghidra-mcp
   

3. Install Ghidra JARs into local Maven:

    python -m tools.setup install-ghidra-deps \
       --ghidra-path /opt/homebrew/opt/ghidra/libexec
   

4. Build and deploy:

    python -m tools.setup ensure-prereqs \
       --ghidra-path /opt/homebrew/opt/ghidra/libexec
    python -m tools.setup build
    python -m tools.setup deploy \
       --ghidra-path /opt/homebrew/opt/ghidra/libexec
   

The extension is installed to ~/Library/ghidra/ghidra_12.1.3_PUBLIC/Extensions/GhidraMCP/.

> Note: --ghidra-version is required when using the Homebrew path because the path contains no version string.

5. Start Ghidra and enable the plugin:

   /opt/homebrew/opt/ghidra/libexec/ghidraRun
   

The server starts with the plugin. Check it from the project window: Tools > GhidraMCP > Server Status

6. Configure Cursor/Claude MCP (~/.cursor/mcp.json) — use the absolute path to uv (which uv), not the bare name; GUI-launched clients do not inherit your shell's PATH (#441):

   {
     "mcpServers": {
       "ghidra": {
         "command": "/opt/homebrew/bin/uv",
         "args": ["run", "--directory", "/path/to/ghidra-mcp", "bridge-mcp-ghidra"]
       }
     }
   }
   

macOS is the sharpest case: apps launched from Finder/Dock get launchd's PATH, which never contains ~/.local/bin or /opt/homebrew/bin.

Installation (Arch Linux — AUR)

@Pandoriaantje maintains community AUR packages:

Install with your AUR helper of choice, e.g.:

yay -S ghidra-mcp        # or ghidra-mcp-git

Basic Usage

Option 1: Stdio Transport (Recommended for AI tools)

uv run bridge-mcp-ghidra          # or: python -m bridge_mcp_ghidra

MCP client config (.mcp.json, ~/.cursor/mcp.json, Claude Desktop config, …). Use the absolute path to uv — see the note below for why:

{
  "mcpServers": {
    "ghidra-mcp": {
      "command": "/home//.local/bin/uv",
      "args": ["run", "--directory", "/path/to/ghidra-mcp", "bridge-mcp-ghidra", "--transport", "stdio"],
      "env": { "GHIDRA_MCP_URL": "http://127.0.0.1:8089" }
    }
  }
}

On Windows the same config points at uv.exe, e.g. "command": "C:\\Users\\\\.local\\bin\\uv.exe". Find your own path with which uv (POSIX) or where.exe uv (Windows), or just run python -m tools.setup preflight, which prints the resolved absolute path and a ready-to-paste snippet.

Why absolute? "command": "uv" fails under service and GUI launchers.
The MCP client resolves command with its own PATH, not your shell's. A
client started from a systemd user service, a .desktop entry, or any
other GUI session inherits that launcher's environment, which routinely lacks
~/.local/bin and ~/.cargo/bin — the very directories uv installs into.
The failure lands at process-spawn time as spawn uv ENOENT, before any
bridge code runs, so there is nothing in any log to read. An absolute path
makes the client's PATH irrelevant and works on the first try. The same
applies to python, python3, and the bridge-mcp-ghidra console script.
(#441)

To add the bridge to Autohand Code from a cloned checkout:

autohand mcp add ghidra /home//.local/bin/uv run --directory /path/to/ghidra-mcp bridge-mcp-ghidra

Add --scope project before ghidra to save the server in the current project's .autohand configuration instead of your user configuration.

Option 2: Streamable HTTP Transport (Recommended for web/HTTP clients)

uv run bridge-mcp-ghidra --transport streamable-http --mcp-host 127.0.0.1 --mcp-port 8081

MCP client config for the HTTP transport (add to your client's MCP config file):

{
  "mcpServers": {
    "ghidra-mcp-http": {
      "url": "http://127.0.0.1:8081/mcp"
    }
  }
}

Browser-based clients (e.g. MCP Inspector) work out of the box: the HTTP transports answer CORS preflight (OPTIONS) requests and expose the mcp-session-id / mcp-protocol-version headers to scripts. Allowed origins mirror the Host-header policy — loopback on any port is always permitted, plus the bind host and any hosts listed in GHIDRA_MCP_ALLOWED_HOSTS.

GHIDRA_MCP_ALLOWED_HOSTS also supports clients that route a loopback-bound bridge through another network namespace. For example, a container can address the host as host.containers.internal without exposing the bridge on a LAN interface:

GHIDRA_MCP_ALLOWED_HOSTS=host.containers.internal \
  uv run bridge-mcp-ghidra --transport streamable-http \
  --mcp-host 127.0.0.1 --mcp-port 8081

The setting extends DNS-rebinding Host/Origin validation only; it does not change the bind address or make the listener reachable on additional interfaces.

Option 3: SSE Transport (Deprecated — use streamable-http instead)

uv run bridge-mcp-ghidra --transport sse --mcp-host 127.0.0.1 --mcp-port 8081

Bridge advanced flags

| Flag | Default | Description | | ------ | --------- | ------------- | | --transport | stdio | stdio (AI tools), streamable-http (web clients), sse (deprecated) | | --mcp-host | 127.0.0.1 | Bind host for HTTP transports | | --mcp-port | — | Port for HTTP transports | | --lazy | (default) | Load only the default tool groups on connect, and let the model pull in the rest with search_tools/load_tool_group. | | --no-lazy | off | Load all tool groups immediately on connect. Needed only by MCP clients that ignore tools/list_changed; rejected outright by the Gemini API (see below). | | --default-groups | listing,function,program | Comma-separated groups loaded on connect under --lazy. |

Lazy tool loading is the default (issue #440)

Advertising all 253 endpoints in a single tools/list is over a hard limit for at least one major provider. Gemini compiles function declarations into a constrained-decoding state machine and rejects the whole request before any tool is ever called:

400 INVALID_ARGUMENT
The specified schema produces a constraint that has too many states for serving

That is not a degradation, it is an outright break, and no client-side setting could work around a server that only ever offered the full set. So the bridge now loads listing,function,program (84 endpoints plus the 8 static tools) on connect and registers the rest on demand.

If your client ignores tools/list_changed it will not notice tools that are registered later, and should turn lazy loading off:

uv run bridge-mcp-ghidra --no-lazy          # when you control the command line
export GHIDRA_MCP_LAZY=0                    # when you don't (Docker, uvx, some client configs)

GHIDRA_MCP_LAZY accepts 0/false/no/off and 1/true/yes/on; an explicit --lazy/--no-lazy on the command line wins over it. Startup logs which mode is in effect.

Strict program routing (multi-program safety)

Set GHIDRA_MCP_REQUIRE_PROGRAM_SELECTORS=1 to make the bridge refuse any program-scoped call that omits a program selector, returning a clear error instead of letting the call ride the server's shared "current program" (the one switch_program and the active GUI tab move).

export GHIDRA_MCP_REQUIRE_PROGRAM_SELECTORS=1
uv run bridge-mcp-ghidra

Without this, a call that leaves program= out runs against whichever program is current, which is fine for a single-program workflow but a hazard once several programs are open: the call can read or edit the wrong binary with no error. The hazard is worse when more than one client shares a server, since each one moves that current-program global out from under the others.

With strict mode on, every program-scoped call must name its target. This covers every selector that picks an open program: plain program= and the cross-program tools' source_program/target_program or program_a/program_b (declared required, but the server still falls back to the current program when one arrives empty). A forgotten selector surfaces as a loud error on the first bad call instead of a silent write to the wrong binary. Tools with no program selector (open_program and close_program take path/name) are unaffected. Off by default: with the variable unset the bridge sends calls unchanged.

Reducing tool-context overhead

The bridge exposes a large catalo

GitHub Stars & Activity

4,279Stars
181Forks
58Open issues
JavaLanguage

GitHub Popularity

GitHub stars4,279
Forks181
Open issues58
Primary languageJava
LicenseApache-2.0
Stars gained today242
Created2025-08-30
Last pushed2026-10-05

Trending History

Daily boardrank #33 · ▲ 242 stars
Weekly boardrank #62 · ▲ 533 stars

Related GitHub Projects

1

NationalSecurityAgency / ghidra

Java★ 81,166⑂ 9,015▲ 270 stars
→
2

kestra-io / kestra

Java★ 29,316⑂ 3,290▲ 63 stars
→
3

LaurieWired / GhidraMCP

Java★ 10,590⑂ 1,090▲ 234 stars
→
4

mattpocock / skills

Shell★ 278,267⑂ 23,302▲ 889 stars
→
5

affaan-m / ECC

JavaScript★ 274,355⑂ 40,933▲ 727 stars
→
6

ossu / computer-science

HTML★ 209,925⑂ 25,961▲ 64 stars
→
7

ohmyzsh / ohmyzsh

Shell★ 190,187⑂ 28,607▲ 48 stars
→
8

ollama / ollama

Go★ 182,412⑂ 18,124▲ 137 stars
→

More Trending Repositories