@n8n/n8n-nodes-langchain
Version:
220 lines • 9.2 kB
JavaScript
"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