Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Build with Maven

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest
env:
GHIDRA_VERSION: 11.3.2
GHIDRA_DATE: 20250415
GHIDRA_LIBS: >-
Features/Base/lib/Base.jar
Features/Decompiler/lib/Decompiler.jar
Framework/Docking/lib/Docking.jar
Framework/Generic/lib/Generic.jar
Framework/Project/lib/Project.jar
Framework/SoftwareModeling/lib/SoftwareModeling.jar
Framework/Utility/lib/Utility.jar
Framework/Gui/lib/Gui.jar

steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven

- name: Download Ghidra
run: |
wget --no-verbose -O ghidra.zip https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${{ env.GHIDRA_VERSION }}_build/ghidra_${{ env.GHIDRA_VERSION }}_PUBLIC_${{ env.GHIDRA_DATE }}.zip
7z x -bd ghidra.zip

- name: Copy Ghidra libs
run: |
mkdir -p ./lib
for libfile in ${{ env.GHIDRA_LIBS }}
do echo "Copying ${libfile} to lib/"
cp ghidra_${{ env.GHIDRA_VERSION }}_PUBLIC/Ghidra/${libfile} ./lib/
done

- name: Build with Maven
run: mvn clean package assembly:single

- name: Assemble release directory
run: |
mkdir release
cp target/GhidraMCP-*-SNAPSHOT.zip release/
cp bridge_mcp_ghidra.py release/

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: GhidraMCP-artifact
path: |
release/*
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ mvnw.cmd
hs_err_pid*
replay_pid*

# Third party JAR files from Ghidra
lib/*.jar

13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ https://github.com/user-attachments/assets/75f0c176-6da1-48dc-ad96-c182eb4648c3

## MCP Clients

Theoretically, any MCP client should work with ghidraMCP. Two examples are given below.
Theoretically, any MCP client should work with ghidraMCP. Three examples are given below.

## Example 1: Claude Desktop
To set up Claude Desktop as a Ghidra MCP client, go to `Claude` -> `Settings` -> `Developer` -> `Edit Config` -> `claude_desktop_config.json` and add the following:
Expand Down Expand Up @@ -99,7 +99,16 @@ Another MCP client that supports multiple models on the backend is [5ire](https:
3. Command: `python /ABSOLUTE_PATH_TO/bridge_mcp_ghidra.py`

# Building from Source
Build with Maven by running:
1. Copy the following files from your Ghidra directory to this project's `lib/` directory:
- `Ghidra/Features/Base/lib/Base.jar`
- `Ghidra/Features/Decompiler/lib/Decompiler.jar`
- `Ghidra/Framework/Docking/lib/Docking.jar`
- `Ghidra/Framework/Generic/lib/Generic.jar`
- `Ghidra/Framework/Project/lib/Project.jar`
- `Ghidra/Framework/SoftwareModeling/lib/SoftwareModeling.jar`
- `Ghidra/Framework/Utility/lib/Utility.jar`
- `Ghidra/Framework/Gui/lib/Gui.jar`
2. Build with Maven by running:

`mvn clean package assembly:single`

Expand Down
73 changes: 70 additions & 3 deletions bridge_mcp_ghidra.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import requests
import argparse
import logging
from urllib.parse import urljoin

from mcp.server.fastmcp import FastMCP

Expand All @@ -29,7 +30,7 @@ def safe_get(endpoint: str, params: dict = None) -> list:
if params is None:
params = {}

url = f"{ghidra_server_url}/{endpoint}"
url = urljoin(ghidra_server_url, endpoint)

try:
response = requests.get(url, params=params, timeout=5)
Expand All @@ -43,10 +44,11 @@ def safe_get(endpoint: str, params: dict = None) -> list:

def safe_post(endpoint: str, data: dict | str) -> str:
try:
url = urljoin(ghidra_server_url, endpoint)
if isinstance(data, dict):
response = requests.post(f"{ghidra_server_url}/{endpoint}", data=data, timeout=5)
response = requests.post(url, data=data, timeout=5)
else:
response = requests.post(f"{ghidra_server_url}/{endpoint}", data=data.encode("utf-8"), timeout=5)
response = requests.post(url, data=data.encode("utf-8"), timeout=5)
response.encoding = 'utf-8'
if response.ok:
return response.text.strip()
Expand Down Expand Up @@ -222,6 +224,69 @@ def set_local_variable_type(function_address: str, variable_name: str, new_type:
"""
return safe_post("set_local_variable_type", {"function_address": function_address, "variable_name": variable_name, "new_type": new_type})

@mcp.tool()
def get_xrefs_to(address: str, offset: int = 0, limit: int = 100) -> list:
"""
Get all references to the specified address (xref to).

Args:
address: Target address in hex format (e.g. "0x1400010a0")
offset: Pagination offset (default: 0)
limit: Maximum number of references to return (default: 100)

Returns:
List of references to the specified address
"""
return safe_get("xrefs_to", {"address": address, "offset": offset, "limit": limit})

@mcp.tool()
def get_xrefs_from(address: str, offset: int = 0, limit: int = 100) -> list:
"""
Get all references from the specified address (xref from).

Args:
address: Source address in hex format (e.g. "0x1400010a0")
offset: Pagination offset (default: 0)
limit: Maximum number of references to return (default: 100)

Returns:
List of references from the specified address
"""
return safe_get("xrefs_from", {"address": address, "offset": offset, "limit": limit})

@mcp.tool()
def get_function_xrefs(name: str, offset: int = 0, limit: int = 100) -> list:
"""
Get all references to the specified function by name.

Args:
name: Function name to search for
offset: Pagination offset (default: 0)
limit: Maximum number of references to return (default: 100)

Returns:
List of references to the specified function
"""
return safe_get("function_xrefs", {"name": name, "offset": offset, "limit": limit})

@mcp.tool()
def list_strings(offset: int = 0, limit: int = 2000, filter: str = None) -> list:
"""
List all defined strings in the program with their addresses.

Args:
offset: Pagination offset (default: 0)
limit: Maximum number of strings to return (default: 2000)
filter: Optional filter to match within string content

Returns:
List of strings with their addresses
"""
params = {"offset": offset, "limit": limit}
if filter:
params["filter"] = filter
return safe_get("strings", params)

def main():
parser = argparse.ArgumentParser(description="MCP server for Ghidra")
parser.add_argument("--ghidra-server", type=str, default=DEFAULT_GHIDRA_SERVER,
Expand All @@ -234,6 +299,8 @@ def main():
help="Transport protocol for MCP, default: stdio")
args = parser.parse_args()

# Use the global variable to ensure it's properly updated
global ghidra_server_url
if args.ghidra_server:
ghidra_server_url = args.ghidra_server

Expand Down
2 changes: 2 additions & 0 deletions lib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
16 changes: 8 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,56 +15,56 @@
<dependency>
<groupId>ghidra</groupId>
<artifactId>Generic</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Generic.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>SoftwareModeling</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/SoftwareModeling.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>Project</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Project.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>Docking</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Docking.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>Decompiler</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Decompiler.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>Utility</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Utility.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>Base</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Base.jar</systemPath>
</dependency>
<dependency>
<groupId>ghidra</groupId>
<artifactId>Gui</artifactId>
<version>11.3.1</version>
<version>11.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/Gui.jar</systemPath>
</dependency>
Expand Down
Loading
Loading