Files
n8n-enterprise-unlocked/packages/@n8n/nodes-langchain/nodes/mcp/McpClientTool/McpClientTool.node.ts

313 lines
7.7 KiB
TypeScript

import { logWrapper } from '@utils/logWrapper';
import { getConnectionHintNoticeField } from '@utils/sharedFields';
import {
NodeConnectionTypes,
NodeOperationError,
type INodeType,
type INodeTypeDescription,
type ISupplyDataFunctions,
type SupplyData,
} from 'n8n-workflow';
import { getTools } from './loadOptions';
import type { McpServerTransport, McpAuthenticationOption, McpToolIncludeMode } from './types';
import {
connectMcpClient,
createCallTool,
getAllTools,
getAuthHeaders,
getSelectedTools,
McpToolkit,
mcpToolToDynamicTool,
} from './utils';
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'],
version: [1, 1.1],
description: 'Connect tools from an MCP Server',
defaults: {
name: 'MCP Client',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Model Context Protocol', '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: NodeConnectionTypes.AiTool, displayName: 'Tools' }],
credentials: [
{
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
name: 'httpBearerAuth',
required: true,
displayOptions: {
show: {
authentication: ['bearerAuth'],
},
},
},
{
name: 'httpHeaderAuth',
required: true,
displayOptions: {
show: {
authentication: ['headerAuth'],
},
},
},
],
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,
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 } }],
},
},
},
{
displayName: 'Server Transport',
name: 'serverTransport',
type: 'options',
options: [
{
name: 'Server Sent Events (Deprecated)',
value: 'sse',
},
{
name: 'HTTP Streamable',
value: 'httpStreamable',
},
],
default: 'sse',
description: 'The transport used by your endpoint',
displayOptions: {
show: {
'@version': [{ _cnd: { gte: 1.1 } }],
},
},
},
{
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',
},
{
displayName: 'Credentials',
name: 'credentials',
type: 'credentials',
default: '',
displayOptions: {
show: {
authentication: ['headerAuth', 'bearerAuth'],
},
},
},
{
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'],
},
},
},
],
};
methods = {
loadOptions: {
getTools,
},
};
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const authentication = this.getNodeParameter(
'authentication',
itemIndex,
) as McpAuthenticationOption;
const node = this.getNode();
let serverTransport: McpServerTransport;
let endpointUrl: string;
if (node.typeVersion === 1) {
serverTransport = 'sse';
endpointUrl = this.getNodeParameter('sseEndpoint', itemIndex) as string;
} else {
serverTransport = this.getNodeParameter('serverTransport', itemIndex) as McpServerTransport;
endpointUrl = this.getNodeParameter('endpointUrl', itemIndex) as string;
}
const { headers } = await getAuthHeaders(this, authentication);
const client = await connectMcpClient({
serverTransport,
endpointUrl,
headers,
name: node.type,
version: node.typeVersion,
});
const setError = (message: string, description?: string): SupplyData => {
const error = new NodeOperationError(node, message, { itemIndex, description });
this.addOutputData(NodeConnectionTypes.AiTool, itemIndex, error);
throw error;
};
if (!client.ok) {
this.logger.error('McpClientTool: Failed to connect to MCP Server', {
error: client.error,
});
switch (client.error.type) {
case 'invalid_url':
return setError('Could not connect to your MCP server. The provided URL is invalid.');
case 'connection':
default:
return setError('Could not connect to your MCP server');
}
}
this.logger.debug('McpClientTool: Successfully connected to MCP Server');
const mode = this.getNodeParameter('include', itemIndex) as McpToolIncludeMode;
const includeTools = this.getNodeParameter('includeTools', itemIndex, []) as string[];
const excludeTools = this.getNodeParameter('excludeTools', itemIndex, []) as string[];
const allTools = await getAllTools(client.result);
const mcpTools = getSelectedTools({
tools: allTools,
mode,
includeTools,
excludeTools,
});
if (!mcpTools.length) {
return setError(
'MCP Server returned no tools',
'Connected successfully to your MCP server but it returned an empty list of tools.',
);
}
const tools = mcpTools.map((tool) =>
logWrapper(
mcpToolToDynamicTool(
tool,
createCallTool(tool.name, client.result, (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,
),
);
this.logger.debug(`McpClientTool: Connected to MCP Server with ${tools.length} tools`);
const toolkit = new McpToolkit(tools);
return { response: toolkit, closeFunction: async () => await client.result.close() };
}
}