2025-11-10 14:05:02 +00:00
|
|
|
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
2026-01-23 10:04:11 +00:00
|
|
|
import { StructuredToolkit } from 'n8n-core';
|
2025-04-09 15:31:53 +00:00
|
|
|
import {
|
2025-10-17 06:44:38 +00:00
|
|
|
type IDataObject,
|
|
|
|
|
type IExecuteFunctions,
|
|
|
|
|
type INodeExecutionData,
|
2025-04-09 15:31:53 +00:00
|
|
|
NodeConnectionTypes,
|
|
|
|
|
NodeOperationError,
|
|
|
|
|
type INodeType,
|
|
|
|
|
type INodeTypeDescription,
|
|
|
|
|
type ISupplyDataFunctions,
|
|
|
|
|
type SupplyData,
|
|
|
|
|
} from 'n8n-workflow';
|
|
|
|
|
|
2026-03-02 09:10:31 +00:00
|
|
|
import { logWrapper, getConnectionHintNoticeField } from '@n8n/ai-utilities';
|
2025-10-17 06:44:38 +00:00
|
|
|
|
2025-04-09 15:31:53 +00:00
|
|
|
import { getTools } from './loadOptions';
|
2025-11-21 08:50:32 +00:00
|
|
|
import type { McpToolIncludeMode } from './types';
|
2026-04-08 07:23:40 +00:00
|
|
|
import { buildMcpToolName, createCallTool, getSelectedTools, mcpToolToDynamicTool } from './utils';
|
2025-11-21 08:50:32 +00:00
|
|
|
import { credentials, transportSelect } from '../shared/descriptions';
|
|
|
|
|
import type { McpAuthenticationOption, McpServerTransport } from '../shared/types';
|
2025-04-09 15:31:53 +00:00
|
|
|
import {
|
|
|
|
|
connectMcpClient,
|
|
|
|
|
getAllTools,
|
|
|
|
|
getAuthHeaders,
|
2025-11-21 08:50:32 +00:00
|
|
|
mapToNodeOperationError,
|
2025-10-27 15:03:50 +00:00
|
|
|
tryRefreshOAuth2Token,
|
2025-11-21 08:50:32 +00:00
|
|
|
} from '../shared/utils';
|
2026-01-06 15:48:14 +00:00
|
|
|
import type { JSONSchema7 } from 'json-schema';
|
|
|
|
|
import pick from 'lodash/pick';
|
2025-04-09 15:31:53 +00:00
|
|
|
|
2025-10-17 06:44:38 +00:00
|
|
|
/**
|
|
|
|
|
* Get node parameters for MCP client configuration
|
|
|
|
|
*/
|
|
|
|
|
function getNodeConfig(
|
|
|
|
|
ctx: ISupplyDataFunctions | IExecuteFunctions,
|
|
|
|
|
itemIndex: number,
|
|
|
|
|
): {
|
|
|
|
|
authentication: McpAuthenticationOption;
|
|
|
|
|
timeout: number;
|
|
|
|
|
serverTransport: McpServerTransport;
|
|
|
|
|
endpointUrl: string;
|
|
|
|
|
mode: McpToolIncludeMode;
|
|
|
|
|
includeTools: string[];
|
|
|
|
|
excludeTools: string[];
|
|
|
|
|
} {
|
|
|
|
|
const node = ctx.getNode();
|
|
|
|
|
const authentication = ctx.getNodeParameter(
|
|
|
|
|
'authentication',
|
|
|
|
|
itemIndex,
|
|
|
|
|
) as McpAuthenticationOption;
|
|
|
|
|
const timeout = ctx.getNodeParameter('options.timeout', itemIndex, 60000) as number;
|
|
|
|
|
|
|
|
|
|
let serverTransport: McpServerTransport;
|
|
|
|
|
let endpointUrl: string;
|
|
|
|
|
if (node.typeVersion === 1) {
|
|
|
|
|
serverTransport = 'sse';
|
|
|
|
|
endpointUrl = ctx.getNodeParameter('sseEndpoint', itemIndex) as string;
|
|
|
|
|
} else {
|
|
|
|
|
serverTransport = ctx.getNodeParameter('serverTransport', itemIndex) as McpServerTransport;
|
|
|
|
|
endpointUrl = ctx.getNodeParameter('endpointUrl', itemIndex) as string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mode = ctx.getNodeParameter('include', itemIndex) as McpToolIncludeMode;
|
|
|
|
|
const includeTools = ctx.getNodeParameter('includeTools', itemIndex, []) as string[];
|
|
|
|
|
const excludeTools = ctx.getNodeParameter('excludeTools', itemIndex, []) as string[];
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
authentication,
|
|
|
|
|
timeout,
|
|
|
|
|
serverTransport,
|
|
|
|
|
endpointUrl,
|
|
|
|
|
mode,
|
|
|
|
|
includeTools,
|
|
|
|
|
excludeTools,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Connect to MCP server and get filtered tools
|
|
|
|
|
*/
|
|
|
|
|
async function connectAndGetTools(
|
|
|
|
|
ctx: ISupplyDataFunctions | IExecuteFunctions,
|
|
|
|
|
config: ReturnType<typeof getNodeConfig>,
|
|
|
|
|
) {
|
|
|
|
|
const node = ctx.getNode();
|
|
|
|
|
const { headers } = await getAuthHeaders(ctx, config.authentication);
|
|
|
|
|
|
|
|
|
|
const client = await connectMcpClient({
|
|
|
|
|
serverTransport: config.serverTransport,
|
|
|
|
|
endpointUrl: config.endpointUrl,
|
|
|
|
|
headers,
|
|
|
|
|
name: node.type,
|
|
|
|
|
version: node.typeVersion,
|
2025-10-27 15:03:50 +00:00
|
|
|
onUnauthorized: async (headers) =>
|
|
|
|
|
await tryRefreshOAuth2Token(ctx, config.authentication, headers),
|
2025-10-17 06:44:38 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!client.ok) {
|
|
|
|
|
return { client, mcpTools: null, error: client.error };
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 12:51:28 +00:00
|
|
|
try {
|
|
|
|
|
const allTools = await getAllTools(client.result);
|
|
|
|
|
const mcpTools = getSelectedTools({
|
|
|
|
|
tools: allTools,
|
|
|
|
|
mode: config.mode,
|
|
|
|
|
includeTools: config.includeTools,
|
|
|
|
|
excludeTools: config.excludeTools,
|
|
|
|
|
});
|
2025-10-17 06:44:38 +00:00
|
|
|
|
2026-04-09 12:51:28 +00:00
|
|
|
return { client: client.result, mcpTools, error: null };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
await client.result.close();
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2025-10-17 06:44:38 +00:00
|
|
|
}
|
|
|
|
|
|
2025-04-09 15:31:53 +00:00
|
|
|
export class McpClientTool implements INodeType {
|
|
|
|
|
description: INodeTypeDescription = {
|
|
|
|
|
displayName: 'MCP Client Tool',
|
|
|
|
|
name: 'mcpClientTool',
|
|
|
|
|
icon: {
|
|
|
|
|
light: 'file:../mcp.svg',
|
|
|
|
|
dark: 'file:../mcp.dark.svg',
|
|
|
|
|
},
|
|
|
|
|
group: ['output'],
|
2025-09-26 12:47:37 +00:00
|
|
|
version: [1, 1.1, 1.2],
|
2025-04-09 15:31:53 +00:00
|
|
|
description: 'Connect tools from an MCP Server',
|
|
|
|
|
defaults: {
|
|
|
|
|
name: 'MCP Client',
|
|
|
|
|
},
|
|
|
|
|
codex: {
|
|
|
|
|
categories: ['AI'],
|
|
|
|
|
subcategories: {
|
2026-01-23 10:04:11 +00:00
|
|
|
AI: ['Tools'],
|
|
|
|
|
Tools: ['Recommended Tools'],
|
2025-04-09 15:31:53 +00:00
|
|
|
},
|
|
|
|
|
alias: ['Model Context Protocol', 'MCP Client'],
|
|
|
|
|
resources: {
|
|
|
|
|
primaryDocumentation: [
|
|
|
|
|
{
|
2025-04-10 06:48:59 +00:00
|
|
|
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolmcp/',
|
2025-04-09 15:31:53 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
inputs: [],
|
|
|
|
|
outputs: [{ type: NodeConnectionTypes.AiTool, displayName: 'Tools' }],
|
2025-11-21 08:50:32 +00:00
|
|
|
credentials,
|
2025-04-09 15:31:53 +00:00
|
|
|
properties: [
|
|
|
|
|
getConnectionHintNoticeField([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,
|
2025-07-18 11:27:21 +00:00
|
|
|
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 } }],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-09-26 12:47:37 +00:00
|
|
|
transportSelect({
|
|
|
|
|
defaultOption: 'sse',
|
|
|
|
|
displayOptions: {
|
|
|
|
|
show: {
|
|
|
|
|
'@version': [1.1],
|
2025-07-18 11:27:21 +00:00
|
|
|
},
|
2025-09-26 12:47:37 +00:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
transportSelect({
|
|
|
|
|
defaultOption: 'httpStreamable',
|
2025-07-18 11:27:21 +00:00
|
|
|
displayOptions: {
|
|
|
|
|
show: {
|
2025-09-26 12:47:37 +00:00
|
|
|
'@version': [{ _cnd: { gte: 1.2 } }],
|
2025-07-18 11:27:21 +00:00
|
|
|
},
|
|
|
|
|
},
|
2025-09-26 12:47:37 +00:00
|
|
|
}),
|
2025-04-09 15:31:53 +00:00
|
|
|
{
|
|
|
|
|
displayName: 'Authentication',
|
|
|
|
|
name: 'authentication',
|
|
|
|
|
type: 'options',
|
|
|
|
|
options: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Bearer Auth',
|
|
|
|
|
value: 'bearerAuth',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Header Auth',
|
|
|
|
|
value: 'headerAuth',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'None',
|
|
|
|
|
value: 'none',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
default: 'none',
|
2025-07-18 11:27:21 +00:00
|
|
|
description: 'The way to authenticate with your endpoint',
|
2025-10-27 15:03:50 +00:00
|
|
|
displayOptions: {
|
|
|
|
|
show: {
|
|
|
|
|
'@version': [{ _cnd: { lt: 1.2 } }],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
displayName: 'Authentication',
|
|
|
|
|
name: 'authentication',
|
|
|
|
|
type: 'options',
|
|
|
|
|
options: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Bearer Auth',
|
|
|
|
|
value: 'bearerAuth',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Header Auth',
|
|
|
|
|
value: 'headerAuth',
|
|
|
|
|
},
|
2025-11-17 08:35:24 +00:00
|
|
|
{
|
|
|
|
|
name: 'MCP OAuth2',
|
|
|
|
|
value: 'mcpOAuth2Api',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Multiple Headers Auth',
|
|
|
|
|
value: 'multipleHeadersAuth',
|
|
|
|
|
},
|
2025-10-27 15:03:50 +00:00
|
|
|
{
|
|
|
|
|
name: 'None',
|
|
|
|
|
value: 'none',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
default: 'none',
|
|
|
|
|
description: 'The way to authenticate with your endpoint',
|
|
|
|
|
displayOptions: {
|
|
|
|
|
show: {
|
|
|
|
|
'@version': [{ _cnd: { gte: 1.2 } }],
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-04-09 15:31:53 +00:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
displayName: 'Credentials',
|
|
|
|
|
name: 'credentials',
|
|
|
|
|
type: 'credentials',
|
|
|
|
|
default: '',
|
|
|
|
|
displayOptions: {
|
|
|
|
|
show: {
|
2025-11-17 08:35:24 +00:00
|
|
|
authentication: ['headerAuth', 'bearerAuth', 'mcpOAuth2Api', 'multipleHeadersAuth'],
|
2025-04-09 15:31:53 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
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'],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-08-14 17:28:12 +00:00
|
|
|
{
|
|
|
|
|
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',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2025-04-09 15:31:53 +00:00
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
methods = {
|
|
|
|
|
loadOptions: {
|
|
|
|
|
getTools,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
|
|
|
|
const node = this.getNode();
|
2025-10-17 06:44:38 +00:00
|
|
|
const config = getNodeConfig(this, itemIndex);
|
2025-04-09 15:31:53 +00:00
|
|
|
|
2025-11-21 08:50:32 +00:00
|
|
|
const setError = (error: NodeOperationError): SupplyData => {
|
2025-04-09 15:31:53 +00:00
|
|
|
this.addOutputData(NodeConnectionTypes.AiTool, itemIndex, error);
|
|
|
|
|
throw error;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-07 13:37:36 +00:00
|
|
|
const signal = this.getExecutionCancelSignal();
|
|
|
|
|
if (signal?.aborted) {
|
|
|
|
|
return setError(new NodeOperationError(node, 'Execution was cancelled', { itemIndex }));
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 06:44:38 +00:00
|
|
|
const { client, mcpTools, error } = await connectAndGetTools(this, config);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
this.logger.error('McpClientTool: Failed to connect to MCP Server', { error });
|
2025-11-21 08:50:32 +00:00
|
|
|
return setError(mapToNodeOperationError(node, error));
|
2025-04-09 15:31:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.logger.debug('McpClientTool: Successfully connected to MCP Server');
|
|
|
|
|
|
2025-11-10 14:05:02 +00:00
|
|
|
if (!mcpTools?.length) {
|
2025-04-09 15:31:53 +00:00
|
|
|
return setError(
|
2025-11-21 08:50:32 +00:00
|
|
|
new NodeOperationError(node, 'MCP Server returned no tools', {
|
|
|
|
|
itemIndex,
|
|
|
|
|
description:
|
|
|
|
|
'Connected successfully to your MCP server but it returned an empty list of tools.',
|
|
|
|
|
}),
|
2025-04-09 15:31:53 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 07:23:40 +00:00
|
|
|
const tools = mcpTools.map((tool) => {
|
|
|
|
|
const prefixedName = buildMcpToolName(node.name, tool.name);
|
|
|
|
|
return logWrapper(
|
2025-04-09 15:31:53 +00:00
|
|
|
mcpToolToDynamicTool(
|
2026-04-08 07:23:40 +00:00
|
|
|
{ ...tool, name: prefixedName },
|
2026-04-07 13:37:36 +00:00
|
|
|
createCallTool(
|
|
|
|
|
tool.name,
|
|
|
|
|
client,
|
|
|
|
|
config.timeout,
|
|
|
|
|
(errorMessage) => {
|
|
|
|
|
const error = new NodeOperationError(node, errorMessage, { itemIndex });
|
|
|
|
|
void this.addOutputData(NodeConnectionTypes.AiTool, itemIndex, error);
|
|
|
|
|
this.logger.error(`McpClientTool: Tool "${tool.name}" failed to execute`, {
|
|
|
|
|
error,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
() => this.getExecutionCancelSignal(),
|
|
|
|
|
),
|
2025-04-09 15:31:53 +00:00
|
|
|
),
|
|
|
|
|
this,
|
2026-04-08 07:23:40 +00:00
|
|
|
);
|
|
|
|
|
});
|
2025-04-09 15:31:53 +00:00
|
|
|
|
|
|
|
|
this.logger.debug(`McpClientTool: Connected to MCP Server with ${tools.length} tools`);
|
|
|
|
|
|
2026-01-23 10:04:11 +00:00
|
|
|
const toolkit = new StructuredToolkit(tools);
|
2025-04-09 15:31:53 +00:00
|
|
|
|
2025-10-17 06:44:38 +00:00
|
|
|
return { response: toolkit, closeFunction: async () => await client.close() };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
|
|
|
const node = this.getNode();
|
|
|
|
|
const items = this.getInputData();
|
|
|
|
|
const returnData: INodeExecutionData[] = [];
|
|
|
|
|
|
|
|
|
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
2026-04-07 13:37:36 +00:00
|
|
|
const signal = this.getExecutionCancelSignal();
|
|
|
|
|
if (signal?.aborted) {
|
|
|
|
|
throw new NodeOperationError(node, 'Execution was cancelled', { itemIndex });
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 06:44:38 +00:00
|
|
|
const item = items[itemIndex];
|
|
|
|
|
const config = getNodeConfig(this, itemIndex);
|
|
|
|
|
|
|
|
|
|
const { client, mcpTools, error } = await connectAndGetTools(this, config);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
throw new NodeOperationError(node, error.error, { itemIndex });
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 12:51:28 +00:00
|
|
|
try {
|
|
|
|
|
if (!mcpTools?.length) {
|
|
|
|
|
throw new NodeOperationError(node, 'MCP Server returned no tools', { itemIndex });
|
|
|
|
|
}
|
2025-10-17 06:44:38 +00:00
|
|
|
|
2026-04-09 12:51:28 +00:00
|
|
|
// Check for tool name in item.json.tool (for toolkit execution from agent)
|
|
|
|
|
if (!item.json.tool || typeof item.json.tool !== 'string') {
|
|
|
|
|
throw new NodeOperationError(node, 'Tool name not found in item.json.tool or item.tool', {
|
|
|
|
|
itemIndex,
|
2025-10-17 06:44:38 +00:00
|
|
|
});
|
|
|
|
|
}
|
2026-04-09 12:51:28 +00:00
|
|
|
|
|
|
|
|
const toolName = item.json.tool;
|
|
|
|
|
for (const tool of mcpTools) {
|
|
|
|
|
const prefixedName = buildMcpToolName(node.name, tool.name);
|
|
|
|
|
if (toolName === prefixedName) {
|
|
|
|
|
// Extract the tool name from arguments before passing to MCP
|
|
|
|
|
const { tool: _, ...toolArguments } = item.json;
|
|
|
|
|
const schema: JSONSchema7 = tool.inputSchema;
|
|
|
|
|
// When additionalProperties is not explicitly true, filter to schema-defined properties.
|
|
|
|
|
// Otherwise, pass all arguments through
|
|
|
|
|
const sanitizedToolArguments: IDataObject =
|
|
|
|
|
schema.additionalProperties !== true
|
|
|
|
|
? pick(toolArguments, Object.keys(schema.properties ?? {}))
|
|
|
|
|
: toolArguments;
|
|
|
|
|
|
|
|
|
|
const params: {
|
|
|
|
|
name: string;
|
|
|
|
|
arguments: IDataObject;
|
|
|
|
|
} = {
|
|
|
|
|
name: tool.name,
|
|
|
|
|
arguments: sanitizedToolArguments,
|
|
|
|
|
};
|
|
|
|
|
const result = await client.callTool(params, CallToolResultSchema, {
|
|
|
|
|
timeout: config.timeout,
|
|
|
|
|
signal: this.getExecutionCancelSignal(),
|
|
|
|
|
});
|
|
|
|
|
returnData.push({
|
|
|
|
|
json: {
|
|
|
|
|
response: result.content as IDataObject,
|
|
|
|
|
},
|
|
|
|
|
pairedItem: {
|
|
|
|
|
item: itemIndex,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
await client.close();
|
2025-10-17 06:44:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [returnData];
|
2025-04-09 15:31:53 +00:00
|
|
|
}
|
|
|
|
|
}
|