mirror of
https://github.com/jtang613/GhidrAssistMCP
synced 2026-06-21 13:54:56 +00:00
Add headless support.
This commit is contained in:
@@ -469,7 +469,8 @@ public class GhidrAssistMCPBackend implements McpBackend {
|
||||
*/
|
||||
private Program resolveTargetProgram(Map<String, Object> arguments) {
|
||||
if (manager == null) {
|
||||
return null;
|
||||
// Headless mode: no manager, fall back to getCurrentProgram()
|
||||
return getCurrentProgram();
|
||||
}
|
||||
|
||||
// Check if a specific program was requested
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Headless-compatible MCP server launcher.
|
||||
* Starts the Jetty MCP server without requiring the GUI plugin infrastructure.
|
||||
* Used by GhidrAssistMCPHeadlessScript to provide MCP tools in analyzeHeadless mode.
|
||||
*/
|
||||
package ghidrassistmcp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Manages a headless MCP server instance that holds a direct program reference
|
||||
* instead of relying on GhidrAssistMCPManager's PluginTool-based program discovery.
|
||||
*/
|
||||
public class GhidrAssistMCPHeadlessServer {
|
||||
|
||||
private static GhidrAssistMCPHeadlessServer instance;
|
||||
private static final Object lock = new Object();
|
||||
|
||||
private GhidrAssistMCPServer server;
|
||||
private HeadlessBackend headlessBackend;
|
||||
private volatile boolean running = false;
|
||||
|
||||
private GhidrAssistMCPHeadlessServer() {
|
||||
}
|
||||
|
||||
public static GhidrAssistMCPHeadlessServer getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (lock) {
|
||||
if (instance == null) {
|
||||
instance = new GhidrAssistMCPHeadlessServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the MCP server with a direct program reference.
|
||||
*
|
||||
* @param program The current program from the headless script
|
||||
* @param host Host to bind (typically "localhost" or "0.0.0.0")
|
||||
* @param port Port to bind (typically 8080)
|
||||
*/
|
||||
public synchronized void start(Program program, String host, int port) throws Exception {
|
||||
if (running) {
|
||||
Msg.info(this, "Headless MCP server already running, updating program");
|
||||
if (headlessBackend != null) {
|
||||
headlessBackend.setProgram(program);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Msg.info(this, "Starting headless MCP server on " + host + ":" + port);
|
||||
|
||||
// Create a backend wrapper that holds the program directly
|
||||
headlessBackend = new HeadlessBackend(program);
|
||||
|
||||
server = new GhidrAssistMCPServer(host, port, headlessBackend);
|
||||
server.start();
|
||||
running = true;
|
||||
|
||||
Msg.info(this, "Headless MCP server started successfully with " +
|
||||
headlessBackend.getAvailableTools().size() + " tools");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the MCP server.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
}
|
||||
Msg.info(this, "Headless MCP server stopped");
|
||||
} catch (Exception e) {
|
||||
Msg.error(this, "Error stopping headless MCP server", e);
|
||||
}
|
||||
server = null;
|
||||
headlessBackend = null;
|
||||
running = false;
|
||||
|
||||
synchronized (lock) {
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the program reference (e.g., when processing a new binary).
|
||||
*/
|
||||
public void setProgram(Program program) {
|
||||
if (headlessBackend != null) {
|
||||
headlessBackend.setProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A thin wrapper around GhidrAssistMCPBackend that provides a direct program
|
||||
* reference instead of going through GhidrAssistMCPManager.
|
||||
*/
|
||||
private static class HeadlessBackend extends GhidrAssistMCPBackend {
|
||||
|
||||
private volatile Program currentProgram;
|
||||
|
||||
HeadlessBackend(Program program) {
|
||||
super();
|
||||
this.currentProgram = program;
|
||||
// Notify backend of the initial program
|
||||
onProgramActivated(program);
|
||||
}
|
||||
|
||||
void setProgram(Program program) {
|
||||
if (this.currentProgram != null) {
|
||||
onProgramDeactivated(this.currentProgram);
|
||||
}
|
||||
this.currentProgram = program;
|
||||
if (program != null) {
|
||||
onProgramActivated(program);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Program getCurrentProgram() {
|
||||
return currentProgram;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Program> getAllOpenPrograms() {
|
||||
if (currentProgram != null) {
|
||||
return Collections.singletonList(currentProgram);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package ghidrassistmcp.scripts;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.util.Msg;
|
||||
import ghidrassistmcp.GhidrAssistMCPHeadlessServer;
|
||||
|
||||
/**
|
||||
* Headless GhidraScript that starts the GhidrAssistMCP server.
|
||||
* Designed to be run as a -preScript before GhidrAssistHL scripts so that
|
||||
* the MCP server is available for tool calls during ReAct analysis.
|
||||
*
|
||||
* Usage in analyzeHeadless:
|
||||
* -preScript GAMCPStartServerScript.java
|
||||
* -postScript GAHLQueryScript.java ...
|
||||
*/
|
||||
public class GAMCPStartServerScript extends GhidraScript {
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
if (currentProgram == null) {
|
||||
Msg.warn(this, "GAMCPStartServerScript: No program loaded, skipping MCP server start");
|
||||
return;
|
||||
}
|
||||
|
||||
String host = "localhost";
|
||||
int port = 8080;
|
||||
|
||||
// Parse optional arguments: host=... port=...
|
||||
String[] args = getScriptArgs();
|
||||
if (args != null) {
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith("host=")) {
|
||||
host = arg.substring(5);
|
||||
} else if (arg.startsWith("port=")) {
|
||||
try {
|
||||
port = Integer.parseInt(arg.substring(5));
|
||||
} catch (NumberFormatException e) {
|
||||
Msg.warn(this, "Invalid port argument, using default 8080");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GhidrAssistMCPHeadlessServer mcpServer = GhidrAssistMCPHeadlessServer.getInstance();
|
||||
|
||||
if (mcpServer.isRunning()) {
|
||||
Msg.info(this, "MCP server already running, updating program reference");
|
||||
mcpServer.setProgram(currentProgram);
|
||||
return;
|
||||
}
|
||||
|
||||
Msg.info(this, "Starting headless MCP server for: " + currentProgram.getName());
|
||||
mcpServer.start(currentProgram, host, port);
|
||||
Msg.info(this, "Headless MCP server ready on " + host + ":" + port);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user