mcp-sample-bip
Version:
Contoh server MCP sederhana untuk Flowise.
183 lines (182 loc) • 7.66 kB
JavaScript
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");
}