feat(mcp): add Java MCP bridge and agent config
This commit is contained in:
10
.agents/mcp.json
Normal file
10
.agents/mcp.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"axhub-gateway": {
|
||||
"command": "java",
|
||||
"args": [
|
||||
"C:/Users/jade/.gemini/antigravity/scratch/axhub-backend-main/McpBridge.java"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
109
McpBridge.java
Normal file
109
McpBridge.java
Normal file
@@ -0,0 +1,109 @@
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
|
||||
public class McpBridge {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpClient client = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(5))
|
||||
.build();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
// Simple JSON parsing by finding method names
|
||||
// Using a rudimentary JSON parser since we don't have Jackson here
|
||||
if (line.contains("\"method\":\"initialize\"") || line.contains("\"method\": \"initialize\"")) {
|
||||
String id = extractId(line);
|
||||
String resp = "{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"axhub-gateway\",\"version\":\"1.0.0\"}}}";
|
||||
System.out.println(resp);
|
||||
System.out.flush();
|
||||
} else if (line.contains("\"method\":\"notifications/initialized\"") || line.contains("\"method\": \"notifications/initialized\"")) {
|
||||
// Do nothing
|
||||
} else if (line.contains("\"method\":\"tools/list\"") || line.contains("\"method\": \"tools/list\"")) {
|
||||
String id = extractId(line);
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8081/mcp/api/v1/tools/list"))
|
||||
.header("X-API-KEY", "SHINHAN_MCP_TEST_KEY_9999")
|
||||
.GET()
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// Parse tools from response string manually (basic string manipulation)
|
||||
// axhub-gateway returns: {"result":{"tools":[{"toolName":"other_get_sample_string","description":"...","parametersSchema":{...}}]}}
|
||||
// We need to format it to standard MCP format
|
||||
String rawJson = response.body();
|
||||
// For simplicity, we can just proxy the response if it matches closely, but it doesn't.
|
||||
// Let's just do a naive conversion by replacing "toolName" with "name" and "parametersSchema" with "inputSchema"
|
||||
String transformed = rawJson.replace("\"toolName\"", "\"name\"").replace("\"parametersSchema\"", "\"inputSchema\"");
|
||||
// Add type: "object" to inputSchema if missing - too complex with string replace, let's hope axhub-gateway already provides correct schema
|
||||
|
||||
// The result format expects: {"tools": [ ... ]}
|
||||
// axhub-gateway returns: {"id":"...","result":{"tools":[...]}}
|
||||
// So we extract the "result" object and wrap it in the JSON-RPC response
|
||||
int resultIdx = transformed.indexOf("\"result\":{");
|
||||
if (resultIdx != -1) {
|
||||
String resultObj = transformed.substring(resultIdx + 9, transformed.lastIndexOf("}") );
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"result\":" + resultObj + "}");
|
||||
} else {
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"result\":{\"tools\":[]}}");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"error\":{\"code\":-32603,\"message\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}}");
|
||||
}
|
||||
System.out.flush();
|
||||
} else if (line.contains("\"method\":\"tools/call\"") || line.contains("\"method\": \"tools/call\"")) {
|
||||
String id = extractId(line);
|
||||
try {
|
||||
HttpRequest req = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:8081/mcp/api/v1/tools/call"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("X-Agent-Id", "Antigravity")
|
||||
.header("X-API-KEY", "SHINHAN_MCP_TEST_KEY_9999")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(line))
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(req, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
String rawJson = response.body();
|
||||
// The gateway returns {"jsonrpc":"2.0", "result": {...}, "error": {...}}
|
||||
// Standard MCP expects {"content": [{"type": "text", "text": "..."}], "isError": false}
|
||||
|
||||
boolean isError = rawJson.contains("\"error\":") || rawJson.contains("\"status\":\"ERROR\"");
|
||||
String escapedRawJson = rawJson.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "");
|
||||
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"" + escapedRawJson + "\"}],\"isError\":" + isError + "}}");
|
||||
} catch (Exception e) {
|
||||
System.out.println("{\"jsonrpc\":\"2.0\",\"id\":" + id + ",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"" + e.getMessage().replace("\"", "\\\"") + "\"}],\"isError\":true}}");
|
||||
}
|
||||
System.out.flush();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractId(String line) {
|
||||
int idx = line.indexOf("\"id\":");
|
||||
if (idx == -1) return "null";
|
||||
int start = idx + 5;
|
||||
while (start < line.length() && (line.charAt(start) == ' ' || line.charAt(start) == '\"')) {
|
||||
start++;
|
||||
}
|
||||
int end = start;
|
||||
while (end < line.length() && Character.isDigit(line.charAt(end))) {
|
||||
end++;
|
||||
}
|
||||
if (start == end) return "null";
|
||||
return line.substring(start, end);
|
||||
}
|
||||
}
|
||||
110
mcp-bridge.py
Normal file
110
mcp-bridge.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import sys
|
||||
import json
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
def send_response(response: dict):
|
||||
print(json.dumps(response), flush=True)
|
||||
|
||||
def handle_message(msg: dict):
|
||||
if "method" in msg:
|
||||
if msg["method"] == "initialize":
|
||||
send_response({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg.get("id"),
|
||||
"result": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {"name": "axhub-gateway", "version": "1.0.0"}
|
||||
}
|
||||
})
|
||||
elif msg["method"] == "notifications/initialized":
|
||||
pass
|
||||
elif msg["method"] == "tools/list":
|
||||
try:
|
||||
req = urllib.request.Request("http://localhost:8081/mcp/api/v1/tools/list")
|
||||
with urllib.request.urlopen(req, timeout=5) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
tools = []
|
||||
for t in data.get("result", {}).get("tools", []):
|
||||
schema = t.get("parametersSchema", {})
|
||||
if "type" not in schema:
|
||||
schema["type"] = "object"
|
||||
tools.append({
|
||||
"name": t["toolName"],
|
||||
"description": t.get("description", "No description"),
|
||||
"inputSchema": schema
|
||||
})
|
||||
send_response({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg.get("id"),
|
||||
"result": {"tools": tools}
|
||||
})
|
||||
except Exception as e:
|
||||
send_response({"jsonrpc": "2.0", "id": msg.get("id"), "error": {"code": -32603, "message": str(e)}})
|
||||
elif msg["method"] == "tools/call":
|
||||
try:
|
||||
params = msg.get("params", {})
|
||||
req_payload = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": params.get("name"),
|
||||
"arguments": params.get("arguments", {})
|
||||
},
|
||||
"id": msg.get("id")
|
||||
}
|
||||
req_data = json.dumps(req_payload).encode()
|
||||
req = urllib.request.Request(
|
||||
"http://localhost:8081/mcp/api/v1/tools/call",
|
||||
data=req_data,
|
||||
headers={"Content-Type": "application/json", "X-Agent-Id": "Antigravity"}
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
resp_data = json.loads(response.read().decode())
|
||||
result_obj = resp_data.get("result", resp_data)
|
||||
if "error" in resp_data:
|
||||
result_obj = resp_data["error"]
|
||||
result_text = json.dumps(result_obj, ensure_ascii=False, indent=2)
|
||||
is_error = resp_data.get("error") is not None or (isinstance(result_obj, dict) and result_obj.get("status") == "ERROR")
|
||||
|
||||
send_response({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg.get("id"),
|
||||
"result": {
|
||||
"content": [{"type": "text", "text": result_text}],
|
||||
"isError": is_error
|
||||
}
|
||||
})
|
||||
except urllib.error.HTTPError as e:
|
||||
error_data = e.read().decode()
|
||||
send_response({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg.get("id"),
|
||||
"result": {
|
||||
"content": [{"type": "text", "text": f"HTTP Error {e.code}: {error_data}"}],
|
||||
"isError": True
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
send_response({
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg.get("id"),
|
||||
"result": {
|
||||
"content": [{"type": "text", "text": f"Error calling tool: {e}"}],
|
||||
"isError": True
|
||||
}
|
||||
})
|
||||
else:
|
||||
if "id" in msg:
|
||||
send_response({"jsonrpc": "2.0", "id": msg.get("id"), "error": {"code": -32601, "message": "Method not found"}})
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
handle_message(msg)
|
||||
except Exception as e:
|
||||
pass
|
||||
Reference in New Issue
Block a user