UNPKG

@n8n/n8n-nodes-langchain

Version:

![Banner image](https://user-images.githubusercontent.com/10284570/173569848-c624317f-42b1-45a6-ab09-f0ea3c247648.png)

412 lines 17.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.McpClientTool = void 0; const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const ai_utilities_1 = require("@n8n/ai-utilities"); const loadOptions_1 = require("./loadOptions"); const utils_1 = require("./utils"); const descriptions_1 = require("../shared/descriptions"); const utils_2 = require("../shared/utils"); const pick_1 = __importDefault(require("lodash/pick")); function getNodeConfig(ctx, itemIndex) { const node = ctx.getNode(); const authentication = ctx.getNodeParameter('authentication', itemIndex); const timeout = ctx.getNodeParameter('options.timeout', itemIndex, 60000); let serverTransport; let endpointUrl; if (node.typeVersion === 1) { serverTransport = 'sse'; endpointUrl = ctx.getNodeParameter('sseEndpoint', itemIndex); } else { serverTransport = ctx.getNodeParameter('serverTransport', itemIndex); endpointUrl = ctx.getNodeParameter('endpointUrl', itemIndex); } const mode = ctx.getNodeParameter('include', itemIndex); const includeTools = ctx.getNodeParameter('includeTools', itemIndex, []); const excludeTools = ctx.getNodeParameter('excludeTools', itemIndex, []); return { authentication, timeout, serverTransport, endpointUrl, mode, includeTools, excludeTools, }; } async function connectAndGetTools(ctx, config) { const node = ctx.getNode(); const { headers } = await (0, utils_2.getAuthHeaders)(ctx, config.authentication); const client = await (0, utils_2.connectMcpClient)({ serverTransport: config.serverTransport, endpointUrl: config.endpointUrl, headers, name: node.type, version: node.typeVersion, onUnauthorized: async (headers) => await (0, utils_2.tryRefreshOAuth2Token)(ctx, config.authentication, headers), }); if (!client.ok) { return { client, mcpTools: null, error: client.error }; } try { const allTools = await (0, utils_2.getAllTools)(client.result); const mcpTools = (0, utils_1.getSelectedTools)({ tools: allTools, mode: config.mode, includeTools: config.includeTools, excludeTools: config.excludeTools, }); return { client: client.result, mcpTools, error: null }; } catch (error) { await client.result.close(); throw error; } } class McpClientTool { constructor() { this.description = { displayName: 'MCP Client Tool', name: 'mcpClientTool', icon: { light: 'file:../mcp.svg', dark: 'file:../mcp.dark.svg', }, group: ['output'], version: [1, 1.1, 1.2], description: 'Connect tools from an MCP Server', defaults: { name: 'MCP Client', }, codex: { categories: ['AI'], subcategories: { AI: ['Tools'], Tools: ['Recommended Tools'], }, alias: ['Model Context Protocol', 'MCP Client'], resources: { primaryDocumentation: [ { url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolmcp/', }, ], }, }, inputs: [], outputs: [{ type: n8n_workflow_1.NodeConnectionTypes.AiTool, displayName: 'Tools' }], credentials: descriptions_1.credentials, properties: [ (0, ai_utilities_1.getConnectionHintNoticeField)([n8n_workflow_1.NodeConnectionTypes.AiAgent]), { displayName: 'SSE Endpoint', name: 'sseEndpoint', type: 'string', description: 'SSE Endpoint of your MCP server', placeholder: 'e.g. https://my-mcp-server.ai/sse', default: '', required: true, displayOptions: { show: { '@version': [1], }, }, }, { displayName: 'Endpoint', name: 'endpointUrl', type: 'string', description: 'Endpoint of your MCP server', placeholder: 'e.g. https://my-mcp-server.ai/mcp', default: '', required: true, displayOptions: { show: { '@version': [{ _cnd: { gte: 1.1 } }], }, }, }, (0, descriptions_1.transportSelect)({ defaultOption: 'sse', displayOptions: { show: { '@version': [1.1], }, }, }), (0, descriptions_1.transportSelect)({ defaultOption: 'httpStreamable', displayOptions: { show: { '@version': [{ _cnd: { gte: 1.2 } }], }, }, }), { displayName: 'Authentication', name: 'authentication', type: 'options', options: [ { name: 'Bearer Auth', value: 'bearerAuth', }, { name: 'Header Auth', value: 'headerAuth', }, { name: 'None', value: 'none', }, ], default: 'none', description: 'The way to authenticate with your endpoint', displayOptions: { show: { '@version': [{ _cnd: { lt: 1.2 } }], }, }, }, { displayName: 'Authentication', name: 'authentication', type: 'options', options: [ { name: 'Bearer Auth', value: 'bearerAuth', }, { name: 'Header Auth', value: 'headerAuth', }, { name: 'MCP OAuth2', value: 'mcpOAuth2Api', }, { name: 'Multiple Headers Auth', value: 'multipleHeadersAuth', }, { name: 'None', value: 'none', }, ], default: 'none', description: 'The way to authenticate with your endpoint', displayOptions: { show: { '@version': [{ _cnd: { gte: 1.2 } }], }, }, }, { displayName: 'Credentials', name: 'credentials', type: 'credentials', default: '', displayOptions: { show: { authentication: ['headerAuth', 'bearerAuth', 'mcpOAuth2Api', 'multipleHeadersAuth'], }, }, }, { displayName: 'Tools to Include', name: 'include', type: 'options', description: 'How to select the tools you want to be exposed to the AI Agent', default: 'all', options: [ { name: 'All', value: 'all', description: 'Also include all unchanged fields from the input', }, { name: 'Selected', value: 'selected', description: 'Also include the tools listed in the parameter "Tools to Include"', }, { name: 'All Except', value: 'except', description: 'Exclude the tools listed in the parameter "Tools to Exclude"', }, ], }, { displayName: 'Tools to Include', name: 'includeTools', type: 'multiOptions', default: [], description: 'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getTools', loadOptionsDependsOn: ['sseEndpoint'], }, displayOptions: { show: { include: ['selected'], }, }, }, { displayName: 'Tools to Exclude', name: 'excludeTools', type: 'multiOptions', default: [], description: 'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>', typeOptions: { loadOptionsMethod: 'getTools', }, displayOptions: { show: { include: ['except'], }, }, }, { displayName: 'Options', name: 'options', placeholder: 'Add Option', description: 'Additional options to add', type: 'collection', default: {}, options: [ { displayName: 'Timeout', name: 'timeout', type: 'number', typeOptions: { minValue: 1, }, default: 60000, description: 'Time in ms to wait for tool calls to finish', }, ], }, ], }; this.methods = { loadOptions: { getTools: loadOptions_1.getTools, }, }; } async supplyData(itemIndex) { const node = this.getNode(); const config = getNodeConfig(this, itemIndex); const setError = (error) => { this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, itemIndex, error); throw error; }; const signal = this.getExecutionCancelSignal(); if (signal?.aborted) { return setError(new n8n_workflow_1.NodeOperationError(node, 'Execution was cancelled', { itemIndex })); } const { client, mcpTools, error } = await connectAndGetTools(this, config); if (error) { this.logger.error('McpClientTool: Failed to connect to MCP Server', { error }); return setError((0, utils_2.mapToNodeOperationError)(node, error)); } this.logger.debug('McpClientTool: Successfully connected to MCP Server'); if (!mcpTools?.length) { await client.close(); return setError(new n8n_workflow_1.NodeOperationError(node, 'MCP Server returned no tools', { itemIndex, description: 'Connected successfully to your MCP server but it returned an empty list of tools.', })); } try { const tools = mcpTools.map((tool) => { const prefixedName = (0, utils_1.buildMcpToolName)(node.name, tool.name); return (0, ai_utilities_1.logWrapper)((0, utils_1.mcpToolToDynamicTool)({ ...tool, name: prefixedName }, (0, utils_1.createCallTool)(tool.name, client, config.timeout, (errorMessage) => { const error = new n8n_workflow_1.NodeOperationError(node, errorMessage, { itemIndex }); void this.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, itemIndex, error); this.logger.error(`McpClientTool: Tool "${tool.name}" failed to execute`, { error, }); }, () => this.getExecutionCancelSignal())), this); }); this.logger.debug(`McpClientTool: Connected to MCP Server with ${tools.length} tools`); const toolkit = new n8n_core_1.StructuredToolkit(tools); return { response: toolkit, closeFunction: async () => await client.close() }; } catch (e) { await client.close(); throw e; } } async execute() { const node = this.getNode(); const items = this.getInputData(); const returnData = []; for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { const signal = this.getExecutionCancelSignal(); if (signal?.aborted) { throw new n8n_workflow_1.NodeOperationError(node, 'Execution was cancelled', { itemIndex }); } const item = items[itemIndex]; const config = getNodeConfig(this, itemIndex); const { client, mcpTools, error } = await connectAndGetTools(this, config); if (error) { throw new n8n_workflow_1.NodeOperationError(node, error.error, { itemIndex }); } try { if (!mcpTools?.length) { throw new n8n_workflow_1.NodeOperationError(node, 'MCP Server returned no tools', { itemIndex }); } if (!item.json.tool || typeof item.json.tool !== 'string') { throw new n8n_workflow_1.NodeOperationError(node, 'Tool name not found in item.json.tool or item.tool', { itemIndex, }); } const toolName = item.json.tool; for (const tool of mcpTools) { const prefixedName = (0, utils_1.buildMcpToolName)(node.name, tool.name); if (toolName === prefixedName) { const { tool: _, ...toolArguments } = item.json; const schema = tool.inputSchema; const sanitizedToolArguments = schema.additionalProperties !== true ? (0, pick_1.default)(toolArguments, Object.keys(schema.properties ?? {})) : toolArguments; const params = { name: tool.name, arguments: sanitizedToolArguments, }; const result = await client.callTool(params, types_js_1.CallToolResultSchema, { timeout: config.timeout, signal: this.getExecutionCancelSignal(), }); returnData.push({ json: { response: result.content, ...((0, utils_2.isStructuredContent)(result.structuredContent) && { structuredContent: result.structuredContent, }), }, pairedItem: { item: itemIndex, }, }); } } } finally { await client.close(); } } return [returnData]; } } exports.McpClientTool = McpClientTool; //# sourceMappingURL=McpClientTool.node.js.map