UNPKG

@n8n/n8n-nodes-langchain

Version:
220 lines 9.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildMcpToolkit = buildMcpToolkit; exports.executeMcpTool = executeMcpTool; exports.loadMcpToolOptions = loadMcpToolOptions; const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const ai_utilities_1 = require("@n8n/ai-utilities"); const di_1 = require("@n8n/di"); const pick_1 = __importDefault(require("lodash/pick")); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const McpClientsManager_1 = require("./McpClientsManager"); const utils_1 = require("./utils"); const utils_2 = require("../McpClientTool/utils"); async function connectAndGetTools(ctx, config) { const client = await (0, utils_1.connectMcpClientForCredential)(ctx, { authentication: config.authentication, serverTransport: config.transport, endpointUrl: config.endpointUrl, registryCredential: config.registryCredential, surface: 'MCP Client Tool', signal: ctx.getExecutionCancelSignal?.(), }); if (!client.ok) { return { client, mcpTools: null, error: client.error }; } try { const allTools = await (0, utils_1.getAllTools)(client.result); const mcpTools = (0, utils_2.getSelectedTools)({ tools: allTools, mode: config.toolFilter.mode, includeTools: config.toolFilter.includeTools, excludeTools: config.toolFilter.excludeTools, }); return { client: client.result, mcpTools, error: null }; } catch (error) { await client.result.close(); throw error; } } async function buildMcpToolkit(ctx, itemIndex, config) { const node = ctx.getNode(); const setError = (error) => { ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, itemIndex, error); throw error; }; const signal = ctx.getExecutionCancelSignal(); if (signal?.aborted) { return setError(new n8n_workflow_1.NodeOperationError(node, 'Execution was cancelled', { itemIndex })); } const { client, mcpTools, error } = await connectAndGetTools(ctx, config); if (error) { ctx.logger.error('MCP client: Failed to connect to MCP Server', { error }); return setError((0, utils_1.mapToNodeOperationError)(node, error)); } ctx.logger.debug('MCP client: 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_2.buildMcpToolName)(node.name, tool.name); return (0, ai_utilities_1.logWrapper)((0, utils_2.mcpToolToDynamicTool)({ ...tool, name: prefixedName }, (0, utils_2.createCallTool)(tool.name, client, config.timeout, (errorMessage) => { const callError = new n8n_workflow_1.NodeOperationError(node, errorMessage, { itemIndex }); void ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiTool, itemIndex, callError); ctx.logger.error(`MCP client: Tool "${tool.name}" failed to execute`, { error: callError, }); }, () => ctx.getExecutionCancelSignal())), ctx); }); ctx.logger.debug(`MCP client: 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 function connectOrThrow(ctx, config, itemIndex) { const node = ctx.getNode(); const { client, mcpTools, error } = await connectAndGetTools(ctx, config); if (error) { throw new n8n_workflow_1.NodeOperationError(node, error.error, { itemIndex }); } if (!mcpTools?.length) { await client.close(); throw new n8n_workflow_1.NodeOperationError(node, 'MCP Server returned no tools', { itemIndex }); } return { client, mcpTools }; } async function runToolCall(opts) { const { ctx, node, item, mcpTools, client, timeout, itemIndex, returnData } = opts; 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_2.buildMcpToolName)(node.name, tool.name); if (toolName !== prefixedName) continue; 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 result = await client.callTool({ name: tool.name, arguments: sanitizedToolArguments }, types_js_1.CallToolResultSchema, { timeout, signal: ctx.getExecutionCancelSignal(), }); if (node.typeVersion >= 1.3 && result.isError) { const errorMessage = (0, utils_2.getErrorDescriptionFromToolCall)(result) ?? `Tool "${tool.name}" returned an error`; throw new n8n_workflow_1.NodeOperationError(node, errorMessage, { itemIndex }); } returnData.push({ json: { response: result.content, ...((0, utils_1.isStructuredContent)(result.structuredContent) && { structuredContent: result.structuredContent, }), }, pairedItem: { item: itemIndex }, }); } } async function executeMcpTool(ctx, resolveConfig, options = {}) { const node = ctx.getNode(); const items = ctx.getInputData(); const returnData = []; const assertNotCancelled = (itemIndex) => { if (ctx.getExecutionCancelSignal()?.aborted) { throw new n8n_workflow_1.NodeOperationError(node, 'Execution was cancelled', { itemIndex }); } }; const executionId = ctx.getExecutionId(); if (options.enableSessionCache && executionId) { assertNotCancelled(0); const manager = di_1.Container.get(McpClientsManager_1.McpClientsManager); const cacheKey = `${executionId}:${node.name}`; const firstConfig = await resolveConfig(0); const { client, mcpTools } = await manager.getOrConnect(cacheKey, async () => await connectOrThrow(ctx, firstConfig, 0), { logger: ctx.logger, onExecutionCancellation: ctx.onExecutionCancellation?.bind(ctx), onExecutionFinish: ctx.onExecutionFinish?.bind(ctx), }); for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { assertNotCancelled(itemIndex); const config = await resolveConfig(itemIndex); await runToolCall({ ctx, node, item: items[itemIndex], mcpTools, client, timeout: config.timeout, itemIndex, returnData, }); } manager.refresh(cacheKey); return [returnData]; } for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { assertNotCancelled(itemIndex); const config = await resolveConfig(itemIndex); const { client, mcpTools } = await connectOrThrow(ctx, config, itemIndex); try { await runToolCall({ ctx, node, item: items[itemIndex], mcpTools, client, timeout: config.timeout, itemIndex, returnData, }); } finally { await client.close(); } } return [returnData]; } async function loadMcpToolOptions(ctx, config) { const node = ctx.getNode(); const client = await (0, utils_1.connectMcpClientForCredential)(ctx, { authentication: config.authentication, serverTransport: config.transport, endpointUrl: config.endpointUrl, registryCredential: config.registryCredential, surface: 'MCP Client Tool', }); if (!client.ok) { throw (0, utils_1.mapToNodeOperationError)(node, client.error); } try { const tools = await (0, utils_1.getAllTools)(client.result); return tools.map((tool) => ({ name: tool.name, value: tool.name, description: tool.description, inputSchema: tool.inputSchema, })); } finally { await client.result.close(); } } //# sourceMappingURL=runtime.js.map