diff --git a/src/server/routes/mcp_route.ts b/src/server/routes/mcp_route.ts index b1d5cf6..18061fb 100644 --- a/src/server/routes/mcp_route.ts +++ b/src/server/routes/mcp_route.ts @@ -1,168 +1,103 @@ -// src/server/routes/mcp_route.ts -import { Elysia, t } from "elysia"; - -function now() { - return new Date().toISOString(); -} +// src/routes/mcp_route.ts +import { Elysia } from "elysia"; export const MCPRoute = new Elysia({ - prefix: "/mcp-server", - tags: ["mcp-server"], + prefix: "/mcp-server", }) + .get("/mcp", ({ set, query }) => { + // n8n akan mengirim method & params lewat query seperti: + // ?method=tools/list atau ?method=tools/sayHello&name=Malik -// ---------- GET: handshake (n8n mengharapkan GET terlebih dahulu) ---------- -.get("/mcp", ({ set, headers }) => { - // disable upstream buffering for nginx (X-Accel-Buffering) and proxies - set.headers["Content-Type"] = "application/json"; - set.headers["Cache-Control"] = "no-cache, no-transform"; - set.headers["X-Accel-Buffering"] = "no"; - set.headers["Connection"] = "keep-alive"; + const id = query.id ?? 1; + const method = query.method as string; + const params = query; - // If client explicitly accepts SSE (some clients use SSE fallback), return SSE style - const accept = (headers["accept"] || "").toString(); - if (accept.includes("text/event-stream")) { - // Return a minimal SSE handshake (keep connection open) - const sseStream = new ReadableStream({ - start(controller) { - // send an initial comment to avoid some proxies closing immediately - controller.enqueue(": mcp-sse\n\n"); - // also send a first data event containing protocol info - controller.enqueue( - `data: ${JSON.stringify({ - jsonrpc: "2.0", - result: { - protocol: "2024-11-05", - capabilities: { "tools/list": true, "tools/call": true }, + // ✅ Header wajib agar n8n mau streaming + set.headers["Content-Type"] = "application/json; charset=utf-8"; + set.headers["Cache-Control"] = "no-cache, no-transform"; + set.headers["Connection"] = "keep-alive"; + set.headers["Transfer-Encoding"] = "chunked"; + set.headers["X-Accel-Buffering"] = "no"; // Untuk Cloudflare & Nginx + + const stream = new ReadableStream({ + async start(controller) { + // --- Handle mcp/version --- + if (method === "mcp/version") { + controller.enqueue( + JSON.stringify({ + jsonrpc: "2.0", + id, + result: { + protocol: "2024-11-05", + capabilities: { + "tools/list": true, + "tools/call": true, + }, + }, + }) + "\n" + ); + return; // Tidak ditutup → tetap open + } + + // --- Handle tools/list --- + if (method === "tools/list") { + controller.enqueue( + JSON.stringify({ + jsonrpc: "2.0", + id, + result: [ + { + name: "sayHello", + description: "Greets a user", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Your name" }, + }, + }, + }, + ], + }) + "\n" + ); + return; + } + + // --- Handle tools/sayHello (progress streaming) --- + if (method === "tools/sayHello") { + controller.enqueue( + JSON.stringify({ + jsonrpc: "2.0", + id, + result: { status: "Processing..." }, + }) + "\n" + ); + await Bun.sleep(500); + + controller.enqueue( + JSON.stringify({ + jsonrpc: "2.0", + id, + result: { message: `Hello ${params?.name || "User"}` }, + }) + "\n" + ); + return; + } + + // --- Jika method tidak dikenal --- + controller.enqueue( + JSON.stringify({ + jsonrpc: "2.0", + id, + error: { + code: -32601, + message: `Method "${method}" not found`, + }, + }) + "\n" + ); }, - })}\n\n` - ); - // keep stream open; do NOT close here (client expects streaming) - }, + }); + + return new Response(stream); }); - return new Response(sseStream, { - headers: { - "Content-Type": "text/event-stream; charset=utf-8", - "Cache-Control": "no-cache, no-transform", - "X-Accel-Buffering": "no", - Connection: "keep-alive", - }, - }); - } - - // Default: return JSON handshake (n8n expects this on GET) - return { - jsonrpc: "2.0", - result: { - protocol: "2024-11-05", - capabilities: { - "tools/list": true, - "tools/call": true, - }, - }, - }; -}) - -// ---------- POST: HTTP Streamable transport (chunked) ---------- -.post( - "/mcp", - ({ body, set, headers }) => { - const { id, method, params } = body as any; - - // Important response headers to help proxies and n8n - set.headers["Content-Type"] = "application/json; charset=utf-8"; - set.headers["Transfer-Encoding"] = "chunked"; - set.headers["Connection"] = "keep-alive"; - set.headers["Cache-Control"] = "no-cache, no-transform"; - set.headers["X-Accel-Buffering"] = "no"; // nginx: disable buffering - // optional helpful header for some proxies - set.headers["X-Content-Type-Options"] = "nosniff"; - - const stream = new ReadableStream({ - async start(controller) { - // send a tiny initial chunk ASAP so client recognizes streaming - controller.enqueue(JSON.stringify({ jsonrpc: "2.0", id, result: { _ping: now() } }) + "\n"); - - // mcp/version might also be called via POST; respond immediately - if (method === "mcp/version") { - controller.enqueue( - JSON.stringify({ - jsonrpc: "2.0", - id, - result: { - protocol: "2024-11-05", - capabilities: { "tools/list": true, "tools/call": true }, - }, - }) + "\n" - ); - // keep a short delay then close; n8n will proceed - await Bun.sleep(50); - controller.close(); - return; - } - - // tools/list - if (method === "tools/list") { - controller.enqueue( - JSON.stringify({ - jsonrpc: "2.0", - id, - result: [ - { - name: "sayHello", - description: "Greets user", - inputSchema: { type: "object", properties: { name: { type: "string" } } }, - }, - ], - }) + "\n" - ); - await Bun.sleep(50); - controller.close(); - return; - } - - // tools/call standard format: params: { name: "toolName", arguments: { ... } } - if (method === "tools/call" && params?.name === "sayHello") { - // stage: processing - controller.enqueue(JSON.stringify({ jsonrpc: "2.0", id, result: { status: "Processing..." } }) + "\n"); - await Bun.sleep(300); - - // final result - controller.enqueue( - JSON.stringify({ - jsonrpc: "2.0", - id, - result: { message: `Hello ${params?.arguments?.name || "User"} 👋` }, - }) + "\n" - ); - await Bun.sleep(50); - controller.close(); - return; - } - - // fallback: method not found - controller.enqueue( - JSON.stringify({ - jsonrpc: "2.0", - id, - error: { code: -32601, message: `Method '${method}' not found` }, - }) + "\n" - ); - await Bun.sleep(50); - controller.close(); - }, - }); - - return new Response(stream); - }, - { - body: t.Object({ - jsonrpc: t.Optional(t.String()), - id: t.Optional(t.Union([t.String(), t.Number()])), - method: t.String(), - params: t.Optional(t.Record(t.String(), t.Any())), - }), - } -); - export default MCPRoute; diff --git a/x.sh b/x.sh index 07b98db..95d4490 100644 --- a/x.sh +++ b/x.sh @@ -1,2 +1,2 @@ -curl -N -X GET https://n8n.wibudev.com/mcp/fd665648-b38d-4bee-9ab8-11ca0cd83d0d \ - -H "Content-Type: application/json" +curl -N -v -X GET "https://n8n.wibudev.com/mcp/fd665648-b38d-4bee-9ab8-11ca0cd83d0d" +