Skip to main content
Glama
BattlePriestBarbara

Ghidra MCP server for Cline

Ghidra MCP server for Cline

Exposes Ghidra (headless, via PyGhidra) to Cline as MCP tools, so the assistant can import binaries, trigger auto-analysis, browse functions/symbols/strings/imports, and read decompiled C code and disassembly inside a normal chat.

Verified on this machine against:

Component

Version / path

Ghidra

12.1.3 PUBLIC — D:\ghidra_12.1.3_PUBLIC (requires JDK >= 21)

JDK used

Temurin 25.0.4.1 — D:\nd (the machine's JAVA_HOME/PATH java is JDK 8/16, i.e. too old)

Python

3.12.10 (C:\Users\...\AppData\Local\Programs\Python\Python312)

pyghidra

3.1.0 (+ jpype1 1.5.2, shipped inside the Ghidra install)

MCP SDK

2.2.0 (mcp.server.mcpserver.MCPServer)

Why this design

  • MCP is the integration layer. Cline cannot drive Ghidra's GUI or Java API directly; the MCP protocol turns Ghidra operations into typed tools the model can call.

  • PyGhidra is the primary engine. One JVM is started lazily inside the MCP server and kept warm; the analysed program stays open, so repeated questions (decompile this, list those xrefs) cost milliseconds instead of a JVM restart.

  • analyzeHeadless is the fallback/batch engine. ghidra_headless_import shells out to Ghidra's own CLI (separate process, good for batch or long jobs). Both engines use the same project store, so a program imported by headless can be queried through PyGhidra and vice versa (covered by tests/smoke_headless.py).

Related MCP server: Binary MCP Server

Layout

D:\ghidra-mcp\
├── server.py                  # MCP entry point (stdio). Flags: --check, --tools
├── ghidra_mcp\
│   ├── config.py              # locates Ghidra + a JDK >= 21, project store, timeouts
│   ├── session.py             # JVM/project/program lifecycle, caching, locking
│   ├── queries.py             # program introspection (Ghidra Java API wrappers)
│   ├── headless.py            # analyzeHeadless subprocess wrapper
│   └── tools.py               # 23 MCP tools (JSON in/out, never raise)
├── tests\
│   ├── probe_pyghidra.py      # JVM + import + analyse + decompile smoke test
│   ├── probe_api.py           # checks every Ghidra Java API the server relies on
│   ├── smoke_tools.py         # calls all 23 tools directly (fast)
│   ├── smoke_mcp_client.py    # real MCP stdio handshake + tool calls (like Cline)
│   └── smoke_headless.py      # analyzeHeadless -> PyGhidra interop
├── projects\                  # Ghidra project store (created at runtime; git-ignored)
├── requirements.txt
├── LICENSE                    # MIT
├── .gitignore                 # excludes .venv\, projects\, __pycache__\
└── cline_mcp_config.json      # snippet to paste into Cline's MCP settings

Setup (already done on this machine)

# 1. venv on an ASCII path (see the non-ASCII caveat below!)
& "$env:LOCALAPPDATA\Programs\Python\Python312\python.exe" -m venv D:\ghidra-mcp\.venv

# 2. pyghidra + jpype from the wheels bundled with Ghidra (works offline)
& D:\ghidra-mcp\.venv\Scripts\python.exe -m pip install --no-index `
    --find-links "D:\ghidra_12.1.3_PUBLIC\Ghidra\Features\PyGhidra\pypkg\dist" pyghidra

# 3. MCP SDK
& D:\ghidra-mcp\.venv\Scripts\python.exe -m pip install mcp

Sanity check before wiring anything up:

& D:\ghidra-mcp\.venv\Scripts\python.exe D:\ghidra-mcp\server.py --check   # environment JSON
& D:\ghidra-mcp\.venv\Scripts\python.exe D:\ghidra-mcp\server.py --tools   # tool list

Cline configuration

Cline keeps MCP servers in %APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json. Merge the ghidra entry from cline_mcp_config.json into mcpServers (a backup of the previous file is written next to it as cline_mcp_settings.json.bak):

{
  "mcpServers": {
    "ghidra": {
      "command": "D:\\ghidra-mcp\\.venv\\Scripts\\python.exe",
      "args": ["D:\\ghidra-mcp\\server.py"],
      "env": {
        "GHIDRA_MCP_INSTALL_DIR": "D:\\ghidra_12.1.3_PUBLIC",
        "GHIDRA_MCP_JAVA_HOME": "D:\\nd",
        "GHIDRA_MCP_PROJECTS": "D:\\ghidra-mcp\\projects"
      },
      "disabled": false,
      "autoApprove": ["ghidra_environment", "ghidra_list_functions", "..."],
      "timeout": 900
    }
  }
}
  • timeout: 900 matters — auto-analysis of a large binary takes minutes.

  • Read-only tools are auto-approved in the provided snippet; the mutating ones (ghidra_import_and_analyze, ghidra_open_program, ghidra_close_program, ghidra_headless_import) still ask for approval.

  • Use "disabled": true to unload it without deleting the entry.

  • Restart the MCP server from Cline's MCP panel after editing the file.

Tools

Tool

What it does

ghidra_environment

Ghidra/JDK/Python report, project store, JVM state (run this first)

ghidra_session_status

Is the JVM up, which project/program is open

ghidra_import_and_analyze

Import + auto-analyse + open a binary (result cached in the project)

ghidra_open_program

Reopen an analysed program (fast path)

ghidra_current_program

Hashes, language/compiler, image base, counts

ghidra_close_program

Save + release the program/project (JVM stays warm)

ghidra_list_projects / ghidra_list_programs

Browse the project store

ghidra_list_functions

Function list, paged, substring filter

ghidra_function_details

Signature, params, locals, callers/callees, xrefs (+optional decompile)

ghidra_decompile_function

C-like pseudocode for one function

ghidra_search_symbols

Symbol search (functions, data, imports)

ghidra_call_graph

Callers/callees of a function

ghidra_disassemble

Instructions from an address or function entry

ghidra_xrefs

Incoming (to) or outgoing (from) cross references

ghidra_list_strings

Defined strings (min length + substring filter)

ghidra_list_imports

Imported symbols and libraries

ghidra_list_entry_points

Entry points/exports

ghidra_read_bytes

Raw memory dump (hex + ASCII)

ghidra_search_bytes

Byte-pattern search with ?? wildcards

ghidra_list_data_types

Structs/enums/typedefs known to the program

ghidra_list_memory_blocks

Segments with ranges and permissions

ghidra_headless_import

Batch import via analyzeHeadless (separate process)

Arguments that take name_or_address accept a function name (FUN_140001008), namespace::name (KERNEL32.DLL::CreateFileW) or an address (0x140001008); a unique substring also matches. Addresses are always reported as 0x-prefixed hex.

Example workflow in chat

  1. ghidra_environment -> confirms Ghidra 12.1.3 + JDK 25 + project store.

  2. ghidra_import_and_analyze(binary_path="C:\\Windows\\System32\\notepad.exe") -> 853 functions, analysis cached in D:\ghidra-mcp\projects\default.

  3. ghidra_search_symbols(query="CreateFileW") -> address of the import thunk.

  4. ghidra_xrefs(name_or_address="0x1400019c0", direction="to") -> who calls it.

  5. ghidra_decompile_function(name_or_address="entry") -> pseudocode to explain.

  6. ghidra_open_program(project_name="default", program_name="notepad.exe") in a later session -> instant, because the analysis is already on disk.

Environment variables

Variable

Default

Purpose

GHIDRA_MCP_INSTALL_DIR (or GHIDRA_INSTALL_DIR)

auto-detected (ghidra_* on C:/D:/E:, Ghidra's lastrun file)

Ghidra installation

GHIDRA_MCP_JAVA_HOME (or JAVA_HOME)

newest JDK >= 21 found (D:\nd, Program Files JDKs, java on PATH)

JDK used by PyGhidra

GHIDRA_MCP_PROJECTS

<server dir>\projects

Ghidra project store

GHIDRA_MCP_TIMEOUT

900

Default per-task timeout (seconds)

GHIDRA_MCP_JAVA_STDOUT_TO_STDERR

1

Route Java's System.out to stderr (keeps MCP's JSON-RPC stream clean)

Design notes and gotchas

  • The server must live on an ASCII path. JPype refuses to apply Ghidra's system class loader when any classpath entry contains non-ASCII characters (ValueError: system classloader cannot be specified with non ascii characters in the classpath), and the venv/interpreter path is always on that classpath. That is why the server sits in D:\ghidra-mcp and not under D:\调用. config.non_ascii_classpath_problems() re-checks this and ghidra_environment reports it.

  • JDK >= 21 is mandatory (Ghidra 12.1.3 application.java.min=21). This machine's JAVA_HOME points at JDK 8 and java on PATH is JDK 16, so the server resolves a suitable JDK itself (D:\nd, Temurin 25) and exports it via JAVA_HOME before starting the JVM.

  • stdout discipline. MCP speaks JSON-RPC over stdout and Ghidra/Java logs would corrupt it. The MCP SDK diverts fd 1 to stderr while serving, and the server additionally redirects Java's System.out to stderr. tests/smoke_mcp_client.py proves the stream stays clean.

  • Analysis is cached. ghidra_import_and_analyze re-imports/re-analyses only when the program is missing or force_reimport=True; GhidraProgramUtilities.shouldAskToAnalyze decides whether analysis is still required.

  • One program open at a time. Ghidra program objects are not thread-safe, so every tool call runs under a single re-entrant lock; opening another program releases the previous one.

  • Bounded output. Large listings are paged/limited (limit, offset, max_chars, max_scan_mb) so a chat turn does not drown in data.

  • API details verified, not guessed. tests/probe_api.py records 66 Ghidra API checks; e.g. Function.getPrototypeString(bool, bool) exists while the no-arg overload and getSignature(bool, bool) do not in Ghidra 12.1.3, and DomainFile.getContentType() returns a String ("Program") rather than a Class.

Troubleshooting

Symptom

Fix

pyghidra is not installed in this interpreter

Install it into this venv (see Setup); do not point Cline at the system Python

system classloader cannot be specified with non ascii characters

Move the server + venv to an ASCII path

Ghidra version ... is not supported or JVM start failure

Provide a JDK >= 21 via GHIDRA_MCP_JAVA_HOME

Ghidra installation not found

Set GHIDRA_MCP_INSTALL_DIR to the folder containing support\analyzeHeadless.bat

Tool times out on a big binary

Raise the MCP timeout, pass a larger timeout argument, or pre-analyse with ghidra_headless_import

no program is open

Call ghidra_import_and_analyze or ghidra_open_program first

project 'x' does not exist

ghidra_list_projects to see what is stored, or import with that project name

Decompiler returns an error string

Retry with a larger timeout; some functions legitimately fail (decompile_completed: false)

Tests (all green on this machine)

$py = "D:\ghidra-mcp\.venv\Scripts\python.exe"
& $py D:\ghidra-mcp\tests\probe_api.py            # 66 Ghidra API checks            (~35 s)
& $py D:\ghidra-mcp\tests\smoke_tools.py          # 23 tools + 9 assertions         (~15 s)
& $py D:\ghidra-mcp\tests\smoke_mcp_client.py     # real MCP handshake + calls      (~15 s)
& $py D:\ghidra-mcp\tests\smoke_headless.py       # analyzeHeadless <-> PyGhidra    (~40 s)
& $py D:\ghidra-mcp\tests\probe_pyghidra.py <bin> # fresh import + analyse + decompile (minutes)

The two probe/headless_smoke projects the tests use are created on demand in D:\ghidra-mcp\projects; analysing notepad.exe there takes ~3 minutes the first time.

Extending

Add a function to ghidra_mcp/queries.py (plain Python, returns JSON-serialisable data), wrap it in ghidra_mcp/tools.py (take SESSION.lock, call SESSION.require_program(), return _ok(...) / _error(...)), then list it in TOOL_FUNCTIONS in server.py. Keep the docstring accurate - Cline shows it to the model as the tool description.

Recovering the original bytes (and the stored hashes)

Ghidra keeps the original imported file bytes in the program database, so the hashes in calc_features.json are reproducible even if the source file is lost. tests/probe_original_export.py demonstrates it:

& D:\ghidra-mcp\.venv\Scripts\python.exe D:\ghidra-mcp\tests\probe_original_export.py weka /uninstall.exe
# stored MD5 in program DB : 59d3469678498a4ece87c7789cd0c74f
# stored sha256 in DB      : 24b839a1e96a590b23f650dc26a35c052d28c2a641c6eb6ee376d8c9af48d024
# OriginalFileExporter.export -> True : %TEMP%\uninstall_from_ghidra.exe
# exported size   : 56722
# exported md5    : 59d3469678498a4ece87c7789cd0c74f
# exported sha256 : 24b839a1e96a590b23f650dc26a35c052d28c2a641c6eb6ee376d8c9af48d024

The exported copy is byte-identical to D:\weka\Weka-3-6\uninstall.exe (same size, MD5 and SHA-256). A hash value itself is a one-way function and cannot be inverted, but a Ghidra program is sufficient to rebuild the file it was imported from (ghidra.app.util.exporter.OriginalFileExporter, or "Export Original File" in the GUI).

Gotcha: do not name your own Python files ghidra*.py. PyGhidra's import hook claims modules whose name starts with ghidra, and a script called ghidra_export_probe.py fails with RecursionError: maximum recursion depth exceeded inside find_spec. The probe above is named probe_original_export.py for that reason.

Feature-extraction CLI

extract_features.py drives the same tool functions as the MCP server without a chat, which is handy for batch/repeatable runs or when the MCP server has not been reloaded yet:

$py = "D:\ghidra-mcp\.venv\Scripts\python.exe"
& $py D:\ghidra-mcp\extract_features.py <binary> [-o out.json] [--project NAME] [--top 5]
                                     [--timeout 900] [--decompile-chars 8000] [--force] [--no-analyze]

It imports + analyses the binary, then writes a JSON document containing the binary hashes and format, the analysis result, features (function count, imported API count, string count, symbols, entry points, memory), the N "core" functions with decompiled pseudo-code, imported_apis, imported_libraries, a string sample, entry points and memory blocks.

"Core" ranking: non-import, non-thunk functions with a body, sorted by caller count (in-degree from ghidra_call_graph) with ties broken by body size; caller counts are computed for the 30 largest functions, and the program entry point is always included. The exact rule is echoed in the JSON as ranking_method.

Example (Weka uninstaller, 2026-09-22):

[extract] ghidra_import_and_analyze: ok        # 31.5 s, imported=True, analyzed=True
[extract] ghidra_list_functions: ok            # 240 functions
[extract] ghidra_list_imports: ok              # 155 APIs from 8 DLLs
[extract] ghidra_list_strings: ok              # 212 strings
[extract] wrote D:\weka\Weka-3-6\calc_features.json

Tool-name mapping

The MCP tools are named after the queries they run; older/other Ghidra MCP servers use different names:

Name used elsewhere

Tool in this server

ghidra_get_functions

ghidra_list_functions

ghidra_get_symbols

ghidra_search_symbols

ghidra_get_imports

ghidra_list_imports

ghidra_get_strings

ghidra_list_strings

ghidra_decompile

ghidra_decompile_function

License

MIT — see LICENSE. Copyright (c) 2026 Xinhang Yu.

Not committed to git

.gitignore keeps machine-local and regenerable state out of the repository: .venv/ (the virtual environment), projects/ (Ghidra's .gpr/.rep project store, created at runtime by ghidra_mcp/config.py:projects_dir()), __pycache__/, and calc_features.json (extract_features.py's default output). Recreate them with the setup steps above; the .venv must be rebuilt per machine because it contains absolute paths and is wired to the local Ghidra and JDK installs.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to autonomously reverse engineer applications by exposing Ghidra's core functionality through MCP tools. Supports decompiling binaries, analyzing code structure, and automatically renaming methods and data.
    2
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes Ghidra reverse engineering capabilities via MCP, enabling LLMs and agents to analyze binaries, decompile, search, and edit programs headlessly or with GUI integration.
    426
    Apache 2.0
  • A
    license
    B
    quality
    B
    maintenance
    Enables AI agents and automation to perform Ghidra reverse-engineering tasks through MCP, including decompilation, live debugging, P-code emulation, data-flow analysis, and headless script execution, with specialized AArch64-aware emulation.
    8
    Apache 2.0