n8n-nodes-mcp-client
Version:
4Runr's custom MCP Client node for n8n — connects to a self-hosted MCP server via SSE and streams tool definitions to n8n AI Agents.
130 lines (129 loc) • 5.33 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpClient = void 0;
// n8n types are resolved at runtime in the n8n environment
const n8n_workflow_1 = require("n8n-workflow");
// Use require for EventSource for compatibility
const EventSource = require('eventsource');
class McpClient {
constructor() {
this.description = {
displayName: '4Runr MCP Client',
name: 'mcpClient',
group: ['input'],
version: 1,
description: 'Streams tool definitions from a 4Runr MCP Server via SSE. Built to power dynamic AI agents using the MCP protocol.',
defaults: {
name: '4Runr MCP Client',
color: '#00b894',
},
inputs: [],
outputs: ["main" /* NodeConnectionType.Main */],
icon: 'fa:plug',
usableAsTool: true,
credentials: [
{
name: 'McpClientApi',
required: true,
displayName: '4Runr MCP Access',
},
],
properties: [
{
displayName: 'Tool Type Filter',
name: 'toolType',
type: 'options',
options: [
{
name: 'All Tools',
value: 'all',
},
{
name: 'Search Tools',
value: 'search_tool',
},
{
name: 'Update Tools',
value: 'update_tool',
},
{
name: 'Report Tools',
value: 'report_tool',
},
],
default: 'all',
description: 'Filter tools by their type',
},
],
};
}
async execute() {
const credentials = await this.getCredentials('McpClientApi');
const { sseUrl, sseTimeout, headers, messageEndpoint } = credentials;
const toolType = this.getNodeParameter('toolType', 0);
console.log(`[McpClient] Connecting to SSE: ${sseUrl}`);
return new Promise((resolve, reject) => {
const eventSourceOptions = {};
// Add headers if provided
if (headers) {
try {
const parsedHeaders = typeof headers === 'string' ? JSON.parse(headers) : headers;
eventSourceOptions.headers = parsedHeaders;
}
catch (err) {
console.error('[McpClient] Failed to parse headers:', err);
}
}
const eventSource = new EventSource(sseUrl, eventSourceOptions);
// Set timeout if provided
if (sseTimeout) {
const timeoutId = setTimeout(() => {
eventSource.close();
reject(new n8n_workflow_1.NodeOperationError(this.getNode(), 'SSE connection timed out'));
}, sseTimeout);
eventSource.onopen = () => {
clearTimeout(timeoutId);
console.log('[McpClient] SSE connection opened');
};
}
else {
eventSource.onopen = () => {
console.log('[McpClient] SSE connection opened');
};
}
eventSource.onerror = (err) => {
console.error('[McpClient] SSE connection error', err);
eventSource.close();
reject(new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to connect to SSE endpoint'));
};
eventSource.addEventListener('tools', (event) => {
console.log('[McpClient] Received tools event:', event.data);
try {
const parsed = JSON.parse(event.data);
if (parsed.tools && Array.isArray(parsed.tools)) {
let tools = parsed.tools.map((tool) => ({
name: tool.name,
description: tool.description,
parameters: tool.parameters || {},
}));
// Filter tools by type if specified
if (toolType !== 'all') {
tools = tools.filter(tool => tool.name.includes(toolType));
}
console.log(`[McpClient] Emitting ${tools.length} tools`);
eventSource.close();
resolve([this.helpers.returnJsonArray(tools)]);
}
else {
console.warn('[McpClient] No tools array in event data');
}
}
catch (err) {
console.error('[McpClient] Failed to parse tools event', err);
}
});
// Ignore all other event types (e.g., ping)
});
}
}
exports.McpClient = McpClient;