pi-ghidra0.3.0
Autonomous headless Ghidra 12 reverse engineering for Pi, omp, and MCP clients: decompile, xrefs, call graphs, strings, memory, and patches without the GUI.
Built for omp
The author tagged this package oh-my-pi.
Reports 0
No reports yet.
Readme
pi-ghidra
Autonomous, headless Ghidra 12 for Pi, omp, and any MCP client.
Decompile, trace xrefs, map call graphs, search bytes, and patch a program database
without opening the GUI, running a bridge server, or preparing a project by hand.
What pi-ghidra is
pi-ghidra gives a coding agent a real reverse-engineering loop. The agent calls one
ghidra tool; the tool invokes Ghidra's official analyzeHeadless launcher, imports
the binary on first use, analyzes it, and answers from a cached project on later calls.
Related questions run in a single JVM through batch.
Nothing else is required: no PyGhidra daemon, no Ghidra plugin, no open project, no
bridge port. The package is a thin TypeScript scheduler around one Java GhidraScript
that is the entire Ghidra API adapter.
| pi-ghidra | GUI automation | Long-lived bridge | |
|---|---|---|---|
| Setup | Install package; Ghidra auto-detected | Launch and drive a GUI | Run and supervise a server |
| Isolation | One disposable JVM per call | Shared GUI state | Shared JVM and project state |
| Cancellation | Process-tree kill | Fragile UI abort | RPC-level only |
| Failure scope | One call | Whole session | Whole daemon |
| Cache | SHA-256 content-addressed projects | Manual | Custom |
| Hosts | Pi, omp, MCP clients | Usually one | Usually one |
The design trades warm-call latency for crash isolation and predictable cancellation. Batch related operations when latency matters.
[!NOTE] Edits (
rename,comment,patch) persist in the cached Ghidra project only. The source binary is copied into the cache and never modified.
Example session
❯ find the config parser, decompile it, and show what calls it
✓ ghidra batch 4.2s
functions query="config" → 6 matches
decompile address=140012a40 → C output + signature
call_graph address=140012a40 → 2 callers, 5 callees
❯ what does the caller at 140010f20 pass in?
✓ ghidra decompile 1.1s
...
The first call imports and analyzes the binary, so it takes as long as a normal headless Ghidra analysis. Later calls reuse the cached project.
Quick start
Pi
pi install npm:pi-ghidra
From Git, or from a checkout:
pi install git:github.com/immutex/pi-ghidra
pi install ./pi-ghidra
omp
omp plugin install pi-ghidra
Requires pi-ghidra 0.3.0 or newer on npm; that release adds the omp.extensions
manifest. From a checkout:
omp plugin link .
The package declares both pi.extensions and omp.extensions, so the same
extensions/index.ts loads under either harness. Skills ship in skills/ and are
discovered by both.
MCP (any client)
The mcp/server.ts entry point is a zero-dependency stdio MCP server over the same
runtime. Run it with Node 22.19+ (native TypeScript stripping):
node /path/to/pi-ghidra/mcp/server.ts
Register it with an MCP client, for example omp's .mcp.json:
{
"mcpServers": {
"pi-ghidra": {
"command": "node",
"args": ["C:/path/to/pi-ghidra/mcp/server.ts"]
}
}
}
Command Code:
cmd mcp add-json pi-ghidra '{"command":"node","args":["C:/path/to/pi-ghidra/mcp/server.ts"]}' -s user
The server exposes one ghidra tool with the same schema as the extension, plus an
optional cwd argument for relative binary paths. It implements initialize,
tools/list, tools/call, ping, and notifications/cancelled.
Requirements
| Component | Version |
|---|---|
| Ghidra | 12.x |
| JDK | 21 or newer |
| Node.js | 22.19+ (MCP server only; Pi and omp bundle their runtime) |
| Pi | 1.x, or omp 18.x, or any MCP client |
Setup
No environment variables or config file are normally needed. On first use,
pi-ghidra searches common install locations on Windows, macOS, and Linux, including
package-manager roots and user folders.
If Ghidra is not found:
/ghidra-setup
Select a detected installation, enter a directory, or pass one directly:
/ghidra-setup C:\ghidra_12.1.2
You can also tell the agent: "My Ghidra is at C:\ghidra_12.1.2." It validates the
path with action: "setup" and persists it. In print, JSON, or MCP mode, call the
setup action directly instead of using the interactive command.
Settings live in the active harness agent directory:
| Harness | Default path |
|---|---|
| Pi | ~/.pi/agent/pi-ghidra.json |
| omp | ~/.omp/agent/pi-ghidra.json |
| Either | PI_CODING_AGENT_DIR/pi-ghidra.json |
| Override | PI_GHIDRA_CONFIG (full file path) |
Ghidra resolution order:
- Per-call
ghidraHome PI_GHIDRA_HOMEGHIDRA_HOME- Saved setup
- Automatic discovery
JAVA_HOME wins when it is JDK 21+; otherwise the saved javaHome, then common
JDK locations on Windows, macOS, and Linux. If none is selected, Ghidra uses the
Java on PATH.
Use discover to list candidates and health to report the active Ghidra, JDK,
cache, config path, and detected homes. health returns error and a null
ghidraHome when Ghidra is missing instead of failing, which makes it a safe first
diagnostic.
Usage
Ask the agent to analyze a binary. The package exposes one ghidra tool and a skill
that teaches the efficient workflow.
Inventory in one JVM:
{
"action": "batch",
"binary": "./sample.exe",
"operations": [
{ "action": "info" },
{ "action": "functions", "query": "main", "limit": 20 },
{ "action": "strings", "query": "password", "limit": 50 },
{ "action": "imports", "limit": 100 }
]
}
Decompile an exact function:
{
"action": "decompile",
"binary": "./sample.exe",
"address": "140001000"
}
Trace what reaches a function, then inspect its callees:
{
"action": "batch",
"binary": "./sample.exe",
"operations": [
{ "action": "references", "address": "140001000", "direction": "to", "limit": 50 },
{ "action": "call_graph", "address": "140001000", "limit": 50 }
]
}
Actions
| Action | Purpose |
|---|---|
discover |
Find installed Ghidra 12 locations |
setup |
Validate and persist Ghidra, JDK, and cache locations |
health |
Report Ghidra, Java, cache, config, and detected homes |
analyze, info |
Import/analyze as needed and return program metadata |
rebuild |
Delete the cached project and analyze from a clean import |
functions, function |
List or inspect functions, parameters, locals, and signatures |
decompile |
Produce Ghidra decompiler C output |
disassemble, data |
Read instructions and defined data |
strings, symbols |
Search analyzer-defined strings and symbols |
imports, exports, entry_points |
Inspect linkage and entry points |
references, call_graph |
Inspect xrefs, callers, and callees |
memory_blocks, memory |
Inspect memory layout and read bytes |
search_bytes, search_text |
Search wildcard byte patterns or raw UTF-8 text |
analysis_options, set_analysis_options, reanalyze |
Inspect/configure analyzers and run fresh analysis passes |
rename, comment, patch |
Persist edits in the cached Ghidra project |
batch |
Run up to 50 operations in one Ghidra process |
Rules enforced before a JVM starts:
binaryis required for every operation action,batch, andrebuild.decompile,function,call_graph,memory,references,comment,rename, andpatchneed anaddressorname.search_bytesneedspattern;search_textneedsquery.patchneedsbytes;renameneedsvalue;set_analysis_optionsneedsoptions.- Unknown fields are rejected so typos fail loudly instead of being ignored.
List results accept offset and limit; the maximum page is 2,000 items. Memory reads
and patches are capped at 1 MiB. Byte and text searches scan at most 64 MiB and return
{ matches, scannedBytes, truncated }. Tool output is capped at 50 KB; the extension
writes the full JSON to a temporary file when truncated, and MCP clients get a
truncation notice.
Cache
Projects are content-addressed by the input binary's SHA-256 and serialized per artifact with a lock file, so concurrent calls cannot corrupt a project.
| Platform | Default cache |
|---|---|
| Windows | %LOCALAPPDATA%\pi-ghidra |
| macOS | ~/Library/Caches/pi-ghidra |
| Linux | ~/pi-ghidra-cache |
Override per call with cacheDir, persist one through setup, or set
PI_GHIDRA_CACHE. Ghidra rejects project paths containing dot-prefixed directory
components, so avoid paths such as ~/.cache/pi-ghidra. On Windows, paths containing
&, %, !, ^, |, <, >, ", ?, or * are also rejected.
Analysis imports are staged in project.next and renamed into place only after a
successful run, so a crash cannot leave a half-written cache. PI_GHIDRA_CPUS
controls -max-cpu (default 2, capped at 4).
Architecture
extensions/index.ts Pi/omp tool + /ghidra-setup command
mcp/server.ts stdio JSON-RPC 2.0 wrapper (no dependencies)
src/runtime.ts validation, discovery, cache, locks, process control
src/schema.ts one JSON Schema shared by every host
src/metadata.ts tool descriptions shared by every host
src/text.ts UTF-8-safe output truncation
ghidra_scripts/
PiGhidra.java the only Ghidra-side adapter
skills/ghidra/
SKILL.md workflow guidance for the agent
One call follows a fixed path:
tool call
-> validate request
-> resolve Ghidra 12 + JDK
-> hash binary, acquire per-artifact lock
-> import + analyze (staged) or reuse cached project
-> analyzeHeadless -process -postScript PiGhidra.java request.json response.json
-> validate response JSON
-> release lock, return result
Ghidra's stdout is treated as diagnostics, never as protocol. Requests and responses are files, so quoting and log noise cannot corrupt a result.
Development
npm install
npm run check # tsc --noEmit
npm test # unit + MCP protocol tests
npm run pack:check # inspect the published file set
Run the real-Ghidra suite:
GHIDRA_HOME=/path/to/ghidra \
GHIDRA_TEST_BINARY=/path/to/test-binary \
npm run test:integration
PowerShell:
$env:GHIDRA_HOME = "C:\ghidra_12.1.2"
$env:GHIDRA_TEST_BINARY = "C:\Windows\System32\where.exe"
npm run test:integration
The integration suite imports a real binary, batches inventory and decompilation, applies a rename/comment/patch, verifies the edit, and restores the original state.
Load the extension under either harness without installing it:
pi -e ./pi-ghidra
omp --no-extensions -e ./extensions/index.ts
Security
Ghidra parses untrusted binaries with native components. This package provides process isolation, bounded output, and a path allowlist for its own artifacts; it is not an OS sandbox. Analyze hostile files in a disposable VM or container.
The tool deliberately does not expose arbitrary Java/Python execution, arbitrary project paths, or a network transport. Patch and rename operations affect only the cached project. Report vulnerabilities privately through GitHub Security Advisories; see SECURITY.md.
FAQ
Does it modify the binary?
No. The binary is copied into the cache and hashed. rename, comment, and patch
change only the cached Ghidra project.
Can I use it without Pi?
Yes. omp loads the same extension, and mcp/server.ts exposes the same tool to any
MCP client.
Does it support Ghidra 11?
No. The adapter uses Ghidra 12 APIs and validates application.version against 12..
Why is the first call slow?
It starts a JVM, imports the binary, and runs the analyzers. Use batch to amortize
the JVM start, and reuse the SHA-256 cache for later calls.
Can two analyses run at once? Different binaries run in parallel. Calls for the same binary are serialized by a lock so the project cannot be corrupted.
Is there a JavaScript API?
src/runtime.ts exports runGhidra, discoverGhidraHomes, resolveGhidraHome,
validateGhidraRequest, and the action constants. The Pi/omp extension and MCP server
are thin adapters over it.
Where do I report a security issue? Through GitHub Security Advisories, never in a public issue.
Contributing
Read CONTRIBUTING.md for setup, the code layout, and the checks CI runs. Every trap that costs real time should end up as a test or a documented constraint.
License
MIT. Ghidra is a separate Apache-2.0 project and is not bundled.
Topics: ghidra · reverse-engineering · binary-analysis · decompiler ·
disassembler · headless · mcp · mcp-server · pi-package · pi · omp ·
oh-my-pi · ai-agent · malware-analysis · firmware