UNPKG

mcp-sample-bip

Version:

Contoh server MCP sederhana untuk Flowise.

183 lines (182 loc) 7.66 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import _ from 'lodash'; import { z } from 'zod'; const serverName = 'mcp-sample-bip'; const serverVersion = '0.1.0'; const BASE_URL = process.env.JENNA_MCP_BASE_URL ?? ""; const mcpServer = new McpServer({ name: serverName, version: serverVersion, }, { instructions: 'Gunakan tool yang tersedia untuk mencoba integrasi awal Model Context Protocol di Flowise.', }); mcpServer.registerTool('greet_user', { description: 'Memberikan sapaan ramah berdasarkan nama yang diberikan.', inputSchema: { name: z .string() .min(1, 'Nama tidak boleh kosong') .describe('Nama yang ingin disapa') .optional(), }, }, async ({ name }) => { const target = name?.trim() ?? 'Flowise'; return { content: [ { type: 'text', text: `Halo, ${target}! MCP server mcp-sample-bip siap membantu.`, }, ], }; }); mcpServer.registerTool('get_server_time', { description: 'Mengembalikan waktu server saat ini dalam format ISO.', }, async () => { return { content: [ { type: 'text', text: `Waktu server sekarang: ${new Date().toISOString()} JENNA_MCP_API_TOKEN = ${process.env.JENNA_MCP_API_TOKEN}`, }, ], }; }); async function main() { console.log("loading mcp.json"); const mcpJsonUrl = `${BASE_URL}/docs/json`; const res = await fetch(mcpJsonUrl); const mcpJson = await res.json(); await registerMcpToolsFromJson(mcpServer, mcpJson); const transport = new StdioServerTransport(); await mcpServer.connect(transport); console.error(`${serverName} v${serverVersion} berjalan melalui STDIO transport.`); const shutdown = async () => { console.error('Menutup MCP server...'); await mcpServer.close(); process.exit(0); }; process.on('SIGINT', () => { void shutdown(); }); process.on('SIGTERM', () => { void shutdown(); }); } void main().catch((error) => { console.error('Gagal menjalankan MCP server:', error); process.exit(1); }); /** * Mendaftarkan seluruh endpoint dari file mcp.json sebagai MCP Tools. * Otomatis membuat input schema & handler HTTP. */ export async function registerMcpToolsFromJson(mcpServer, openApiJson) { if (!openApiJson?.paths || typeof openApiJson.paths !== "object") { console.error("❌ openapi.json tidak valid — tidak ada field 'paths'."); return; } const baseUrl = BASE_URL; const paths = openApiJson.paths; for (const [path, methods] of Object.entries(paths)) { if (typeof methods !== "object" || methods === null) continue; for (const [method, spec] of Object.entries(methods)) { const operation = spec; const summary = `${operation.summary ?? ""} \n ${operation.description ?? ""} \n`; const operationId = `${path}`; const tags = operation.tags ?? ["default"]; const tag = tags[0]; // ambil tag utama sebagai kategori // --- Ambil parameter dari requestBody atau parameters const schemaShape = {}; const requiredParams = []; // Dari parameters[] if (Array.isArray(operation.parameters)) { for (const p of operation.parameters) { const name = p.name; const type = p.schema?.type ?? "string"; let zType = z.string().describe(`${name} (${type})`); if (p.required) zType = zType.min(1, `${name} wajib diisi`); schemaShape[name] = zType; if (p.required) requiredParams.push(name); } } // Dari requestBody.content const reqBody = operation.requestBody?.content ?? {}; const contentSchema = reqBody["application/json"]?.schema ?? reqBody["multipart/form-data"]?.schema ?? reqBody["text/plain"]?.schema; if (contentSchema?.properties) { for (const [key, val] of Object.entries(contentSchema.properties)) { const type = val.type ?? "string"; let zType = z.string().describe(`${key} (${type})`); if ((contentSchema.required ?? []).includes(key)) zType = zType.min(1, `${key} wajib diisi`); schemaShape[key] = zType; } } const zodSchema = Object.keys(schemaShape).length > 0 ? z.object(schemaShape) : z.object({}).optional(); const toolId = `${_.snakeCase(operationId)}`; let _url = ""; // --- ⚙️ Registrasi tool ke MCP mcpServer.registerTool(toolId, { description: summary, inputSchema: schemaShape, }, async (input) => { try { const validatedInput = Object.keys(schemaShape).length ? zodSchema.parse(input) : input ?? {}; // Ganti {param} di path let finalPath = path; for (const key of Object.keys(validatedInput ?? {})) { if (finalPath.includes(`{${key}}`)) { finalPath = finalPath.replace(`{${key}}`, encodeURIComponent(validatedInput?.[key] ?? '')); } } const url = new URL(finalPath, baseUrl).toString(); _url = url; const options = { method: method.toUpperCase(), headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.JENNA_MCP_API_TOKEN ?? ""}`, }, }; // Tambah body kalau method-nya mengizinkan if (["POST", "PUT", "PATCH", "DELETE"].includes(method.toUpperCase()) && validatedInput) { options.body = JSON.stringify(validatedInput); } const res = await fetch(url, options); const contentType = res.headers.get("content-type") || ""; const isJson = contentType.includes("application/json"); const result = isJson ? await res.json() : await res.text(); return { content: [ { type: "text", text: `✅ [${method.toUpperCase()}] ${url}\nResponse:\n${JSON.stringify(result, null, 2)}`, }, ], }; } catch (err) { return { content: [ { type: "text", text: `❌ Gagal menjalankan ${toolId} :\n${err.message} \nURL= ${_url}`, }, ], }; } }); console.log(`⚙️ ${toolId} \n ${summary} \n`); } } console.log("🚀 Semua tool MCP berhasil diregistrasi dari OpenAPI JSON"); }