diff --git a/xxx/tool_convert.ts b/src/server/lib/mcp_tool_convert.ts similarity index 91% rename from xxx/tool_convert.ts rename to src/server/lib/mcp_tool_convert.ts index e02ea3e..614f420 100644 --- a/xxx/tool_convert.ts +++ b/src/server/lib/mcp_tool_convert.ts @@ -19,7 +19,7 @@ interface McpTool { * Convert OpenAPI 3.x JSON spec into MCP-compatible tool definitions (without run()). * Each tool corresponds to an endpoint, with metadata stored under `x-props`. */ -export function convertOpenApiToMcpTools(openApiJson: any, baseUrl: string = ""): McpTool[] { +export function convertOpenApiToMcpTools(openApiJson: any): McpTool[] { const tools: McpTool[] = []; const paths = openApiJson.paths || {}; @@ -92,9 +92,14 @@ function cleanToolName(name: string): string { // const tools = convertOpenApiToMcpTools(openApiJson, "https://api.wibudev.com"); // console.log(JSON.stringify(tools, null, 2)); -if (import.meta.main) { - const data = await fetch("http://localhost:3000/docs/json"); +export async function getMcpTools(){ + const data = await fetch(`${process.env.BUN_PUBLIC_BASE_URL}/docs/json`); const openApiJson = await data.json(); - const tools = convertOpenApiToMcpTools(openApiJson, "http://localhost:3000"); + const tools = convertOpenApiToMcpTools(openApiJson); + return tools; +} + +if (import.meta.main) { + const tools = await getMcpTools(); Bun.write("./tools.json", JSON.stringify(tools, null, 2)); } diff --git a/src/server/routes/mcp_route.ts b/src/server/routes/mcp_route.ts index a3c298d..8fd4086 100644 --- a/src/server/routes/mcp_route.ts +++ b/src/server/routes/mcp_route.ts @@ -1,147 +1,150 @@ import { Elysia } from "elysia"; -import tools from "./../../../tools.json"; +import { getMcpTools } from "../lib/mcp_tool_convert"; +// import tools from "./../../../tools.json"; + +var tools = [] as any[]; // ===================== // MCP Protocol Types // ===================== type JSONRPCRequest = { - jsonrpc: "2.0"; - id: string | number; - method: string; - params?: any; + jsonrpc: "2.0"; + id: string | number; + method: string; + params?: any; }; type JSONRPCResponse = { - jsonrpc: "2.0"; - id: string | number; - result?: any; - error?: { - code: number; - message: string; - data?: any; - }; + jsonrpc: "2.0"; + id: string | number; + result?: any; + error?: { + code: number; + message: string; + data?: any; + }; }; // ===================== // Tool Executor // ===================== export async function executeTool( - tool: any, - args: Record = {}, - baseUrl: string + tool: any, + args: Record = {}, + baseUrl: string ) { - const x = tool["x-props"] || {}; + const x = tool["x-props"] || {}; - const method = (x.method || "GET").toUpperCase(); - const path = x.path || `/${tool.name}`; - const url = `${baseUrl}${path}`; + const method = (x.method || "GET").toUpperCase(); + const path = x.path || `/${tool.name}`; + const url = `${baseUrl}${path}`; - const opts: RequestInit = { - method, - headers: { "Content-Type": "application/json" }, - }; + const opts: RequestInit = { + method, + headers: { "Content-Type": "application/json" }, + }; - if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) { - opts.body = JSON.stringify(args || {}); - } + if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) { + opts.body = JSON.stringify(args || {}); + } - const res = await fetch(url, opts); - const contentType = res.headers.get("content-type") || ""; - const data = contentType.includes("application/json") - ? await res.json() - : await res.text(); + const res = await fetch(url, opts); + const contentType = res.headers.get("content-type") || ""; + const data = contentType.includes("application/json") + ? await res.json() + : await res.text(); - return { - success: res.ok, - status: res.status, - method, - path, - data, - }; + return { + success: res.ok, + status: res.status, + method, + path, + data, + }; } // ===================== // MCP Handler (Async) // ===================== async function handleMCPRequestAsync( - request: JSONRPCRequest + request: JSONRPCRequest ): Promise { - const { id, method, params } = request; + const { id, method, params } = request; - switch (method) { - case "initialize": - return { - jsonrpc: "2.0", - id, - result: { - protocolVersion: "2024-11-05", - capabilities: { tools: {} }, - serverInfo: { name: "elysia-mcp-server", version: "1.0.0" }, - }, - }; + switch (method) { + case "initialize": + return { + jsonrpc: "2.0", + id, + result: { + protocolVersion: "2024-11-05", + capabilities: { tools: {} }, + serverInfo: { name: "elysia-mcp-server", version: "1.0.0" }, + }, + }; - case "tools/list": - return { - jsonrpc: "2.0", - id, - result: { - tools: tools.map(({ name, description, inputSchema, ["x-props"]: x }) => ({ - name, - description, - inputSchema, - "x-props": x, - })), - }, - }; + case "tools/list": + return { + jsonrpc: "2.0", + id, + result: { + tools: tools.map(({ name, description, inputSchema, ["x-props"]: x }) => ({ + name, + description, + inputSchema, + "x-props": x, + })), + }, + }; - case "tools/call": { - const toolName = params?.name; - const tool = tools.find((t) => t.name === toolName); + case "tools/call": { + const toolName = params?.name; + const tool = tools.find((t) => t.name === toolName); - if (!tool) { - return { - jsonrpc: "2.0", - id, - error: { code: -32601, message: `Tool '${toolName}' not found` }, - }; - } + if (!tool) { + return { + jsonrpc: "2.0", + id, + error: { code: -32601, message: `Tool '${toolName}' not found` }, + }; + } - try { - const baseUrl = - process.env.BUN_PUBLIC_BASE_URL || "http://localhost:3000"; - const result = await executeTool(tool, params?.arguments || {}, baseUrl); + try { + const baseUrl = + process.env.BUN_PUBLIC_BASE_URL || "http://localhost:3000"; + const result = await executeTool(tool, params?.arguments || {}, baseUrl); - return { - jsonrpc: "2.0", - id, - result: { - content: [ - { - type: "text", - text: JSON.stringify(result, null, 2), - }, - ], - }, - }; - } catch (error: any) { - return { - jsonrpc: "2.0", - id, - error: { code: -32603, message: error.message }, - }; - } + return { + jsonrpc: "2.0", + id, + result: { + content: [ + { + type: "text", + text: JSON.stringify(result, null, 2), + }, + ], + }, + }; + } catch (error: any) { + return { + jsonrpc: "2.0", + id, + error: { code: -32603, message: error.message }, + }; + } + } + + case "ping": + return { jsonrpc: "2.0", id, result: {} }; + + default: + return { + jsonrpc: "2.0", + id, + error: { code: -32601, message: `Method '${method}' not found` }, + }; } - - case "ping": - return { jsonrpc: "2.0", id, result: {} }; - - default: - return { - jsonrpc: "2.0", - id, - error: { code: -32601, message: `Method '${method}' not found` }, - }; - } } // ===================== @@ -150,75 +153,91 @@ async function handleMCPRequestAsync( export const MCPRoute = new Elysia({ tags: ["MCP"] }) - .post("/mcp", async ({ request, set }) => { - set.headers["Content-Type"] = "application/json"; - set.headers["Access-Control-Allow-Origin"] = "*"; + .post("/mcp", async ({ request, set }) => { + if (!tools.length) { + tools = await getMcpTools(); + } + set.headers["Content-Type"] = "application/json"; + set.headers["Access-Control-Allow-Origin"] = "*"; - try { - const body = await request.json(); + try { + const body = await request.json(); - if (!Array.isArray(body)) { - const res = await handleMCPRequestAsync(body); - return res; - } + if (!Array.isArray(body)) { + const res = await handleMCPRequestAsync(body); + return res; + } - const results = await Promise.all( - body.map((req) => handleMCPRequestAsync(req)) - ); - return results; - } catch (error: any) { - set.status = 400; - return { - jsonrpc: "2.0", - id: null, - error: { - code: -32700, - message: "Parse error", - data: error.message, - }, - }; - } - }) + const results = await Promise.all( + body.map((req) => handleMCPRequestAsync(req)) + ); + return results; + } catch (error: any) { + set.status = 400; + return { + jsonrpc: "2.0", + id: null, + error: { + code: -32700, + message: "Parse error", + data: error.message, + }, + }; + } + }) - // Tools list (debug) - .get("/mcp/tools", ({ set }) => { - set.headers["Access-Control-Allow-Origin"] = "*"; - return { - tools: tools.map(({ name, description, inputSchema, ["x-props"]: x }) => ({ - name, - description, - inputSchema, - "x-props": x, - })), - }; - }) + // Tools list (debug) + .get("/mcp/tools", async ({ set }) => { + if (!tools.length) { + tools = await getMcpTools(); + } + set.headers["Access-Control-Allow-Origin"] = "*"; + return { + tools: tools.map(({ name, description, inputSchema, ["x-props"]: x }) => ({ + name, + description, + inputSchema, + "x-props": x, + })), + }; + }) - // MCP status - .get("/mcp/status", ({ set }) => { - set.headers["Access-Control-Allow-Origin"] = "*"; - return { status: "active", timestamp: Date.now() }; - }) + // MCP status + .get("/mcp/status", ({ set }) => { + set.headers["Access-Control-Allow-Origin"] = "*"; + return { status: "active", timestamp: Date.now() }; + }) - // Health check - .get("/health", ({ set }) => { - set.headers["Access-Control-Allow-Origin"] = "*"; - return { status: "ok", timestamp: Date.now(), tools: tools.length }; - }) + // Health check + .get("/health", ({ set }) => { + set.headers["Access-Control-Allow-Origin"] = "*"; + return { status: "ok", timestamp: Date.now(), tools: tools.length }; + }) + .get("/mcp/init", async ({ set }) => { - // CORS - .options("/mcp", ({ set }) => { - set.headers["Access-Control-Allow-Origin"] = "*"; - set.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"; - set.headers["Access-Control-Allow-Headers"] = - "Content-Type,Authorization,X-API-Key"; - set.status = 204; - return ""; - }) - .options("/mcp/tools", ({ set }) => { - set.headers["Access-Control-Allow-Origin"] = "*"; - set.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS"; - set.headers["Access-Control-Allow-Headers"] = - "Content-Type,Authorization,X-API-Key"; - set.status = 204; - return ""; - }); + const _tools = await getMcpTools(); + tools = _tools; + return { + success: true, + message: "MCP initialized", + tools: tools.length, + }; + }) + + // CORS + .options("/mcp", ({ set }) => { + set.headers["Access-Control-Allow-Origin"] = "*"; + set.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"; + set.headers["Access-Control-Allow-Headers"] = + "Content-Type,Authorization,X-API-Key"; + set.status = 204; + return ""; + }) + .options("/mcp/tools", ({ set }) => { + set.headers["Access-Control-Allow-Origin"] = "*"; + set.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS"; + set.headers["Access-Control-Allow-Headers"] = + "Content-Type,Authorization,X-API-Key"; + set.status = 204; + return ""; + });