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}
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

View File

@ -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,57 +647,33 @@ 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);
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=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;