Fix project info endpoints with simpler JSON generation

Simplify JSON response generation for better reliability. Both root and info
endpoints now use direct string building instead of the JSON library.
This commit is contained in:
Teal Bauer 2025-03-30 03:10:48 +02:00
parent 5cf8f5fb16
commit a615813e2d
2 changed files with 154 additions and 82 deletions

View File

@ -146,26 +146,56 @@ def register_instance(port: int, url: str = None) -> str:
project_info = {"url": url} project_info = {"url": url}
try: try:
# 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:
print(f"Got response from root: {root_response.text}", file=sys.stderr)
root_data = root_response.json()
# 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"]
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" 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) info_response = requests.get(info_url, timeout=2)
if info_response.ok: if info_response.ok:
try: try:
# Parse JSON response
info_data = info_response.json() info_data = info_response.json()
# Extract relevant information # Extract relevant information
project_info["project"] = info_data.get("project", "Unknown") if "project" in info_data and info_data["project"]:
project_info["project"] = info_data["project"]
# Handle file information which is nested # Handle file information
file_info = info_data.get("file", {}) file_info = info_data.get("file", {})
if file_info: if isinstance(file_info, dict) and file_info.get("name"):
project_info["file"] = file_info.get("name", "") project_info["file"] = file_info.get("name", "")
project_info["path"] = file_info.get("path", "") project_info["path"] = file_info.get("path", "")
project_info["architecture"] = file_info.get("architecture", "") project_info["architecture"] = file_info.get("architecture", "")
project_info["endian"] = file_info.get("endian", "") project_info["endian"] = file_info.get("endian", "")
except ValueError: print(f"Info data parsed: {project_info}", file=sys.stderr)
# Not valid JSON except Exception as e:
pass 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: except Exception:
# Non-critical, continue with registration even if project info fails # Non-critical, continue with registration even if project info fails
pass pass

View File

@ -213,21 +213,99 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
sendResponse(exchange, sb.toString()); sendResponse(exchange, sb.toString());
}); });
// Info endpoints - both root and /info for flexibility // Super simple info endpoint with guaranteed response
server.createContext("/info", exchange -> { server.createContext("/info", exchange -> {
if ("GET".equals(exchange.getRequestMethod())) { try {
sendJsonResponse(exchange, getProjectInfo()); String response = "{\n";
} else { response += "\"port\": " + port + ",\n";
exchange.sendResponseHeaders(405, -1); // Method Not Allowed 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 -> { server.createContext("/", exchange -> {
if ("GET".equals(exchange.getRequestMethod())) { try {
sendJsonResponse(exchange, getProjectInfo()); String response = "{\n";
} else { response += "\"port\": " + port + ",\n";
exchange.sendResponseHeaders(405, -1); // Method Not Allowed 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(); 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() { public Program getCurrentProgram() {
JSONObject info = new JSONObject(); if (tool == null) {
Program program = getCurrentProgram(); Msg.debug(this, "Tool is null when trying to get current program");
return null;
// Get project information if available
Project project = tool.getProject();
if (project != null) {
info.put("project", project.getName());
} else {
info.put("project", "Unknown");
} }
// Create file information object try {
JSONObject fileInfo = new JSONObject(); ProgramManager pm = tool.getService(ProgramManager.class);
if (pm == null) {
// Get current file information if available Msg.debug(this, "ProgramManager service is not available");
if (program != null) { return 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());
} }
// Add any additional file info we might want Program program = pm.getCurrentProgram();
fileInfo.put("architecture", program.getLanguage().getProcessor().toString()); Msg.debug(this, "Got current program: " + (program != null ? program.getName() : "null"));
fileInfo.put("endian", program.getLanguage().isBigEndian() ? "big" : "little"); return program;
}
info.put("file", fileInfo); catch (Exception e) {
} else { Msg.error(this, "Error getting current program", e);
info.put("file", null); return null;
info.put("status", "No file open"); }
} }
// Add server metadata
info.put("port", port);
info.put("isBaseInstance", isBaseInstance);
return info;
}
private void sendResponse(HttpExchange exchange, String response) throws IOException { private void sendResponse(HttpExchange exchange, String response) throws IOException {
byte[] bytes = response.getBytes(StandardCharsets.UTF_8); 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() { private int findAvailablePort() {
int basePort = 8192; int basePort = 8192;