From 33be44d7efbfd68d8cca882dda11c1ea75a7e853 Mon Sep 17 00:00:00 2001 From: Teal Bauer Date: Sat, 29 Mar 2025 22:53:59 +0100 Subject: [PATCH] Make multi-headed and more RESTful --- README.md | 91 ++++++--- bridge_mcp_ghidra.py => bridge_mcp_hydra.py | 56 ++++-- pom.xml | 12 +- requirements.txt | 2 - src/assembly/ghidra-extension.xml | 10 +- .../starsong/ghidra/GhydraMCPPlugin.java} | 180 +++++++++++------- src/main/resources/META-INF/MANIFEST.MF | 10 +- src/main/resources/Module.manifest | 4 +- src/main/resources/extension.properties | 10 +- .../starsong/ghidra}/AppTest.java | 2 +- 10 files changed, 240 insertions(+), 137 deletions(-) rename bridge_mcp_ghidra.py => bridge_mcp_hydra.py (73%) delete mode 100644 requirements.txt rename src/main/java/{com/lauriewired/HydraMCPPlugin.java => eu/starsong/ghidra/GhydraMCPPlugin.java} (74%) rename src/test/java/{com/lauriewired => eu/starsong/ghidra}/AppTest.java (95%) diff --git a/README.md b/README.md index 27a729f..6707a09 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,28 @@ ![ghidra_MCP_logo](https://github.com/user-attachments/assets/4986d702-be3f-4697-acce-aea55cd79ad3) - -# ghidraMCP -ghidraMCP is an Model Context Protocol server for allowing LLMs to autonomously reverse engineer applications. It exposes numerous tools from core Ghidra functionality to MCP clients. +# GhydraMCP +GhydraMCP is an Model Context Protocol server for allowing LLMs to autonomously reverse engineer applications. It exposes numerous tools from core Ghidra functionality to MCP clients. https://github.com/user-attachments/assets/36080514-f227-44bd-af84-78e29ee1d7f9 +GhydraMCP is based on [GhidraMCP by Laurie Wired](https://github.com/LaurieWired/GhidraMCP/). # Features MCP Server + Ghidra Plugin -- Decompile and analyze binaries in Ghidra -- Automatically rename methods and data -- List methods, classes, imports, and exports +- Full program analysis capabilities: + - Decompile functions to C code + - Cross-reference analysis + - Data type propagation +- Interactive reverse engineering: + - Rename functions, variables, and data + - Add comments and labels + - Modify data types +- Program exploration: + - List functions, classes, namespaces + - View imports, exports, segments + - Search by name or pattern # Installation @@ -29,14 +38,14 @@ MCP Server + Ghidra Plugin - MCP [SDK](https://github.com/modelcontextprotocol/python-sdk) ## Ghidra -First, download the latest [release](https://github.com/LaurieWired/GhidraMCP/releases) from this repository. This contains the Ghidra plugin and Python MCP client. Then, you can directly import the plugin into Ghidra. +First, download the latest [release](https://github.com/teal-bauer/GhydraMCP/releases) from this repository. This contains the Ghidra plugin and Python MCP client. Then, you can directly import the plugin into Ghidra. 1. Run Ghidra 2. Select `File` -> `Install Extensions` 3. Click the `+` button -4. Select the `GhidraMCP-1-0.zip` (or your chosen version) from the downloaded release +4. Select the `GhydraMCP-1-1.zip` (or your chosen version) from the downloaded release 5. Restart Ghidra -6. Make sure the GhidraMCPPlugin is enabled in `File` -> `Configure` -> `Developer` +6. Make sure the GhydraMCPPlugin is enabled in `File` -> `Configure` -> `Developer` Video Installation Guide: @@ -47,35 +56,63 @@ 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 GhydraMCP. Two 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: +## API Reference +### Available Tools + +**Program Analysis**: +- `list_methods`: List all functions (params: offset, limit) +- `list_classes`: List all classes/namespaces (params: offset, limit) +- `decompile_function`: Get decompiled C code (params: name) +- `rename_function`: Rename a function (params: old_name, new_name) +- `rename_data`: Rename data at address (params: address, new_name) +- `list_segments`: View memory segments (params: offset, limit) +- `list_imports`: List imported symbols (params: offset, limit) +- `list_exports`: List exported functions (params: offset, limit) +- `list_namespaces`: Show namespaces (params: offset, limit) +- `list_data_items`: View data labels (params: offset, limit) +- `search_functions_by_name`: Find functions (params: query, offset, limit) + +**Instance Management**: +- `list_instances`: List active Ghidra instances (no params) +- `register_instance`: Register new instance (params: port, url) +- `unregister_instance`: Remove instance (params: port) + +**Example Usage**: +```python +# Program analysis +client.use_tool("ghydra", "decompile_function", {"name": "main"}) + +# Instance management +client.use_tool("ghydra", "register_instance", {"port": 8192, "url": "http://localhost:8192/"}) +client.use_tool("ghydra", "register_instance", {"port": 8193}) +``` + +## Client Setup + +### Claude Desktop Configuration ```json { "mcpServers": { - "ghidra": { + "ghydra": { "command": "python", "args": [ - "/ABSOLUTE_PATH_TO/bridge_mcp_ghidra.py" - ] + "/ABSOLUTE_PATH_TO/bridge_mcp_hydra.py" + ], + "env": { + "GHIDRA_HYDRA_HOST": "localhost" // Optional - defaults to localhost + } } } } ``` -Alternatively, edit this file directly: -``` -/Users/YOUR_USER/Library/Application Support/Claude/claude_desktop_config.json -``` - -## Example 2: 5ire -Another MCP client that supports multiple models on the backend is [5ire](https://github.com/nanbingxyz/5ire). To set up GhidraMCP, open 5ire and go to `Tools` -> `New` and set the following configurations: - -1. Tool Key: ghidra -2. Name: GhidraMCP -3. Command: `python /ABSOLUTE_PATH_TO/bridge_mcp_ghidra.py` +### 5ire Configuration +1. Tool Key: ghydra +2. Name: GhydraMCP +3. Command: `python /ABSOLUTE_PATH_TO/bridge_mcp_hydra.py` # Building from Source Build with Maven by running: @@ -84,6 +121,6 @@ Build with Maven by running: The generated zip file includes the built Ghidra plugin and its resources. These files are required for Ghidra to recognize the new extension. -- lib/GhidraMCP.jar +- lib/GhydraMCP.jar - extensions.properties - Module.manifest diff --git a/bridge_mcp_ghidra.py b/bridge_mcp_hydra.py similarity index 73% rename from bridge_mcp_ghidra.py rename to bridge_mcp_hydra.py index e9043e9..92227ad 100644 --- a/bridge_mcp_ghidra.py +++ b/bridge_mcp_hydra.py @@ -5,6 +5,7 @@ # "requests==2.32.3", # ] # /// +import os import sys import requests from typing import Dict @@ -19,11 +20,11 @@ DEFAULT_GHIDRA_HOST = "localhost" mcp = FastMCP("hydra-mcp") -# Get host from command line or use default -ghidra_host = DEFAULT_GHIDRA_HOST +# Get host from environment variable, command line, or use default +ghidra_host = os.environ.get("GHIDRA_HYDRA_HOST", DEFAULT_GHIDRA_HOST) if len(sys.argv) > 1: ghidra_host = sys.argv[1] - print(f"Using Ghidra host: {ghidra_host}") +print(f"Using Ghidra host: {ghidra_host}") def get_instance_url(port: int) -> str: """Get URL for a Ghidra instance by port""" @@ -65,6 +66,26 @@ def safe_get(port: int, endpoint: str, params: dict = None) -> list: except Exception as e: return [f"Request failed: {str(e)}"] +def safe_put(port: int, endpoint: str, data: dict) -> str: + """Perform a PUT request to a specific Ghidra instance""" + try: + url = f"{get_instance_url(port)}/{endpoint}" + response = requests.put(url, data=data, timeout=5) + response.encoding = 'utf-8' + if response.ok: + return response.text.strip() + elif response.status_code == 404 and port != DEFAULT_GHIDRA_PORT: + # Try falling back to default instance + return safe_put(DEFAULT_GHIDRA_PORT, endpoint, data) + else: + return f"Error {response.status_code}: {response.text.strip()}" + except requests.exceptions.ConnectionError: + if port != DEFAULT_GHIDRA_PORT: + return safe_put(DEFAULT_GHIDRA_PORT, endpoint, data) + return "Error: Failed to connect to Ghidra instance" + except Exception as e: + return f"Request failed: {str(e)}" + def safe_post(port: int, endpoint: str, data: dict | str) -> str: """Perform a POST request to a specific Ghidra instance""" try: @@ -129,25 +150,32 @@ def unregister_instance(port: int) -> str: return f"No instance found on port {port}" # Updated tool implementations with port parameter +from urllib.parse import quote + @mcp.tool() -def list_methods(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: - return safe_get(port, "methods", {"offset": offset, "limit": limit}) +def list_functions(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: + """List all functions with pagination""" + return safe_get(port, "functions", {"offset": offset, "limit": limit}) @mcp.tool() def list_classes(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: + """List all classes with pagination""" return safe_get(port, "classes", {"offset": offset, "limit": limit}) @mcp.tool() -def decompile_function(port: int = DEFAULT_GHIDRA_PORT, name: str = "") -> str: - return safe_post(port, "decompile", name) +def get_function(port: int = DEFAULT_GHIDRA_PORT, name: str = "") -> str: + """Get decompiled code for a specific function""" + return safe_get(port, f"functions/{quote(name)}", {}) @mcp.tool() -def rename_function(port: int = DEFAULT_GHIDRA_PORT, old_name: str = "", new_name: str = "") -> str: - return safe_post(port, "renameFunction", {"oldName": old_name, "newName": new_name}) +def update_function(port: int = DEFAULT_GHIDRA_PORT, name: str = "", new_name: str = "") -> str: + """Rename a function""" + return safe_put(port, f"functions/{quote(name)}", {"newName": new_name}) @mcp.tool() -def rename_data(port: int = DEFAULT_GHIDRA_PORT, address: str = "", new_name: str = "") -> str: - return safe_post(port, "renameData", {"address": address, "newName": new_name}) +def update_data(port: int = DEFAULT_GHIDRA_PORT, address: str = "", new_name: str = "") -> str: + """Rename data at specified address""" + return safe_put(port, "data", {"address": address, "newName": new_name}) @mcp.tool() def list_segments(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: @@ -155,11 +183,11 @@ def list_segments(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = @mcp.tool() def list_imports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: - return safe_get(port, "imports", {"offset": offset, "limit": limit}) + return safe_get(port, "symbols/imports", {"offset": offset, "limit": limit}) @mcp.tool() def list_exports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: - return safe_get(port, "exports", {"offset": offset, "limit": limit}) + return safe_get(port, "symbols/exports", {"offset": offset, "limit": limit}) @mcp.tool() def list_namespaces(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list: @@ -173,7 +201,7 @@ def list_data_items(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", offset: int = 0, limit: int = 100) -> list: if not query: return ["Error: query string is required"] - return safe_get(port, "searchFunctions", {"query": query, "offset": offset, "limit": limit}) + return safe_get(port, "functions", {"query": query, "offset": offset, "limit": limit}) # Handle graceful shutdown import signal diff --git a/pom.xml b/pom.xml index f7f5dc4..1fdadc5 100644 --- a/pom.xml +++ b/pom.xml @@ -3,12 +3,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.lauriewired - HydraMCP + eu.starsong.ghidra + GhydraMCP jar 1.1 - HydraMCP - https://github.com/LaurieWired/GhidraMCP + GhydraMCP + https://github.com/teal-bauer/GhydraMCP @@ -93,7 +93,7 @@ src/main/resources/META-INF/MANIFEST.MF - HydraMCP + GhydraMCP **/App.class @@ -115,7 +115,7 @@ - HydraMCP-${project.version} + GhydraMCP-${project.version} false diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 7d62d8d..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -mcp==1.5.0 -requests==2.32.3 diff --git a/src/assembly/ghidra-extension.xml b/src/assembly/ghidra-extension.xml index 24cc449..86bf855 100644 --- a/src/assembly/ghidra-extension.xml +++ b/src/assembly/ghidra-extension.xml @@ -17,24 +17,24 @@ + of a folder named GhydraMCP/ (the actual extension folder). --> src/main/resources extension.properties Module.manifest - HydraMCP + GhydraMCP - + ${project.build.directory} - HydraMCP.jar + GhydraMCP.jar - HydraMCP/lib + GhydraMCP/lib diff --git a/src/main/java/com/lauriewired/HydraMCPPlugin.java b/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java similarity index 74% rename from src/main/java/com/lauriewired/HydraMCPPlugin.java rename to src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java index bd28383..bc07381 100644 --- a/src/main/java/com/lauriewired/HydraMCPPlugin.java +++ b/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java @@ -1,7 +1,6 @@ -package com.lauriewired; +package eu.starsong.ghidra; import ghidra.framework.plugintool.*; -import ghidra.framework.plugintool.util.*; import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.program.model.address.Address; import ghidra.program.model.address.GlobalNamespace; @@ -38,17 +37,16 @@ import java.util.concurrent.atomic.*; shortDescription = "HTTP server plugin", description = "Starts an embedded HTTP server to expose program data." ) -public class HydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { +public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { - private static final Map activeInstances = new ConcurrentHashMap<>(); - private static final AtomicInteger nextPort = new AtomicInteger(8192); + private static final Map activeInstances = new ConcurrentHashMap<>(); private static final Object baseInstanceLock = new Object(); private HttpServer server; private int port; private boolean isBaseInstance = false; - public HydraMCPPlugin(PluginTool tool) { + public GhydraMCPPlugin(PluginTool tool) { super(tool); // Find available port @@ -64,8 +62,8 @@ public class HydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { } // Log to both console and log file - Msg.info(this, "HydraMCPPlugin loaded on port " + port); - System.out.println("[HydraMCP] Plugin loaded on port " + port); + Msg.info(this, "GhydraMCPPlugin loaded on port " + port); + System.out.println("[GhydraMCP] Plugin loaded on port " + port); try { startServer(); @@ -82,85 +80,127 @@ public class HydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { server = HttpServer.create(new InetSocketAddress(port), 0); // Each listing endpoint uses offset & limit from query params: - server.createContext("/methods", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, getAllFunctionNames(offset, limit)); + // Function resources + server.createContext("/functions", exchange -> { + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + String query = qparams.get("query"); + + if (query != null && !query.isEmpty()) { + sendResponse(exchange, searchFunctionsByName(query, offset, limit)); + } else { + sendResponse(exchange, getAllFunctionNames(offset, limit)); + } + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); + server.createContext("/functions/", exchange -> { + String path = exchange.getRequestURI().getPath(); + String name = path.substring(path.lastIndexOf('/') + 1); + try { + name = java.net.URLDecoder.decode(name, StandardCharsets.UTF_8.name()); + } catch (Exception e) { + Msg.error(this, "Failed to decode function name", e); + exchange.sendResponseHeaders(400, -1); // Bad Request + return; + } + + if ("GET".equals(exchange.getRequestMethod())) { + sendResponse(exchange, decompileFunctionByName(name)); + } else if ("PUT".equals(exchange.getRequestMethod())) { + Map params = parsePostParams(exchange); + String newName = params.get("newName"); + String response = renameFunction(name, newName) + ? "Renamed successfully" : "Rename failed"; + sendResponse(exchange, response); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } + }); + + // Class resources server.createContext("/classes", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, getAllClassNames(offset, limit)); - }); - - server.createContext("/decompile", exchange -> { - String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); - sendResponse(exchange, decompileFunctionByName(name)); - }); - - server.createContext("/renameFunction", exchange -> { - Map params = parsePostParams(exchange); - String response = renameFunction(params.get("oldName"), params.get("newName")) - ? "Renamed successfully" : "Rename failed"; - sendResponse(exchange, response); - }); - - server.createContext("/renameData", exchange -> { - Map params = parsePostParams(exchange); - renameDataAtAddress(params.get("address"), params.get("newName")); - sendResponse(exchange, "Rename data attempted"); + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, getAllClassNames(offset, limit)); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); + // Memory segments server.createContext("/segments", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listSegments(offset, limit)); + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listSegments(offset, limit)); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); - server.createContext("/imports", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listImports(offset, limit)); + // Symbol resources (imports/exports) + server.createContext("/symbols/imports", exchange -> { + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listImports(offset, limit)); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); - server.createContext("/exports", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listExports(offset, limit)); + server.createContext("/symbols/exports", exchange -> { + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listExports(offset, limit)); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); + // Namespace resources server.createContext("/namespaces", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listNamespaces(offset, limit)); + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listNamespaces(offset, limit)); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); + // Data resources server.createContext("/data", exchange -> { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listDefinedData(offset, limit)); - }); - - server.createContext("/searchFunctions", exchange -> { - Map qparams = parseQueryParams(exchange); - String searchTerm = qparams.get("query"); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, searchFunctionsByName(searchTerm, offset, limit)); + if ("GET".equals(exchange.getRequestMethod())) { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listDefinedData(offset, limit)); + } else if ("PUT".equals(exchange.getRequestMethod())) { + Map params = parsePostParams(exchange); + renameDataAtAddress(params.get("address"), params.get("newName")); + sendResponse(exchange, "Rename data attempted"); + } else { + exchange.sendResponseHeaders(405, -1); // Method Not Allowed + } }); // Instance management endpoints server.createContext("/instances", exchange -> { StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : activeInstances.entrySet()) { + for (Map.Entry entry : activeInstances.entrySet()) { sb.append(entry.getKey()).append(": ") .append(entry.getValue().isBaseInstance ? "base" : "secondary") .append("\n"); @@ -192,9 +232,9 @@ public class HydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { server.setExecutor(null); new Thread(() -> { server.start(); - Msg.info(this, "HydraMCP HTTP server started on port " + port); - System.out.println("[HydraMCP] HTTP server started on port " + port); - }, "HydraMCP-HTTP-Server").start(); + Msg.info(this, "GhydraMCP HTTP server started on port " + port); + System.out.println("[GhydraMCP] HTTP server started on port " + port); + }, "GhydraMCP-HTTP-Server").start(); } // ---------------------------------------------------------------------------------- @@ -542,7 +582,7 @@ public class HydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { if (server != null) { server.stop(0); Msg.info(this, "HTTP server stopped on port " + port); - System.out.println("[HydraMCP] HTTP server stopped on port " + port); + System.out.println("[GhydraMCP] HTTP server stopped on port " + port); } activeInstances.remove(port); super.dispose(); diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF index 4145ab2..02a73ab 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -1,6 +1,6 @@ Manifest-Version: 1.0 -Plugin-Class: com.lauriewired.GhidraMCP -Plugin-Name: GhidraMCP -Plugin-Version: 1.0 -Plugin-Author: LaurieWired -Plugin-Description: A custom plugin by LaurieWired +Plugin-Class: eu.starsong.ghidra.GhydraMCP +Plugin-Name: GhydraMCP +Plugin-Version: 1.1 +Plugin-Author: LaurieWired, Teal Bauer +Plugin-Description: Expose multiple Ghidra tools to MCP servers diff --git a/src/main/resources/Module.manifest b/src/main/resources/Module.manifest index 1aa8264..00f5a85 100644 --- a/src/main/resources/Module.manifest +++ b/src/main/resources/Module.manifest @@ -1,2 +1,2 @@ -GHIDRA_MODULE_NAME=GhidraMCP -GHIDRA_MODULE_DESC=An HTTP server plugin for Ghidra +GHIDRA_MODULE_NAME=GhydraMCP +GHIDRA_MODULE_DESC=A multi-headed REST interface for Ghidra for use with MCP agents. diff --git a/src/main/resources/extension.properties b/src/main/resources/extension.properties index 409fa12..4b45921 100644 --- a/src/main/resources/extension.properties +++ b/src/main/resources/extension.properties @@ -1,6 +1,6 @@ -name=HydraMCP -description=A plugin that runs an embedded HTTP server to expose program data. -author=LaurieWired -createdOn=2025-03-22 +name=GhydraMCP +description=A multi-headed REST interface for Ghidra for use with MCP agents. +author=Laurie Wired, Teal Bauer +createdOn=2025-03-29 version=11.3.1 -ghidraVersion=11.3.1 \ No newline at end of file +ghidraVersion=11.3.1 diff --git a/src/test/java/com/lauriewired/AppTest.java b/src/test/java/eu/starsong/ghidra/AppTest.java similarity index 95% rename from src/test/java/com/lauriewired/AppTest.java rename to src/test/java/eu/starsong/ghidra/AppTest.java index 77b1a97..481f612 100644 --- a/src/test/java/com/lauriewired/AppTest.java +++ b/src/test/java/eu/starsong/ghidra/AppTest.java @@ -1,4 +1,4 @@ -package com.lauriewired; +package eu.starsong.ghidra; import junit.framework.Test; import junit.framework.TestCase;