From 7db0b4e8af2f1c471b6e17128bbcee8fa85e4a3d Mon Sep 17 00:00:00 2001 From: jade Date: Thu, 9 Jul 2026 16:03:58 +0900 Subject: [PATCH] feat(mcp): add Java MCP bridge and agent config --- .agents/mcp.json | 10 +++++ McpBridge.java | 109 ++++++++++++++++++++++++++++++++++++++++++++++ mcp-bridge.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 .agents/mcp.json create mode 100644 McpBridge.java create mode 100644 mcp-bridge.py diff --git a/.agents/mcp.json b/.agents/mcp.json new file mode 100644 index 0000000..a08f1b0 --- /dev/null +++ b/.agents/mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "axhub-gateway": { + "command": "java", + "args": [ + "C:/Users/jade/.gemini/antigravity/scratch/axhub-backend-main/McpBridge.java" + ] + } + } +} diff --git a/McpBridge.java b/McpBridge.java new file mode 100644 index 0000000..3ca4f32 --- /dev/null +++ b/McpBridge.java @@ -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 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 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); + } +} diff --git a/mcp-bridge.py b/mcp-bridge.py new file mode 100644 index 0000000..34355ac --- /dev/null +++ b/mcp-bridge.py @@ -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