UNPKG

node-red-contrib-processcube-mcp

Version:
185 lines (150 loc) 6.49 kB
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { isInitializeRequest } = require('@modelcontextprotocol/sdk/types.js'); const { InMemoryEventStore } = require('@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js'); const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { randomUUID } = require('crypto'); const { z } = require('zod'); const EventEmitter = require('node:events'); module.exports = function (RED) { function MCPToolInput(config) { RED.nodes.createNode(this, config); var node = this; node.config = config; node.eventEmitter = new EventEmitter(); } RED.nodes.registerType('mcp-tool-input', MCPToolInput); async function getServer() { const server = new McpServer( { name: "ProcessCube LowCode", version: "1.0.0" }, { capabilities: { tools: { } } }); RED.nodes.eachNode(function(n) { if (n.type === "mcp-tool-input") { const node = RED.nodes.getNode(n.id); server.tool( node.config.name, node.config.description, { data: z.any().describe('Daten vom Benutzer, welche das Tool benötigt werden. Gib sie immer als Objekt (z.B. { argument1: wert1, argument2: wert2}) an.') }, async ({data})=> { return new Promise((resolve, reject) => { const msgId = RED.util.generateId(); node.eventEmitter.once(`finish-${msgId}`, (result) => { resolve({ content: [{ type: "text", text: result }] }) }); node.eventEmitter.once(`error-${msgId}`, (result) => { resolve({ content: [{ type: "text", text: `Error: ${result}` }] }) }); const payload = isStrictObject(data) ? data : JSON.parse(data); let msg = { payload: payload, _msgid: msgId, mcpToolInputNodeId: node.id } node.send(msg); }); } ); } }); return server; } const transports = {}; RED.httpNode.all("/mcp", async function (req, res) { console.log(`Received ${req.method} request to /mcp`); try { // Check for existing session ID const sessionId = req.headers['mcp-session-id']; let transport; if (sessionId && transports[sessionId]) { // Check if the transport is of the correct type const existingTransport = transports[sessionId]; if (existingTransport instanceof StreamableHTTPServerTransport) { // Reuse existing transport transport = existingTransport; } else { // Transport exists but is not a StreamableHTTPServerTransport (could be SSEServerTransport) res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: Session exists but uses a different transport protocol', }, id: null, }); return; } } else if (!sessionId && req.method === 'POST' && isInitializeRequest(req.body)) { const eventStore = new InMemoryEventStore(); transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), eventStore, // Enable resumability onsessioninitialized: (sessionId) => { // Store the transport by session ID when session is initialized console.log(`StreamableHTTP session initialized with ID: ${sessionId}`); transports[sessionId] = transport; } }); console.log(`Transport: ${transport}`); // Set up onclose handler to clean up transport when closed transport.onclose = () => { const sid = transport.sessionId; if (sid && transports[sid]) { console.log(`Transport closed for session ${sid}, removing from transports map`); delete transports[sid]; } }; console.log('Vor dem Server') const server = await getServer(); // Connect the transport to the MCP server await server.connect(transport); console.log('Nach dem Server') } else { // Invalid request - no session ID or not initialization request res.status(400).json({ jsonrpc: '2.0', error: { code: -32000, message: 'Bad Request: No valid session ID provided', }, id: null, }); return; } console.log(`Vor dem handleRequest: ${JSON.stringify(req.body)}`); // Handle the request with the transport await transport.handleRequest(req, res, req.body); console.log('Nach dem handleRequest') } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); function isStrictObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof String); } };