Implement n8n-MCP integration
This commit adds a complete integration between n8n workflow automation and the Model Context Protocol (MCP): Features: - MCP server that exposes n8n workflows as tools, resources, and prompts - Custom n8n node for connecting to MCP servers from workflows - Bidirectional bridge for data format conversion - Token-based authentication and credential management - Comprehensive error handling and logging - Full test coverage for core components Infrastructure: - TypeScript/Node.js project setup with proper build configuration - Docker support with multi-stage builds - Development and production docker-compose configurations - Installation script for n8n custom node deployment Documentation: - Detailed README with usage examples and API reference - Environment configuration templates - Troubleshooting guide 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
51
src/n8n/MCPApi.credentials.ts
Normal file
51
src/n8n/MCPApi.credentials.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class MCPApi implements ICredentialType {
|
||||
name = 'mcpApi';
|
||||
displayName = 'MCP API';
|
||||
documentationUrl = 'mcp';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Server URL',
|
||||
name: 'serverUrl',
|
||||
type: 'string',
|
||||
default: 'http://localhost:3000',
|
||||
placeholder: 'http://localhost:3000',
|
||||
description: 'The URL of the MCP server',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication Token',
|
||||
name: 'authToken',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Authentication token for the MCP server (if required)',
|
||||
},
|
||||
{
|
||||
displayName: 'Connection Type',
|
||||
name: 'connectionType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'HTTP',
|
||||
value: 'http',
|
||||
},
|
||||
{
|
||||
name: 'WebSocket',
|
||||
value: 'websocket',
|
||||
},
|
||||
{
|
||||
name: 'STDIO',
|
||||
value: 'stdio',
|
||||
},
|
||||
],
|
||||
default: 'http',
|
||||
description: 'How to connect to the MCP server',
|
||||
},
|
||||
];
|
||||
}
|
||||
280
src/n8n/MCPNode.node.ts
Normal file
280
src/n8n/MCPNode.node.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
NodeConnectionType,
|
||||
} from 'n8n-workflow';
|
||||
import { MCPClient } from '../utils/mcp-client';
|
||||
import { N8NMCPBridge } from '../utils/bridge';
|
||||
|
||||
export class MCPNode implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'MCP',
|
||||
name: 'mcp',
|
||||
icon: 'file:mcp.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Interact with Model Context Protocol (MCP) servers',
|
||||
defaults: {
|
||||
name: 'MCP',
|
||||
},
|
||||
inputs: [NodeConnectionType.Main],
|
||||
outputs: [NodeConnectionType.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'mcpApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Call Tool',
|
||||
value: 'callTool',
|
||||
description: 'Execute an MCP tool',
|
||||
},
|
||||
{
|
||||
name: 'List Tools',
|
||||
value: 'listTools',
|
||||
description: 'List available MCP tools',
|
||||
},
|
||||
{
|
||||
name: 'Read Resource',
|
||||
value: 'readResource',
|
||||
description: 'Read an MCP resource',
|
||||
},
|
||||
{
|
||||
name: 'List Resources',
|
||||
value: 'listResources',
|
||||
description: 'List available MCP resources',
|
||||
},
|
||||
{
|
||||
name: 'Get Prompt',
|
||||
value: 'getPrompt',
|
||||
description: 'Get an MCP prompt',
|
||||
},
|
||||
{
|
||||
name: 'List Prompts',
|
||||
value: 'listPrompts',
|
||||
description: 'List available MCP prompts',
|
||||
},
|
||||
],
|
||||
default: 'callTool',
|
||||
},
|
||||
// Tool-specific fields
|
||||
{
|
||||
displayName: 'Tool Name',
|
||||
name: 'toolName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['callTool'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the MCP tool to execute',
|
||||
},
|
||||
{
|
||||
displayName: 'Tool Arguments',
|
||||
name: 'toolArguments',
|
||||
type: 'json',
|
||||
required: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['callTool'],
|
||||
},
|
||||
},
|
||||
default: '{}',
|
||||
description: 'Arguments to pass to the MCP tool',
|
||||
},
|
||||
// Resource-specific fields
|
||||
{
|
||||
displayName: 'Resource URI',
|
||||
name: 'resourceUri',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['readResource'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'URI of the MCP resource to read',
|
||||
},
|
||||
// Prompt-specific fields
|
||||
{
|
||||
displayName: 'Prompt Name',
|
||||
name: 'promptName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getPrompt'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the MCP prompt to retrieve',
|
||||
},
|
||||
{
|
||||
displayName: 'Prompt Arguments',
|
||||
name: 'promptArguments',
|
||||
type: 'json',
|
||||
required: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getPrompt'],
|
||||
},
|
||||
},
|
||||
default: '{}',
|
||||
description: 'Arguments to pass to the MCP prompt',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
// Get credentials
|
||||
const credentials = await this.getCredentials('mcpApi');
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
let result: any;
|
||||
|
||||
switch (operation) {
|
||||
case 'callTool':
|
||||
const toolName = this.getNodeParameter('toolName', itemIndex) as string;
|
||||
const toolArgumentsJson = this.getNodeParameter('toolArguments', itemIndex) as string;
|
||||
const toolArguments = JSON.parse(toolArgumentsJson);
|
||||
|
||||
result = await (this as any).callMCPTool(credentials, toolName, toolArguments);
|
||||
break;
|
||||
|
||||
case 'listTools':
|
||||
result = await (this as any).listMCPTools(credentials);
|
||||
break;
|
||||
|
||||
case 'readResource':
|
||||
const resourceUri = this.getNodeParameter('resourceUri', itemIndex) as string;
|
||||
result = await (this as any).readMCPResource(credentials, resourceUri);
|
||||
break;
|
||||
|
||||
case 'listResources':
|
||||
result = await (this as any).listMCPResources(credentials);
|
||||
break;
|
||||
|
||||
case 'getPrompt':
|
||||
const promptName = this.getNodeParameter('promptName', itemIndex) as string;
|
||||
const promptArgumentsJson = this.getNodeParameter('promptArguments', itemIndex) as string;
|
||||
const promptArguments = JSON.parse(promptArgumentsJson);
|
||||
|
||||
result = await (this as any).getMCPPrompt(credentials, promptName, promptArguments);
|
||||
break;
|
||||
|
||||
case 'listPrompts':
|
||||
result = await (this as any).listMCPPrompts(credentials);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
json: result,
|
||||
pairedItem: itemIndex,
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
pairedItem: itemIndex,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
|
||||
// MCP client methods
|
||||
private async getMCPClient(credentials: any): Promise<MCPClient> {
|
||||
const client = new MCPClient({
|
||||
serverUrl: credentials.serverUrl,
|
||||
authToken: credentials.authToken,
|
||||
connectionType: credentials.connectionType || 'websocket',
|
||||
});
|
||||
await client.connect();
|
||||
return client;
|
||||
}
|
||||
|
||||
private async callMCPTool(credentials: any, toolName: string, args: any): Promise<any> {
|
||||
const client = await this.getMCPClient(credentials);
|
||||
try {
|
||||
const result = await client.callTool(toolName, args);
|
||||
return N8NMCPBridge.mcpToN8NExecutionData(result).json;
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async listMCPTools(credentials: any): Promise<any> {
|
||||
const client = await this.getMCPClient(credentials);
|
||||
try {
|
||||
return await client.listTools();
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async readMCPResource(credentials: any, uri: string): Promise<any> {
|
||||
const client = await this.getMCPClient(credentials);
|
||||
try {
|
||||
const result = await client.readResource(uri);
|
||||
return N8NMCPBridge.mcpToN8NExecutionData(result).json;
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async listMCPResources(credentials: any): Promise<any> {
|
||||
const client = await this.getMCPClient(credentials);
|
||||
try {
|
||||
return await client.listResources();
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async getMCPPrompt(credentials: any, promptName: string, args: any): Promise<any> {
|
||||
const client = await this.getMCPClient(credentials);
|
||||
try {
|
||||
const result = await client.getPrompt(promptName, args);
|
||||
return N8NMCPBridge.mcpPromptArgsToN8N(result);
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private async listMCPPrompts(credentials: any): Promise<any> {
|
||||
const client = await this.getMCPClient(credentials);
|
||||
try {
|
||||
return await client.listPrompts();
|
||||
} finally {
|
||||
await client.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user