Files
n8n-mcp/tests/bridge.test.ts
czlonkowski 1f8140c45c 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>
2025-06-07 15:43:02 +00:00

173 lines
4.9 KiB
TypeScript

import { N8NMCPBridge } from '../src/utils/bridge';
describe('N8NMCPBridge', () => {
describe('n8nToMCPToolArgs', () => {
it('should extract json from n8n data object', () => {
const n8nData = { json: { foo: 'bar' } };
const result = N8NMCPBridge.n8nToMCPToolArgs(n8nData);
expect(result).toEqual({ foo: 'bar' });
});
it('should remove n8n metadata', () => {
const n8nData = { foo: 'bar', pairedItem: 0 };
const result = N8NMCPBridge.n8nToMCPToolArgs(n8nData);
expect(result).toEqual({ foo: 'bar' });
});
});
describe('mcpToN8NExecutionData', () => {
it('should convert MCP content array to n8n format', () => {
const mcpResponse = {
content: [{ type: 'text', text: '{"result": "success"}' }],
};
const result = N8NMCPBridge.mcpToN8NExecutionData(mcpResponse, 1);
expect(result).toEqual({
json: { result: 'success' },
pairedItem: 1,
});
});
it('should handle non-JSON text content', () => {
const mcpResponse = {
content: [{ type: 'text', text: 'plain text response' }],
};
const result = N8NMCPBridge.mcpToN8NExecutionData(mcpResponse);
expect(result).toEqual({
json: { result: 'plain text response' },
pairedItem: 0,
});
});
it('should handle direct object response', () => {
const mcpResponse = { foo: 'bar' };
const result = N8NMCPBridge.mcpToN8NExecutionData(mcpResponse);
expect(result).toEqual({
json: { foo: 'bar' },
pairedItem: 0,
});
});
});
describe('n8nWorkflowToMCP', () => {
it('should convert n8n workflow to MCP format', () => {
const n8nWorkflow = {
id: '123',
name: 'Test Workflow',
nodes: [
{
id: 'node1',
type: 'n8n-nodes-base.start',
name: 'Start',
parameters: {},
position: [100, 100],
},
],
connections: {},
settings: { executionOrder: 'v1' },
active: true,
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
};
const result = N8NMCPBridge.n8nWorkflowToMCP(n8nWorkflow);
expect(result).toEqual({
id: '123',
name: 'Test Workflow',
description: '',
nodes: [
{
id: 'node1',
type: 'n8n-nodes-base.start',
name: 'Start',
parameters: {},
position: [100, 100],
},
],
connections: {},
settings: { executionOrder: 'v1' },
metadata: {
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
active: true,
},
});
});
});
describe('mcpToN8NWorkflow', () => {
it('should convert MCP workflow to n8n format', () => {
const mcpWorkflow = {
name: 'Test Workflow',
nodes: [{ id: 'node1', type: 'n8n-nodes-base.start' }],
connections: { node1: { main: [[]] } },
};
const result = N8NMCPBridge.mcpToN8NWorkflow(mcpWorkflow);
expect(result).toEqual({
name: 'Test Workflow',
nodes: [{ id: 'node1', type: 'n8n-nodes-base.start' }],
connections: { node1: { main: [[]] } },
settings: { executionOrder: 'v1' },
staticData: null,
pinData: {},
});
});
});
describe('sanitizeData', () => {
it('should handle null and undefined', () => {
expect(N8NMCPBridge.sanitizeData(null)).toEqual({});
expect(N8NMCPBridge.sanitizeData(undefined)).toEqual({});
});
it('should wrap non-objects', () => {
expect(N8NMCPBridge.sanitizeData('string')).toEqual({ value: 'string' });
expect(N8NMCPBridge.sanitizeData(123)).toEqual({ value: 123 });
});
it('should handle circular references', () => {
const obj: any = { a: 1 };
obj.circular = obj;
const result = N8NMCPBridge.sanitizeData(obj);
expect(result).toEqual({ a: 1, circular: '[Circular]' });
});
});
describe('formatError', () => {
it('should format standard errors', () => {
const error = new Error('Test error');
error.stack = 'stack trace';
const result = N8NMCPBridge.formatError(error);
expect(result).toEqual({
message: 'Test error',
type: 'Error',
stack: 'stack trace',
details: {
code: undefined,
statusCode: undefined,
data: undefined,
},
});
});
it('should include additional error properties', () => {
const error: any = new Error('API error');
error.code = 'ERR_API';
error.statusCode = 404;
error.data = { field: 'value' };
const result = N8NMCPBridge.formatError(error);
expect(result.details).toEqual({
code: 'ERR_API',
statusCode: 404,
data: { field: 'value' },
});
});
});
});