UNPKG

@it-era/n8n-nodes-mcp

Version:
488 lines 23 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.McpClient = void 0; const n8n_workflow_1 = require("n8n-workflow"); const tools_1 = require("@langchain/core/tools"); const zod_1 = require("zod"); const zod_to_json_schema_1 = require("zod-to-json-schema"); const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js"); class McpClient { constructor() { this.description = { displayName: 'MCP Client', name: 'mcpClient', icon: 'file:mcpClient.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"]}}', description: 'Use MCP client', defaults: { name: 'MCP Client', }, inputs: [{ type: "main" }], outputs: [{ type: "main" }], usableAsTool: true, credentials: [ { name: 'mcpClientApi', required: false, displayOptions: { show: { connectionType: ['cmd'], }, }, }, { name: 'mcpClientSseApi', required: false, displayOptions: { show: { connectionType: ['sse'], }, }, }, ], properties: [ { displayName: 'Connection Type', name: 'connectionType', type: 'options', options: [ { name: 'Command Line (STDIO)', value: 'cmd', }, { name: 'Server-Sent Events (SSE)', value: 'sse', }, ], default: 'cmd', description: 'Choose the transport type to connect to MCP server', }, { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Execute Tool', value: 'executeTool', description: 'Execute a specific tool', action: 'Execute a tool', }, { name: 'Get Prompt', value: 'getPrompt', description: 'Get a specific prompt template', action: 'Get a prompt template', }, { name: 'List Prompts', value: 'listPrompts', description: 'Get available prompts', action: 'List available prompts', }, { name: 'List Resources', value: 'listResources', description: 'Get a list of available resources', action: 'List available resources', }, { name: 'List Tools', value: 'listTools', description: 'Get available tools', action: 'List available tools', }, { name: 'Read Resource', value: 'readResource', description: 'Read a specific resource by URI', action: 'Read a resource', }, ], default: 'listTools', required: true, }, { displayName: 'Resource URI', name: 'resourceUri', type: 'string', required: true, displayOptions: { show: { operation: ['readResource'], }, }, default: '', description: 'URI of the resource to read', }, { displayName: 'Tool Name', name: 'toolName', type: 'string', required: true, displayOptions: { show: { operation: ['executeTool'], }, }, default: '', description: 'Name of the tool to execute', }, { displayName: 'Tool Parameters', name: 'toolParameters', type: 'json', required: true, displayOptions: { show: { operation: ['executeTool'], }, }, default: '{}', description: 'Parameters to pass to the tool in JSON format', }, { displayName: 'Prompt Name', name: 'promptName', type: 'string', required: true, displayOptions: { show: { operation: ['getPrompt'], }, }, default: '', description: 'Name of the prompt template to get', }, ], }; } async execute() { var _a; const returnData = []; const operation = this.getNodeParameter('operation', 0); let connectionType = 'cmd'; try { connectionType = this.getNodeParameter('connectionType', 0); } catch (error) { this.logger.debug('ConnectionType parameter not found, using default "cmd" transport'); } try { let transport; if (connectionType === 'sse') { const sseCredentials = await this.getCredentials('mcpClientSseApi'); const { SSEClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/sse.js'))); const sseUrl = sseCredentials.sseUrl; const messagesPostEndpoint = sseCredentials.messagesPostEndpoint || ''; const headers = {}; if (sseCredentials.headers) { const headerLines = sseCredentials.headers.split('\n'); for (const line of headerLines) { const [name, value] = line.split(':', 2); if (name && value) { headers[name.trim()] = value.trim(); } } } transport = new SSEClientTransport(new URL(sseUrl), { eventSourceInit: { headers }, requestInit: { headers, ...(messagesPostEndpoint ? { endpoint: new URL(messagesPostEndpoint), } : {}), }, }); this.logger.debug(`Created SSE transport for MCP client URL: ${sseUrl}`); if (messagesPostEndpoint) { this.logger.debug(`Using custom POST endpoint: ${messagesPostEndpoint}`); } } else { const cmdCredentials = await this.getCredentials('mcpClientApi'); const env = { PATH: process.env.PATH || '', }; this.logger.debug(`Original PATH: ${process.env.PATH}`); if (cmdCredentials.environments) { const envPairs = cmdCredentials.environments.split(/[,\n\s]+/); for (const pair of envPairs) { const trimmedPair = pair.trim(); if (trimmedPair) { const equalsIndex = trimmedPair.indexOf('='); if (equalsIndex > 0) { const name = trimmedPair.substring(0, equalsIndex).trim(); const value = trimmedPair.substring(equalsIndex + 1).trim(); if (name && value !== undefined) { env[name] = value; } } } } } for (const key in process.env) { if (key.startsWith('MCP_') && process.env[key]) { const envName = key.substring(4); env[envName] = process.env[key]; } } transport = new stdio_js_1.StdioClientTransport({ command: cmdCredentials.command, args: ((_a = cmdCredentials.args) === null || _a === void 0 ? void 0 : _a.split(' ')) || [], env: env, }); this.logger.debug(`Transport created for MCP client command: ${cmdCredentials.command}, PATH: ${env.PATH}`); } transport.onerror = (error) => { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Transport error: ${error}`); }; const client = new index_js_1.Client({ name: `${McpClient.name}-client`, version: '1.0.0', }, { capabilities: { prompts: {}, resources: {}, tools: {}, }, }); try { await client.connect(transport); this.logger.debug('Client connected to MCP server'); } catch (connectionError) { this.logger.error(`MCP client connection error: ${connectionError.message}`); throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to connect to MCP server: ${connectionError.message}`); } switch (operation) { case 'listResources': { const resources = await client.listResources(); returnData.push({ json: { resources }, }); break; } case 'readResource': { const uri = this.getNodeParameter('resourceUri', 0); const resource = await client.readResource({ uri, }); returnData.push({ json: { resource }, }); break; } case 'listTools': { const rawTools = await client.listTools(); const tools = Array.isArray(rawTools) ? rawTools : Array.isArray(rawTools === null || rawTools === void 0 ? void 0 : rawTools.tools) ? rawTools.tools : Object.values((rawTools === null || rawTools === void 0 ? void 0 : rawTools.tools) || {}); if (!tools.length) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No tools found from MCP client'); } const aiTools = tools.map((tool) => { var _a; const paramSchema = ((_a = tool.inputSchema) === null || _a === void 0 ? void 0 : _a.properties) ? zod_1.z.object(Object.entries(tool.inputSchema.properties).reduce((acc, [key, prop]) => { var _a, _b, _c, _d, _e; let zodType; switch (prop.type) { case 'string': zodType = zod_1.z.string(); break; case 'number': zodType = zod_1.z.number(); break; case 'integer': zodType = zod_1.z.number().int(); break; case 'boolean': zodType = zod_1.z.boolean(); break; case 'array': if (((_a = prop.items) === null || _a === void 0 ? void 0 : _a.type) === 'string') { zodType = zod_1.z.array(zod_1.z.string()); } else if (((_b = prop.items) === null || _b === void 0 ? void 0 : _b.type) === 'number') { zodType = zod_1.z.array(zod_1.z.number()); } else if (((_c = prop.items) === null || _c === void 0 ? void 0 : _c.type) === 'boolean') { zodType = zod_1.z.array(zod_1.z.boolean()); } else { zodType = zod_1.z.array(zod_1.z.any()); } break; case 'object': zodType = zod_1.z.record(zod_1.z.string(), zod_1.z.any()); break; default: zodType = zod_1.z.any(); } if (prop.description) { zodType = zodType.describe(prop.description); } if (!((_e = (_d = tool.inputSchema) === null || _d === void 0 ? void 0 : _d.required) === null || _e === void 0 ? void 0 : _e.includes(key))) { zodType = zodType.optional(); } return { ...acc, [key]: zodType, }; }, {})) : zod_1.z.object({}); return new tools_1.DynamicStructuredTool({ name: tool.name, description: tool.description || `Execute the ${tool.name} tool`, schema: paramSchema, func: async (params) => { try { const result = await client.callTool({ name: tool.name, arguments: params, }); return typeof result === 'object' ? JSON.stringify(result) : String(result); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute ${tool.name}: ${error.message}`); } }, }); }); returnData.push({ json: { tools: aiTools.map((t) => ({ name: t.name, description: t.description, schema: (0, zod_to_json_schema_1.zodToJsonSchema)(t.schema || {}), })), }, }); break; } case 'executeTool': { const toolName = this.getNodeParameter('toolName', 0); let toolParams; try { const rawParams = this.getNodeParameter('toolParameters', 0); this.logger.debug(`Raw tool parameters: ${JSON.stringify(rawParams)}`); if (rawParams === undefined || rawParams === null) { toolParams = {}; } else if (typeof rawParams === 'string') { if (!rawParams || rawParams.trim() === '') { toolParams = {}; } else { toolParams = JSON.parse(rawParams); } } else if (typeof rawParams === 'object') { toolParams = rawParams; } else { try { toolParams = JSON.parse(JSON.stringify(rawParams)); } catch (parseError) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid parameter type: ${typeof rawParams}`); } } if (typeof toolParams !== 'object' || toolParams === null || Array.isArray(toolParams)) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Tool parameters must be a JSON object'); } } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse tool parameters: ${error.message}. Make sure the parameters are valid JSON.`); } try { const availableTools = await client.listTools(); const toolsList = Array.isArray(availableTools) ? availableTools : Array.isArray(availableTools === null || availableTools === void 0 ? void 0 : availableTools.tools) ? availableTools.tools : Object.values((availableTools === null || availableTools === void 0 ? void 0 : availableTools.tools) || {}); const toolExists = toolsList.some((tool) => tool.name === toolName); if (!toolExists) { const availableToolNames = toolsList.map((t) => t.name).join(', '); throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Tool '${toolName}' does not exist. Available tools: ${availableToolNames}`); } this.logger.debug(`Executing tool: ${toolName} with params: ${JSON.stringify(toolParams)}`); const result = await client.callTool({ name: toolName, arguments: toolParams, }); this.logger.debug(`Tool executed successfully: ${JSON.stringify(result)}`); returnData.push({ json: { result }, }); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute tool '${toolName}': ${error.message}`); } break; } case 'listPrompts': { const prompts = await client.listPrompts(); returnData.push({ json: { prompts }, }); break; } case 'getPrompt': { const promptName = this.getNodeParameter('promptName', 0); const prompt = await client.getPrompt({ name: promptName, }); returnData.push({ json: { prompt }, }); break; } default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation ${operation} not supported`); } return [returnData]; } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute operation: ${error.message}`); } } } exports.McpClient = McpClient; //# sourceMappingURL=McpClient.node.js.map