n8n-nodes-mcp-dragorium
Version:
MCP nodes for n8n
637 lines • 30.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpClientBearer = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const tools_1 = require("@langchain/core/tools");
const zod_1 = require("zod");
const zod_to_json_schema_1 = require("zod-to-json-schema");
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const n8n_workflow_2 = require("n8n-workflow");
class McpClientBearer {
constructor() {
this.description = {
displayName: 'MCP Client Bearer',
name: 'mcpClientBearer',
icon: 'file:mcpClient.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Use MCP client with Bearer Token Authentication',
defaults: {
name: 'MCP Client (Bearer)',
},
inputs: [{ type: "main" }],
outputs: [{ type: "main" }],
usableAsTool: true,
properties: [
{
displayName: 'Bearer Token',
name: 'bearerToken',
type: 'string',
typeOptions: {
password: true,
},
required: true,
default: '',
description: 'Your API Bearer token for authentication (used primarily for SSE)',
},
{
displayName: 'Connection Type',
name: 'connectionType',
type: 'options',
options: [
{
name: 'Command Line (STDIO)',
value: 'cmd',
},
{
name: 'Server-Sent Events (SSE)',
value: 'sse',
},
],
default: 'cmd',
description: 'Choose the transport type to connect to MCP server',
},
{
displayName: 'SSE URL',
name: 'sseUrl',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
connectionType: ['sse'],
},
},
description: 'The full URL for the Server-Sent Events endpoint',
placeholder: 'https://example.com/mcp-sse',
},
{
displayName: 'SSE Timeout (Ms)',
name: 'sseTimeout',
type: 'number',
typeOptions: {
minValue: 1000,
},
default: 60000,
displayOptions: {
show: {
connectionType: ['sse'],
},
},
description: 'Timeout in milliseconds for SSE requests',
},
{
displayName: 'SSE Messages POST Endpoint (Optional)',
name: 'sseMessagesPostEndpoint',
type: 'string',
default: '',
displayOptions: {
show: {
connectionType: ['sse'],
},
},
description: 'Optional separate URL endpoint for posting messages (if different from SSE URL)',
placeholder: 'https://example.com/mcp-post',
},
{
displayName: 'SSE Additional Headers (JSON)',
name: 'sseAdditionalHeaders',
type: 'json',
default: '{}',
displayOptions: {
show: {
connectionType: ['sse'],
},
},
description: 'Additional headers to send with SSE requests (JSON format, e.g., {"X-Custom-Header": "value"})',
},
{
displayName: 'Command',
name: 'cmdCommand',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
connectionType: ['cmd'],
},
},
description: 'The command to execute for the MCP server process',
placeholder: '/path/to/mcp/server or mcp-server-command',
},
{
displayName: 'Arguments (Space Separated)',
name: 'cmdArgs',
type: 'string',
default: '',
displayOptions: {
show: {
connectionType: ['cmd'],
},
},
description: 'Arguments to pass to the command (separated by spaces)',
placeholder: '--verbose --port 8080',
},
{
displayName: 'Environment Variables (Key=value Pairs, One per Line)',
name: 'cmdEnvironmentVariables',
type: 'string',
typeOptions: {
rows: 4,
},
default: '',
displayOptions: {
show: {
connectionType: ['cmd'],
},
},
description: 'Additional environment variables for the command process (e.g., MY_VAR=123)',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Execute Tool',
value: 'executeTool',
description: 'Execute a specific tool',
action: 'Execute a tool',
},
{
name: 'Get Prompt',
value: 'getPrompt',
description: 'Get a specific prompt template',
action: 'Get a prompt template',
},
{
name: 'List Prompts',
value: 'listPrompts',
description: 'Get available prompts',
action: 'List available prompts',
},
{
name: 'List Resource Templates',
value: 'listResourceTemplates',
description: 'Get a list of available resource templates',
action: 'List available resource templates',
},
{
name: 'List Resources',
value: 'listResources',
description: 'Get a list of available resources',
action: 'List available resources',
},
{
name: 'List Tools',
value: 'listTools',
description: 'Get available tools',
action: 'List available tools',
},
{
name: 'Read Resource',
value: 'readResource',
description: 'Read a specific resource by URI',
action: 'Read a resource',
},
],
default: 'listTools',
required: true,
},
{
displayName: 'Resource URI',
name: 'resourceUri',
type: 'string',
required: true,
displayOptions: {
show: {
operation: ['readResource'],
},
},
default: '',
description: 'URI of the resource to read',
},
{
displayName: 'Tool Name',
name: 'toolName',
type: 'string',
required: true,
displayOptions: {
show: {
operation: ['executeTool'],
},
},
default: '',
description: 'Name of the tool to execute',
},
{
displayName: 'Tool Parameters',
name: 'toolParameters',
type: 'json',
required: true,
displayOptions: {
show: {
operation: ['executeTool'],
},
},
default: '{}',
description: 'Parameters to pass to the tool in JSON format',
},
{
displayName: 'Prompt Name',
name: 'promptName',
type: 'string',
required: true,
displayOptions: {
show: {
operation: ['getPrompt'],
},
},
default: '',
description: 'Name of the prompt template to get',
},
],
};
}
async execute() {
const returnData = [];
const operation = this.getNodeParameter('operation', 0);
let transport;
const bearerToken = this.getNodeParameter('bearerToken', 0);
let connectionType = 'cmd';
try {
connectionType = this.getNodeParameter('connectionType', 0);
}
catch (error) {
this.logger.debug('ConnectionType parameter not found, using default "cmd" transport');
}
let timeout = 600000;
try {
if (connectionType === 'sse') {
const sseUrl = this.getNodeParameter('sseUrl', 0);
const messagesPostEndpoint = this.getNodeParameter('sseMessagesPostEndpoint', 0, '');
timeout = this.getNodeParameter('sseTimeout', 0, 60000);
const additionalHeadersRaw = this.getNodeParameter('sseAdditionalHeaders', 0, '{}');
if (!sseUrl) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'SSE URL parameter is required.');
}
if (!bearerToken) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Bearer Token is required for SSE connection type.');
}
const { SSEClientTransport } = await Promise.resolve().then(() => __importStar(require('@modelcontextprotocol/sdk/client/sse.js')));
let additionalHeaders = {};
try {
additionalHeaders = JSON.parse(additionalHeadersRaw || '{}');
if (typeof additionalHeaders !== 'object' || Array.isArray(additionalHeaders)) {
throw new n8n_workflow_2.ApplicationError('Additional Headers must be a JSON object.');
}
}
catch (e) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid JSON in Additional Headers: ${e.message}`);
}
const headers = {
...additionalHeaders,
Authorization: `Bearer ${bearerToken}`,
};
transport = new SSEClientTransport(new URL(sseUrl), {
eventSourceInit: { headers },
requestInit: {
headers,
...(messagesPostEndpoint
? {
endpoint: new URL(messagesPostEndpoint),
}
: {}),
},
});
this.logger.debug(`Created SSE transport for MCP client URL: ${sseUrl}`);
if (messagesPostEndpoint) {
this.logger.debug(`Using custom POST endpoint: ${messagesPostEndpoint}`);
}
this.logger.debug(`Using headers: ${JSON.stringify(headers)}`);
}
else {
const cmdCommand = this.getNodeParameter('cmdCommand', 0);
const cmdArgsRaw = this.getNodeParameter('cmdArgs', 0, '');
const cmdEnvVarsRaw = this.getNodeParameter('cmdEnvironmentVariables', 0, '');
if (!cmdCommand) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Command parameter is required.');
}
const env = {
PATH: process.env.PATH || '',
};
this.logger.debug(`Original PATH: ${process.env.PATH}`);
if (cmdEnvVarsRaw) {
const envPairs = cmdEnvVarsRaw.split(/[\r\n]+/);
for (const pair of envPairs) {
const trimmedPair = pair.trim();
if (trimmedPair) {
const equalsIndex = trimmedPair.indexOf('=');
if (equalsIndex > 0) {
const name = trimmedPair.substring(0, equalsIndex).trim();
const value = trimmedPair.substring(equalsIndex + 1).trim();
if (name && value !== undefined && !env[name]) {
env[name] = value;
}
}
}
}
}
for (const key in process.env) {
if (key.startsWith('MCP_') && process.env[key]) {
const envName = key.substring(4);
env[envName] = process.env[key];
}
}
const cmdArgs = cmdArgsRaw.split(' ').filter((arg) => arg !== '');
transport = new stdio_js_1.StdioClientTransport({
command: cmdCommand,
args: cmdArgs,
env: env,
});
this.logger.debug(`Transport created for MCP client command: ${cmdCommand} ${cmdArgs.join(' ')}`);
this.logger.debug(`Using environment: ${JSON.stringify(env)}`);
}
transport.onerror = (error) => {
if (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Transport error: ${error}`);
}
else {
this.logger.warn('Transport reported an error, but the error object was null.');
}
};
const client = new index_js_1.Client({
name: `mcoClientBearer-client`,
version: '1.0.0',
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
},
});
try {
await client.connect(transport);
this.logger.debug('Client connected to MCP server');
}
catch (connectionError) {
this.logger.error(`MCP client connection error: ${connectionError.message}`);
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to connect to MCP server: ${connectionError.message}`);
}
const requestOptions = {
timeout: timeout,
};
switch (operation) {
case 'listResources': {
const resources = await client.listResources(undefined, requestOptions);
returnData.push({
json: { resources },
});
break;
}
case 'listResourceTemplates': {
const resourceTemplates = await client.listResourceTemplates(undefined, requestOptions);
returnData.push({
json: { resourceTemplates },
});
break;
}
case 'readResource': {
const uri = this.getNodeParameter('resourceUri', 0);
const resource = await client.readResource({
uri,
}, requestOptions);
returnData.push({
json: { resource },
});
break;
}
case 'listTools': {
const rawTools = await client.listTools(undefined, requestOptions);
const tools = Array.isArray(rawTools)
? rawTools
: Array.isArray(rawTools === null || rawTools === void 0 ? void 0 : rawTools.tools)
? rawTools.tools
: Object.values((rawTools === null || rawTools === void 0 ? void 0 : rawTools.tools) || {});
if (!tools.length) {
this.logger.warn('No tools found from MCP client');
returnData.push({ json: { tools: [] } });
break;
}
const aiTools = tools.map((tool) => {
var _a;
const paramSchema = ((_a = tool.inputSchema) === null || _a === void 0 ? void 0 : _a.properties)
? zod_1.z.object(Object.entries(tool.inputSchema.properties).reduce((acc, [key, prop]) => {
var _a, _b, _c, _d, _e;
let zodType;
switch (prop.type) {
case 'string':
zodType = zod_1.z.string();
break;
case 'number':
zodType = zod_1.z.number();
break;
case 'integer':
zodType = zod_1.z.number().int();
break;
case 'boolean':
zodType = zod_1.z.boolean();
break;
case 'array':
if (((_a = prop.items) === null || _a === void 0 ? void 0 : _a.type) === 'string') {
zodType = zod_1.z.array(zod_1.z.string());
}
else if (((_b = prop.items) === null || _b === void 0 ? void 0 : _b.type) === 'number') {
zodType = zod_1.z.array(zod_1.z.number());
}
else if (((_c = prop.items) === null || _c === void 0 ? void 0 : _c.type) === 'boolean') {
zodType = zod_1.z.array(zod_1.z.boolean());
}
else {
zodType = zod_1.z.array(zod_1.z.any());
}
break;
case 'object':
zodType = zod_1.z.record(zod_1.z.string(), zod_1.z.any());
break;
default:
zodType = zod_1.z.any();
}
if (prop.description) {
zodType = zodType.describe(prop.description);
}
if (!((_e = (_d = tool.inputSchema) === null || _d === void 0 ? void 0 : _d.required) === null || _e === void 0 ? void 0 : _e.includes(key))) {
zodType = zodType.optional();
}
return {
...acc,
[key]: zodType,
};
}, {}))
: zod_1.z.object({});
return new tools_1.DynamicStructuredTool({
name: tool.name,
description: tool.description || `Execute the ${tool.name} tool`,
schema: paramSchema,
func: async (params) => {
try {
const result = await client.callTool({
name: tool.name,
arguments: params,
}, types_js_1.CallToolResultSchema, requestOptions);
return typeof result === 'object' ? JSON.stringify(result) : String(result);
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute ${tool.name}: ${error.message}`);
}
},
});
});
returnData.push({
json: {
tools: aiTools.map((t) => ({
name: t.name,
description: t.description,
schema: (0, zod_to_json_schema_1.zodToJsonSchema)(t.schema || {}),
})),
},
});
break;
}
case 'executeTool': {
const toolName = this.getNodeParameter('toolName', 0);
let toolParams;
try {
const rawParams = this.getNodeParameter('toolParameters', 0);
this.logger.debug(`Raw tool parameters: ${JSON.stringify(rawParams)}`);
if (rawParams === undefined || rawParams === null) {
toolParams = {};
}
else if (typeof rawParams === 'string') {
if (!rawParams || rawParams.trim() === '') {
toolParams = {};
}
else {
toolParams = JSON.parse(rawParams);
}
}
else if (typeof rawParams === 'object') {
toolParams = rawParams;
}
else {
try {
toolParams = JSON.parse(JSON.stringify(rawParams));
}
catch (parseError) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid parameter type: ${typeof rawParams}`);
}
}
if (typeof toolParams !== 'object' ||
toolParams === null ||
Array.isArray(toolParams)) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Tool parameters must be a JSON object');
}
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to parse tool parameters: ${error.message}. Make sure the parameters are valid JSON.`);
}
try {
const availableTools = await client.listTools(undefined, requestOptions);
const toolsList = Array.isArray(availableTools)
? availableTools
: Array.isArray(availableTools === null || availableTools === void 0 ? void 0 : availableTools.tools)
? availableTools.tools
: Object.values((availableTools === null || availableTools === void 0 ? void 0 : availableTools.tools) || {});
const toolExists = toolsList.some((tool) => tool.name === toolName);
if (!toolExists) {
const availableToolNames = toolsList.map((t) => t.name).join(', ');
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Tool '${toolName}' does not exist. Available tools: ${availableToolNames || 'None'}`);
}
this.logger.debug(`Executing tool: ${toolName} with params: ${JSON.stringify(toolParams)}`);
const result = await client.callTool({
name: toolName,
arguments: toolParams,
}, types_js_1.CallToolResultSchema, requestOptions);
this.logger.debug(`Tool executed successfully: ${JSON.stringify(result)}`);
returnData.push({
json: { result },
});
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute tool '${toolName}': ${error.message}`);
}
break;
}
case 'listPrompts': {
const prompts = await client.listPrompts(undefined, requestOptions);
returnData.push({
json: { prompts },
});
break;
}
case 'getPrompt': {
const promptName = this.getNodeParameter('promptName', 0);
const prompt = await client.getPrompt({
name: promptName,
}, requestOptions);
returnData.push({
json: { prompt },
});
break;
}
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation ${operation} not supported`);
}
return [returnData];
}
catch (error) {
this.logger.error(`Error during MCP Client execution: ${error.message}`, { stack: error.stack });
if (error instanceof n8n_workflow_1.NodeOperationError) {
throw error;
}
else {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute operation: ${error.message}`);
}
}
finally {
if (transport) {
try {
await transport.close();
this.logger.debug('MCP transport closed successfully.');
}
catch (closeError) {
this.logger.error(`Error closing MCP transport: ${closeError.message}`);
}
}
}
}
}
exports.McpClientBearer = McpClientBearer;
//# sourceMappingURL=McpClientBearer.node.js.map