tambahan
This commit is contained in:
@@ -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));
|
||||
}
|
||||
@@ -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<string, any> = {},
|
||||
baseUrl: string
|
||||
tool: any,
|
||||
args: Record<string, any> = {},
|
||||
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<JSONRPCResponse> {
|
||||
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 "";
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user