Install GhidraMCP 2.0 and connect it to Claude Desktop

Switch Ghidra to 12.1.4 (with openjdk-21-jdk), the version the
GhidraMCP 2.0 release is built for; Ghidra only loads extensions built
for its exact version.

From LaurieWired's GhidraMCP 2.0 release zip (SHA-256 checked):
- unpack the Ghidra extension into /opt/ghidra/Ghidra/Extensions; the
  build fails if it doesn't target GHIDRA_VERSION. GhidraMCPPlugin is a
  RELEASED Developer plugin, so the CodeBrowser config loads it, and it
  serves its HTTP backend on 127.0.0.1:8080.
- install the ghidra-mcp wheel into /opt/ghidra-mcp, with every
  dependency pinned to GhidraMCP's uv.lock at the release commit
  (ghidra/uvlock-to-constraints.py, honouring per-Python markers).

claude-desktop/claude_desktop_config.json adds a stdio "ghidra" server
running ghidra-mcp against http://127.0.0.1:8080/ and is copied into
~/.config/Claude.

Verified with a test project: ghidra-mcp started from that config
connected to Ghidra 12.1.4 in GUI mode and returned program info,
segments and imports for the open program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 13:12:59 +01:00
co-authored by Claude Opus 5.5
parent 51573544a4
commit ab6fc403e4
5 changed files with 93 additions and 18 deletions
+10 -11
View File
@@ -13,7 +13,7 @@ The goal is that `docker compose up` gives you a working setup where Claude Desk
## Status
Early stage. What exists: a single image (`Dockerfile`, `launch-apps.sh`) with Claude Desktop and Ghidra, started by `run.sh`; no compose setup yet. The layout below is the **intended** structure. Update this file when the real structure differs.
Early stage. What exists: a single image (`Dockerfile`, `launch-apps.sh`) with Claude Desktop, Ghidra and GhidraMCP, started by `run.sh`; Claude Desktop's MCP config is `claude-desktop/claude_desktop_config.json`; Ghidra config files are in `ghidra/`. No compose setup yet. The layout below is the **intended** structure. Update this file when the real structure differs.
## Intended layout
@@ -37,18 +37,17 @@ Early stage. What exists: a single image (`Dockerfile`, `launch-apps.sh`) with C
## Architecture
- **Claude Desktop and Ghidra share one container** (the root `Dockerfile`, started by `run.sh`), at the user's request. Ghidra is the official GitHub release (version, build date and SHA-256 are build args), runs on Ubuntu's `openjdk-25-jdk`, lives in `/opt/ghidra` and starts together with Claude Desktop. The **Developer** plugin package is enabled in both the project window and the CodeBrowser. A bare `<PACKAGE NAME="Developer" />` only adds the package's RELEASED plugins, so the STABLE ones are listed with `<INCLUDE CLASS=...>`. CodeBrowser: the build inserts `ghidra/codebrowser-developer.xml` into `defaultTools/CodeBrowser.tool` inside `Public_Release.jar`. Project window: it has no default to patch, so `ghidra/FrontEndTool.xml` is copied into the user settings, along with `ghidra/preferences` (`USER_AGREEMENT=ACCEPT`, because a pre-created settings dir otherwise makes Ghidra ask for its user agreement). To check a config really loaded, close Ghidra cleanly and see whether the plugins survive in the file it rewrites; Ghidra drops what it didn't load.
- **Claude Desktop and Ghidra share one container** (the root `Dockerfile`, started by `run.sh`), at the user's request. Ghidra is the official GitHub release (version, build date and SHA-256 are build args), is pinned to **12.1.4** because that's what the GhidraMCP release is built for (Ghidra only loads extensions built for its exact version), runs on Ubuntu's `openjdk-21-jdk`, lives in `/opt/ghidra` and starts together with Claude Desktop. The **Developer** plugin package is enabled in both the project window and the CodeBrowser. A bare `<PACKAGE NAME="Developer" />` only adds the package's RELEASED plugins, so the STABLE ones are listed with `<INCLUDE CLASS=...>`. CodeBrowser: the build inserts `ghidra/codebrowser-developer.xml` into `defaultTools/CodeBrowser.tool` inside `Public_Release.jar`. Project window: it has no default to patch, so `ghidra/FrontEndTool.xml` is copied into the user settings, along with `ghidra/preferences` (`USER_AGREEMENT=ACCEPT`, because a pre-created settings dir otherwise makes Ghidra ask for its user agreement). To check a config really loaded, close Ghidra cleanly and see whether the plugins survive in the file it rewrites; Ghidra drops what it didn't load.
- **Shared workspace volume.** Every container mounts the workspace at `/workspace`, so a file path means the same thing to Claude, Ghidra and the RE MCP. `run.sh` asks for the host folder on each start in the desktop's folder picker (`zenity`, or `kdialog` on KDE; terminal prompt if neither exists), opening at the last folder used, else `./workspace`. Skip it with `--workspace DIR` or `WORKSPACE=DIR`. It refuses `/` and `$HOME`. The image symlinks `~/workspace` to `/workspace` for Claude Desktop's file picker.
- **GUI forwarding.** Claude Desktop and Ghidra are GUI apps. Default to X11 forwarding (`/tmp/.X11-unix` + `DISPLAY`). Support Wayland through XWayland. Keep a noVNC/VNC fallback in mind for headless hosts. Never solve display access with a blanket `xhost +`. Scope it (`xhost +local:docker` or `+SI:localuser:...`).
- **GhidraMCP wiring.** GhidraMCP has two parts:
- a Ghidra plugin that runs an HTTP server inside Ghidra (default port `8080`), and
- a Python MCP bridge (`bridge_mcp_ghidra.py`) that speaks MCP to Claude and forwards calls to that HTTP server.
- **GhidraMCP wiring.** LaurieWired's GhidraMCP (https://github.com/LaurieWired/GhidraMCP), release 2.0, installed from the official release zip (SHA-256 checked). It has two parts:
- a Ghidra extension, unpacked into `/opt/ghidra/Ghidra/Extensions/GhidraMCP`. Its `GhidraMCPPlugin` is a RELEASED plugin in the Developer package, so the CodeBrowser config loads it. While a CodeBrowser is open it serves an HTTP backend on `127.0.0.1:8080` (port: *Edit → Tool Options → GhidraMCP HTTP Server*).
- the `ghidra-mcp` Python MCP server (2.0 replaced `bridge_mcp_ghidra.py`), installed from the release wheel into the venv `/opt/ghidra-mcp`. Its dependencies are pinned to GhidraMCP's `uv.lock` at the release commit via `ghidra/uvlock-to-constraints.py`. CLI: `ghidra-mcp --ghidra-server http://127.0.0.1:8080/` (stdio by default; also `--transport streamable-http`, `--headless`).
The bridge is configured with the Ghidra server URL (e.g. `http://ghidra:8080/`). The plugin must be enabled in the CodeBrowser and a program must be open, or the bridge has nothing to talk to.
- **Container lifetime follows the apps.** The entrypoint `launch-apps.sh` starts Claude Desktop and Ghidra and stops both, ending the container, when either is closed. Ghidra counts as closed when its process exits (closing the project window quits it; CodeBrowser or Help windows don't). Claude Desktop's window only hides the app on close (GNOME shows no tray icon), so its X windows are watched: none open or minimized for ~3 s counts as closed. `run.sh` uses `--init` so signals and zombie processes are handled.
- **MCP transport between containers.** Claude Desktop starts local MCP servers as stdio subprocesses. Because the servers live in other containers, pick one approach and apply it consistently:
- *Preferred:* run the MCP servers with an HTTP/SSE transport in their own containers. Bridge them into Claude Desktop's stdio config with a small proxy (e.g. `mcp-remote` or `mcp-proxy`) installed in the Claude Desktop image.
- *Alternative:* install the bridges inside the Claude Desktop image and point them at the other containers over the network. This works well for the GhidraMCP bridge, which is just Python + HTTP.
A program must be open in the CodeBrowser for the tools to return data. Claude Desktop only starts MCP servers after sign-in.
- **MCP transport.** Claude Desktop starts local MCP servers as stdio subprocesses. GhidraMCP runs in the same container, so `ghidra-mcp` is a plain stdio entry in `claude_desktop_config.json` that reaches Ghidra on `127.0.0.1`. For future servers in other containers, pick one approach and apply it consistently:
- *Preferred:* run them with an HTTP/SSE transport in their own containers and bridge them into Claude Desktop's stdio config with a small proxy (e.g. `mcp-remote` or `mcp-proxy`) installed in the Claude Desktop image.
- *Alternative:* install the client side inside the Claude Desktop image and point it at the other container over the network.
- Avoid mounting the Docker socket into the Claude Desktop container just to `docker exec` MCP servers.
## Conventions
@@ -78,7 +77,7 @@ docker compose down
- **Claude Desktop on Linux.** Check whether an official Linux build is available at the time of work. If not, use a maintained community repackaging and document which one and why. Confirm the config path, usually `~/.config/Claude/claude_desktop_config.json`.
- **Reverse Engineer Anything MCP.** Confirm the upstream repository, install method, runtime (Python/Node), required external tools and supported transports before writing its Dockerfile. Record the source URL in the README.
- **GhidraMCP.** Confirm the current release, its supported Ghidra version, the bridge's CLI flags and its default ports from the upstream README.
- **GhidraMCP.** Before bumping GhidraMCP or Ghidra, confirm the release's supported Ghidra version (`version=` in the extension's `extension.properties`; the build checks it), the `ghidra-mcp` CLI flags and its default ports from the upstream README. The Developer plugin lists in `ghidra/` were checked against 12.1.4 and 12.2; re-check them for other versions.
## When making changes