mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 22:42:04 +00:00
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>
166 lines
4.2 KiB
TypeScript
166 lines
4.2 KiB
TypeScript
import { INodeExecutionData, IDataObject } from 'n8n-workflow';
|
|
|
|
export class N8NMCPBridge {
|
|
/**
|
|
* Convert n8n workflow data to MCP tool arguments
|
|
*/
|
|
static n8nToMCPToolArgs(data: IDataObject): any {
|
|
// Handle different data formats from n8n
|
|
if (data.json) {
|
|
return data.json;
|
|
}
|
|
|
|
// Remove n8n-specific metadata
|
|
const { pairedItem, ...cleanData } = data;
|
|
return cleanData;
|
|
}
|
|
|
|
/**
|
|
* Convert MCP tool response to n8n execution data
|
|
*/
|
|
static mcpToN8NExecutionData(mcpResponse: any, itemIndex: number = 0): INodeExecutionData {
|
|
// Handle MCP content array format
|
|
if (mcpResponse.content && Array.isArray(mcpResponse.content)) {
|
|
const textContent = mcpResponse.content
|
|
.filter((c: any) => c.type === 'text')
|
|
.map((c: any) => c.text)
|
|
.join('\n');
|
|
|
|
try {
|
|
// Try to parse as JSON if possible
|
|
const parsed = JSON.parse(textContent);
|
|
return {
|
|
json: parsed,
|
|
pairedItem: itemIndex,
|
|
};
|
|
} catch {
|
|
// Return as text if not JSON
|
|
return {
|
|
json: { result: textContent },
|
|
pairedItem: itemIndex,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Handle direct object response
|
|
return {
|
|
json: mcpResponse,
|
|
pairedItem: itemIndex,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert n8n workflow definition to MCP-compatible format
|
|
*/
|
|
static n8nWorkflowToMCP(workflow: any): any {
|
|
return {
|
|
id: workflow.id,
|
|
name: workflow.name,
|
|
description: workflow.description || '',
|
|
nodes: workflow.nodes?.map((node: any) => ({
|
|
id: node.id,
|
|
type: node.type,
|
|
name: node.name,
|
|
parameters: node.parameters,
|
|
position: node.position,
|
|
})),
|
|
connections: workflow.connections,
|
|
settings: workflow.settings,
|
|
metadata: {
|
|
createdAt: workflow.createdAt,
|
|
updatedAt: workflow.updatedAt,
|
|
active: workflow.active,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert MCP workflow format to n8n-compatible format
|
|
*/
|
|
static mcpToN8NWorkflow(mcpWorkflow: any): any {
|
|
return {
|
|
name: mcpWorkflow.name,
|
|
nodes: mcpWorkflow.nodes || [],
|
|
connections: mcpWorkflow.connections || {},
|
|
settings: mcpWorkflow.settings || {
|
|
executionOrder: 'v1',
|
|
},
|
|
staticData: null,
|
|
pinData: {},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert n8n execution data to MCP resource format
|
|
*/
|
|
static n8nExecutionToMCPResource(execution: any): any {
|
|
return {
|
|
uri: `execution://${execution.id}`,
|
|
name: `Execution ${execution.id}`,
|
|
description: `Workflow: ${execution.workflowData?.name || 'Unknown'}`,
|
|
mimeType: 'application/json',
|
|
data: {
|
|
id: execution.id,
|
|
workflowId: execution.workflowId,
|
|
status: execution.finished ? 'completed' : execution.stoppedAt ? 'stopped' : 'running',
|
|
mode: execution.mode,
|
|
startedAt: execution.startedAt,
|
|
stoppedAt: execution.stoppedAt,
|
|
error: execution.data?.resultData?.error,
|
|
executionData: execution.data,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convert MCP prompt arguments to n8n-compatible format
|
|
*/
|
|
static mcpPromptArgsToN8N(promptArgs: any): IDataObject {
|
|
return {
|
|
prompt: promptArgs.name || '',
|
|
arguments: promptArgs.arguments || {},
|
|
messages: promptArgs.messages || [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate and sanitize data before conversion
|
|
*/
|
|
static sanitizeData(data: any): any {
|
|
if (data === null || data === undefined) {
|
|
return {};
|
|
}
|
|
|
|
if (typeof data !== 'object') {
|
|
return { value: data };
|
|
}
|
|
|
|
// Remove circular references
|
|
const seen = new WeakSet();
|
|
return JSON.parse(JSON.stringify(data, (_key, value) => {
|
|
if (typeof value === 'object' && value !== null) {
|
|
if (seen.has(value)) {
|
|
return '[Circular]';
|
|
}
|
|
seen.add(value);
|
|
}
|
|
return value;
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Extract error information for both n8n and MCP formats
|
|
*/
|
|
static formatError(error: any): any {
|
|
return {
|
|
message: error.message || 'Unknown error',
|
|
type: error.name || 'Error',
|
|
stack: error.stack,
|
|
details: {
|
|
code: error.code,
|
|
statusCode: error.statusCode,
|
|
data: error.data,
|
|
},
|
|
};
|
|
}
|
|
} |