From a59e8ecfa933cd6e024472749b9942e216f851ca Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 14:36:01 +0200 Subject: [PATCH] Make Ghidra decompileFunction timeout configurable This commit adds "Decompile Timeout" option to the MCP plugin that allows for setting a custom timeout to the `decompileFunction`. In particular it is useful to set to 0 or a large value when dealing with large functions that take a while to decompile. The default value is set to 30. In addition as part of this commit, the request timeout from the MCP bridge to the plugin is made configurable with the `--ghidra-timeout` argument. The default value is 5 seconds. --- bridge_mcp_ghidra.py | 19 ++++++++++++++----- .../java/com/lauriewired/GhidraMCPPlugin.java | 17 +++++++++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/bridge_mcp_ghidra.py b/bridge_mcp_ghidra.py index c492562d..86172cb7 100644 --- a/bridge_mcp_ghidra.py +++ b/bridge_mcp_ghidra.py @@ -15,6 +15,7 @@ from mcp.server.fastmcp import FastMCP DEFAULT_GHIDRA_SERVER = "http://127.0.0.1:8080/" +DEFAULT_REQUEST_TIMEOUT = 5 logger = logging.getLogger(__name__) @@ -22,6 +23,8 @@ # Initialize ghidra_server_url with default value ghidra_server_url = DEFAULT_GHIDRA_SERVER +# Initialize ghidra_request_timeout with default value +ghidra_request_timeout = DEFAULT_REQUEST_TIMEOUT def safe_get(endpoint: str, params: dict = None) -> list: """ @@ -33,7 +36,7 @@ def safe_get(endpoint: str, params: dict = None) -> list: url = urljoin(ghidra_server_url, endpoint) try: - response = requests.get(url, params=params, timeout=5) + response = requests.get(url, params=params, timeout=ghidra_request_timeout) response.encoding = 'utf-8' if response.ok: return response.text.splitlines() @@ -46,9 +49,9 @@ def safe_post(endpoint: str, data: dict | str) -> str: try: url = urljoin(ghidra_server_url, endpoint) if isinstance(data, dict): - response = requests.post(url, data=data, timeout=5) + response = requests.post(url, data=data, timeout=ghidra_request_timeout) else: - response = requests.post(url, data=data.encode("utf-8"), timeout=5) + response = requests.post(url, data=data.encode("utf-8"), timeout=ghidra_request_timeout) response.encoding = 'utf-8' if response.ok: return response.text.strip() @@ -297,13 +300,19 @@ def main(): help="Port to run MCP server on (only used for sse), default: 8081") parser.add_argument("--transport", type=str, default="stdio", choices=["stdio", "sse"], help="Transport protocol for MCP, default: stdio") + parser.add_argument("--ghidra-timeout", type=int, default=DEFAULT_REQUEST_TIMEOUT, + help=f"MCP requests timeout, default: {DEFAULT_REQUEST_TIMEOUT}") 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 - + + global ghidra_request_timeout + if args.ghidra_timeout: + ghidra_request_timeout = args.ghidra_timeout + if args.transport == "sse": try: # Set up logging diff --git a/src/main/java/com/lauriewired/GhidraMCPPlugin.java b/src/main/java/com/lauriewired/GhidraMCPPlugin.java index 88583bab..089c011b 100644 --- a/src/main/java/com/lauriewired/GhidraMCPPlugin.java +++ b/src/main/java/com/lauriewired/GhidraMCPPlugin.java @@ -70,7 +70,11 @@ public class GhidraMCPPlugin extends Plugin { private HttpServer server; private static final String OPTION_CATEGORY_NAME = "GhidraMCP HTTP Server"; private static final String PORT_OPTION_NAME = "Server Port"; + private static final String DECOMPILE_TIMEOUT_OPTION_NAME = "Decompile Timeout"; private static final int DEFAULT_PORT = 8080; + private static final int DEFAULT_DECOMPILE_TIMEOUT = 30; + + private int decompileTimeout; public GhidraMCPPlugin(PluginTool tool) { super(tool); @@ -83,6 +87,10 @@ public GhidraMCPPlugin(PluginTool tool) { "The network port number the embedded HTTP server will listen on. " + "Requires Ghidra restart or plugin reload to take effect after changing."); + options.registerOption(DECOMPILE_TIMEOUT_OPTION_NAME, DEFAULT_DECOMPILE_TIMEOUT, + null, + "Decompilation timeout. " + + "Requires Ghidra restart or plugin reload to take effect after changing."); try { startServer(); } @@ -96,6 +104,7 @@ private void startServer() throws IOException { // Read the configured port Options options = tool.getOptions(OPTION_CATEGORY_NAME); int port = options.getInt(PORT_OPTION_NAME, DEFAULT_PORT); + this.decompileTimeout = options.getInt(DECOMPILE_TIMEOUT_OPTION_NAME, DEFAULT_DECOMPILE_TIMEOUT); // Stop existing server if running (e.g., if plugin is reloaded) if (server != null) { @@ -498,7 +507,7 @@ private String decompileFunctionByName(String name) { for (Function func : program.getFunctionManager().getFunctions(true)) { if (func.getName().equals(name)) { DecompileResults result = - decomp.decompileFunction(func, 30, new ConsoleTaskMonitor()); + decomp.decompileFunction(func, this.decompileTimeout, new ConsoleTaskMonitor()); if (result != null && result.decompileCompleted()) { return result.getDecompiledFunction().getC(); } else { @@ -593,7 +602,7 @@ private String renameVariableInFunction(String functionName, String oldVarName, return "Function not found"; } - DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor()); + DecompileResults result = decomp.decompileFunction(func, this.decompileTimeout, new ConsoleTaskMonitor()); if (result == null || !result.decompileCompleted()) { return "Decompilation failed"; } @@ -806,7 +815,7 @@ private String decompileFunctionByAddress(String addressStr) { DecompInterface decomp = new DecompInterface(); decomp.openProgram(program); - DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor()); + DecompileResults result = decomp.decompileFunction(func, this.decompileTimeout, new ConsoleTaskMonitor()); return (result != null && result.decompileCompleted()) ? result.getDecompiledFunction().getC() @@ -1209,7 +1218,7 @@ private DecompileResults decompileFunction(Function func, Program program) { decomp.setSimplificationStyle("decompile"); // Full decompilation // Decompile the function - DecompileResults results = decomp.decompileFunction(func, 60, new ConsoleTaskMonitor()); + DecompileResults results = decomp.decompileFunction(func, this.decompileTimeout, new ConsoleTaskMonitor()); if (!results.decompileCompleted()) { Msg.error(this, "Could not decompile function: " + results.getErrorMessage());