mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
315 lines
7.7 KiB
TypeScript
315 lines
7.7 KiB
TypeScript
import {
|
|
NodeConnectionTypes,
|
|
NodeOperationError,
|
|
type INodeType,
|
|
type INodeTypeDescription,
|
|
type ISupplyDataFunctions,
|
|
type SupplyData,
|
|
} from 'n8n-workflow';
|
|
|
|
import { logWrapper } from '@utils/logWrapper';
|
|
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
|
|
|
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, (error) => {
|
|
this.logger.error(`McpClientTool: Tool "${tool.name}" failed to execute`, { error });
|
|
throw new NodeOperationError(node, `Failed to execute tool "${tool.name}"`, {
|
|
description: 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() };
|
|
}
|
|
}
|