这是一个基于 Model Context Protocol 的服务器,将 Ghidra 的逆向工程能力桥接到 AI 工具和自动化框架。它提供超过 200 个 MCP 工具,覆盖函数分析、重命名、类型标注、注释、脚本执行、P-code 模拟和实时调试等操作。项目支持 GUI 插件和 headless 模式,具备原子事务、批量操作、命名约定强制和跨二进制文档传播等特性,并集成了 Ghidra Server 和 Docker 部署。适合需要 AI 辅助二进制分析的场景。
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. 209 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.
Every release ships the two
pieces you need: the Ghidra extension (GhidraMCP-.zip) and the MCP
bridge (ghidra_mcp_bridge--py3-none-any.whl).
Install the prerequisites: Ghidra 12.1.4, Java 21 and uv. The extension only loads in the exact Ghidra version it was built for.
Install the extension: in Ghidra's project window, File > Install
Extensions, click +, pick the GhidraMCP-.zip you downloaded,
and restart Ghidra.
Enable the plugin: in the project window, File > Configure > Utility > Configure, tick GhidraMCPPlugin. Check it answers:
curl http://127.0.0.1:8089/check_connection
# {"status": "ok", "server_kind": "gui", "version": "7.0.0"}
Install the bridge (copy the wheel's link from the same release page):
uv tool install https://github.com/bethington/ghidra-mcp/releases/download/v7.0.0/ghidra_mcp_bridge-7.0.0-py3-none-any.whl
Point your MCP client at it, using the absolute path (which bridge-mcp-ghidra, or where.exe bridge-mcp-ghidra on Windows):
{
"mcpServers": {
"ghidra-mcp": { "command": "/home/<you>/.local/bin/bridge-mcp-ghidra" }
}
}
On Windows: "command": "C:\\Users\\\\.local\\bin\\bridge-mcp-ghidra.exe".
A client launched from a GUI or a service does not see your shell's PATH,
so a bare bridge-mcp-ghidra fails with spawn ENOENT.
Open a program in Ghidra and ask your AI tool to list its functions. Screenshots of steps 2-3: docs/INSTALL_GUI.md. Building from source, Docker, the HTTP transports and every bridge flag are further down.
Which version should I run? Tagged releases (7.0.0, 7.1.0, ...) are
stable. Release candidates and main are beta: feature-complete and
live-tested, but shapes can still change before final. dev is experimental:
tools are merged, renamed and refactored there freely, so build nothing on its
API.
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.
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.
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/statusare advertised as names likedebugger_status_2when needed to avoid collisions with static bridge tools.
EmulatorHelper; brute-force API hash resolution in milliseconds/debugger/* endpoints over Ghidra's TraceRmi framework (GUI plugin only; dbgeng on Windows PE, gdb/lldb otherwise): launch, interrupt/resume, step into/over/out, breakpoints, registers, memory reads, stack traces, ASLR-aware static↔dynamic address translation. The bridge can also proxy 22 debugger_* tools to an external debugger server; they are opt-in (see below)GHIDRA_MCP_ALLOW_SCRIPTS=1)The Quickstart above needs
none of this. Build from source to run dev, to contribute, or to use
python -m tools.setup deploy, which installs, configures and restarts Ghidra
for you.
python -m tools.setup commands below (Maven is their default backend). Not needed if you build with the committed Gradle wrapper instead — see step 6Shared Ghidra Server users: Ghidra 12.1.4 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.4 ships Jython as an optional extension. Java scripts work by default, but
.pyscripts inghidra_scripts/require installing the Jython extension from File > Install Extensions and restarting Ghidra.
Recommended for all platforms: use
python -m tools.setupdirectly.
ensure-prereqsinstalls runtime Python requirements plus the Ghidra JARs needed in the local Maven repository.deploycopies the build output, installs the user-profile extension, and patches Ghidra user config.
Clone the repository:
git clone https://github.com/bethington/ghidra-mcp.git
cd ghidra-mcp
Recommended: run environment preflight first:
python -m tools.setup preflight --ghidra-path "F:\ghidra_12.1.4_PUBLIC"
Build and deploy to Ghidra:
python -m tools.setup ensure-prereqs --ghidra-path "F:\ghidra_12.1.4_PUBLIC"
python -m tools.setup build
python -m tools.setup deploy --ghidra-path "F:\ghidra_12.1.4_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.
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.4_PUBLIC"
Show command help:
python -m tools.setup --help
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.
Clone the repository:
git clone https://github.com/bethington/ghidra-mcp.git
cd ghidra-mcp
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 installfails witherror: 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.venvautomatically, 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 bridgeor a classic virtual environment:
python3 -m venv .venv && source .venv/bin/activate pip install -e . bridge-mcp-ghidra
Run environment preflight:
python -m tools.setup preflight --ghidra-path ~/ghidra_12.1.4_PUBLIC
Build and deploy to Ghidra (single command):
python -m tools.setup ensure-prereqs --ghidra-path ~/ghidra_12.1.4_PUBLIC
python -m tools.setup build
python -m tools.setup deploy --ghidra-path ~/ghidra_12.1.4_PUBLIC
This will:
~/.m2/repositoryGhidraMCP-.zip with Maven~/.config/ghidra/ghidra__PUBLIC/Extensions/preferences with LastExtensionImportDirectoryOptional: setup only Maven dependencies:
python -m tools.setup install-ghidra-deps --ghidra-path ~/ghidra_12.1.4_PUBLIC
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/.
Install prerequisites:
brew install openjdk@21 maven python ghidra
Clone the repository:
git clone https://github.com/bethington/ghidra-mcp.git
cd ghidra-mcp
Install Ghidra JARs into local Maven:
python -m tools.setup install-ghidra-deps \
--ghidra-path /opt/homebrew/opt/ghidra/libexec
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.4_PUBLIC/Extensions/GhidraMCP/.
Note: the Homebrew path contains no version string, so
tools.setupreads the Ghidra version fromGhidra/application.propertiesinside the installation instead.
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
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.
@Pandoriaantje maintains community AUR packages:
ghidra-mcp-git — tracks mainghidra-mcp — tracks tagged releasesInstall with your AUR helper of choice, e.g.:
yay -S ghidra-mcp # or ghidra-mcp-git
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/<you>/.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 resolvescommandwith its own PATH, not your shell's. A client started from a systemd user service, a.desktopentry, or any other GUI session inherits that launcher's environment, which routinely lacks~/.local/binand~/.cargo/bin— the very directoriesuvinstalls into. The failure lands at process-spawn time asspawn 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 topython,python3, and thebridge-mcp-ghidraconsole script. (#441)
To add the bridge to Autohand Code from a cloned checkout:
autohand mcp add ghidra /home/<you>/.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.
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.
uv run bridge-mcp-ghidra --transport sse --mcp-host 127.0.0.1 --mcp-port 8081
| 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. |
--tools-page-size | 0 | Serve tools/list in pages of this size (0 = one page). Only for a client that cannot take one large response; a client that ignores nextCursor sees only the first page. |
--json-response | off | streamable-http: answer POSTs with plain JSON instead of an SSE stream. Server-initiated messages such as tools/list_changed are then not delivered. |
--stateless-http | off | streamable-http: no session id and no server-initiated notifications, for running several bridge workers behind a load balancer. Pair it with --no-lazy, since a group loaded later can never be announced. |
To require a token from MCP clients of an HTTP transport, set
GHIDRA_MCP_INBOUND_TOKEN=; clients must then send
Authorization: Bearer . The bridge logs a warning when it binds a
non-loopback --mcp-host without one. Separately, when GHIDRA_MCP_AUTH_TOKEN
(the token the bridge sends to Ghidra) is set and the bridge binds a
non-loopback host, clients must present that same token, so the bridge cannot
be used to relay it.
Advertising all 209 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 (68 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.
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.
The bridge exposes a large catalog, so it loads only listing,function,program
on connect (see above) and lets the model discover the rest on demand
instead of registering everything:
search_tools("rename function") — keyword-search the entire catalog,
including tools whose group isn't loaded. Each result says whether it's
callable now and, if not, the exact load_tool_group(...) call to enable it.list_tool_groups() — list all categories and their load state.load_tool_group("datatype") / unload_tool_group("datatype") — load or
drop a category at runtime.check_tools("rename_symbol,batch_set_comments") — confirm specific tools
are callable right now.search_tools works in both lazy and --no-lazy modes, so agents that honor
tools/list_changed get full discovery without the upfront context cost.
Some MCP clients gate tools through an explicit allowlist. Cut it too far and
the agent loses discovery — it cannot find entry points or enumerate
functions through MCP, so it works around the allowlist by curl-ing the HTTP
API on 127.0.0.1:8089 directly, which defeats the point of having one. The
allowlist has to be small and self-sufficient.
Minimum viable read-only set (4 tools):
| Tool | Group | What it buys you |
|---|---|---|
get_metadata | program | Which binary is loaded — name, architecture, image base, function count. Orientation, and it confirms the bridge reached Ghidra at all. |
find_functions | listing | Paginated function enumeration (offset, limit); with no filter it lists the whole program, and name_pattern/regex turn it into a name search. This is the discovery tool — without it the agent cannot answer "what is in this binary". |
get_entry_points | listing | Where execution starts, so analysis has a root to work down from. |
get_functions | function | The payload. Takes function= (name or address) or functions= (comma-separated names or addresses, up to 20), and fields= to pick what comes back: decompiled_code, signature, callers, callees, xrefs, comments, and more. |
That set is genuinely closed: get_entry_points and find_functions supply the
addresses and names that get_functions consumes, and its callees field names
the next functions to feed straight back into it.
The three tools suggested in #441
were get_metadata, get_entry_points and decompile_function. The first two
still exist under those names; decompile_function was folded into
get_functions in 7.0.0 (fields=decompiled_code), and
the migration guide
maps every other removed name. find_functions is the one addition worth
making: without it the agent can only reach code that is reachable by name from
something it already decompiled, so anything not referenced from an entry point
is invisible.
Useful next additions, in order:
| Tool | Group | Why |
|---|---|---|
get_function_call_graph | xref | A multi-level call graph (depth, direction) in one call. One level of callers and callees already comes from get_functions. |
get_xrefs_to | xref | Who touches this address — the standard question about a global. Takes addresses= for several at once. |
list_strings | listing | Strings are the cheapest orientation signal in an unknown binary. |
list_program_items | listing | kind=imports / kind=exports: the binary's external surface. Other kinds list segments, classes, namespaces, data items and external locations. |
Every tool above is a GET; none of them writes to the Ghidra database.
Two things to check when your allowlist is narrow:
category on the Java @McpTool annotation (which the running
server publishes at /mcp/schema) — not the category field in
tests/endpoints.json, which is a separate hand-maintained column and does
not always agree. All four tools in the minimum set fall inside the default
listing,function,program groups, so they are registered even under --lazy.
Of the additions above, only the xref ones fall outside: under --lazy you
must either allow load_tool_group as well, or start the bridge with
--default-groups listing,function,program,xref.--lazy needs the group tools. If you allowlist
only leaf tools and run lazily, the agent has no way to load anything else.
Either run eagerly (--no-lazy; lazy is the default) or add search_tools,
list_tool_groups, load_tool_group, and check_tools to the allowlist.Verify any allowlist against the running server rather than against this table:
curl http://127.0.0.1:8089/mcp/schema lists every tool with the category
the bridge groups it by.
The bridge can proxy 22 debugger_* tools to an external dbgeng/WinDbg
debugger server speaking the bridge's debugger HTTP API. That server is not
part of this repository; this repo ships only the proxies. They are off by
default and register only when you opt in:
# point the bridge at your debugger server (loopback only)
export GHIDRA_DEBUGGER_URL=http://127.0.0.1:8099
# or force registration against the default URL (http://127.0.0.1:8099)
export GHIDRA_DEBUGGER_TOOLS=1
GHIDRA_DEBUGGER_TOOLS decides outright when set: 1/true/yes/on
registers the tools, anything else (0, false, ...) keeps them off even with
a URL configured. With neither variable set the tools simply do not appear,
rather than appearing and failing. The host platform plays no part.
Ghidra's own TraceRmi debugger endpoints (debugger_status, debugger_launch,
...) are separate: they live in the GUI plugin and need no extra server.
Calls are only ever proxied to a loopback URL (127.0.0.1, localhost or
::1), so a non-loopback GHIDRA_DEBUGGER_URL does not register the tools. To
reach a debugger server on another machine, forward its port to loopback (for
example an SSH tunnel) and point GHIDRA_DEBUGGER_URL there.
The bridge reads these from its own process environment (the MCP client's
env block, or your shell); it does not read a .env file.
deploy does; enabling it in CodeBrowser also works, but then the server runs only while CodeBrowser is open)http://127.0.0.1:8089/ by defaultScreenshots of every step: docs/INSTALL_GUI.md.
# Quick health check
curl http://127.0.0.1:8089/check_connection
# Expected: {"status": "ok", "server_kind": "gui", "version": "7.0.0", "program": "<name>"}
# ("program" appears only while a program is current)
# Fuller health: build details, uptime, open program count, HTTP pool, memory
curl http://127.0.0.1:8089/mcp/health
# Every tool the server advertises
curl -s http://127.0.0.1:8089/mcp/schema | jq '.tools | length'
If Ghidra MCP saves you engineering or reverse-engineering time, consider sponsoring the project.
GhidraMCP is designed for localhost-only development. The default configuration — HTTP server bound to 127.0.0.1, no authentication — is safe on a trusted single-user workstation and matches pre-v5.4.1 behavior.
If you expose the server beyond loopback, configure these three environment variables first. The server refuses to start on a non-loopback bind without a token.
| Env var | Effect |
|---|---|
GHIDRA_MCP_AUTH_TOKEN | When set, every HTTP request must carry Authorization: Bearer . Timing-safe comparison. /mcp/health and /check_connection are exempt. |
GHIDRA_MCP_ALLOW_SCRIPTS | Set to 1, true, or yes to enable /run_script_inline and /run_ghidra_script. Off by default as of v5.4.1 — these endpoints execute arbitrary Java against the Ghidra process. In headless mode this also triggers OSGi BundleHost initialization at server startup (Felix framework, ~hundreds of ms); leave it off if you don't need script execution. |
GHIDRA_MCP_FILE_ROOT | When set to a directory path, filesystem-path endpoints (/import_file, /open_project, /delete_file, etc.) canonicalize the input and require it to fall under this root. Prevents path-traversal. |
Name-quality enforcement is separate from security. By default,
rename_function and global write endpoints reject names that fail
the built-in quality gates, and struct field writes apply the built-in field
prefix convention. Disable the built-in convention layer with Edit > Tool
Options > GhidraMCP HTTP Server > Strict Naming Enforcement. The same Tool
Options checkbox covers rename_symbol (all symbol kinds),
set_global, the apply_data_type prefix/type guard, and struct-field
Hungarian prefix auto-fixes in create_struct, add_struct_field, and
modify_struct_field. The setting is read when the MCP server starts or
restarts. Function/global convention warnings are still returned when
enforcement is disabled.
export GHIDRA_MCP_AUTH_TOKEN=$(openssl rand -hex 32)
export GHIDRA_MCP_ALLOW_SCRIPTS=1 # only if your workflow needs it
export GHIDRA_MCP_FILE_ROOT=/srv/ghidra/inputs
# Headless server (`mvn clean package -P headless -DskipTests`). The jar does not
# bundle Ghidra, so Ghidra's Framework/Features/Processors jars go on the
# classpath too -- docker/entrypoint.sh builds exactly that command.
java -cp "target/GhidraMCP-<version>.jar:<ghidra jars>" \
com.xebyte.headless.GhidraMCPHeadlessServer --bind 0.0.0.0 --port 8089
The headless server serves only its Unix domain socket unless --port or
--bind is given; either one adds the TCP listener.
When connecting to a shared Ghidra Server, GhidraMCP can suppress the password dialog automatically. It resolves credentials in this order (first non-empty value wins):
Compatibility note: Ghidra 12.1.4 clients require Ghidra Server 12.1.2, 12.0.5, or a newer compatible server. Older shared servers are not safe targets for a 12.1 client upgrade.
GHIDRA_SERVER_PASSWORD environment variable (or .env file in the Ghidra install directory or ~)~/.ghidra-cred — single-line password file in your home directory/.ghidra-credUsername resolves similarly: GHIDRA_SERVER_USER env var → user.name system property.
If no password is found, Ghidra shows its normal GUI prompt. Set these in .env (see .env.template for the full block) to enable silent auth.
/run_script_inline or /run_ghidra_script, export GHIDRA_MCP_ALLOW_SCRIPTS=1. This is a deliberate breaking change; the prior default was unsafe.spawn uv ENOENT / spawn python ENOENT when the client starts the serverCause: the MCP client could not find the command on its own PATH. This
happens before any bridge code runs, so there is nothing in the Ghidra log or
the bridge log to look at — the process was never created.
It shows up when the client is launched by something other than a login shell:
systemctl --user), whose PATH is
/usr/local/bin:/usr/bin:/bin unless you extend it;.desktop entry, dock icon, or app launcher;launchd's PATH.None of those contain ~/.local/bin (uv's default install location) or
~/.cargo/bin, so "command": "uv" cannot resolve even though uv works
perfectly in your terminal.
Solution: use an absolute path in the client config.
// before — resolves against the client's PATH, fails under a service/GUI launcher
"command": "uv"
// after — no PATH lookup at all
"command": "/home/<you>/.local/bin/uv"
Find yours with which uv (POSIX) or where.exe uv (Windows). Or run:
python -m tools.setup preflight
which prints the resolved absolute path, the PATH entries it searched when a launcher is missing, and a ready-to-paste config snippet. Note what that check can and cannot tell you: it resolves against your shell's PATH, not the client's, so a pass there is evidence, not proof — the absolute path is what actually removes the failure mode. (#441)
If you must keep a bare command name, give the service manager the PATH instead
— e.g. Environment="PATH=%h/.local/bin:/usr/local/bin:/usr/bin:/bin" in the
unit file, or systemctl --user import-environment PATH — but the absolute
path is the smaller and more portable fix.
Cause: Plugin not enabled or installed incorrectly.
Solution:
Illustrated walkthrough: docs/INSTALL_GUI.md.
Cause: Server not started or wrong port.
Solution:
Check the server state: Tools > GhidraMCP > Server Status (it starts with the plugin; use Restart Server if it is stopped)
Check configured port: Edit > Tool Options > GhidraMCP HTTP Server
Check if port is in use:
# Linux/macOS
lsof -i :8089
# Windows
netstat -ano | findstr :8089
Look for errors in Ghidra console: Window > Console
pip install fails with error: externally-managed-environmentCause: PEP 668. Debian-family distros (Debian 12+, Kali, Ubuntu 23.04+)
mark the system Python as externally managed, so global pip install is
blocked to protect apt-managed packages.
Solution: Use a virtual environment — never --break-system-packages.
The recommended path is uv, which manages a
project-local .venv automatically:
curl -LsSf https://astral.sh/uv/install.sh | sh
cd ghidra-mcp
uv run bridge-mcp-ghidra
Or a classic venv:
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
bridge-mcp-ghidra
debugger_* tools do not appearCause: The 22 bridge-side debugger proxies are off by default. They register
only when GHIDRA_DEBUGGER_URL is set or GHIDRA_DEBUGGER_TOOLS=1, and they
forward to an external debugger server that is not part of this repository.
Solution: start your debugger server, then set the URL before launching the bridge:
export GHIDRA_DEBUGGER_URL=http://127.0.0.1:8099
If the tools appear but every call reports that the server is not running,
the URL is wrong or the server is down. GHIDRA_DEBUGGER_TOOLS=0 turns them off
again even with a URL configured.
Cause: Server-side exception, often due to missing program data.
Solution:
Cause: Endpoint doesn't exist or wrong URL.
Solution:
curl -s http://127.0.0.1:8089/mcp/schema lists every tool the server advertisesdecompile_function, list_functions, search_functions, get_function_callers, list_imports, ...), it was consolidated: the 7.0.0 migration guide names the replacement for every removed tool/debugger/* and /tool/* are GUI-only, and /create_project, /close_project, /delete_project and /list_projects are headless-only (see the API Reference)Cause: In Ghidra 12.1.4, Jython support is no longer enabled by
default. .py scripts need the bundled Jython extension; Python 3
scripts should use PyGhidra instead of the Ghidra Script Manager.
Solution:
Cause: JAR file in wrong location.
Solution:
~/.config/ghidra/ghidra_12.1.4_PUBLIC/Extensions/GhidraMCP/lib/GhidraMCP.jar
(%APPDATA%\ghidra\... on Windows, ~/Library/ghidra/... on macOS)Cause: Ghidra JARs not installed in local Maven repository (Maven backend only; Gradle reads them straight from the installation).
Solution:
# Windows (recommended)
python -m tools.setup install-ghidra-deps --ghidra-path "C:\ghidra_12.1.4_PUBLIC"
Under Gradle, a wall of package ghidra.program.model.address does not exist
errors instead means -PGHIDRA_INSTALL_DIR resolved to nothing — in Git Bash,
write the path with forward slashes.
209 MCP tools backed by HTTP endpoints, grouped by catalog category. Generated from tests/endpoints.json by python -m tools.gen_readme_api_reference --write; the live schema at /mcp/schema is authoritative at runtime. Usage patterns: docs/prompts/TOOL_USAGE_GUIDE.md.
186 of these are served by both the GUI plugin and the standalone headless server. The rest are marked (GUI only) (19) or (headless only) (4) — calling one against the other server returns a 404, not an error message. See python -m tools.audit_server_scope for how the split is derived.
analysis_status - Get auto-analysis status for open programsclose_program - Close an open program by project path or namecreate_memory_block - Create memory block, optionally initialized with byte contents (hex or base64)create_property_map - Create a user property map to store typed values keyed by addressdelete_bookmark - Delete bookmarkdelete_property_map - Delete a user property map and all values it holdsexit_ghidra - Save and exit Ghidraget_address_spaces - List all physical and overlay address spaces in the program (overlays include is_overlay flag and overlayed_space name)get_language_metadata - Dump the program's language description: address spaces, registers, default symbols, endianness, pointer size (issue #192)get_metadata - Get program metadataget_program_options - Read all options in a program option group with types, current values, defaults, and descriptionsget_property - Read the value stored at an address in a property mapimport_file - Import a binary file from disk into the current Ghidra project and open itlist_bookmarks - List bookmarkslist_open_programs - List open programslist_project_files - List project fileslist_properties - List (address, value) entries stored in a property map, with paginationlist_scripts - List available Ghidra scriptsopen_program - Open program from projectread_memory - Read raw memoryreanalyze - Trigger full auto-analysis on a programremove_program_option - Remove an option from a program option groupremove_property - Remove the value stored at a single address in a property maprun_ghidra_script - Run script with output capturerun_script_inline - Run inline script codesave_all_programs - Save all open programssave_program - Save current programset_bookmark - Set bookmarkset_image_base - Set the base address of the program (rebases all addresses)set_memory_block - Change an existing memory block's permissions or volatilityset_program_option - Set a typed program optionset_property - Set a value at an address in a property mapswitch_program - Switch current programarchive_project - Archive the currently open project to a Ghidra-native .gar filecreate_folder - Create a folder in the projectdelete_file - Delete a file from the projectdelete_project - Delete a Ghidra project (headless only)export_program - Export an open or project-resident program to a Ghidra Zip File (.gzf)get_project_info - Get info about the currently open projectimport_program - Import a Ghidra Zip File (.gzf) into the currently open project as a new DomainFile under target_folder (default '/')list_projects - List available Ghidra projects (headless only)move_file - Move a program file to a different folder in the project, preserving analysis and documentationmove_folder - Move a project folder and everything under it into another folderrestore_project - Restore a Ghidra .gar archive into a fresh on-disk project at parent_dir/project_nameAvailable on the standalone headless server (GhidraMCPHeadlessServer).
close_project - Close the currently open project (headless only)create_project - Create a new Ghidra project (headless only)open_project - Open an existing Ghidra project (.gpr file or directory)convert_number - Convert number between basesfind_functions - Find functions: every filter is optional, so with none it lists the whole program a page at a timeget_entry_points - Get program entry pointsget_external_location - Get external location detailsget_function_count - Return the number of functions in the loaded programlist_calling_conventions - List available calling conventionslist_data_items_by_xrefs - List data sorted by xref countlist_globals - List global variableslist_program_items - List one kind of program inventory with paginationlist_shadowed_globals - List named global DATA symbols that have NO type of their own because a larger data unit starting at an earlier address covers themlist_strings - List defined stringssearch_strings - Search defined strings by a regex/substring patternget_ui_cursor - What the analyst is looking at right now: cursor address, the function under it, the listing selection, and the focused program — one call instead of fouradd_function_tag - Attach tags to ONE function (function + tags) OR MANY in one transaction (assignments=[{function,tags}, ...])batch_rename_function_components - Batch rename function componentsclear_flow_and_repair - Run Ghidra's GUI 'Clear Flow and Repair' action on a seed range: clears instruction flow reachable from the seed, then repairs function bodies and re-disassembles retained flow (ClearFlowAndRepairCmd with clear_data=false, clear_labels=false, repair=true)clear_instruction_flow_override - Clear flow overridecreate_function - Create function at addressdelete_function - Delete function at addressdelete_function_tag - Delete a program-wide function tag definitiondisassemble_bytes - Disassemble byte rangedisassemble_function - Disassemble functionforce_decompile - Force fresh decompilation (output stamped with language_id + variant; optional variant= is checked against the loaded one)get_functions - Everything about one or many functions in a single calllist_class_members - List the member functions of a C++ classlist_function_tags - List all program-wide function tag definitions with their use countsremove_function_tag - Detach one or more tags from a functionrename_function - Rename function by namerename_variables - Batch rename variablesset_function_no_return - Set no-return attributeset_function_prototype - Set function prototype (return type, param types, calling convention)set_function_tag_comment - Update the comment/description on an existing program-wide function tagset_function_this_type - Set the decompiler/database type of the implicit 'this' pointer (ECX on x86 __thiscall/__fastcall)set_variable_storage - Set variable storageset_variable_type - Set the data type of a function variable (local OR parameter) by name at the decompiler (high-level) layerset_variables - Set types and names for multiple variables atomicallycan_rename_at_address - Check if address can be renamedcreate_label - Create labeldelete_label - Delete label at addressrename_symbol - Rename a symbol of any kindadd_memory_reference - Create a user-defined cross-reference between two memory addresses that the auto-analyzer can't infer (runtime-populated pointer tables, vtables, late-bound function pointers, missed jump/switch tables)analyze_call_graph - Analyze function call graph patternsget_assembly_context - Get assembly contextget_full_call_graph - Get full call graphget_function_call_graph - Get call graphget_xrefs_from - Get references from addressget_xrefs_to - Get references to addressremove_reference - Remove memory cross-reference(s) from one address to another — the inverse of add_memory_referenceadd_struct_field - Add struct fieldanalyze_global_completeness - Score a global variable's documentation completeness on a budgeted 0-100 scale — the data-address analog of analyze_function_completenessanalyze_struct_field_usage - Analyze struct field usageapply_data_classification - Apply data classificationapply_data_type - Apply data typeaudit_global - Audit a global variable's documentation stateaudit_globals_in_function - Audit every global variable referenced from within a function in one callclone_data_type - Clone data typecreate_data_type_category - Create data type categorycreate_derived_type - Create a type built on another: a typedef alias, an array or a pointercreate_enum - Create enumerationcreate_function_signature - Create function signature typecreate_struct - Create structurecreate_union - Create uniondelete_data_type - Delete data typefind_data_types - Find data types by name or path pattern, category and kind, one record per type (name, kind, category, size, path)get_enum_values - Get enumeration valuesget_struct_layout - Get structure layoutget_type_size - Get data type size and infoget_valid_data_types - Get valid data type namesimport_data_types - Import data types from GDTmodify_struct_field - Modify a field in a structure: retype it (new_type, which also embeds a struct by value, e.gmove_data_type_to_category - Move data type to categoryrecreate_struct - Replace a structure in one step: optionally remove an existing same-named type, then create with fields JSON (same shape as create_struct)remove_struct_field - Remove struct fieldrename_data_type - Rename a data type (struct, union, enum, typedef) in place, preserving existing applications of itresize_struct - Grow or shrink an existing structure by total byte sizeresolve_duplicate_type - Find duplicate data types by simple name; delete unused /Demangler size-1 stubs when a larger canonical type existsset_global - Atomically apply name + type + plate-comment + array length to a global variablesuggest_field_names - Suggest field namesvalidate_data_type - Validate data type syntaxvalidate_function_prototype - Validate function prototypebatch_set_comments - Set multiple commentsclear_function_comments - Clear all comments for a functionget_comment - Get listing comments (plate/pre/eol/post/repeatable) at ANY address, including data addresses (works on functions and data globals alike), for ONE address (address=) or MANY in one call (addresses=a,b,c)set_comment - Set a listing comment of a given kind (plate/pre/eol/post/repeatable) at ANY address, including data addressesanalyze_control_flow - Analyze control flowanalyze_data_region - Analyze data regionanalyze_dataflow - Trace value propagation through a function (PCode graph, forward/backward)analyze_for_documentation - Composite RE documentation analysis (decompile + classify + variables + completeness); decompiled_code is stamped with language_id + variant, and withheld if a supplied variant= contradicts the programanalyze_function_complete - Comprehensive single-call function analysisanalyze_function_completeness - Analyze documentation completenessapply_documentation - Apply documentation to ONE function (fields at the top level) OR MANY (entries=[{address, ...}, ...])configure_analyzer - Configure an analysis plugindetect_array_bounds - Detect array boundsfind_code_gaps - Find gaps of undefined bytes between functions in executable memoryfind_dead_code - Find dead codefind_next_undefined_function - Find next undefined functionfind_similar_functions - Find similar functionsget_field_access_context - Get field access contextget_function_pcode - Dump raw P-code for a function (issue #192)inspect_memory_content - Inspect memory byteslist_analyzers - List available analysis pluginsrun_analysis - Run auto-analysis on the current programsearch_byte_patterns - Search for byte patternssearch_instructions - Search for instructions by mnemonic and/or operand substringanalyze_api_call_chains - Analyze API call chainsdetect_crypto_constants - Detect crypto constantsdetect_malware_behaviors - Detect malware behaviorsextract_iocs_with_context - Extract IOCs with contextfind_anti_analysis_techniques - Find anti-analysis techniques内容由编辑整理,部分信息来自 GitHub 与项目官网,版权归原作者所有。发现错误?报告问题