diff --git a/bridge_mcp_hydra.py b/bridge_mcp_hydra.py index b376960..50ac6e2 100644 --- a/bridge_mcp_hydra.py +++ b/bridge_mcp_hydra.py @@ -146,26 +146,56 @@ def register_instance(port: int, url: str = None) -> str: project_info = {"url": url} try: - info_url = f"{url}/info" - info_response = requests.get(info_url, timeout=2) - if info_response.ok: + # Try the root endpoint first + root_url = f"{url}/" + print(f"Trying to get root info from {root_url}", file=sys.stderr) + root_response = requests.get(root_url, timeout=1.5) # Short timeout for root + + if root_response.ok: try: - # Parse JSON response - info_data = info_response.json() + print(f"Got response from root: {root_response.text}", file=sys.stderr) + root_data = root_response.json() - # Extract relevant information - project_info["project"] = info_data.get("project", "Unknown") + # Extract basic information from root + if "project" in root_data and root_data["project"]: + project_info["project"] = root_data["project"] + if "program" in root_data and root_data["program"]: + project_info["file"] = root_data["program"] + if "programID" in root_data and root_data["programID"]: + project_info["program_id"] = root_data["programID"] - # Handle file information which is nested - file_info = info_data.get("file", {}) - if file_info: - project_info["file"] = file_info.get("name", "") - project_info["path"] = file_info.get("path", "") - project_info["architecture"] = file_info.get("architecture", "") - project_info["endian"] = file_info.get("endian", "") - except ValueError: - # Not valid JSON - pass + print(f"Root data parsed: {project_info}", file=sys.stderr) + except Exception as e: + print(f"Error parsing root info: {e}", file=sys.stderr) + else: + print(f"Root endpoint returned {root_response.status_code}", file=sys.stderr) + + # If we don't have project info yet, try the /info endpoint as a fallback + if not project_info.get("project") and not project_info.get("file"): + info_url = f"{url}/info" + print(f"Trying fallback info from {info_url}", file=sys.stderr) + + try: + info_response = requests.get(info_url, timeout=2) + if info_response.ok: + try: + info_data = info_response.json() + # Extract relevant information + if "project" in info_data and info_data["project"]: + project_info["project"] = info_data["project"] + + # Handle file information + file_info = info_data.get("file", {}) + if isinstance(file_info, dict) and file_info.get("name"): + project_info["file"] = file_info.get("name", "") + project_info["path"] = file_info.get("path", "") + project_info["architecture"] = file_info.get("architecture", "") + project_info["endian"] = file_info.get("endian", "") + print(f"Info data parsed: {project_info}", file=sys.stderr) + except Exception as e: + print(f"Error parsing info endpoint: {e}", file=sys.stderr) + except Exception as e: + print(f"Error connecting to info endpoint: {e}", file=sys.stderr) except Exception: # Non-critical, continue with registration even if project info fails pass diff --git a/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java b/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java index ea71816..b5b78ce 100644 --- a/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java +++ b/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java @@ -213,21 +213,99 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { sendResponse(exchange, sb.toString()); }); - // Info endpoints - both root and /info for flexibility + // Super simple info endpoint with guaranteed response server.createContext("/info", exchange -> { - if ("GET".equals(exchange.getRequestMethod())) { - sendJsonResponse(exchange, getProjectInfo()); - } else { - exchange.sendResponseHeaders(405, -1); // Method Not Allowed + try { + String response = "{\n"; + response += "\"port\": " + port + ",\n"; + response += "\"isBaseInstance\": " + isBaseInstance + ",\n"; + + // Try to get program info if available + Program program = getCurrentProgram(); + String programName = "\"\""; + if (program != null) { + programName = "\"" + program.getName() + "\""; + } + + // Try to get project info if available + Project project = tool.getProject(); + String projectName = "\"\""; + if (project != null) { + projectName = "\"" + project.getName() + "\""; + } + + response += "\"project\": " + projectName + ",\n"; + response += "\"file\": " + programName + "\n"; + response += "}"; + + Msg.info(this, "Sending /info response: " + response); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } catch (Exception e) { + Msg.error(this, "Error serving /info endpoint", e); + try { + String error = "{\"error\": \"Internal error\", \"port\": " + port + "}"; + byte[] bytes = error.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } catch (IOException ioe) { + Msg.error(this, "Failed to send error response", ioe); + } } }); - // Root endpoint also returns project info + // Super simple root endpoint - exact same as /info for consistency server.createContext("/", exchange -> { - if ("GET".equals(exchange.getRequestMethod())) { - sendJsonResponse(exchange, getProjectInfo()); - } else { - exchange.sendResponseHeaders(405, -1); // Method Not Allowed + try { + String response = "{\n"; + response += "\"port\": " + port + ",\n"; + response += "\"isBaseInstance\": " + isBaseInstance + ",\n"; + + // Try to get program info if available + Program program = getCurrentProgram(); + String programName = "\"\""; + if (program != null) { + programName = "\"" + program.getName() + "\""; + } + + // Try to get project info if available + Project project = tool.getProject(); + String projectName = "\"\""; + if (project != null) { + projectName = "\"" + project.getName() + "\""; + } + + response += "\"project\": " + projectName + ",\n"; + response += "\"file\": " + programName + "\n"; + response += "}"; + + Msg.info(this, "Sending / response: " + response); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } catch (Exception e) { + Msg.error(this, "Error serving / endpoint", e); + try { + String error = "{\"error\": \"Internal error\", \"port\": " + port + "}"; + byte[] bytes = error.getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); + exchange.sendResponseHeaders(200, bytes.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(bytes); + } + } catch (IOException ioe) { + Msg.error(this, "Failed to send error response", ioe); + } } }); @@ -569,56 +647,32 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { return sb.toString(); } - public Program getCurrentProgram() { - ProgramManager pm = tool.getService(ProgramManager.class); - return pm != null ? pm.getCurrentProgram() : null; - } - /** - * Get information about the current project and open file in JSON format + * Get the current program from the tool */ - private JSONObject getProjectInfo() { - JSONObject info = new JSONObject(); - Program program = getCurrentProgram(); - - // Get project information if available - Project project = tool.getProject(); - if (project != null) { - info.put("project", project.getName()); - } else { - info.put("project", "Unknown"); + public Program getCurrentProgram() { + if (tool == null) { + Msg.debug(this, "Tool is null when trying to get current program"); + return null; } - - // Create file information object - JSONObject fileInfo = new JSONObject(); - - // Get current file information if available - if (program != null) { - // Basic info - fileInfo.put("name", program.getName()); - - // Try to get more detailed info - DomainFile domainFile = program.getDomainFile(); - if (domainFile != null) { - fileInfo.put("path", domainFile.getPathname()); + + try { + ProgramManager pm = tool.getService(ProgramManager.class); + if (pm == null) { + Msg.debug(this, "ProgramManager service is not available"); + return null; } - // Add any additional file info we might want - fileInfo.put("architecture", program.getLanguage().getProcessor().toString()); - fileInfo.put("endian", program.getLanguage().isBigEndian() ? "big" : "little"); - - info.put("file", fileInfo); - } else { - info.put("file", null); - info.put("status", "No file open"); + Program program = pm.getCurrentProgram(); + Msg.debug(this, "Got current program: " + (program != null ? program.getName() : "null")); + return program; + } + catch (Exception e) { + Msg.error(this, "Error getting current program", e); + return null; } - - // Add server metadata - info.put("port", port); - info.put("isBaseInstance", isBaseInstance); - - return info; } + private void sendResponse(HttpExchange exchange, String response) throws IOException { byte[] bytes = response.getBytes(StandardCharsets.UTF_8); @@ -629,18 +683,6 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin { } } - /** - * Send a JSON response to the client - */ - private void sendJsonResponse(HttpExchange exchange, JSONObject json) throws IOException { - String jsonString = json.toJSONString(); - byte[] bytes = jsonString.getBytes(StandardCharsets.UTF_8); - exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); - exchange.sendResponseHeaders(200, bytes.length); - try (OutputStream os = exchange.getResponseBody()) { - os.write(bytes); - } - } private int findAvailablePort() { int basePort = 8192;