diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..ec29081
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,145 @@
+
+
+ 4.0.0
+ com.lauriewired
+ GhidraMCP
+ jar
+ 1.0-SNAPSHOT
+ GhidraMCP
+ http://maven.apache.org
+
+
+
+
+ ghidra
+ Generic
+ 11.3.1
+ system
+ ${project.basedir}/lib/Generic.jar
+
+
+ ghidra
+ SoftwareModeling
+ 11.3.1
+ system
+ ${project.basedir}/lib/SoftwareModeling.jar
+
+
+ ghidra
+ Project
+ 11.3.1
+ system
+ ${project.basedir}/lib/Project.jar
+
+
+ ghidra
+ Docking
+ 11.3.1
+ system
+ ${project.basedir}/lib/Docking.jar
+
+
+ ghidra
+ Decompiler
+ 11.3.1
+ system
+ ${project.basedir}/lib/Decompiler.jar
+
+
+ ghidra
+ Utility
+ 11.3.1
+ system
+ ${project.basedir}/lib/Utility.jar
+
+
+ ghidra
+ Base
+ 11.3.1
+ system
+ ${project.basedir}/lib/Base.jar
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+
+
+
+
+
+ maven-jar-plugin
+ 3.2.2
+
+
+ src/main/resources/META-INF/MANIFEST.MF
+
+
+ GhidraMCP
+
+
+ **/App.class
+
+
+ ${project.build.directory}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.3.0
+
+
+
+ src/assembly/ghidra-extension.xml
+
+
+
+ GhidraMCP-${project.version}
+
+
+ false
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 3.1.2
+
+
+ copy-dependencies
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/lib
+ runtime
+
+
+
+
+
+
+
diff --git a/src/assembly/ghidra-extension.xml b/src/assembly/ghidra-extension.xml
new file mode 100644
index 0000000..5941ca0
--- /dev/null
+++ b/src/assembly/ghidra-extension.xml
@@ -0,0 +1,40 @@
+
+
+
+ ghidra-extension
+
+
+
+ zip
+
+
+
+ false
+
+
+
+
+ src/main/resources
+
+ extension.properties
+ Module.manifest
+
+ GhidraMCP
+
+
+
+
+ ${project.build.directory}
+
+
+ GhidraMCP.jar
+
+ GhidraMCP/lib
+
+
+
diff --git a/src/main/java/com/lauriewired/App.java b/src/main/java/com/lauriewired/App.java
new file mode 100644
index 0000000..5155129
--- /dev/null
+++ b/src/main/java/com/lauriewired/App.java
@@ -0,0 +1,13 @@
+package com.lauriewired;
+
+/**
+ * Hello world!
+ *
+ */
+public class App
+{
+ public static void main( String[] args )
+ {
+ System.out.println( "Hello World!" );
+ }
+}
diff --git a/src/main/java/com/lauriewired/GhidraMCPPlugin.java b/src/main/java/com/lauriewired/GhidraMCPPlugin.java
new file mode 100644
index 0000000..323e133
--- /dev/null
+++ b/src/main/java/com/lauriewired/GhidraMCPPlugin.java
@@ -0,0 +1,288 @@
+package com.lauriewired;
+
+import ghidra.framework.plugintool.Plugin;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.symbol.*;
+import ghidra.program.model.address.*;
+import ghidra.program.model.mem.*;
+import ghidra.app.decompiler.DecompInterface;
+import ghidra.app.decompiler.DecompileResults;
+import ghidra.util.task.ConsoleTaskMonitor;
+import ghidra.util.Msg;
+
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.app.DeveloperPluginPackage;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.framework.plugintool.PluginInfo;
+import ghidra.app.services.ProgramManager;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.SwingUtilities;
+
+@PluginInfo(
+ status = PluginStatus.RELEASED,
+ packageName = DeveloperPluginPackage.NAME,
+ category = PluginCategoryNames.ANALYSIS,
+ shortDescription = "HTTP server plugin",
+ description = "Starts an embedded HTTP server to expose program data."
+)
+public class GhidraMCPPlugin extends Plugin {
+
+ private HttpServer server;
+
+ public GhidraMCPPlugin(PluginTool tool) {
+ super(tool);
+ Msg.info(this, "✅ GhidraMCPPlugin loaded!");
+
+ try {
+ startServer();
+ } catch (IOException e) {
+ Msg.error(this, "Failed to start HTTP server", e);
+ }
+ }
+
+ private void startServer() throws IOException {
+ int port = 8080;
+ server = HttpServer.create(new InetSocketAddress(port), 0);
+
+ server.createContext("/methods", exchange -> sendResponse(exchange, getAllFunctionNames()));
+ server.createContext("/classes", exchange -> sendResponse(exchange, getAllClassNames()));
+ server.createContext("/decompile", exchange -> {
+ String name = new String(exchange.getRequestBody().readAllBytes());
+ 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");
+ });
+ server.createContext("/segments", exchange -> sendResponse(exchange, listSegments()));
+ server.createContext("/imports", exchange -> sendResponse(exchange, listImports()));
+ server.createContext("/exports", exchange -> sendResponse(exchange, listExports()));
+ server.createContext("/namespaces", exchange -> sendResponse(exchange, listNamespaces()));
+ server.createContext("/data", exchange -> sendResponse(exchange, listDefinedData()));
+
+ server.setExecutor(null);
+ new Thread(() -> {
+ server.start();
+ Msg.info(this, "🌐 GhidraMCP HTTP server started on port " + port);
+ }, "GhidraMCP-HTTP-Server").start();
+ }
+
+ private void sendResponse(HttpExchange exchange, String response) throws IOException {
+ byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
+ exchange.sendResponseHeaders(200, bytes.length);
+ try (OutputStream os = exchange.getResponseBody()) {
+ os.write(bytes);
+ }
+ }
+
+ private Map parsePostParams(HttpExchange exchange) throws IOException {
+ String body = new String(exchange.getRequestBody().readAllBytes());
+ Map params = new HashMap<>();
+ for (String pair : body.split("&")) {
+ String[] kv = pair.split("=");
+ if (kv.length == 2) params.put(kv[0], kv[1]);
+ }
+ return params;
+ }
+
+ private String getAllFunctionNames() {
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+ StringBuilder sb = new StringBuilder();
+ for (Function f : program.getFunctionManager().getFunctions(true)) {
+ sb.append(f.getName()).append("\n");
+ }
+ return sb.toString();
+ }
+
+ private String getAllClassNames() {
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+ Set classNames = new HashSet<>();
+ for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
+ Namespace ns = symbol.getParentNamespace();
+ if (ns != null && !ns.isGlobal()) {
+ classNames.add(ns.getName());
+ }
+ }
+ return String.join("\n", classNames);
+ }
+
+ private String decompileFunctionByName(String name) {
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+ DecompInterface decomp = new DecompInterface();
+ decomp.openProgram(program);
+ for (Function func : program.getFunctionManager().getFunctions(true)) {
+ if (func.getName().equals(name)) {
+ DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
+ if (result != null && result.decompileCompleted()) {
+ return result.getDecompiledFunction().getC();
+ } else return "Decompilation failed";
+ }
+ }
+ return "Function not found";
+ }
+
+ private boolean renameFunction(String oldName, String newName) {
+ Program program = getCurrentProgram();
+ if (program == null) return false;
+
+ // Use AtomicBoolean to capture the result from inside the Task
+ AtomicBoolean successFlag = new AtomicBoolean(false);
+
+ try {
+ // Run in Swing EDT to ensure proper transaction handling
+ SwingUtilities.invokeAndWait(() -> {
+ int tx = program.startTransaction("Rename function via HTTP");
+ try {
+ for (Function func : program.getFunctionManager().getFunctions(true)) {
+ if (func.getName().equals(oldName)) {
+ func.setName(newName, SourceType.USER_DEFINED);
+ successFlag.set(true);
+ break;
+ }
+ }
+ }
+ catch (Exception e) {
+ Msg.error(this, "Error renaming function", e);
+ }
+ finally {
+ program.endTransaction(tx, successFlag.get());
+ }
+ });
+ }
+ catch (InterruptedException | InvocationTargetException e) {
+ Msg.error(this, "Failed to execute rename on Swing thread", e);
+ }
+
+ return successFlag.get();
+ }
+
+ private void renameDataAtAddress(String addressStr, String newName) {
+ Program program = getCurrentProgram();
+ if (program == null) return;
+
+ try {
+ // Run in Swing EDT to ensure proper transaction handling
+ SwingUtilities.invokeAndWait(() -> {
+ int tx = program.startTransaction("Rename data");
+ try {
+ Address addr = program.getAddressFactory().getAddress(addressStr);
+ Listing listing = program.getListing();
+ Data data = listing.getDefinedDataAt(addr);
+ if (data != null) {
+ SymbolTable symTable = program.getSymbolTable();
+ Symbol symbol = symTable.getPrimarySymbol(addr);
+ if (symbol != null) {
+ symbol.setName(newName, SourceType.USER_DEFINED);
+ } else {
+ symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
+ }
+ }
+ }
+ catch (Exception e) {
+ Msg.error(this, "Rename data error", e);
+ }
+ finally {
+ program.endTransaction(tx, true);
+ }
+ });
+ }
+ catch (InterruptedException | InvocationTargetException e) {
+ Msg.error(this, "Failed to execute rename data on Swing thread", e);
+ }
+ }
+
+ private String listSegments() {
+ Program program = getCurrentProgram();
+ StringBuilder sb = new StringBuilder();
+ for (MemoryBlock block : program.getMemory().getBlocks()) {
+ sb.append(String.format("%s: %s - %s\n", block.getName(), block.getStart(), block.getEnd()));
+ }
+ return sb.toString();
+ }
+
+ private String listImports() {
+ Program program = getCurrentProgram();
+ StringBuilder sb = new StringBuilder();
+ for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) {
+ sb.append(symbol.getName()).append(" -> ").append(symbol.getAddress()).append("\n");
+ }
+ return sb.toString();
+ }
+
+ private String listExports() {
+ Program program = getCurrentProgram();
+ StringBuilder sb = new StringBuilder();
+ for (Function func : program.getFunctionManager().getFunctions(true)) {
+ if (func.isExternal()) {
+ sb.append(func.getName()).append(" -> ").append(func.getEntryPoint()).append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ private String listNamespaces() {
+ Program program = getCurrentProgram();
+ Set namespaces = new HashSet<>();
+ for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
+ Namespace ns = symbol.getParentNamespace();
+ if (ns != null && !(ns instanceof GlobalNamespace)) {
+ namespaces.add(ns.getName());
+ }
+ }
+ return String.join("\n", namespaces);
+ }
+
+ private String listDefinedData() {
+ Program program = getCurrentProgram();
+ StringBuilder sb = new StringBuilder();
+ for (MemoryBlock block : program.getMemory().getBlocks()) {
+ DataIterator it = program.getListing().getDefinedData(block.getStart(), true);
+ while (it.hasNext()) {
+ Data data = it.next();
+ if (block.contains(data.getAddress())) {
+ sb.append(String.format("%s: %s = %s\n",
+ data.getAddress(),
+ data.getLabel() != null ? data.getLabel() : "(unnamed)",
+ data.getDefaultValueRepresentation()));
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ public Program getCurrentProgram() {
+ ProgramManager programManager = tool.getService(ProgramManager.class);
+ return programManager != null ? programManager.getCurrentProgram() : null;
+ }
+
+ @Override
+ public void dispose() {
+ if (server != null) {
+ server.stop(0);
+ Msg.info(this, "🛑 HTTP server stopped.");
+ }
+ super.dispose();
+ }
+}
diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..4145ab2
--- /dev/null
+++ b/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +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
diff --git a/src/main/resources/Module.manifest b/src/main/resources/Module.manifest
new file mode 100644
index 0000000..1aa8264
--- /dev/null
+++ b/src/main/resources/Module.manifest
@@ -0,0 +1,2 @@
+GHIDRA_MODULE_NAME=GhidraMCP
+GHIDRA_MODULE_DESC=An HTTP server plugin for Ghidra
diff --git a/src/main/resources/extension.properties b/src/main/resources/extension.properties
new file mode 100644
index 0000000..6d09547
--- /dev/null
+++ b/src/main/resources/extension.properties
@@ -0,0 +1,6 @@
+name=GhidraMCP
+description=A plugin that runs an embedded HTTP server to expose program data.
+author=LaurieWired
+createdOn=2025-03-22
+version=11.2
+ghidraVersion=11.2
\ No newline at end of file
diff --git a/src/test/java/com/lauriewired/AppTest.java b/src/test/java/com/lauriewired/AppTest.java
new file mode 100644
index 0000000..77b1a97
--- /dev/null
+++ b/src/test/java/com/lauriewired/AppTest.java
@@ -0,0 +1,38 @@
+package com.lauriewired;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}