This is page 2 of 3. Use http://codebase.md/makafeli/n8n-workflow-builder?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .github
│ ├── assets
│ │ ├── README.md
│ │ └── social-preview.svg
│ ├── SOCIAL_PREVIEW.md
│ └── workflows
│ ├── ci.yml
│ ├── create-release.yml
│ ├── publish-packages.yml
│ └── release.yml
├── .gitignore
├── .vscode
│ └── settings.json
├── COMPARISON.md
├── dist
│ └── index.js
├── GETTING_STARTED.md
├── jest.config.ci.cjs
├── jest.config.cjs
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── RELEASE_SETUP.md
├── scripts
│ └── verify-package.js
├── SMITHERY_DEPLOYMENT.md
├── smithery.yaml
├── src
│ ├── config
│ │ └── constants.ts
│ ├── index.cjs
│ ├── index.ts
│ ├── sdk-schemas.ts
│ ├── server.ts
│ ├── services
│ │ ├── n8nApi.ts
│ │ └── workflowBuilder.ts
│ ├── types
│ │ ├── api.ts
│ │ ├── node.ts
│ │ ├── sdk.d.ts
│ │ └── workflow.ts
│ └── utils
│ ├── positioning.ts
│ └── validation.ts
├── test-results
│ └── junit.xml
├── tests
│ ├── activate-workflow.js
│ ├── check-workflow.js
│ ├── create-final-workflow.js
│ ├── create-support-workflow.js
│ ├── helpers
│ │ ├── mcpClient.ts
│ │ └── mockData.ts
│ ├── integration
│ │ ├── credentials.test.ts
│ │ ├── endToEnd.test.ts
│ │ ├── errorHandling.test.ts
│ │ ├── execution.test.ts
│ │ ├── newWorkflowTools.test.ts
│ │ ├── resources.test.ts
│ │ └── tags.test.ts
│ ├── setup.ts
│ ├── test-all-tools.js
│ ├── test-integration.js
│ ├── test-mcp-tools.js
│ ├── test-simple-workflow.js
│ └── tsconfig.json
├── TROUBLESHOOTING.md
├── tsconfig.json
├── tsconfig.smithery.json
└── USE_CASES.md
```
# Files
--------------------------------------------------------------------------------
/tests/create-support-workflow.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Script to create the comprehensive Support Optimization System workflow
5 | */
6 |
7 | const { spawn } = require('child_process');
8 |
9 | class WorkflowCreator {
10 | constructor() {
11 | this.serverProcess = null;
12 | }
13 |
14 | async startServer() {
15 | console.log('🚀 Starting n8n MCP server...');
16 |
17 | this.serverProcess = spawn('npx', ['.'], {
18 | cwd: '/Users/yasinboelhouwer/n8n-workflow-builder',
19 | env: {
20 | ...process.env,
21 | N8N_HOST: 'https://n8n.yasin.nu/api/v1',
22 | N8N_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMmE2NzM0NC05ZWI1LTQ0NmMtODczNi1lNWYyOGE4MjY4NTIiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzUzMzQzODU5fQ.PhpEIzzSGROy9Kok26SXmj9RRH1K3ArahexaVbQ2-Ho'
23 | },
24 | stdio: ['pipe', 'pipe', 'pipe']
25 | });
26 |
27 | return new Promise((resolve, reject) => {
28 | let output = '';
29 |
30 | this.serverProcess.stderr.on('data', (data) => {
31 | output += data.toString();
32 | if (output.includes('N8N Workflow Builder MCP server running on stdio')) {
33 | console.log('✅ Server started successfully');
34 | resolve();
35 | }
36 | });
37 |
38 | this.serverProcess.on('error', (error) => {
39 | console.error('❌ Failed to start server:', error);
40 | reject(error);
41 | });
42 |
43 | setTimeout(() => {
44 | reject(new Error('Server startup timeout'));
45 | }, 10000);
46 | });
47 | }
48 |
49 | async sendMCPRequest(method, params = {}) {
50 | return new Promise((resolve, reject) => {
51 | const request = {
52 | jsonrpc: '2.0',
53 | id: Date.now(),
54 | method: method,
55 | params: params
56 | };
57 |
58 | let response = '';
59 | let timeout;
60 |
61 | const onData = (data) => {
62 | response += data.toString();
63 | try {
64 | const parsed = JSON.parse(response);
65 | clearTimeout(timeout);
66 | this.serverProcess.stdout.removeListener('data', onData);
67 | resolve(parsed);
68 | } catch (e) {
69 | // Continue collecting data
70 | }
71 | };
72 |
73 | this.serverProcess.stdout.on('data', onData);
74 |
75 | timeout = setTimeout(() => {
76 | this.serverProcess.stdout.removeListener('data', onData);
77 | reject(new Error(`Timeout waiting for response to ${method}`));
78 | }, 10000);
79 |
80 | this.serverProcess.stdin.write(JSON.stringify(request) + '\n');
81 | });
82 | }
83 |
84 | getSupportOptimizationWorkflow() {
85 | return {
86 | name: "Support Optimization System - HelpScout AI Analysis",
87 | nodes: [
88 | {
89 | id: "schedule-trigger",
90 | name: "Schedule Trigger",
91 | type: "n8n-nodes-base.scheduleTrigger",
92 | typeVersion: 1,
93 | position: [240, 300],
94 | parameters: {
95 | interval: [
96 | {
97 | field: "unit",
98 | value: "hours"
99 | },
100 | {
101 | field: "intervalValue",
102 | value: 6
103 | }
104 | ]
105 | }
106 | },
107 | {
108 | id: "helpscout-fetch",
109 | name: "HelpScout - Fetch Messages",
110 | type: "n8n-nodes-base.httpRequest",
111 | typeVersion: 4,
112 | position: [460, 300],
113 | parameters: {
114 | url: "https://api.helpscout.net/v2/conversations",
115 | method: "GET",
116 | sendQuery: true,
117 | queryParameters: {
118 | parameters: [
119 | { name: "status", value: "all" },
120 | { name: "embed", value: "threads" },
121 | { name: "page", value: "1" },
122 | { name: "sortField", value: "modifiedAt" },
123 | { name: "sortOrder", value: "desc" }
124 | ]
125 | },
126 | sendHeaders: true,
127 | headerParameters: {
128 | parameters: [
129 | { name: "Authorization", value: "Bearer YOUR_HELPSCOUT_API_TOKEN" },
130 | { name: "Content-Type", value: "application/json" }
131 | ]
132 | },
133 | options: {
134 | timeout: 30000
135 | }
136 | }
137 | },
138 | {
139 | id: "data-processor",
140 | name: "Process Support Data",
141 | type: "n8n-nodes-base.code",
142 | typeVersion: 2,
143 | position: [680, 300],
144 | parameters: {
145 | mode: "runOnceForAllItems",
146 | jsCode: "// Process HelpScout data\nconst data = $input.all();\nconsole.log('Processing', data.length, 'items');\nreturn data;"
147 | }
148 | }
149 | ],
150 | connections: {
151 | "Schedule Trigger": {
152 | "main": [
153 | [
154 | {
155 | "node": "HelpScout - Fetch Messages",
156 | "type": "main",
157 | "index": 0
158 | }
159 | ]
160 | ]
161 | },
162 | "HelpScout - Fetch Messages": {
163 | "main": [
164 | [
165 | {
166 | "node": "Process Support Data",
167 | "type": "main",
168 | "index": 0
169 | }
170 | ]
171 | ]
172 | }
173 | },
174 | settings: {
175 | saveExecutionProgress: true,
176 | saveManualExecutions: true,
177 | saveDataErrorExecution: "all",
178 | saveDataSuccessExecution: "all",
179 | executionTimeout: 3600,
180 | timezone: "America/New_York"
181 | },
182 | tags: [
183 | {
184 | name: "Support Optimization"
185 | },
186 | {
187 | name: "AI Analysis"
188 | },
189 | {
190 | name: "HelpScout"
191 | }
192 | ]
193 | };
194 | }
195 |
196 | async createWorkflow() {
197 | try {
198 | await this.startServer();
199 |
200 | console.log('📋 Creating Support Optimization System workflow...\n');
201 |
202 | const workflow = this.getSupportOptimizationWorkflow();
203 |
204 | const response = await this.sendMCPRequest('tools/call', {
205 | name: 'create_workflow',
206 | arguments: { workflow }
207 | });
208 |
209 | if (response.error) {
210 | console.log('❌ Workflow creation failed:', response.error.message);
211 | return false;
212 | } else {
213 | console.log('✅ Support Optimization System workflow created successfully!');
214 | console.log('📊 Workflow Details:');
215 |
216 | try {
217 | const responseText = response.result.content[0].text;
218 | console.log('Raw response:', responseText.substring(0, 200) + '...');
219 |
220 | const result = JSON.parse(responseText);
221 | console.log(` - ID: ${result.id}`);
222 | console.log(` - Name: ${result.name}`);
223 | console.log(` - Nodes: ${result.nodes.length}`);
224 | console.log(` - Active: ${result.active}`);
225 | } catch (parseError) {
226 | console.log('⚠️ Could not parse workflow details, but creation succeeded');
227 | console.log('Response:', response.result.content[0].text.substring(0, 500));
228 | }
229 |
230 | return true;
231 | }
232 | } catch (error) {
233 | console.error('❌ Workflow creation error:', error.message);
234 | return false;
235 | } finally {
236 | this.cleanup();
237 | }
238 | }
239 |
240 | cleanup() {
241 | if (this.serverProcess) {
242 | console.log('\n🧹 Cleaning up server process...');
243 | this.serverProcess.kill();
244 | }
245 | }
246 | }
247 |
248 | // Create the workflow
249 | const creator = new WorkflowCreator();
250 | creator.createWorkflow().then(success => {
251 | if (success) {
252 | console.log('\n🎉 Support Optimization System workflow is ready!');
253 | console.log('📝 Next steps:');
254 | console.log(' 1. Configure HelpScout API credentials in n8n');
255 | console.log(' 2. Add AI analysis nodes (OpenAI, Claude, etc.)');
256 | console.log(' 3. Configure output nodes (Slack, Google Sheets)');
257 | console.log(' 4. Test and activate the workflow');
258 | process.exit(0);
259 | } else {
260 | console.log('\n❌ Failed to create workflow. Check the errors above.');
261 | process.exit(1);
262 | }
263 | }).catch(console.error);
264 |
```
--------------------------------------------------------------------------------
/tests/integration/newWorkflowTools.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { MCPTestClient } from '../helpers/mcpClient';
2 | import { mockWorkflow, mockAuditReport, mockN8nResponses } from '../helpers/mockData';
3 | import axios from 'axios';
4 |
5 | jest.mock('axios');
6 | const mockedAxios = axios as jest.Mocked<typeof axios>;
7 |
8 | describe('New Workflow Tools Integration Tests', () => {
9 | let client: MCPTestClient;
10 |
11 | beforeEach(() => {
12 | client = new MCPTestClient();
13 | jest.clearAllMocks();
14 | });
15 |
16 | describe('execute_workflow', () => {
17 | it('should execute workflow successfully', async () => {
18 | mockedAxios.post.mockResolvedValueOnce({
19 | data: {
20 | id: 'new-execution-id',
21 | workflowId: 'test-workflow-id',
22 | status: 'running',
23 | startedAt: new Date().toISOString()
24 | }
25 | });
26 |
27 | const result = await client.callTool('execute_workflow', {
28 | id: 'test-workflow-id'
29 | });
30 |
31 | expect(result.content).toBeDefined();
32 | const response = JSON.parse((result.content as any)[0].text);
33 | expect(response.success).toBe(true);
34 | expect(response.execution).toBeDefined();
35 | expect(response.execution.workflowId).toBe('test-workflow-id');
36 | expect(response.message).toContain('executed successfully');
37 | });
38 |
39 | it('should require workflow ID', async () => {
40 | const result = await client.callTool('execute_workflow', {});
41 |
42 | expect(result.isError).toBe(true);
43 | expect((result.content as any)[0].text).toContain('Workflow ID is required');
44 | });
45 |
46 | it('should handle inactive workflow errors', async () => {
47 | mockedAxios.post.mockRejectedValueOnce(new Error('Workflow is not active'));
48 |
49 | const result = await client.callTool('execute_workflow', {
50 | id: 'inactive-workflow-id'
51 | });
52 |
53 | expect(result.isError).toBe(true);
54 | expect((result.content as any)[0].text).toContain('Error: Workflow is not active');
55 | });
56 |
57 | it('should handle workflow not found errors', async () => {
58 | mockedAxios.post.mockRejectedValueOnce(new Error('Workflow not found'));
59 |
60 | const result = await client.callTool('execute_workflow', {
61 | id: 'nonexistent-workflow-id'
62 | });
63 |
64 | expect(result.isError).toBe(true);
65 | expect((result.content as any)[0].text).toContain('Error: Workflow not found');
66 | });
67 |
68 | it('should handle execution errors', async () => {
69 | mockedAxios.post.mockRejectedValueOnce(new Error('Execution failed'));
70 |
71 | const result = await client.callTool('execute_workflow', {
72 | id: 'test-workflow-id'
73 | });
74 |
75 | expect(result.isError).toBe(true);
76 | expect((result.content as any)[0].text).toContain('Error: Execution failed');
77 | });
78 | });
79 |
80 | describe('create_workflow_and_activate', () => {
81 | it('should create and activate workflow successfully', async () => {
82 | // Mock workflow creation
83 | mockedAxios.post
84 | .mockResolvedValueOnce({
85 | data: { ...mockWorkflow, id: 'new-workflow-id' }
86 | })
87 | // Mock workflow activation
88 | .mockResolvedValueOnce({
89 | data: { ...mockWorkflow, id: 'new-workflow-id', active: true }
90 | });
91 |
92 | const result = await client.callTool('create_workflow_and_activate', {
93 | workflow: mockWorkflow
94 | });
95 |
96 | expect(result.content).toBeDefined();
97 | const response = JSON.parse((result.content as any)[0].text);
98 | expect(response.success).toBe(true);
99 | expect(response.workflow).toBeDefined();
100 | expect(response.workflow.active).toBe(true);
101 | expect(response.message).toContain('created and activated successfully');
102 | });
103 |
104 | it('should require workflow data', async () => {
105 | const result = await client.callTool('create_workflow_and_activate', {});
106 |
107 | expect(result.isError).toBe(true);
108 | expect((result.content as any)[0].text).toContain('Workflow data is required');
109 | });
110 |
111 | it('should handle workflow creation errors', async () => {
112 | mockedAxios.post.mockRejectedValueOnce(new Error('Invalid workflow data'));
113 |
114 | const result = await client.callTool('create_workflow_and_activate', {
115 | workflow: { name: 'Invalid Workflow' }
116 | });
117 |
118 | expect(result.isError).toBe(true);
119 | expect((result.content as any)[0].text).toContain('Error: Invalid workflow data');
120 | });
121 |
122 | it('should handle activation errors after successful creation', async () => {
123 | // Mock successful creation
124 | mockedAxios.post
125 | .mockResolvedValueOnce({
126 | data: { ...mockWorkflow, id: 'new-workflow-id' }
127 | })
128 | // Mock activation failure
129 | .mockRejectedValueOnce(new Error('Activation failed'));
130 |
131 | const result = await client.callTool('create_workflow_and_activate', {
132 | workflow: mockWorkflow
133 | });
134 |
135 | expect(result.isError).toBe(true);
136 | expect((result.content as any)[0].text).toContain('Error: Activation failed');
137 | });
138 |
139 | it('should validate workflow structure', async () => {
140 | const invalidWorkflow = {
141 | name: 'Test Workflow'
142 | // Missing required nodes array
143 | };
144 |
145 | const result = await client.callTool('create_workflow_and_activate', {
146 | workflow: invalidWorkflow
147 | });
148 |
149 | // The mock client should handle this gracefully
150 | expect(result.content).toBeDefined();
151 | });
152 | });
153 |
154 | describe('generate_audit', () => {
155 | it('should generate security audit successfully', async () => {
156 | mockedAxios.post.mockResolvedValueOnce({
157 | data: mockAuditReport
158 | });
159 |
160 | const result = await client.callTool('generate_audit', {});
161 |
162 | expect(result.content).toBeDefined();
163 | const response = JSON.parse((result.content as any)[0].text);
164 | expect(response.success).toBe(true);
165 | expect(response.audit).toBeDefined();
166 | expect(response.audit.instance).toBeDefined();
167 | expect(response.audit.security).toBeDefined();
168 | expect(response.audit.recommendations).toBeDefined();
169 | expect(response.message).toContain('Security audit generated successfully');
170 | });
171 |
172 | it('should support custom audit options', async () => {
173 | mockedAxios.post.mockResolvedValueOnce({
174 | data: mockAuditReport
175 | });
176 |
177 | const result = await client.callTool('generate_audit', {
178 | additionalOptions: {
179 | daysAbandonedWorkflow: 30,
180 | categories: ['credentials', 'workflows']
181 | }
182 | });
183 |
184 | expect(result.content).toBeDefined();
185 | const response = JSON.parse((result.content as any)[0].text);
186 | expect(response.success).toBe(true);
187 | expect(response.audit).toBeDefined();
188 | });
189 |
190 | it('should handle audit generation errors', async () => {
191 | mockedAxios.post.mockRejectedValueOnce(new Error('Audit generation failed'));
192 |
193 | const result = await client.callTool('generate_audit', {});
194 |
195 | expect(result.isError).toBe(true);
196 | expect((result.content as any)[0].text).toContain('Error: Audit generation failed');
197 | });
198 |
199 | it('should handle insufficient permissions', async () => {
200 | mockedAxios.post.mockRejectedValueOnce(new Error('Insufficient permissions for audit'));
201 |
202 | const result = await client.callTool('generate_audit', {});
203 |
204 | expect(result.isError).toBe(true);
205 | expect((result.content as any)[0].text).toContain('Error: Insufficient permissions for audit');
206 | });
207 |
208 | it('should validate audit categories', async () => {
209 | const result = await client.callTool('generate_audit', {
210 | additionalOptions: {
211 | categories: ['credentials', 'database', 'nodes', 'filesystem', 'instance']
212 | }
213 | });
214 |
215 | expect(result.content).toBeDefined();
216 | const response = JSON.parse((result.content as any)[0].text);
217 | expect(response.success).toBe(true);
218 | });
219 | });
220 | });
221 |
```
--------------------------------------------------------------------------------
/tests/test-integration.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Integration test to verify MCP server compatibility and tool availability
5 | */
6 |
7 | const { spawn } = require('child_process');
8 |
9 | class IntegrationTester {
10 | constructor() {
11 | this.serverProcess = null;
12 | }
13 |
14 | async startServer() {
15 | console.log('🚀 Starting n8n MCP server for integration test...');
16 |
17 | this.serverProcess = spawn('npx', ['.'], {
18 | cwd: '/Users/yasinboelhouwer/n8n-workflow-builder',
19 | env: {
20 | ...process.env,
21 | N8N_HOST: 'https://n8n.yasin.nu/api/v1',
22 | N8N_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMmE2NzM0NC05ZWI1LTQ0NmMtODczNi1lNWYyOGE4MjY4NTIiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzUzMzQzODU5fQ.PhpEIzzSGROy9Kok26SXmj9RRH1K3ArahexaVbQ2-Ho'
23 | },
24 | stdio: ['pipe', 'pipe', 'pipe']
25 | });
26 |
27 | return new Promise((resolve, reject) => {
28 | let output = '';
29 |
30 | this.serverProcess.stderr.on('data', (data) => {
31 | output += data.toString();
32 | if (output.includes('N8N Workflow Builder MCP server running on stdio')) {
33 | console.log('✅ Server started successfully');
34 | resolve();
35 | }
36 | });
37 |
38 | this.serverProcess.on('error', (error) => {
39 | console.error('❌ Failed to start server:', error);
40 | reject(error);
41 | });
42 |
43 | setTimeout(() => {
44 | reject(new Error('Server startup timeout'));
45 | }, 10000);
46 | });
47 | }
48 |
49 | async sendMCPRequest(method, params = {}) {
50 | return new Promise((resolve, reject) => {
51 | const request = {
52 | jsonrpc: '2.0',
53 | id: Date.now(),
54 | method: method,
55 | params: params
56 | };
57 |
58 | let response = '';
59 | let timeout;
60 |
61 | const onData = (data) => {
62 | response += data.toString();
63 | try {
64 | const parsed = JSON.parse(response);
65 | clearTimeout(timeout);
66 | this.serverProcess.stdout.removeListener('data', onData);
67 | resolve(parsed);
68 | } catch (e) {
69 | // Continue collecting data
70 | }
71 | };
72 |
73 | this.serverProcess.stdout.on('data', onData);
74 |
75 | timeout = setTimeout(() => {
76 | this.serverProcess.stdout.removeListener('data', onData);
77 | reject(new Error(`Timeout waiting for response to ${method}`));
78 | }, 5000);
79 |
80 | this.serverProcess.stdin.write(JSON.stringify(request) + '\n');
81 | });
82 | }
83 |
84 | async testToolsAvailability() {
85 | console.log('🔍 Testing tools availability...');
86 |
87 | try {
88 | const response = await this.sendMCPRequest('tools/list');
89 |
90 | if (response.error) {
91 | console.log('❌ Failed to list tools:', response.error.message);
92 | return false;
93 | }
94 |
95 | const tools = response.result.tools || [];
96 | const expectedTools = [
97 | 'list_workflows',
98 | 'create_workflow',
99 | 'get_workflow',
100 | 'execute_workflow',
101 | 'update_workflow',
102 | 'activate_workflow',
103 | 'deactivate_workflow',
104 | 'delete_workflow',
105 | 'create_workflow_and_activate'
106 | ];
107 |
108 | console.log(`📋 Found ${tools.length} tools:`);
109 |
110 | const availableToolNames = tools.map(tool => tool.name);
111 | const missingTools = expectedTools.filter(tool => !availableToolNames.includes(tool));
112 | const extraTools = availableToolNames.filter(tool => !expectedTools.includes(tool));
113 |
114 | expectedTools.forEach(toolName => {
115 | const isAvailable = availableToolNames.includes(toolName);
116 | console.log(` ${isAvailable ? '✅' : '❌'} ${toolName}`);
117 | });
118 |
119 | if (extraTools.length > 0) {
120 | console.log('\n📎 Additional tools found:');
121 | extraTools.forEach(tool => console.log(` ➕ ${tool}`));
122 | }
123 |
124 | if (missingTools.length === 0) {
125 | console.log('\n🎉 All expected tools are available!');
126 | return true;
127 | } else {
128 | console.log('\n❌ Missing tools:', missingTools);
129 | return false;
130 | }
131 |
132 | } catch (error) {
133 | console.error('❌ Error testing tools availability:', error.message);
134 | return false;
135 | }
136 | }
137 |
138 | async testMCPCompatibility() {
139 | console.log('\n🔧 Testing MCP protocol compatibility...');
140 |
141 | try {
142 | // Test initialize
143 | const initResponse = await this.sendMCPRequest('initialize', {
144 | protocolVersion: '2024-11-05',
145 | capabilities: {},
146 | clientInfo: {
147 | name: 'integration-test',
148 | version: '1.0.0'
149 | }
150 | });
151 |
152 | if (initResponse.error) {
153 | console.log('❌ MCP initialize failed:', initResponse.error.message);
154 | return false;
155 | }
156 |
157 | console.log('✅ MCP initialize successful');
158 |
159 | // Test ping (if supported)
160 | try {
161 | const pingResponse = await this.sendMCPRequest('ping');
162 | if (!pingResponse.error) {
163 | console.log('✅ MCP ping successful');
164 | }
165 | } catch (e) {
166 | // Ping might not be supported, that's okay
167 | console.log('ℹ️ MCP ping not supported (optional)');
168 | }
169 |
170 | return true;
171 |
172 | } catch (error) {
173 | console.error('❌ MCP compatibility test failed:', error.message);
174 | return false;
175 | }
176 | }
177 |
178 | async testBasicFunctionality() {
179 | try {
180 | // Test list_workflows
181 | const listResponse = await this.sendMCPRequest('tools/call', {
182 | name: 'list_workflows',
183 | arguments: {}
184 | });
185 |
186 | if (listResponse.error) {
187 | console.log('❌ Basic functionality test failed:', listResponse.error.message);
188 | return false;
189 | }
190 |
191 | console.log('✅ Basic functionality test passed');
192 | return true;
193 |
194 | } catch (error) {
195 | console.log('❌ Basic functionality test error:', error.message);
196 | return false;
197 | }
198 | }
199 |
200 | async runIntegrationTests() {
201 | try {
202 | await this.startServer();
203 |
204 | console.log('\n🧪 Running integration tests...\n');
205 |
206 | // Test 1: Tools availability
207 | const toolsAvailable = await this.testToolsAvailability();
208 |
209 | // Test 2: MCP compatibility
210 | const mcpCompatible = await this.testMCPCompatibility();
211 |
212 | // Test 3: Quick functional test
213 | console.log('\n⚡ Running quick functional test...');
214 | const functionalTest = await this.testBasicFunctionality();
215 |
216 | // Summary
217 | console.log('\n📊 Integration Test Results:');
218 | console.log('============================');
219 | console.log(`Tools Availability: ${toolsAvailable ? '✅ PASS' : '❌ FAIL'}`);
220 | console.log(`MCP Compatibility: ${mcpCompatible ? '✅ PASS' : '❌ FAIL'}`);
221 | console.log(`Basic Functionality: ${functionalTest ? '✅ PASS' : '❌ FAIL'}`);
222 |
223 | const allPassed = toolsAvailable && mcpCompatible && functionalTest;
224 |
225 | if (allPassed) {
226 | console.log('\n🎉 All integration tests passed!');
227 | console.log('✅ Enhanced MCP server is fully compatible and functional');
228 | return true;
229 | } else {
230 | console.log('\n❌ Some integration tests failed');
231 | return false;
232 | }
233 |
234 | } catch (error) {
235 | console.error('❌ Integration test execution failed:', error);
236 | return false;
237 | } finally {
238 | this.cleanup();
239 | }
240 | }
241 |
242 | cleanup() {
243 | if (this.serverProcess) {
244 | console.log('\n🧹 Cleaning up server process...');
245 | this.serverProcess.kill();
246 | }
247 | }
248 | }
249 |
250 | // Run integration tests
251 | const tester = new IntegrationTester();
252 | tester.runIntegrationTests().then(success => {
253 | if (success) {
254 | console.log('\n🚀 Enhanced n8n-workflow-builder MCP server is ready for production!');
255 | process.exit(0);
256 | } else {
257 | console.log('\n⚠️ Integration tests failed. Please review the issues above.');
258 | process.exit(1);
259 | }
260 | }).catch(console.error);
261 |
```
--------------------------------------------------------------------------------
/tests/create-final-workflow.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Create the final Support Optimization System workflow
5 | */
6 |
7 | const { spawn } = require('child_process');
8 |
9 | class FinalWorkflowCreator {
10 | constructor() {
11 | this.serverProcess = null;
12 | }
13 |
14 | async startServer() {
15 | console.log('🚀 Starting n8n MCP server...');
16 |
17 | this.serverProcess = spawn('npx', ['.'], {
18 | cwd: '/Users/yasinboelhouwer/n8n-workflow-builder',
19 | env: {
20 | ...process.env,
21 | N8N_HOST: 'https://n8n.yasin.nu/api/v1',
22 | N8N_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMmE2NzM0NC05ZWI1LTQ0NmMtODczNi1lNWYyOGE4MjY4NTIiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzUzMzQzODU5fQ.PhpEIzzSGROy9Kok26SXmj9RRH1K3ArahexaVbQ2-Ho'
23 | },
24 | stdio: ['pipe', 'pipe', 'pipe']
25 | });
26 |
27 | return new Promise((resolve, reject) => {
28 | let output = '';
29 |
30 | this.serverProcess.stderr.on('data', (data) => {
31 | output += data.toString();
32 | if (output.includes('N8N Workflow Builder MCP server running on stdio')) {
33 | console.log('✅ Server started successfully');
34 | resolve();
35 | }
36 | });
37 |
38 | this.serverProcess.on('error', (error) => {
39 | console.error('❌ Failed to start server:', error);
40 | reject(error);
41 | });
42 |
43 | setTimeout(() => {
44 | reject(new Error('Server startup timeout'));
45 | }, 10000);
46 | });
47 | }
48 |
49 | async sendMCPRequest(method, params = {}) {
50 | return new Promise((resolve, reject) => {
51 | const request = {
52 | jsonrpc: '2.0',
53 | id: Date.now(),
54 | method: method,
55 | params: params
56 | };
57 |
58 | let response = '';
59 | let timeout;
60 |
61 | const onData = (data) => {
62 | response += data.toString();
63 | try {
64 | const parsed = JSON.parse(response);
65 | clearTimeout(timeout);
66 | this.serverProcess.stdout.removeListener('data', onData);
67 | resolve(parsed);
68 | } catch (e) {
69 | // Continue collecting data
70 | }
71 | };
72 |
73 | this.serverProcess.stdout.on('data', onData);
74 |
75 | timeout = setTimeout(() => {
76 | this.serverProcess.stdout.removeListener('data', onData);
77 | reject(new Error(`Timeout waiting for response to ${method}`));
78 | }, 10000);
79 |
80 | this.serverProcess.stdin.write(JSON.stringify(request) + '\n');
81 | });
82 | }
83 |
84 | getSupportOptimizationWorkflow() {
85 | return {
86 | name: "Support Optimization System - HelpScout AI Analysis",
87 | nodes: [
88 | {
89 | id: "schedule-trigger",
90 | name: "Schedule Trigger",
91 | type: "n8n-nodes-base.scheduleTrigger",
92 | typeVersion: 1,
93 | position: [240, 300],
94 | parameters: {
95 | interval: [
96 | {
97 | field: "unit",
98 | value: "hours"
99 | },
100 | {
101 | field: "intervalValue",
102 | value: 6
103 | }
104 | ]
105 | }
106 | },
107 | {
108 | id: "helpscout-fetch",
109 | name: "HelpScout API Request",
110 | type: "n8n-nodes-base.httpRequest",
111 | typeVersion: 4,
112 | position: [460, 300],
113 | parameters: {
114 | url: "https://api.helpscout.net/v2/conversations",
115 | method: "GET"
116 | }
117 | },
118 | {
119 | id: "data-processor",
120 | name: "Process Support Data",
121 | type: "n8n-nodes-base.code",
122 | typeVersion: 2,
123 | position: [680, 300],
124 | parameters: {
125 | mode: "runOnceForAllItems",
126 | jsCode: "// Process HelpScout data\nconst data = $input.all();\nconsole.log('Processing', data.length, 'items');\nreturn data;"
127 | }
128 | },
129 | {
130 | id: "ai-analysis",
131 | name: "AI Analysis Placeholder",
132 | type: "n8n-nodes-base.code",
133 | typeVersion: 2,
134 | position: [900, 300],
135 | parameters: {
136 | mode: "runOnceForAllItems",
137 | jsCode: "// AI Analysis placeholder\nconst analysisResults = {\n summary: 'Analysis complete',\n insights: ['Insight 1', 'Insight 2'],\n recommendations: ['Recommendation 1', 'Recommendation 2']\n};\nreturn [{ json: analysisResults }];"
138 | }
139 | },
140 | {
141 | id: "output-results",
142 | name: "Output Results",
143 | type: "n8n-nodes-base.code",
144 | typeVersion: 2,
145 | position: [1120, 300],
146 | parameters: {
147 | mode: "runOnceForAllItems",
148 | jsCode: "// Format and output results\nconst results = $input.all();\nconsole.log('Support optimization analysis complete:', JSON.stringify(results, null, 2));\nreturn results;"
149 | }
150 | }
151 | ],
152 | connections: {
153 | "Schedule Trigger": {
154 | "main": [
155 | [
156 | {
157 | "node": "HelpScout API Request",
158 | "type": "main",
159 | "index": 0
160 | }
161 | ]
162 | ]
163 | },
164 | "HelpScout API Request": {
165 | "main": [
166 | [
167 | {
168 | "node": "Process Support Data",
169 | "type": "main",
170 | "index": 0
171 | }
172 | ]
173 | ]
174 | },
175 | "Process Support Data": {
176 | "main": [
177 | [
178 | {
179 | "node": "AI Analysis Placeholder",
180 | "type": "main",
181 | "index": 0
182 | }
183 | ]
184 | ]
185 | },
186 | "AI Analysis Placeholder": {
187 | "main": [
188 | [
189 | {
190 | "node": "Output Results",
191 | "type": "main",
192 | "index": 0
193 | }
194 | ]
195 | ]
196 | }
197 | }
198 | };
199 | }
200 |
201 | async createWorkflow() {
202 | try {
203 | await this.startServer();
204 |
205 | console.log('📋 Creating Support Optimization System workflow...\n');
206 |
207 | const workflow = this.getSupportOptimizationWorkflow();
208 |
209 | const response = await this.sendMCPRequest('tools/call', {
210 | name: 'create_workflow',
211 | arguments: { workflow }
212 | });
213 |
214 | if (response.error) {
215 | console.log('❌ Workflow creation failed:', response.error.message);
216 | return false;
217 | } else {
218 | console.log('✅ Support Optimization System workflow created successfully!');
219 |
220 | try {
221 | const result = JSON.parse(response.result.content[0].text);
222 | console.log('📊 Workflow Details:');
223 | console.log(` - ID: ${result.id}`);
224 | console.log(` - Name: ${result.name}`);
225 | console.log(` - Nodes: ${result.nodes.length}`);
226 | console.log(` - Active: ${result.active}`);
227 | console.log(` - Created: ${result.createdAt}`);
228 | } catch (parseError) {
229 | console.log('⚠️ Workflow created but could not parse details');
230 | }
231 |
232 | return true;
233 | }
234 | } catch (error) {
235 | console.error('❌ Workflow creation error:', error.message);
236 | return false;
237 | } finally {
238 | this.cleanup();
239 | }
240 | }
241 |
242 | cleanup() {
243 | if (this.serverProcess) {
244 | console.log('\n🧹 Cleaning up server process...');
245 | this.serverProcess.kill();
246 | }
247 | }
248 | }
249 |
250 | // Create the workflow
251 | const creator = new FinalWorkflowCreator();
252 | creator.createWorkflow().then(success => {
253 | if (success) {
254 | console.log('\n🎉 Support Optimization System workflow created successfully!');
255 | console.log('\n📝 Workflow Components:');
256 | console.log(' ✅ Schedule Trigger (runs every 6 hours)');
257 | console.log(' ✅ HelpScout API Request node');
258 | console.log(' ✅ Data Processing node');
259 | console.log(' ✅ AI Analysis placeholder node');
260 | console.log(' ✅ Output Results node');
261 | console.log('\n🔧 Next Steps:');
262 | console.log(' 1. Configure HelpScout API credentials');
263 | console.log(' 2. Replace AI Analysis placeholder with OpenAI/Claude node');
264 | console.log(' 3. Add Slack/Email notification nodes');
265 | console.log(' 4. Add Google Sheets storage node');
266 | console.log(' 5. Test and activate the workflow');
267 | process.exit(0);
268 | } else {
269 | console.log('\n❌ Failed to create workflow. Check the errors above.');
270 | process.exit(1);
271 | }
272 | }).catch(console.error);
273 |
```
--------------------------------------------------------------------------------
/test-results/junit.xml:
--------------------------------------------------------------------------------
```
1 | <?xml version="1.0" encoding="UTF-8"?>
2 | <testsuites name="n8n-workflow-builder CI Tests" tests="31" failures="0" errors="0" time="2.789">
3 | <testsuite name="Execution Management Integration Tests" errors="0" failures="0" skipped="0" timestamp="2025-07-27T20:07:54" time="2.002" tests="10">
4 | <testcase classname="Execution Management Integration Tests list_executions should list all executions successfully" name="Execution Management Integration Tests list_executions should list all executions successfully" time="0.015">
5 | </testcase>
6 | <testcase classname="Execution Management Integration Tests list_executions should support filtering by workflow ID" name="Execution Management Integration Tests list_executions should support filtering by workflow ID" time="0.003">
7 | </testcase>
8 | <testcase classname="Execution Management Integration Tests list_executions should support status filtering" name="Execution Management Integration Tests list_executions should support status filtering" time="0.001">
9 | </testcase>
10 | <testcase classname="Execution Management Integration Tests list_executions should support pagination" name="Execution Management Integration Tests list_executions should support pagination" time="0">
11 | </testcase>
12 | <testcase classname="Execution Management Integration Tests get_execution should retrieve execution by ID" name="Execution Management Integration Tests get_execution should retrieve execution by ID" time="0">
13 | </testcase>
14 | <testcase classname="Execution Management Integration Tests get_execution should support including execution data" name="Execution Management Integration Tests get_execution should support including execution data" time="0.001">
15 | </testcase>
16 | <testcase classname="Execution Management Integration Tests get_execution should require execution ID" name="Execution Management Integration Tests get_execution should require execution ID" time="0.001">
17 | </testcase>
18 | <testcase classname="Execution Management Integration Tests get_execution should handle not found errors" name="Execution Management Integration Tests get_execution should handle not found errors" time="0">
19 | </testcase>
20 | <testcase classname="Execution Management Integration Tests delete_execution should delete execution successfully" name="Execution Management Integration Tests delete_execution should delete execution successfully" time="0">
21 | </testcase>
22 | <testcase classname="Execution Management Integration Tests delete_execution should require execution ID" name="Execution Management Integration Tests delete_execution should require execution ID" time="0.001">
23 | </testcase>
24 | </testsuite>
25 | <testsuite name="MCP Resources Integration Tests" errors="0" failures="0" skipped="0" timestamp="2025-07-27T20:07:54" time="2.024" tests="8">
26 | <testcase classname="MCP Resources Integration Tests Resource Templates should list available resource templates" name="MCP Resources Integration Tests Resource Templates should list available resource templates" time="0.014">
27 | </testcase>
28 | <testcase classname="MCP Resources Integration Tests Static Resources should read /workflows resource" name="MCP Resources Integration Tests Static Resources should read /workflows resource" time="0.004">
29 | </testcase>
30 | <testcase classname="MCP Resources Integration Tests Static Resources should read /execution-stats resource" name="MCP Resources Integration Tests Static Resources should read /execution-stats resource" time="0.005">
31 | </testcase>
32 | <testcase classname="MCP Resources Integration Tests Static Resources should handle execution stats API errors gracefully" name="MCP Resources Integration Tests Static Resources should handle execution stats API errors gracefully" time="0.001">
33 | </testcase>
34 | <testcase classname="MCP Resources Integration Tests Dynamic Resources should read workflow by ID resource" name="MCP Resources Integration Tests Dynamic Resources should read workflow by ID resource" time="0">
35 | </testcase>
36 | <testcase classname="MCP Resources Integration Tests Dynamic Resources should read execution by ID resource" name="MCP Resources Integration Tests Dynamic Resources should read execution by ID resource" time="0">
37 | </testcase>
38 | <testcase classname="MCP Resources Integration Tests Dynamic Resources should handle not found resources" name="MCP Resources Integration Tests Dynamic Resources should handle not found resources" time="0.024">
39 | </testcase>
40 | <testcase classname="MCP Resources Integration Tests Resource Listing should list all available resources" name="MCP Resources Integration Tests Resource Listing should list all available resources" time="0">
41 | </testcase>
42 | </testsuite>
43 | <testsuite name="Error Handling Integration Tests" errors="0" failures="0" skipped="0" timestamp="2025-07-27T20:07:56" time="0.113" tests="11">
44 | <testcase classname="Error Handling Integration Tests Network and API Errors should handle network connection errors" name="Error Handling Integration Tests Network and API Errors should handle network connection errors" time="0.001">
45 | </testcase>
46 | <testcase classname="Error Handling Integration Tests Network and API Errors should handle n8n API authentication errors" name="Error Handling Integration Tests Network and API Errors should handle n8n API authentication errors" time="0">
47 | </testcase>
48 | <testcase classname="Error Handling Integration Tests Network and API Errors should handle n8n API rate limiting" name="Error Handling Integration Tests Network and API Errors should handle n8n API rate limiting" time="0.001">
49 | </testcase>
50 | <testcase classname="Error Handling Integration Tests Network and API Errors should handle n8n server errors" name="Error Handling Integration Tests Network and API Errors should handle n8n server errors" time="0">
51 | </testcase>
52 | <testcase classname="Error Handling Integration Tests Invalid Parameters should validate missing required parameters" name="Error Handling Integration Tests Invalid Parameters should validate missing required parameters" time="0">
53 | </testcase>
54 | <testcase classname="Error Handling Integration Tests Invalid Parameters should validate invalid workflow data structure" name="Error Handling Integration Tests Invalid Parameters should validate invalid workflow data structure" time="0">
55 | </testcase>
56 | <testcase classname="Error Handling Integration Tests Invalid Parameters should handle invalid execution filters" name="Error Handling Integration Tests Invalid Parameters should handle invalid execution filters" time="0">
57 | </testcase>
58 | <testcase classname="Error Handling Integration Tests Resource Access Errors should handle invalid resource URIs" name="Error Handling Integration Tests Resource Access Errors should handle invalid resource URIs" time="0.031">
59 | </testcase>
60 | <testcase classname="Error Handling Integration Tests Resource Access Errors should handle resource not found errors" name="Error Handling Integration Tests Resource Access Errors should handle resource not found errors" time="0">
61 | </testcase>
62 | <testcase classname="Error Handling Integration Tests Tool Not Found should handle calls to non-existent tools" name="Error Handling Integration Tests Tool Not Found should handle calls to non-existent tools" time="0.001">
63 | </testcase>
64 | <testcase classname="Error Handling Integration Tests MCP Server Connection Errors should handle server startup failures gracefully" name="Error Handling Integration Tests MCP Server Connection Errors should handle server startup failures gracefully" time="0.002">
65 | </testcase>
66 | </testsuite>
67 | <testsuite name="End-to-End Workflow Tests" errors="0" failures="0" skipped="0" timestamp="2025-07-27T20:07:56" time="0.157" tests="2">
68 | <testcase classname="End-to-End Workflow Tests should complete full workflow lifecycle: create → activate → list → get → deactivate → delete" name="End-to-End Workflow Tests should complete full workflow lifecycle: create → activate → list → get → deactivate → delete" time="0.013">
69 | </testcase>
70 | <testcase classname="End-to-End Workflow Tests should handle workflow execution flow" name="End-to-End Workflow Tests should handle workflow execution flow" time="0.019">
71 | </testcase>
72 | </testsuite>
73 | </testsuites>
```
--------------------------------------------------------------------------------
/tests/integration/tags.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { MCPTestClient } from '../helpers/mcpClient';
2 | import { mockTag, mockN8nResponses } from '../helpers/mockData';
3 | import axios from 'axios';
4 |
5 | jest.mock('axios');
6 | const mockedAxios = axios as jest.Mocked<typeof axios>;
7 |
8 | describe('Tag Management Integration Tests', () => {
9 | let client: MCPTestClient;
10 |
11 | beforeEach(() => {
12 | client = new MCPTestClient();
13 | jest.clearAllMocks();
14 | });
15 |
16 | describe('list_tags', () => {
17 | it('should list all tags successfully', async () => {
18 | mockedAxios.get.mockResolvedValueOnce({
19 | data: mockN8nResponses.tags.list
20 | });
21 |
22 | const result = await client.callTool('list_tags', {});
23 |
24 | expect(result.content).toBeDefined();
25 | const response = JSON.parse((result.content as any)[0].text);
26 | expect(response.data).toBeDefined();
27 | expect(Array.isArray(response.data)).toBe(true);
28 | });
29 |
30 | it('should support pagination with limit', async () => {
31 | const result = await client.callTool('list_tags', {
32 | limit: 10
33 | });
34 |
35 | expect(result.content).toBeDefined();
36 | const response = JSON.parse((result.content as any)[0].text);
37 | expect(response.data).toBeDefined();
38 | });
39 |
40 | it('should support pagination with cursor', async () => {
41 | const result = await client.callTool('list_tags', {
42 | cursor: 'next-page-cursor'
43 | });
44 |
45 | expect(result.content).toBeDefined();
46 | const response = JSON.parse((result.content as any)[0].text);
47 | expect(response.data).toBeDefined();
48 | });
49 | });
50 |
51 | describe('create_tag', () => {
52 | it('should create a new tag successfully', async () => {
53 | mockedAxios.post.mockResolvedValueOnce({
54 | data: mockN8nResponses.tags.create.data
55 | });
56 |
57 | const result = await client.callTool('create_tag', {
58 | name: 'Production'
59 | });
60 |
61 | expect(result.content).toBeDefined();
62 | const response = JSON.parse((result.content as any)[0].text);
63 | expect(response.success).toBe(true);
64 | expect(response.tag.name).toBe('Production');
65 | expect(response.message).toContain('created successfully');
66 | });
67 |
68 | it('should require tag name', async () => {
69 | const result = await client.callTool('create_tag', {});
70 |
71 | expect(result.isError).toBe(true);
72 | expect((result.content as any)[0].text).toContain('Tag name is required');
73 | });
74 |
75 | it('should handle duplicate tag names', async () => {
76 | mockedAxios.post.mockRejectedValueOnce(new Error('Tag name already exists'));
77 |
78 | const result = await client.callTool('create_tag', {
79 | name: 'Existing Tag'
80 | });
81 |
82 | expect(result.isError).toBe(true);
83 | expect((result.content as any)[0].text).toContain('Error: Tag name already exists');
84 | });
85 | });
86 |
87 | describe('get_tag', () => {
88 | it('should retrieve tag by ID successfully', async () => {
89 | mockedAxios.get.mockResolvedValueOnce({
90 | data: mockN8nResponses.tags.get.data
91 | });
92 |
93 | const result = await client.callTool('get_tag', {
94 | id: 'test-tag-id'
95 | });
96 |
97 | expect(result.content).toBeDefined();
98 | const response = JSON.parse((result.content as any)[0].text);
99 | expect(response.success).toBe(true);
100 | expect(response.tag).toBeDefined();
101 | });
102 |
103 | it('should require tag ID', async () => {
104 | const result = await client.callTool('get_tag', {});
105 |
106 | expect(result.isError).toBe(true);
107 | expect((result.content as any)[0].text).toContain('Tag ID is required');
108 | });
109 |
110 | it('should handle not found errors', async () => {
111 | mockedAxios.get.mockRejectedValueOnce(new Error('Tag not found'));
112 |
113 | const result = await client.callTool('get_tag', {
114 | id: 'nonexistent-id'
115 | });
116 |
117 | expect(result.isError).toBe(true);
118 | expect((result.content as any)[0].text).toContain('Error: Tag not found');
119 | });
120 | });
121 |
122 | describe('update_tag', () => {
123 | it('should update tag name successfully', async () => {
124 | mockedAxios.put.mockResolvedValueOnce({
125 | data: mockN8nResponses.tags.update.data
126 | });
127 |
128 | const result = await client.callTool('update_tag', {
129 | id: 'test-tag-id',
130 | name: 'Updated Tag Name'
131 | });
132 |
133 | expect(result.content).toBeDefined();
134 | const response = JSON.parse((result.content as any)[0].text);
135 | expect(response.success).toBe(true);
136 | expect(response.tag.name).toBe('Updated Tag Name');
137 | expect(response.message).toContain('updated successfully');
138 | });
139 |
140 | it('should require tag ID and name', async () => {
141 | const result = await client.callTool('update_tag', {
142 | id: 'test-id'
143 | });
144 |
145 | expect(result.isError).toBe(true);
146 | expect((result.content as any)[0].text).toContain('Tag ID and name are required');
147 | });
148 |
149 | it('should handle duplicate names', async () => {
150 | mockedAxios.put.mockRejectedValueOnce(new Error('Tag name already exists'));
151 |
152 | const result = await client.callTool('update_tag', {
153 | id: 'test-id',
154 | name: 'Existing Name'
155 | });
156 |
157 | expect(result.isError).toBe(true);
158 | expect((result.content as any)[0].text).toContain('Error: Tag name already exists');
159 | });
160 | });
161 |
162 | describe('delete_tag', () => {
163 | it('should delete tag successfully', async () => {
164 | mockedAxios.delete.mockResolvedValueOnce({
165 | data: mockN8nResponses.tags.delete.data
166 | });
167 |
168 | const result = await client.callTool('delete_tag', {
169 | id: 'test-tag-id'
170 | });
171 |
172 | expect(result.content).toBeDefined();
173 | const response = JSON.parse((result.content as any)[0].text);
174 | expect(response.success).toBe(true);
175 | expect(response.message).toContain('deleted successfully');
176 | });
177 |
178 | it('should require tag ID', async () => {
179 | const result = await client.callTool('delete_tag', {});
180 |
181 | expect(result.isError).toBe(true);
182 | expect((result.content as any)[0].text).toContain('Tag ID is required');
183 | });
184 |
185 | it('should handle tags in use', async () => {
186 | mockedAxios.delete.mockRejectedValueOnce(new Error('Tag is in use by workflows'));
187 |
188 | const result = await client.callTool('delete_tag', {
189 | id: 'in-use-tag-id'
190 | });
191 |
192 | expect(result.isError).toBe(true);
193 | expect((result.content as any)[0].text).toContain('Error: Tag is in use by workflows');
194 | });
195 | });
196 |
197 | describe('get_workflow_tags', () => {
198 | it('should retrieve workflow tags successfully', async () => {
199 | mockedAxios.get.mockResolvedValueOnce({
200 | data: [mockTag]
201 | });
202 |
203 | const result = await client.callTool('get_workflow_tags', {
204 | workflowId: 'test-workflow-id'
205 | });
206 |
207 | expect(result.content).toBeDefined();
208 | const response = JSON.parse((result.content as any)[0].text);
209 | expect(response.success).toBe(true);
210 | expect(response.workflowId).toBe('test-workflow-id');
211 | expect(response.tags).toBeDefined();
212 | });
213 |
214 | it('should require workflow ID', async () => {
215 | const result = await client.callTool('get_workflow_tags', {});
216 |
217 | expect(result.isError).toBe(true);
218 | expect((result.content as any)[0].text).toContain('Workflow ID is required');
219 | });
220 | });
221 |
222 | describe('update_workflow_tags', () => {
223 | it('should update workflow tags successfully', async () => {
224 | mockedAxios.put.mockResolvedValueOnce({
225 | data: ['tag-1', 'tag-2']
226 | });
227 |
228 | const result = await client.callTool('update_workflow_tags', {
229 | workflowId: 'test-workflow-id',
230 | tagIds: ['tag-1', 'tag-2']
231 | });
232 |
233 | expect(result.content).toBeDefined();
234 | const response = JSON.parse((result.content as any)[0].text);
235 | expect(response.success).toBe(true);
236 | expect(response.workflowId).toBe('test-workflow-id');
237 | expect(response.assignedTags).toEqual(['tag-1', 'tag-2']);
238 | });
239 |
240 | it('should require workflow ID and tag IDs', async () => {
241 | const result = await client.callTool('update_workflow_tags', {
242 | workflowId: 'test-id'
243 | });
244 |
245 | expect(result.isError).toBe(true);
246 | expect((result.content as any)[0].text).toContain('Workflow ID and tag IDs are required');
247 | });
248 |
249 | it('should handle invalid tag IDs', async () => {
250 | mockedAxios.put.mockRejectedValueOnce(new Error('Invalid tag IDs'));
251 |
252 | const result = await client.callTool('update_workflow_tags', {
253 | workflowId: 'test-workflow-id',
254 | tagIds: ['invalid-tag-id']
255 | });
256 |
257 | expect(result.isError).toBe(true);
258 | expect((result.content as any)[0].text).toContain('Error: Invalid tag IDs');
259 | });
260 | });
261 | });
262 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2 | import { z } from "zod";
3 | import axios from "axios";
4 |
5 | // Configuration schema for Smithery
6 | export const configSchema = z.object({
7 | n8nHost: z.string().describe("n8n instance URL (e.g., http://localhost:5678)").default("http://localhost:5678"),
8 | n8nApiKey: z.string().describe("n8n API key for authentication")
9 | });
10 |
11 | export default function ({ config }: { config: z.infer<typeof configSchema> }) {
12 | // Create axios instance for n8n API
13 | const n8nApi = axios.create({
14 | baseURL: config.n8nHost,
15 | headers: {
16 | 'X-N8N-API-KEY': config.n8nApiKey,
17 | 'Content-Type': 'application/json'
18 | }
19 | });
20 |
21 | // Create MCP server with modern SDK 1.17.0 API
22 | const server = new McpServer({
23 | name: "n8n-workflow-builder",
24 | version: "0.10.3"
25 | });
26 |
27 | // Register workflow management tools using modern MCP SDK 1.17.0 API
28 | server.tool(
29 | "list_workflows",
30 | "List all workflows from your n8n instance. Returns a comprehensive list of all workflows with their IDs, names, status (active/inactive), creation dates, and basic metadata. Perfect for getting an overview of your automation landscape.",
31 | {},
32 | async () => {
33 | try {
34 | const response = await n8nApi.get('/workflows');
35 | return {
36 | content: [{
37 | type: "text",
38 | text: JSON.stringify(response.data, null, 2)
39 | }]
40 | };
41 | } catch (error) {
42 | return {
43 | content: [{
44 | type: "text",
45 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
46 | }],
47 | isError: true
48 | };
49 | }
50 | }
51 | );
52 |
53 | server.tool(
54 | "create_workflow",
55 | "Create a new workflow in n8n with custom nodes, connections, and settings. Build complex automation workflows programmatically by defining nodes (triggers, actions, conditions) and their relationships. Supports all n8n node types and advanced workflow configurations.",
56 | {
57 | workflow: z.object({
58 | name: z.string().describe("Name of the workflow"),
59 | nodes: z.array(z.any()).describe("Array of workflow nodes"),
60 | connections: z.record(z.string(), z.any()).describe("Node connections").optional(),
61 | settings: z.record(z.string(), z.any()).describe("Workflow settings").optional(),
62 | tags: z.array(z.string()).describe("Workflow tags").optional()
63 | }).describe("Workflow configuration")
64 | },
65 | async ({ workflow }) => {
66 | try {
67 | const response = await n8nApi.post('/workflows', workflow);
68 | return {
69 | content: [{
70 | type: "text",
71 | text: JSON.stringify(response.data, null, 2)
72 | }]
73 | };
74 | } catch (error) {
75 | return {
76 | content: [{
77 | type: "text",
78 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
79 | }],
80 | isError: true
81 | };
82 | }
83 | }
84 | );
85 |
86 | server.tool(
87 | "get_workflow",
88 | "Retrieve detailed information about a specific workflow by its ID. Returns complete workflow configuration including all nodes, connections, settings, triggers, and metadata. Essential for inspecting workflow structure before making modifications.",
89 | {
90 | id: z.string().describe("Workflow ID")
91 | },
92 | async ({ id }) => {
93 | try {
94 | const response = await n8nApi.get(`/workflows/${id}`);
95 | return {
96 | content: [{
97 | type: "text",
98 | text: JSON.stringify(response.data, null, 2)
99 | }]
100 | };
101 | } catch (error) {
102 | return {
103 | content: [{
104 | type: "text",
105 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
106 | }],
107 | isError: true
108 | };
109 | }
110 | }
111 | );
112 |
113 | server.tool(
114 | "execute_workflow",
115 | "Trigger immediate execution of a workflow by its ID. Starts the workflow manually regardless of its normal triggers (webhooks, schedules, etc.). Returns execution details including status, start time, and any immediate results or errors.",
116 | {
117 | id: z.string().describe("Workflow ID")
118 | },
119 | async ({ id }) => {
120 | try {
121 | const response = await n8nApi.post(`/workflows/${id}/execute`);
122 | return {
123 | content: [{
124 | type: "text",
125 | text: JSON.stringify(response.data, null, 2)
126 | }]
127 | };
128 | } catch (error) {
129 | return {
130 | content: [{
131 | type: "text",
132 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
133 | }],
134 | isError: true
135 | };
136 | }
137 | }
138 | );
139 |
140 | server.tool(
141 | "update_workflow",
142 | "Modify an existing workflow by updating its configuration, nodes, connections, or settings. Supports partial updates - you can change specific aspects without affecting the entire workflow. Perfect for iterative workflow development and maintenance.",
143 | {
144 | id: z.string().describe("Workflow ID"),
145 | workflow: z.object({
146 | name: z.string().describe("Name of the workflow").optional(),
147 | nodes: z.array(z.any()).describe("Array of workflow nodes").optional(),
148 | connections: z.record(z.string(), z.any()).describe("Node connections").optional(),
149 | settings: z.record(z.string(), z.any()).describe("Workflow settings").optional(),
150 | tags: z.array(z.string()).describe("Workflow tags").optional()
151 | }).describe("Updated workflow configuration")
152 | },
153 | async ({ id, workflow }) => {
154 | try {
155 | const response = await n8nApi.put(`/workflows/${id}`, workflow);
156 | return {
157 | content: [{
158 | type: "text",
159 | text: JSON.stringify(response.data, null, 2)
160 | }]
161 | };
162 | } catch (error) {
163 | return {
164 | content: [{
165 | type: "text",
166 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
167 | }],
168 | isError: true
169 | };
170 | }
171 | }
172 | );
173 |
174 | server.tool(
175 | "activate_workflow",
176 | "Enable a workflow to start processing triggers and executing automatically. Once activated, the workflow will respond to its configured triggers (webhooks, schedules, file changes, etc.) and run according to its automation logic.",
177 | {
178 | id: z.string().describe("Workflow ID")
179 | },
180 | async ({ id }) => {
181 | try {
182 | const response = await n8nApi.patch(`/workflows/${id}/activate`);
183 | return {
184 | content: [{
185 | type: "text",
186 | text: JSON.stringify(response.data, null, 2)
187 | }]
188 | };
189 | } catch (error) {
190 | return {
191 | content: [{
192 | type: "text",
193 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
194 | }],
195 | isError: true
196 | };
197 | }
198 | }
199 | );
200 |
201 | server.tool(
202 | "deactivate_workflow",
203 | "Disable a workflow to stop it from processing triggers and executing automatically. The workflow will remain in your n8n instance but won't respond to triggers until reactivated. Useful for maintenance, debugging, or temporary suspension.",
204 | {
205 | id: z.string().describe("Workflow ID")
206 | },
207 | async ({ id }) => {
208 | try {
209 | const response = await n8nApi.patch(`/workflows/${id}/deactivate`);
210 | return {
211 | content: [{
212 | type: "text",
213 | text: JSON.stringify(response.data, null, 2)
214 | }]
215 | };
216 | } catch (error) {
217 | return {
218 | content: [{
219 | type: "text",
220 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
221 | }],
222 | isError: true
223 | };
224 | }
225 | }
226 | );
227 |
228 | server.tool(
229 | "delete_workflow",
230 | "Permanently remove a workflow from your n8n instance. This action cannot be undone - the workflow, its configuration, nodes, and execution history will be completely deleted. Use with caution for cleanup and workflow lifecycle management.",
231 | {
232 | id: z.string().describe("Workflow ID")
233 | },
234 | async ({ id }) => {
235 | try {
236 | const response = await n8nApi.delete(`/workflows/${id}`);
237 | return {
238 | content: [{
239 | type: "text",
240 | text: JSON.stringify(response.data, null, 2)
241 | }]
242 | };
243 | } catch (error) {
244 | return {
245 | content: [{
246 | type: "text",
247 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
248 | }],
249 | isError: true
250 | };
251 | }
252 | }
253 | );
254 |
255 | return server.server;
256 | }
257 |
```
--------------------------------------------------------------------------------
/COMPARISON.md:
--------------------------------------------------------------------------------
```markdown
1 | # 🔍 n8n Workflow Builder MCP Server vs Alternatives
2 |
3 | **Compare the n8n Workflow Builder MCP Server with other n8n integration solutions to understand why it's the best choice for AI-powered workflow automation.**
4 |
5 | ## 🎯 Quick Comparison Overview
6 |
7 | | Feature | n8n MCP Server | n8n Web UI | n8n CLI | Zapier | Make.com |
8 | |---------|----------------|------------|---------|--------|----------|
9 | | **AI Assistant Integration** | ✅ Native | ❌ None | ❌ None | ❌ None | ❌ None |
10 | | **Natural Language Control** | ✅ Full | ❌ None | ❌ None | ❌ None | ❌ None |
11 | | **Programmatic Access** | ✅ Complete | ⚠️ Limited | ✅ Basic | ⚠️ API Only | ⚠️ API Only |
12 | | **Real-time Workflow Creation** | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes | ✅ Yes |
13 | | **Bulk Operations** | ✅ Yes | ❌ Manual | ⚠️ Limited | ❌ Manual | ❌ Manual |
14 | | **Cost** | 🆓 Free | 🆓 Free | 🆓 Free | 💰 Paid | 💰 Paid |
15 |
16 | ## 🆚 Detailed Comparisons
17 |
18 | ### vs n8n Web Interface
19 |
20 | #### n8n MCP Server Advantages:
21 | - **🤖 AI-Powered**: Create workflows using natural language descriptions
22 | - **⚡ Speed**: "Create a webhook workflow" vs 10+ clicks in UI
23 | - **🔄 Bulk Operations**: Manage multiple workflows simultaneously
24 | - **📱 Voice Control**: Use voice commands through AI assistants
25 | - **🔍 Smart Search**: Find workflows by description, not just name
26 | - **🤝 Collaborative**: Share workflow creation with team through AI
27 |
28 | #### When to Use n8n Web UI:
29 | - **🎨 Visual Design**: Complex workflow visualization needs
30 | - **🔧 Node Configuration**: Detailed parameter tweaking
31 | - **🐛 Debugging**: Visual workflow execution debugging
32 | - **📊 Monitoring**: Built-in execution monitoring dashboard
33 |
34 | **Best Practice**: Use MCP Server for creation/management, Web UI for detailed configuration.
35 |
36 | ### vs n8n CLI
37 |
38 | #### n8n MCP Server Advantages:
39 | - **🗣️ Natural Language**: "Activate all inactive workflows" vs complex CLI commands
40 | - **🧠 Context Awareness**: AI understands workflow relationships
41 | - **📚 Documentation**: Built-in help and examples
42 | - **🔄 Interactive**: Conversational workflow management
43 | - **🎯 Error Handling**: Intelligent error messages and suggestions
44 |
45 | #### When to Use n8n CLI:
46 | - **🚀 CI/CD Integration**: Automated deployment pipelines
47 | - **📜 Scripting**: Bash/shell script automation
48 | - **🔧 Server Management**: n8n instance administration
49 | - **⚙️ Configuration**: Environment and settings management
50 |
51 | **Best Practice**: Use MCP Server for daily workflow management, CLI for DevOps tasks.
52 |
53 | ### vs Zapier
54 |
55 | #### n8n MCP Server Advantages:
56 | - **🆓 Cost**: Completely free vs Zapier's subscription model
57 | - **🏠 Self-Hosted**: Full data control and privacy
58 | - **🔧 Customization**: Unlimited workflow complexity
59 | - **🤖 AI Integration**: Native AI assistant support
60 | - **⚡ Performance**: No external API rate limits
61 | - **🔒 Security**: Data stays in your infrastructure
62 |
63 | #### Zapier Advantages:
64 | - **☁️ Hosted**: No infrastructure management needed
65 | - **🔌 Integrations**: 5000+ pre-built app connections
66 | - **👥 User-Friendly**: Non-technical user interface
67 | - **📞 Support**: Professional customer support
68 |
69 | **Migration Path**: Use our MCP server to recreate Zapier workflows in n8n with AI assistance.
70 |
71 | ### vs Make.com (Integromat)
72 |
73 | #### n8n MCP Server Advantages:
74 | - **🆓 Free**: No usage limits or subscription fees
75 | - **🤖 AI-Powered**: Create workflows through conversation
76 | - **🏠 Data Privacy**: Self-hosted, no data sharing
77 | - **🔧 Flexibility**: Custom code and unlimited complexity
78 | - **⚡ Performance**: Direct API access, no middleman
79 |
80 | #### Make.com Advantages:
81 | - **🎨 Visual Builder**: Drag-and-drop interface
82 | - **☁️ Managed Service**: No server maintenance
83 | - **📱 Mobile App**: Mobile workflow management
84 | - **🔌 Templates**: Pre-built scenario templates
85 |
86 | **Why Choose n8n MCP**: Better for developers, privacy-conscious users, and AI-first workflows.
87 |
88 | ## 🎯 Use Case Specific Comparisons
89 |
90 | ### For Developers
91 |
92 | **n8n MCP Server Wins Because:**
93 | - **Code Integration**: Natural language to code workflow creation
94 | - **Version Control**: Git-friendly workflow definitions
95 | - **API-First**: Programmatic workflow management
96 | - **Debugging**: AI-assisted troubleshooting
97 | - **Customization**: Unlimited extensibility
98 |
99 | ### For Small Businesses
100 |
101 | **n8n MCP Server Wins Because:**
102 | - **Zero Cost**: No subscription fees or usage limits
103 | - **Easy Setup**: NPX installation in minutes
104 | - **AI Assistance**: No technical expertise required
105 | - **Scalability**: Grows with your business
106 | - **Data Control**: Keep sensitive data in-house
107 |
108 | ### For Enterprises
109 |
110 | **n8n MCP Server Wins Because:**
111 | - **Security**: Self-hosted, air-gapped deployments
112 | - **Compliance**: Full audit trail and data control
113 | - **Integration**: Works with existing AI assistant infrastructure
114 | - **Customization**: Unlimited workflow complexity
115 | - **Cost**: No per-user or per-execution fees
116 |
117 | ## 🚀 Migration Guides
118 |
119 | ### From Zapier to n8n MCP
120 |
121 | 1. **Export Zapier Workflows**: Document your current Zaps
122 | 2. **Describe to AI**: "Recreate my Zapier workflow that..."
123 | 3. **Test and Refine**: Use AI to adjust and improve
124 | 4. **Deploy**: Activate your new n8n workflows
125 | 5. **Monitor**: Track performance and optimize
126 |
127 | **Example Migration**:
128 | ```
129 | "Create an n8n workflow that replicates my Zapier automation:
130 | when a new lead comes from our website form, add them to our CRM,
131 | send a welcome email, and notify our sales team on Slack"
132 | ```
133 |
134 | ### From Make.com to n8n MCP
135 |
136 | 1. **Document Scenarios**: List your Make.com automations
137 | 2. **AI Recreation**: Use natural language to recreate workflows
138 | 3. **Enhanced Features**: Add AI-powered improvements
139 | 4. **Testing**: Validate all integrations work correctly
140 | 5. **Cutover**: Gradually migrate workflows
141 |
142 | ### From Manual Processes to n8n MCP
143 |
144 | 1. **Process Mapping**: Document manual steps
145 | 2. **AI Consultation**: "How can I automate this process?"
146 | 3. **Workflow Creation**: Let AI build the automation
147 | 4. **Training**: Teach team to use AI for workflow management
148 | 5. **Optimization**: Continuously improve with AI suggestions
149 |
150 | ## 📊 Performance Comparison
151 |
152 | ### Speed of Workflow Creation
153 |
154 | | Method | Time to Create Simple Workflow | Time to Create Complex Workflow |
155 | |--------|--------------------------------|----------------------------------|
156 | | **n8n MCP Server** | 30 seconds | 2-5 minutes |
157 | | **n8n Web UI** | 5-10 minutes | 30-60 minutes |
158 | | **n8n CLI** | 10-20 minutes | 1-2 hours |
159 | | **Zapier** | 2-5 minutes | 15-30 minutes |
160 | | **Make.com** | 3-7 minutes | 20-45 minutes |
161 |
162 | ### Learning Curve
163 |
164 | | Solution | Time to Productivity | Technical Skill Required |
165 | |----------|---------------------|-------------------------|
166 | | **n8n MCP Server** | 5 minutes | None (AI-assisted) |
167 | | **n8n Web UI** | 1-2 hours | Basic technical |
168 | | **n8n CLI** | 2-4 hours | Advanced technical |
169 | | **Zapier** | 30 minutes | Basic computer skills |
170 | | **Make.com** | 45 minutes | Basic technical |
171 |
172 | ## 🏆 Why Choose n8n MCP Server?
173 |
174 | ### Unique Advantages:
175 |
176 | 1. **🤖 AI-First Design**: Built specifically for AI assistant integration
177 | 2. **🗣️ Natural Language**: Create workflows by describing them
178 | 3. **🆓 Completely Free**: No hidden costs or usage limits
179 | 4. **⚡ Instant Setup**: Running in under 5 minutes
180 | 5. **🔒 Privacy-First**: Your data never leaves your infrastructure
181 | 6. **🚀 Future-Proof**: Designed for the AI-powered automation future
182 |
183 | ### Perfect For:
184 |
185 | - **Developers** who want AI-powered workflow automation
186 | - **Teams** using AI assistants like Claude Desktop or ChatGPT
187 | - **Businesses** wanting cost-effective automation solutions
188 | - **Privacy-conscious** organizations needing data control
189 | - **Innovation-focused** companies embracing AI-first tools
190 |
191 | ## 🎯 Decision Matrix
192 |
193 | **Choose n8n MCP Server if you:**
194 | - ✅ Use AI assistants regularly
195 | - ✅ Want natural language workflow creation
196 | - ✅ Need cost-effective automation
197 | - ✅ Value data privacy and control
198 | - ✅ Want cutting-edge AI integration
199 |
200 | **Consider alternatives if you:**
201 | - ❌ Prefer visual drag-and-drop interfaces
202 | - ❌ Need extensive pre-built integrations
203 | - ❌ Want fully managed cloud service
204 | - ❌ Have non-technical team members only
205 | - ❌ Need mobile app management
206 |
207 | ## 🚀 Ready to Switch?
208 |
209 | **Getting Started is Easy:**
210 |
211 | 1. **Install**: `npx @makafeli/n8n-workflow-builder`
212 | 2. **Configure**: Add your n8n credentials
213 | 3. **Test**: "List my workflows"
214 | 4. **Create**: "Build a workflow that..."
215 | 5. **Enjoy**: AI-powered automation!
216 |
217 | **Migration Support**: Our AI can help recreate workflows from any platform - just describe what you currently have!
218 |
219 | ---
220 |
221 | **Experience the future of workflow automation with AI-powered natural language control.** 🤖✨
222 |
```
--------------------------------------------------------------------------------
/tests/test-all-tools.js:
--------------------------------------------------------------------------------
```javascript
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Comprehensive test script for all n8n MCP workflow management tools
5 | */
6 |
7 | const { spawn } = require('child_process');
8 |
9 | class ComprehensiveMCPTester {
10 | constructor() {
11 | this.serverProcess = null;
12 | this.testResults = [];
13 | this.createdWorkflowId = null;
14 | }
15 |
16 | async startServer() {
17 | console.log('🚀 Starting n8n MCP server...');
18 |
19 | this.serverProcess = spawn('npx', ['.'], {
20 | cwd: '/Users/yasinboelhouwer/n8n-workflow-builder',
21 | env: {
22 | ...process.env,
23 | N8N_HOST: 'https://n8n.yasin.nu/api/v1',
24 | N8N_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMmE2NzM0NC05ZWI1LTQ0NmMtODczNi1lNWYyOGE4MjY4NTIiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzUzMzQzODU5fQ.PhpEIzzSGROy9Kok26SXmj9RRH1K3ArahexaVbQ2-Ho'
25 | },
26 | stdio: ['pipe', 'pipe', 'pipe']
27 | });
28 |
29 | return new Promise((resolve, reject) => {
30 | let output = '';
31 |
32 | this.serverProcess.stderr.on('data', (data) => {
33 | output += data.toString();
34 | if (output.includes('N8N Workflow Builder MCP server running on stdio')) {
35 | console.log('✅ Server started successfully');
36 | resolve();
37 | }
38 | });
39 |
40 | this.serverProcess.on('error', (error) => {
41 | console.error('❌ Failed to start server:', error);
42 | reject(error);
43 | });
44 |
45 | setTimeout(() => {
46 | reject(new Error('Server startup timeout'));
47 | }, 10000);
48 | });
49 | }
50 |
51 | async sendMCPRequest(method, params = {}) {
52 | return new Promise((resolve, reject) => {
53 | const request = {
54 | jsonrpc: '2.0',
55 | id: Date.now(),
56 | method: method,
57 | params: params
58 | };
59 |
60 | let response = '';
61 | let timeout;
62 |
63 | const onData = (data) => {
64 | response += data.toString();
65 | try {
66 | const parsed = JSON.parse(response);
67 | clearTimeout(timeout);
68 | this.serverProcess.stdout.removeListener('data', onData);
69 | resolve(parsed);
70 | } catch (e) {
71 | // Continue collecting data
72 | }
73 | };
74 |
75 | this.serverProcess.stdout.on('data', onData);
76 |
77 | timeout = setTimeout(() => {
78 | this.serverProcess.stdout.removeListener('data', onData);
79 | reject(new Error(`Timeout waiting for response to ${method}`));
80 | }, 10000);
81 |
82 | this.serverProcess.stdin.write(JSON.stringify(request) + '\n');
83 | });
84 | }
85 |
86 | async testTool(toolName, params = {}, expectSuccess = true) {
87 | console.log(`🧪 Testing tool: ${toolName}`);
88 |
89 | try {
90 | const response = await this.sendMCPRequest('tools/call', {
91 | name: toolName,
92 | arguments: params
93 | });
94 |
95 | if (response.error) {
96 | if (expectSuccess) {
97 | console.log(`❌ ${toolName} failed:`, response.error.message);
98 | this.testResults.push({ tool: toolName, status: 'failed', error: response.error.message });
99 | return { success: false, data: null };
100 | } else {
101 | console.log(`✅ ${toolName} failed as expected:`, response.error.message);
102 | this.testResults.push({ tool: toolName, status: 'passed', note: 'Expected failure' });
103 | return { success: true, data: null };
104 | }
105 | } else {
106 | console.log(`✅ ${toolName} succeeded`);
107 | this.testResults.push({ tool: toolName, status: 'passed' });
108 |
109 | // Extract workflow ID if this was a create operation
110 | if (toolName.includes('create') && response.result?.content?.[0]?.text) {
111 | try {
112 | const result = JSON.parse(response.result.content[0].text);
113 | if (result.id) {
114 | this.createdWorkflowId = result.id;
115 | console.log(` 📝 Created workflow ID: ${this.createdWorkflowId}`);
116 | } else if (result.created?.id) {
117 | this.createdWorkflowId = result.created.id;
118 | console.log(` 📝 Created workflow ID: ${this.createdWorkflowId}`);
119 | }
120 | } catch (e) {
121 | // Ignore parsing errors
122 | }
123 | }
124 |
125 | return { success: true, data: response.result };
126 | }
127 | } catch (error) {
128 | console.log(`❌ ${toolName} error:`, error.message);
129 | this.testResults.push({ tool: toolName, status: 'error', error: error.message });
130 | return { success: false, data: null };
131 | }
132 | }
133 |
134 | getTestWorkflow() {
135 | return {
136 | name: `MCP Test Workflow ${Date.now()}`,
137 | nodes: [
138 | {
139 | id: "schedule-trigger",
140 | name: "Schedule Trigger",
141 | type: "n8n-nodes-base.scheduleTrigger",
142 | typeVersion: 1,
143 | position: [240, 300],
144 | parameters: {
145 | interval: [
146 | {
147 | field: "unit",
148 | value: "hours"
149 | },
150 | {
151 | field: "intervalValue",
152 | value: 24
153 | }
154 | ]
155 | }
156 | }
157 | ],
158 | connections: {}
159 | };
160 | }
161 |
162 | async runAllTests() {
163 | try {
164 | await this.startServer();
165 |
166 | console.log('\n📋 Running comprehensive MCP tool tests...\n');
167 |
168 | // Test all 9 workflow management tools
169 | await this.testTool('list_workflows');
170 |
171 | const testWorkflow = this.getTestWorkflow();
172 | await this.testTool('create_workflow', { workflow: testWorkflow });
173 |
174 | if (this.createdWorkflowId) {
175 | await this.testTool('get_workflow', { id: this.createdWorkflowId });
176 | await this.testTool('update_workflow', { id: this.createdWorkflowId, workflow: { name: `Updated ${testWorkflow.name}` } });
177 | await this.testTool('activate_workflow', { id: this.createdWorkflowId });
178 | await this.testTool('deactivate_workflow', { id: this.createdWorkflowId });
179 | await this.testTool('execute_workflow', { id: this.createdWorkflowId });
180 | await this.testTool('delete_workflow', { id: this.createdWorkflowId });
181 | }
182 |
183 | // Test create and activate
184 | const createActivateWorkflow = { ...this.getTestWorkflow(), name: `MCP Create+Activate Test ${Date.now()}` };
185 | const createActivateResult = await this.testTool('create_workflow_and_activate', { workflow: createActivateWorkflow });
186 |
187 | // Cleanup
188 | if (createActivateResult.success && createActivateResult.data) {
189 | try {
190 | const result = JSON.parse(createActivateResult.data.content[0].text);
191 | const workflowId = result.created?.id;
192 | if (workflowId) {
193 | await this.testTool('delete_workflow', { id: workflowId });
194 | }
195 | } catch (e) {
196 | console.log('⚠️ Could not extract workflow ID for cleanup');
197 | }
198 | }
199 |
200 | } catch (error) {
201 | console.error('❌ Test execution failed:', error);
202 | } finally {
203 | this.cleanup();
204 | }
205 | }
206 |
207 | cleanup() {
208 | if (this.serverProcess) {
209 | console.log('\n🧹 Cleaning up server process...');
210 | this.serverProcess.kill();
211 | }
212 |
213 | console.log('\n📊 Test Results Summary:');
214 | console.log('========================');
215 |
216 | const toolCategories = {
217 | 'Basic Operations': ['list_workflows', 'get_workflow'],
218 | 'Workflow Creation': ['create_workflow', 'create_workflow_and_activate'],
219 | 'Workflow Management': ['update_workflow', 'activate_workflow', 'deactivate_workflow'],
220 | 'Workflow Execution': ['execute_workflow'],
221 | 'Workflow Deletion': ['delete_workflow']
222 | };
223 |
224 | Object.entries(toolCategories).forEach(([category, tools]) => {
225 | console.log(`\n${category}:`);
226 | tools.forEach(tool => {
227 | const result = this.testResults.find(r => r.tool === tool);
228 | if (result) {
229 | const status = result.status === 'passed' ? '✅' : '❌';
230 | console.log(` ${status} ${tool}: ${result.status}`);
231 | if (result.error) {
232 | console.log(` Error: ${result.error}`);
233 | }
234 | if (result.note) {
235 | console.log(` Note: ${result.note}`);
236 | }
237 | } else {
238 | console.log(` ⚪ ${tool}: not tested`);
239 | }
240 | });
241 | });
242 |
243 | const passed = this.testResults.filter(r => r.status === 'passed').length;
244 | const total = this.testResults.length;
245 |
246 | console.log(`\n🎯 Overall Results: ${passed}/${total} tests passed`);
247 |
248 | if (passed === total) {
249 | console.log('🎉 All tests passed! Complete n8n workflow management is working correctly.');
250 | process.exit(0);
251 | } else {
252 | console.log('⚠️ Some tests failed. Check the errors above.');
253 | process.exit(1);
254 | }
255 | }
256 | }
257 |
258 | // Run the comprehensive tests
259 | const tester = new ComprehensiveMCPTester();
260 | tester.runAllTests().catch(console.error);
261 |
```
--------------------------------------------------------------------------------
/USE_CASES.md:
--------------------------------------------------------------------------------
```markdown
1 | # 💼 Real-World Use Cases for n8n Workflow Builder MCP Server
2 |
3 | **Discover how teams use AI assistants to automate n8n workflows across industries and use cases.**
4 |
5 | ## 🛒 E-commerce Automation
6 |
7 | ### Automated Order Processing
8 | **Scenario**: E-commerce store needs to process orders, update inventory, and notify customers.
9 |
10 | **AI Command**:
11 | ```
12 | "Create an n8n workflow that triggers when a new order comes in, updates inventory in our database, sends a confirmation email to the customer, and creates a shipping label"
13 | ```
14 |
15 | **Workflow Components**:
16 | - **Trigger**: Webhook from e-commerce platform
17 | - **Actions**: Database update, email notification, shipping API call
18 | - **Benefits**: Reduces manual processing time by 90%
19 |
20 | ### Inventory Management
21 | **Scenario**: Monitor stock levels and automatically reorder products.
22 |
23 | **AI Command**:
24 | ```
25 | "Set up a workflow that checks inventory levels daily and automatically creates purchase orders when stock is below threshold"
26 | ```
27 |
28 | **Real Impact**: Prevents stockouts and reduces manual inventory monitoring.
29 |
30 | ### Customer Support Automation
31 | **Scenario**: Route customer inquiries based on urgency and type.
32 |
33 | **AI Command**:
34 | ```
35 | "Create a workflow that analyzes incoming support tickets, categorizes them by urgency, and assigns them to the appropriate team member"
36 | ```
37 |
38 | ## 📊 Data Processing Workflows
39 |
40 | ### Automated Reporting
41 | **Scenario**: Generate daily sales reports from multiple data sources.
42 |
43 | **AI Command**:
44 | ```
45 | "Build a workflow that pulls data from our CRM, payment processor, and analytics platform, then generates a daily sales report and emails it to the management team"
46 | ```
47 |
48 | **Workflow Features**:
49 | - **Data Sources**: CRM API, Stripe, Google Analytics
50 | - **Processing**: Data aggregation, calculations, formatting
51 | - **Output**: PDF report, email distribution
52 |
53 | ### Data Synchronization
54 | **Scenario**: Keep customer data synchronized across multiple platforms.
55 |
56 | **AI Command**:
57 | ```
58 | "Create a workflow that syncs customer information between our CRM, email marketing platform, and support system whenever a customer profile is updated"
59 | ```
60 |
61 | **Benefits**: Eliminates data silos and ensures consistency.
62 |
63 | ### ETL Pipeline Management
64 | **Scenario**: Extract, transform, and load data from various sources.
65 |
66 | **AI Command**:
67 | ```
68 | "Set up a workflow that extracts data from our database, transforms it according to our business rules, and loads it into our data warehouse every night"
69 | ```
70 |
71 | ## 🔗 API Integration Workflows
72 |
73 | ### Multi-Platform Content Publishing
74 | **Scenario**: Publish content across social media platforms simultaneously.
75 |
76 | **AI Command**:
77 | ```
78 | "Create a workflow that takes a blog post from our CMS and automatically publishes it to Twitter, LinkedIn, and Facebook with appropriate formatting for each platform"
79 | ```
80 |
81 | **Automation Benefits**:
82 | - **Time Savings**: 15 minutes → 30 seconds
83 | - **Consistency**: Same message across platforms
84 | - **Scheduling**: Optimal posting times
85 |
86 | ### Lead Management
87 | **Scenario**: Capture leads from multiple sources and route them appropriately.
88 |
89 | **AI Command**:
90 | ```
91 | "Build a workflow that captures leads from our website forms, LinkedIn ads, and trade shows, then enriches the data and adds them to our CRM with proper lead scoring"
92 | ```
93 |
94 | ### Payment Processing Integration
95 | **Scenario**: Handle payment notifications and update customer accounts.
96 |
97 | **AI Command**:
98 | ```
99 | "Set up a workflow that processes payment webhooks, updates customer subscription status, and sends receipt emails with invoice attachments"
100 | ```
101 |
102 | ## 🚨 Monitoring and Alerting
103 |
104 | ### System Health Monitoring
105 | **Scenario**: Monitor application health and alert teams when issues occur.
106 |
107 | **AI Command**:
108 | ```
109 | "Create a monitoring workflow that checks our API endpoints every 5 minutes, monitors response times, and sends Slack alerts if any service is down or slow"
110 | ```
111 |
112 | **Monitoring Features**:
113 | - **Health Checks**: API endpoints, database connections
114 | - **Performance**: Response times, error rates
115 | - **Alerts**: Slack, email, SMS notifications
116 |
117 | ### Security Monitoring
118 | **Scenario**: Monitor for suspicious activities and security threats.
119 |
120 | **AI Command**:
121 | ```
122 | "Build a security workflow that monitors login attempts, detects unusual patterns, and automatically locks accounts while notifying the security team"
123 | ```
124 |
125 | ### Infrastructure Monitoring
126 | **Scenario**: Monitor server resources and automatically scale when needed.
127 |
128 | **AI Command**:
129 | ```
130 | "Set up a workflow that monitors server CPU and memory usage, and automatically triggers scaling actions when thresholds are exceeded"
131 | ```
132 |
133 | ## 🏢 Business Process Automation
134 |
135 | ### Employee Onboarding
136 | **Scenario**: Automate new employee setup across multiple systems.
137 |
138 | **AI Command**:
139 | ```
140 | "Create an onboarding workflow that sets up new employee accounts in all our systems, sends welcome emails, schedules orientation meetings, and creates task lists for managers"
141 | ```
142 |
143 | ### Invoice Processing
144 | **Scenario**: Automate invoice approval and payment workflows.
145 |
146 | **AI Command**:
147 | ```
148 | "Build a workflow that processes incoming invoices, routes them for approval based on amount and department, and schedules payments once approved"
149 | ```
150 |
151 | ### Contract Management
152 | **Scenario**: Track contract renewals and automate renewal processes.
153 |
154 | **AI Command**:
155 | ```
156 | "Set up a workflow that monitors contract expiration dates, sends renewal reminders 90 days before expiry, and automatically generates renewal documents"
157 | ```
158 |
159 | ## 🎯 Marketing Automation
160 |
161 | ### Lead Nurturing Campaigns
162 | **Scenario**: Automatically nurture leads based on their behavior.
163 |
164 | **AI Command**:
165 | ```
166 | "Create a lead nurturing workflow that sends personalized email sequences based on lead source, industry, and engagement level"
167 | ```
168 |
169 | ### Event Management
170 | **Scenario**: Automate event registration and follow-up processes.
171 |
172 | **AI Command**:
173 | ```
174 | "Build an event workflow that handles registrations, sends confirmation emails, manages waitlists, and follows up with attendees post-event"
175 | ```
176 |
177 | ### Social Media Monitoring
178 | **Scenario**: Monitor brand mentions and respond appropriately.
179 |
180 | **AI Command**:
181 | ```
182 | "Set up a workflow that monitors social media mentions of our brand, analyzes sentiment, and alerts the marketing team for negative mentions while auto-responding to positive ones"
183 | ```
184 |
185 | ## 🔧 DevOps and IT Automation
186 |
187 | ### Deployment Pipeline
188 | **Scenario**: Automate code deployment and testing processes.
189 |
190 | **AI Command**:
191 | ```
192 | "Create a deployment workflow that triggers when code is pushed to main branch, runs tests, deploys to staging, and promotes to production after approval"
193 | ```
194 |
195 | ### Backup Management
196 | **Scenario**: Automate database backups and verification.
197 |
198 | **AI Command**:
199 | ```
200 | "Build a backup workflow that creates daily database backups, verifies backup integrity, and stores them in multiple locations with retention policies"
201 | ```
202 |
203 | ### Incident Response
204 | **Scenario**: Automate incident detection and response procedures.
205 |
206 | **AI Command**:
207 | ```
208 | "Set up an incident response workflow that detects system anomalies, creates incident tickets, notifies on-call engineers, and escalates if not acknowledged"
209 | ```
210 |
211 | ## 📈 Analytics and Insights
212 |
213 | ### Performance Dashboards
214 | **Scenario**: Automatically update business dashboards with fresh data.
215 |
216 | **AI Command**:
217 | ```
218 | "Create a dashboard workflow that pulls data from all our business systems, calculates KPIs, and updates our executive dashboard every hour"
219 | ```
220 |
221 | ### Customer Behavior Analysis
222 | **Scenario**: Analyze customer behavior patterns and trigger actions.
223 |
224 | **AI Command**:
225 | ```
226 | "Build an analysis workflow that tracks customer behavior, identifies at-risk customers, and automatically triggers retention campaigns"
227 | ```
228 |
229 | ## 🎉 Success Stories
230 |
231 | ### E-commerce Company
232 | - **Challenge**: Manual order processing taking 2 hours daily
233 | - **Solution**: AI-created n8n workflow automation
234 | - **Result**: 95% time reduction, zero processing errors
235 |
236 | ### SaaS Startup
237 | - **Challenge**: Customer onboarding taking 3 days
238 | - **Solution**: Automated onboarding workflow
239 | - **Result**: Same-day onboarding, 40% faster time-to-value
240 |
241 | ### Marketing Agency
242 | - **Challenge**: Managing campaigns across 50+ clients
243 | - **Solution**: Automated reporting and monitoring workflows
244 | - **Result**: 60% reduction in manual work, better client satisfaction
245 |
246 | ## 🚀 Getting Started with Your Use Case
247 |
248 | 1. **Identify Repetitive Tasks**: What do you do manually that could be automated?
249 | 2. **Map Your Process**: Break down the steps involved
250 | 3. **Describe to AI**: Use natural language to explain what you want
251 | 4. **Iterate and Improve**: Refine the workflow based on results
252 | 5. **Scale**: Apply successful patterns to other processes
253 |
254 | ## 💡 Pro Tips for Use Case Implementation
255 |
256 | - **Start Small**: Begin with simple, low-risk workflows
257 | - **Document Everything**: Keep track of what works and what doesn't
258 | - **Monitor Performance**: Track time savings and error reduction
259 | - **Get Team Buy-in**: Show results to encourage adoption
260 | - **Think Integration**: Look for opportunities to connect existing tools
261 |
262 | ---
263 |
264 | **Ready to automate your workflows? Describe your use case to your AI assistant and watch the magic happen!** ✨
265 |
```
--------------------------------------------------------------------------------
/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # 🔧 Troubleshooting Guide for n8n Workflow Builder MCP Server
2 |
3 | **Comprehensive solutions for common issues when connecting AI assistants to n8n workflows.**
4 |
5 | ## 🚨 Quick Diagnostic Commands
6 |
7 | Before diving into specific issues, try these diagnostic commands with your AI assistant:
8 |
9 | ```
10 | "Test my n8n connection"
11 | "Show me the server status"
12 | "List my workflows" (basic connectivity test)
13 | "What's my n8n instance information?"
14 | ```
15 |
16 | ## 🔌 Connection Issues
17 |
18 | ### "Connection Refused" or "ECONNREFUSED"
19 |
20 | **Symptoms:**
21 | - Cannot connect to n8n instance
22 | - Timeout errors when trying to list workflows
23 | - "Network unreachable" messages
24 |
25 | **Solutions:**
26 |
27 | 1. **Verify n8n Instance is Running**
28 | ```bash
29 | # Check if n8n is accessible
30 | curl -I https://your-n8n-instance.com
31 | ```
32 |
33 | 2. **Check N8N_HOST Configuration**
34 | - ✅ Correct: `https://your-n8n.com`
35 | - ✅ Correct: `http://localhost:5678`
36 | - ❌ Wrong: `your-n8n.com` (missing protocol)
37 | - ❌ Wrong: `https://your-n8n.com/` (trailing slash)
38 |
39 | 3. **Network Connectivity**
40 | - Test from browser: Visit your n8n instance URL
41 | - Check firewall settings
42 | - Verify VPN/proxy configuration
43 |
44 | 4. **Port Configuration**
45 | - Default n8n port: 5678
46 | - n8n Cloud: Use full HTTPS URL
47 | - Self-hosted: Include custom port if different
48 |
49 | **AI Assistant Command to Test:**
50 | ```
51 | "Check if my n8n instance at [your-url] is accessible"
52 | ```
53 |
54 | ### "Unauthorized" or "401 Authentication Error"
55 |
56 | **Symptoms:**
57 | - "Invalid API key" messages
58 | - "Unauthorized access" errors
59 | - Can connect but cannot perform actions
60 |
61 | **Solutions:**
62 |
63 | 1. **Verify API Key Format**
64 | - ✅ Correct: `n8n_api_1234567890abcdef...`
65 | - ❌ Wrong: Missing `n8n_api_` prefix
66 | - ❌ Wrong: Truncated or incomplete key
67 |
68 | 2. **Check API Key Permissions**
69 | - Ensure key has workflow management permissions
70 | - Verify key hasn't expired
71 | - Test key with direct API call:
72 | ```bash
73 | curl -H "X-N8N-API-KEY: your-api-key" https://your-n8n.com/api/v1/workflows
74 | ```
75 |
76 | 3. **Regenerate API Key**
77 | - Go to n8n Settings → API Keys
78 | - Delete old key and create new one
79 | - Update MCP server configuration
80 |
81 | **AI Assistant Command to Test:**
82 | ```
83 | "Verify my n8n API key permissions"
84 | ```
85 |
86 | ### "Workflow Not Found" or "404 Error"
87 |
88 | **Symptoms:**
89 | - Specific workflows cannot be accessed
90 | - "Workflow ID does not exist" messages
91 | - Some workflows visible, others not
92 |
93 | **Solutions:**
94 |
95 | 1. **List All Workflows First**
96 | ```
97 | "List all my n8n workflows with their IDs"
98 | ```
99 |
100 | 2. **Check Workflow ID Format**
101 | - Use exact ID from workflow list
102 | - IDs are typically numeric or UUID format
103 | - Case-sensitive in some configurations
104 |
105 | 3. **Verify Workflow Permissions**
106 | - Ensure API key has access to specific workflows
107 | - Check if workflow is in different project/workspace
108 |
109 | ## 🚀 Installation and Setup Issues
110 |
111 | ### "Server Won't Start" or "Module Not Found"
112 |
113 | **Symptoms:**
114 | - MCP server fails to launch
115 | - "Cannot find module" errors
116 | - "Command not found" messages
117 |
118 | **Solutions:**
119 |
120 | 1. **Check Node.js Version**
121 | ```bash
122 | node --version # Must be 18.0.0 or higher
123 | ```
124 |
125 | 2. **Clear npm Cache**
126 | ```bash
127 | npm cache clean --force
128 | ```
129 |
130 | 3. **Reinstall Package**
131 | ```bash
132 | npm uninstall -g @makafeli/n8n-workflow-builder
133 | npm install -g @makafeli/n8n-workflow-builder
134 | ```
135 |
136 | 4. **Use NPX Instead**
137 | ```bash
138 | npx @makafeli/n8n-workflow-builder
139 | ```
140 |
141 | ### "Permission Denied" Errors
142 |
143 | **Symptoms:**
144 | - Cannot install package globally
145 | - "EACCES" permission errors
146 | - Installation fails with permission issues
147 |
148 | **Solutions:**
149 |
150 | 1. **Use NPX (Recommended)**
151 | ```bash
152 | npx @makafeli/n8n-workflow-builder
153 | ```
154 |
155 | 2. **Fix npm Permissions**
156 | ```bash
157 | # Configure npm to use different directory
158 | mkdir ~/.npm-global
159 | npm config set prefix '~/.npm-global'
160 | echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
161 | source ~/.bashrc
162 | ```
163 |
164 | 3. **Use Node Version Manager**
165 | ```bash
166 | # Install nvm and use it to manage Node.js
167 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
168 | nvm install 18
169 | nvm use 18
170 | ```
171 |
172 | ## 🤖 AI Assistant Integration Issues
173 |
174 | ### Claude Desktop Configuration Problems
175 |
176 | **Symptoms:**
177 | - MCP server not appearing in Claude Desktop
178 | - "Server failed to start" in Claude
179 | - Configuration not loading
180 |
181 | **Solutions:**
182 |
183 | 1. **Check Configuration File Location**
184 | - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
185 | - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
186 |
187 | 2. **Validate JSON Syntax**
188 | ```json
189 | {
190 | "mcpServers": {
191 | "n8n-workflow-builder": {
192 | "command": "npx",
193 | "args": ["@makafeli/n8n-workflow-builder"],
194 | "env": {
195 | "N8N_HOST": "https://your-n8n-instance.com",
196 | "N8N_API_KEY": "your-api-key-here"
197 | }
198 | }
199 | }
200 | }
201 | ```
202 |
203 | 3. **Restart Claude Desktop**
204 | - Completely quit Claude Desktop
205 | - Wait 10 seconds
206 | - Restart application
207 |
208 | 4. **Check Environment Variables**
209 | - Ensure no quotes around environment values
210 | - Verify special characters are properly escaped
211 | - Test with minimal configuration first
212 |
213 | ### Cline (VS Code) Integration Issues
214 |
215 | **Symptoms:**
216 | - MCP server not recognized in Cline
217 | - "Failed to connect to MCP server" errors
218 | - Tools not appearing in Cline interface
219 |
220 | **Solutions:**
221 |
222 | 1. **Update Cline Extension**
223 | - Ensure latest version of Cline is installed
224 | - Check VS Code extension updates
225 |
226 | 2. **Verify MCP Configuration**
227 | - Check Cline settings for MCP server configuration
228 | - Ensure configuration matches expected format
229 |
230 | 3. **Restart VS Code**
231 | - Reload VS Code window
232 | - Restart VS Code completely if needed
233 |
234 | ## 🔧 Workflow Operation Issues
235 |
236 | ### "Workflow Creation Failed"
237 |
238 | **Symptoms:**
239 | - AI cannot create new workflows
240 | - "Invalid workflow configuration" errors
241 | - Workflows created but not functional
242 |
243 | **Solutions:**
244 |
245 | 1. **Simplify Workflow Request**
246 | ```
247 | "Create a simple webhook workflow with just a trigger and HTTP response"
248 | ```
249 |
250 | 2. **Check Node Availability**
251 | - Verify required nodes are installed in n8n
252 | - Update n8n to latest version for node compatibility
253 |
254 | 3. **Validate Workflow Structure**
255 | - Ensure proper node connections
256 | - Check required parameters are provided
257 |
258 | ### "Execution Failed" or "Workflow Won't Run"
259 |
260 | **Symptoms:**
261 | - Workflows created but fail to execute
262 | - "Node execution error" messages
263 | - Partial workflow execution
264 |
265 | **Solutions:**
266 |
267 | 1. **Check Node Configuration**
268 | ```
269 | "Show me the configuration of my [workflow-name] workflow"
270 | ```
271 |
272 | 2. **Test Individual Nodes**
273 | - Execute workflow step by step
274 | - Identify failing node
275 |
276 | 3. **Review Error Logs**
277 | ```
278 | "Show me the execution logs for workflow [workflow-id]"
279 | ```
280 |
281 | ## 🐛 Debug Mode and Logging
282 |
283 | ### Enable Debug Mode
284 |
285 | **For Detailed Logging:**
286 | ```bash
287 | DEBUG=n8n-workflow-builder npx @makafeli/n8n-workflow-builder
288 | ```
289 |
290 | **For Network Debugging:**
291 | ```bash
292 | DEBUG=axios npx @makafeli/n8n-workflow-builder
293 | ```
294 |
295 | **For Full Debug Output:**
296 | ```bash
297 | DEBUG=* npx @makafeli/n8n-workflow-builder
298 | ```
299 |
300 | ### Common Debug Patterns
301 |
302 | **Connection Issues:**
303 | ```
304 | DEBUG: Attempting connection to https://your-n8n.com
305 | DEBUG: Request headers: { 'X-N8N-API-KEY': 'n8n_api_...' }
306 | ERROR: ECONNREFUSED - Connection refused
307 | ```
308 |
309 | **Authentication Issues:**
310 | ```
311 | DEBUG: API request to /api/v1/workflows
312 | DEBUG: Response status: 401
313 | ERROR: Unauthorized - Invalid API key
314 | ```
315 |
316 | ## 📞 Getting Additional Help
317 |
318 | ### Self-Diagnosis Checklist
319 |
320 | Before seeking help, verify:
321 | - ✅ Node.js version 18.0.0 or higher
322 | - ✅ n8n instance is accessible via browser
323 | - ✅ API key is valid and has proper permissions
324 | - ✅ MCP server configuration is correct
325 | - ✅ AI assistant is properly configured
326 |
327 | ### AI Assistant Diagnostic Commands
328 |
329 | ```
330 | "Run a full diagnostic of my n8n MCP setup"
331 | "Test all my n8n workflow tools"
332 | "Show me my current n8n configuration"
333 | "Verify my API key permissions"
334 | ```
335 |
336 | ### Community Resources
337 |
338 | 1. **GitHub Issues**: [Report bugs and get help](https://github.com/makafeli/n8n-workflow-builder/issues)
339 | 2. **n8n Community**: [General n8n support](https://community.n8n.io/)
340 | 3. **MCP Documentation**: [Model Context Protocol docs](https://modelcontextprotocol.io/)
341 | 4. **Claude Desktop Support**: [Anthropic support](https://support.anthropic.com/)
342 |
343 | ### Creating Effective Bug Reports
344 |
345 | When reporting issues, include:
346 |
347 | 1. **Environment Information**
348 | - Operating system and version
349 | - Node.js version
350 | - n8n version and hosting type (cloud/self-hosted)
351 | - AI assistant type and version
352 |
353 | 2. **Configuration Details**
354 | - MCP server configuration (remove sensitive data)
355 | - Environment variables (remove API keys)
356 | - Debug logs (if available)
357 |
358 | 3. **Steps to Reproduce**
359 | - Exact commands or requests made
360 | - Expected vs actual behavior
361 | - Error messages (full text)
362 |
363 | 4. **Diagnostic Output**
364 | ```bash
365 | # Include output from these commands
366 | node --version
367 | npm list -g @makafeli/n8n-workflow-builder
368 | DEBUG=n8n-workflow-builder npx @makafeli/n8n-workflow-builder
369 | ```
370 |
371 | ## 🎯 Prevention Tips
372 |
373 | ### Best Practices
374 |
375 | 1. **Regular Updates**
376 | - Keep n8n instance updated
377 | - Update MCP server package regularly
378 | - Update AI assistant applications
379 |
380 | 2. **Configuration Management**
381 | - Use environment files for credentials
382 | - Document your configuration
383 | - Test configuration changes in development first
384 |
385 | 3. **Monitoring**
386 | - Set up basic monitoring for n8n instance
387 | - Monitor API key usage and permissions
388 | - Track workflow execution success rates
389 |
390 | 4. **Backup and Recovery**
391 | - Export important workflows regularly
392 | - Document custom configurations
393 | - Keep API key backup in secure location
394 |
395 | ---
396 |
397 | **Still having issues? Ask your AI assistant: "Help me troubleshoot my n8n MCP server connection"** 🔧
398 |
```
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5 | import { z } from "zod";
6 | import axios from "axios";
7 |
8 | // Configuration
9 | const N8N_HOST = process.env.N8N_HOST || 'http://localhost:5678';
10 | const N8N_API_KEY = process.env.N8N_API_KEY || '';
11 |
12 | console.error("N8N API Configuration:");
13 | console.error("Host:", N8N_HOST);
14 | console.error("API Key:", N8N_API_KEY ? `${N8N_API_KEY.substring(0, 4)}****` : 'Not set');
15 |
16 | // Create axios instance for n8n API
17 | const n8nApi = axios.create({
18 | baseURL: N8N_HOST,
19 | headers: {
20 | 'X-N8N-API-KEY': N8N_API_KEY,
21 | 'Content-Type': 'application/json'
22 | }
23 | });
24 |
25 | // Create MCP server with modern SDK 1.17.0 API
26 | const server = new McpServer({
27 | name: "n8n-workflow-builder",
28 | version: "0.10.3"
29 | });
30 |
31 | // Register workflow management tools using modern MCP SDK 1.17.0 API
32 | server.tool(
33 | "list_workflows",
34 | "List all workflows from n8n instance",
35 | {},
36 | async () => {
37 | try {
38 | const response = await n8nApi.get('/workflows');
39 | return {
40 | content: [{
41 | type: "text",
42 | text: JSON.stringify(response.data, null, 2)
43 | }]
44 | };
45 | } catch (error) {
46 | return {
47 | content: [{
48 | type: "text",
49 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
50 | }],
51 | isError: true
52 | };
53 | }
54 | }
55 | );
56 |
57 | server.tool(
58 | "create_workflow",
59 | "Create a new workflow in n8n",
60 | {
61 | workflow: z.object({
62 | name: z.string().describe("Name of the workflow"),
63 | nodes: z.array(z.any()).describe("Array of workflow nodes"),
64 | connections: z.record(z.string(), z.any()).optional().describe("Node connections"),
65 | settings: z.record(z.string(), z.any()).optional().describe("Workflow settings"),
66 | tags: z.array(z.any()).optional().describe("Workflow tags")
67 | }).describe("Workflow configuration")
68 | },
69 | async ({ workflow }) => {
70 | try {
71 | const response = await n8nApi.post('/workflows', workflow);
72 | return {
73 | content: [{
74 | type: "text",
75 | text: JSON.stringify(response.data, null, 2)
76 | }]
77 | };
78 | } catch (error) {
79 | return {
80 | content: [{
81 | type: "text",
82 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
83 | }],
84 | isError: true
85 | };
86 | }
87 | }
88 | );
89 |
90 | server.tool(
91 | "get_workflow",
92 | "Get a workflow by ID",
93 | {
94 | id: z.string().describe("Workflow ID")
95 | },
96 | async ({ id }) => {
97 | try {
98 | const response = await n8nApi.get(`/workflows/${id}`);
99 | return {
100 | content: [{
101 | type: "text",
102 | text: JSON.stringify(response.data, null, 2)
103 | }]
104 | };
105 | } catch (error) {
106 | return {
107 | content: [{
108 | type: "text",
109 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
110 | }],
111 | isError: true
112 | };
113 | }
114 | }
115 | );
116 |
117 | server.tool(
118 | "update_workflow",
119 | "Update an existing workflow by ID",
120 | {
121 | id: z.string().describe("Workflow ID"),
122 | workflow: z.object({
123 | name: z.string().optional().describe("Name of the workflow"),
124 | nodes: z.array(z.any()).optional().describe("Array of workflow nodes"),
125 | connections: z.record(z.string(), z.any()).optional().describe("Node connections"),
126 | settings: z.record(z.string(), z.any()).optional().describe("Workflow settings"),
127 | tags: z.array(z.any()).optional().describe("Workflow tags")
128 | }).describe("Updated workflow configuration")
129 | },
130 | async ({ id, workflow }) => {
131 | try {
132 | const response = await n8nApi.put(`/workflows/${id}`, workflow);
133 | return {
134 | content: [{
135 | type: "text",
136 | text: JSON.stringify(response.data, null, 2)
137 | }]
138 | };
139 | } catch (error) {
140 | return {
141 | content: [{
142 | type: "text",
143 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
144 | }],
145 | isError: true
146 | };
147 | }
148 | }
149 | );
150 |
151 | server.tool(
152 | "delete_workflow",
153 | "Delete a workflow by ID",
154 | {
155 | id: z.string().describe("Workflow ID")
156 | },
157 | async ({ id }) => {
158 | try {
159 | const response = await n8nApi.delete(`/workflows/${id}`);
160 | return {
161 | content: [{
162 | type: "text",
163 | text: JSON.stringify({
164 | success: true,
165 | message: `Workflow ${id} deleted successfully`,
166 | deletedWorkflow: response.data
167 | }, null, 2)
168 | }]
169 | };
170 | } catch (error) {
171 | return {
172 | content: [{
173 | type: "text",
174 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
175 | }],
176 | isError: true
177 | };
178 | }
179 | }
180 | );
181 |
182 | server.tool(
183 | "activate_workflow",
184 | "Activate a workflow by ID",
185 | {
186 | id: z.string().describe("Workflow ID")
187 | },
188 | async ({ id }) => {
189 | try {
190 | const response = await n8nApi.post(`/workflows/${id}/activate`);
191 | return {
192 | content: [{
193 | type: "text",
194 | text: JSON.stringify({
195 | success: true,
196 | message: `Workflow ${id} activated successfully`,
197 | workflow: response.data
198 | }, null, 2)
199 | }]
200 | };
201 | } catch (error) {
202 | return {
203 | content: [{
204 | type: "text",
205 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
206 | }],
207 | isError: true
208 | };
209 | }
210 | }
211 | );
212 |
213 | server.tool(
214 | "deactivate_workflow",
215 | "Deactivate a workflow by ID",
216 | {
217 | id: z.string().describe("Workflow ID")
218 | },
219 | async ({ id }) => {
220 | try {
221 | const response = await n8nApi.post(`/workflows/${id}/deactivate`);
222 | return {
223 | content: [{
224 | type: "text",
225 | text: JSON.stringify({
226 | success: true,
227 | message: `Workflow ${id} deactivated successfully`,
228 | workflow: response.data
229 | }, null, 2)
230 | }]
231 | };
232 | } catch (error) {
233 | return {
234 | content: [{
235 | type: "text",
236 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
237 | }],
238 | isError: true
239 | };
240 | }
241 | }
242 | );
243 |
244 | server.tool(
245 | "execute_workflow",
246 | "Execute a workflow manually",
247 | {
248 | id: z.string().describe("Workflow ID")
249 | },
250 | async ({ id }) => {
251 | try {
252 | const response = await n8nApi.post(`/workflows/${id}/execute`);
253 | return {
254 | content: [{
255 | type: "text",
256 | text: JSON.stringify({
257 | success: true,
258 | message: `Workflow ${id} executed successfully`,
259 | execution: response.data
260 | }, null, 2)
261 | }]
262 | };
263 | } catch (error) {
264 | return {
265 | content: [{
266 | type: "text",
267 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
268 | }],
269 | isError: true
270 | };
271 | }
272 | }
273 | );
274 |
275 | server.tool(
276 | "create_workflow_and_activate",
277 | "Create a new workflow and immediately activate it",
278 | {
279 | workflow: z.object({
280 | name: z.string().describe("Name of the workflow"),
281 | nodes: z.array(z.any()).describe("Array of workflow nodes"),
282 | connections: z.record(z.string(), z.any()).optional().describe("Node connections"),
283 | settings: z.record(z.string(), z.any()).optional().describe("Workflow settings"),
284 | tags: z.array(z.any()).optional().describe("Workflow tags")
285 | }).describe("Workflow configuration")
286 | },
287 | async ({ workflow }) => {
288 | try {
289 | // First create the workflow
290 | const createResponse = await n8nApi.post('/workflows', workflow);
291 | const workflowId = createResponse.data.id;
292 |
293 | // Then activate it
294 | const activateResponse = await n8nApi.post(`/workflows/${workflowId}/activate`);
295 |
296 | return {
297 | content: [{
298 | type: "text",
299 | text: JSON.stringify({
300 | success: true,
301 | message: `Workflow created and activated successfully`,
302 | workflow: activateResponse.data
303 | }, null, 2)
304 | }]
305 | };
306 | } catch (error) {
307 | return {
308 | content: [{
309 | type: "text",
310 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
311 | }],
312 | isError: true
313 | };
314 | }
315 | }
316 | );
317 |
318 | // Execution Management Tools
319 | server.tool(
320 | "list_executions",
321 | "List workflow executions with filtering and pagination support",
322 | {
323 | includeData: z.boolean().optional().describe("Include execution's detailed data"),
324 | status: z.enum(["error", "success", "waiting"]).optional().describe("Filter by execution status"),
325 | workflowId: z.string().optional().describe("Filter by specific workflow ID"),
326 | projectId: z.string().optional().describe("Filter by project ID"),
327 | limit: z.number().min(1).max(250).optional().describe("Number of executions to return (max: 250)"),
328 | cursor: z.string().optional().describe("Pagination cursor for next page")
329 | },
330 | async ({ includeData, status, workflowId, projectId, limit, cursor }) => {
331 | try {
332 | const params = new URLSearchParams();
333 |
334 | if (includeData !== undefined) params.append('includeData', includeData.toString());
335 | if (status) params.append('status', status);
336 | if (workflowId) params.append('workflowId', workflowId);
337 | if (projectId) params.append('projectId', projectId);
338 | if (limit) params.append('limit', limit.toString());
339 | if (cursor) params.append('cursor', cursor);
340 |
341 | const response = await n8nApi.get(`/executions?${params.toString()}`);
342 | return {
343 | content: [{
344 | type: "text",
345 | text: JSON.stringify(response.data, null, 2)
346 | }]
347 | };
348 | } catch (error) {
349 | return {
350 | content: [{
351 | type: "text",
352 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
353 | }],
354 | isError: true
355 | };
356 | }
357 | }
358 | );
359 |
360 | server.tool(
361 | "get_execution",
362 | "Get detailed information about a specific workflow execution",
363 | {
364 | id: z.string().describe("Execution ID"),
365 | includeData: z.boolean().optional().describe("Include detailed execution data")
366 | },
367 | async ({ id, includeData }) => {
368 | try {
369 | const params = new URLSearchParams();
370 | if (includeData !== undefined) params.append('includeData', includeData.toString());
371 |
372 | const url = `/executions/${id}${params.toString() ? `?${params.toString()}` : ''}`;
373 | const response = await n8nApi.get(url);
374 | return {
375 | content: [{
376 | type: "text",
377 | text: JSON.stringify(response.data, null, 2)
378 | }]
379 | };
380 | } catch (error) {
381 | return {
382 | content: [{
383 | type: "text",
384 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
385 | }],
386 | isError: true
387 | };
388 | }
389 | }
390 | );
391 |
392 | server.tool(
393 | "delete_execution",
394 | "Delete a workflow execution record from the n8n instance",
395 | {
396 | id: z.string().describe("Execution ID to delete")
397 | },
398 | async ({ id }) => {
399 | try {
400 | const response = await n8nApi.delete(`/executions/${id}`);
401 | return {
402 | content: [{
403 | type: "text",
404 | text: JSON.stringify({
405 | success: true,
406 | message: `Execution ${id} deleted successfully`,
407 | deletedExecution: response.data
408 | }, null, 2)
409 | }]
410 | };
411 | } catch (error) {
412 | return {
413 | content: [{
414 | type: "text",
415 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
416 | }],
417 | isError: true
418 | };
419 | }
420 | }
421 | );
422 |
423 | // Tag Management Tools
424 | server.tool(
425 | "list_tags",
426 | "List all workflow tags with pagination support",
427 | {
428 | limit: z.number().min(1).max(250).optional().describe("Number of tags to return (max: 250)"),
429 | cursor: z.string().optional().describe("Pagination cursor for next page")
430 | },
431 | async ({ limit, cursor }) => {
432 | try {
433 | const params = new URLSearchParams();
434 |
435 | if (limit) params.append('limit', limit.toString());
436 | if (cursor) params.append('cursor', cursor);
437 |
438 | const response = await n8nApi.get(`/tags?${params.toString()}`);
439 | return {
440 | content: [{
441 | type: "text",
442 | text: JSON.stringify(response.data, null, 2)
443 | }]
444 | };
445 | } catch (error) {
446 | return {
447 | content: [{
448 | type: "text",
449 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
450 | }],
451 | isError: true
452 | };
453 | }
454 | }
455 | );
456 |
457 | server.tool(
458 | "create_tag",
459 | "Create a new workflow tag for organization and categorization",
460 | {
461 | name: z.string().describe("Name of the tag to create")
462 | },
463 | async ({ name }) => {
464 | try {
465 | const response = await n8nApi.post('/tags', { name });
466 | return {
467 | content: [{
468 | type: "text",
469 | text: JSON.stringify({
470 | success: true,
471 | message: `Tag '${name}' created successfully`,
472 | tag: response.data
473 | }, null, 2)
474 | }]
475 | };
476 | } catch (error) {
477 | return {
478 | content: [{
479 | type: "text",
480 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
481 | }],
482 | isError: true
483 | };
484 | }
485 | }
486 | );
487 |
488 | server.tool(
489 | "get_tag",
490 | "Retrieve individual tag details by ID",
491 | {
492 | id: z.string().describe("Tag ID")
493 | },
494 | async ({ id }) => {
495 | try {
496 | const response = await n8nApi.get(`/tags/${id}`);
497 | return {
498 | content: [{
499 | type: "text",
500 | text: JSON.stringify({
501 | success: true,
502 | tag: response.data,
503 | message: `Tag ${id} retrieved successfully`
504 | }, null, 2)
505 | }]
506 | };
507 | } catch (error) {
508 | return {
509 | content: [{
510 | type: "text",
511 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
512 | }],
513 | isError: true
514 | };
515 | }
516 | }
517 | );
518 |
519 | server.tool(
520 | "update_tag",
521 | "Modify tag names for better organization",
522 | {
523 | id: z.string().describe("Tag ID"),
524 | name: z.string().describe("New name for the tag")
525 | },
526 | async ({ id, name }) => {
527 | try {
528 | const response = await n8nApi.put(`/tags/${id}`, { name });
529 | return {
530 | content: [{
531 | type: "text",
532 | text: JSON.stringify({
533 | success: true,
534 | message: `Tag ${id} updated successfully`,
535 | tag: response.data
536 | }, null, 2)
537 | }]
538 | };
539 | } catch (error) {
540 | return {
541 | content: [{
542 | type: "text",
543 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
544 | }],
545 | isError: true
546 | };
547 | }
548 | }
549 | );
550 |
551 | server.tool(
552 | "delete_tag",
553 | "Remove unused tags from the system",
554 | {
555 | id: z.string().describe("Tag ID to delete")
556 | },
557 | async ({ id }) => {
558 | try {
559 | const response = await n8nApi.delete(`/tags/${id}`);
560 | return {
561 | content: [{
562 | type: "text",
563 | text: JSON.stringify({
564 | success: true,
565 | message: `Tag ${id} deleted successfully`,
566 | deletedTag: response.data
567 | }, null, 2)
568 | }]
569 | };
570 | } catch (error) {
571 | return {
572 | content: [{
573 | type: "text",
574 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
575 | }],
576 | isError: true
577 | };
578 | }
579 | }
580 | );
581 |
582 | server.tool(
583 | "get_workflow_tags",
584 | "Get all tags associated with a specific workflow",
585 | {
586 | workflowId: z.string().describe("Workflow ID")
587 | },
588 | async ({ workflowId }) => {
589 | try {
590 | const response = await n8nApi.get(`/workflows/${workflowId}/tags`);
591 | return {
592 | content: [{
593 | type: "text",
594 | text: JSON.stringify({
595 | success: true,
596 | workflowId,
597 | tags: response.data,
598 | message: `Tags for workflow ${workflowId} retrieved successfully`
599 | }, null, 2)
600 | }]
601 | };
602 | } catch (error) {
603 | return {
604 | content: [{
605 | type: "text",
606 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
607 | }],
608 | isError: true
609 | };
610 | }
611 | }
612 | );
613 |
614 | server.tool(
615 | "update_workflow_tags",
616 | "Assign or remove tags from workflows",
617 | {
618 | workflowId: z.string().describe("Workflow ID"),
619 | tagIds: z.array(z.string()).describe("Array of tag IDs to assign to the workflow")
620 | },
621 | async ({ workflowId, tagIds }) => {
622 | try {
623 | const response = await n8nApi.put(`/workflows/${workflowId}/tags`, { tagIds });
624 | return {
625 | content: [{
626 | type: "text",
627 | text: JSON.stringify({
628 | success: true,
629 | message: `Tags for workflow ${workflowId} updated successfully`,
630 | workflowId,
631 | assignedTags: response.data
632 | }, null, 2)
633 | }]
634 | };
635 | } catch (error) {
636 | return {
637 | content: [{
638 | type: "text",
639 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
640 | }],
641 | isError: true
642 | };
643 | }
644 | }
645 | );
646 |
647 | // Credential Management Tools
648 | server.tool(
649 | "create_credential",
650 | "Create a new credential for workflow authentication. Use get_credential_schema first to understand required fields for the credential type.",
651 | {
652 | name: z.string().describe("Name for the credential"),
653 | type: z.string().describe("Credential type (e.g., 'httpBasicAuth', 'httpHeaderAuth', 'oAuth2Api', etc.)"),
654 | data: z.record(z.string(), z.any()).describe("Credential data object with required fields for the credential type")
655 | },
656 | async ({ name, type, data }) => {
657 | try {
658 | const response = await n8nApi.post('/credentials', {
659 | name,
660 | type,
661 | data
662 | });
663 | return {
664 | content: [{
665 | type: "text",
666 | text: JSON.stringify({
667 | success: true,
668 | message: `Credential '${name}' created successfully`,
669 | credential: {
670 | id: response.data.id,
671 | name: response.data.name,
672 | type: response.data.type,
673 | createdAt: response.data.createdAt
674 | }
675 | }, null, 2)
676 | }]
677 | };
678 | } catch (error) {
679 | return {
680 | content: [{
681 | type: "text",
682 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
683 | }],
684 | isError: true
685 | };
686 | }
687 | }
688 | );
689 |
690 | server.tool(
691 | "get_credential_schema",
692 | "Get the schema for a specific credential type to understand what fields are required when creating credentials.",
693 | {
694 | credentialType: z.string().describe("Credential type name (e.g., 'httpBasicAuth', 'httpHeaderAuth', 'oAuth2Api', 'githubApi', 'slackApi', etc.)")
695 | },
696 | async ({ credentialType }) => {
697 | try {
698 | const response = await n8nApi.get(`/credentials/schema/${credentialType}`);
699 | return {
700 | content: [{
701 | type: "text",
702 | text: JSON.stringify({
703 | success: true,
704 | credentialType,
705 | schema: response.data,
706 | message: `Schema for credential type '${credentialType}' retrieved successfully`
707 | }, null, 2)
708 | }]
709 | };
710 | } catch (error) {
711 | return {
712 | content: [{
713 | type: "text",
714 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
715 | }],
716 | isError: true
717 | };
718 | }
719 | }
720 | );
721 |
722 | server.tool(
723 | "delete_credential",
724 | "Delete a credential by ID. This will remove the credential and make it unavailable for workflows. Use with caution as this action cannot be undone.",
725 | {
726 | id: z.string().describe("Credential ID to delete")
727 | },
728 | async ({ id }) => {
729 | try {
730 | const response = await n8nApi.delete(`/credentials/${id}`);
731 | return {
732 | content: [{
733 | type: "text",
734 | text: JSON.stringify({
735 | success: true,
736 | message: `Credential ${id} deleted successfully`,
737 | deletedCredential: response.data
738 | }, null, 2)
739 | }]
740 | };
741 | } catch (error) {
742 | return {
743 | content: [{
744 | type: "text",
745 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
746 | }],
747 | isError: true
748 | };
749 | }
750 | }
751 | );
752 |
753 | // Security Audit Tool
754 | server.tool(
755 | "generate_audit",
756 | "Generate a comprehensive security audit report for the n8n instance",
757 | {
758 | additionalOptions: z.object({
759 | daysAbandonedWorkflow: z.number().optional().describe("Number of days to consider a workflow abandoned"),
760 | categories: z.array(z.enum(["credentials", "database", "nodes", "filesystem", "instance"])).optional().describe("Audit categories to include")
761 | }).optional().describe("Additional audit configuration options")
762 | },
763 | async ({ additionalOptions }) => {
764 | try {
765 | const auditPayload: any = {};
766 |
767 | if (additionalOptions) {
768 | if (additionalOptions.daysAbandonedWorkflow !== undefined) {
769 | auditPayload.daysAbandonedWorkflow = additionalOptions.daysAbandonedWorkflow;
770 | }
771 | if (additionalOptions.categories) {
772 | auditPayload.categories = additionalOptions.categories;
773 | }
774 | }
775 |
776 | const response = await n8nApi.post('/audit', auditPayload);
777 | return {
778 | content: [{
779 | type: "text",
780 | text: JSON.stringify({
781 | success: true,
782 | message: "Security audit generated successfully",
783 | audit: response.data
784 | }, null, 2)
785 | }]
786 | };
787 | } catch (error) {
788 | return {
789 | content: [{
790 | type: "text",
791 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
792 | }],
793 | isError: true
794 | };
795 | }
796 | }
797 | );
798 |
799 | // Start the server
800 | async function main() {
801 | const transport = new StdioServerTransport();
802 | await server.connect(transport);
803 | console.error("N8N Workflow Builder MCP server v0.10.3 running on stdio");
804 | console.error("Modern SDK 1.17.0 with 23 tools: 9 workflow + 3 execution + 7 tag + 3 credential + 1 audit");
805 | }
806 |
807 | main().catch((error) => {
808 | console.error("Server error:", error);
809 | process.exit(1);
810 | });
811 |
```
--------------------------------------------------------------------------------
/src/index.cjs:
--------------------------------------------------------------------------------
```
1 | #!/usr/bin/env node
2 | "use strict";
3 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5 | return new (P || (P = Promise))(function (resolve, reject) {
6 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9 | step((generator = generator.apply(thisArg, _arguments || [])).next());
10 | });
11 | };
12 | Object.defineProperty(exports, "__esModule", { value: true });
13 | const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
14 | const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
15 | const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
16 | const axios = require('axios'); // Use require for CommonJS
17 |
18 | class N8NWorkflowBuilder {
19 | constructor() {
20 | this.nodes = [];
21 | this.connections = [];
22 | this.nextPosition = { x: 100, y: 100 };
23 | }
24 | addNode(nodeType, name, parameters) {
25 | const node = {
26 | type: nodeType,
27 | name: name,
28 | parameters: parameters,
29 | position: Object.assign({}, this.nextPosition)
30 | };
31 | this.nodes.push(node);
32 | this.nextPosition.x += 200;
33 | return name;
34 | }
35 | connectNodes(source, target, sourceOutput = 0, targetInput = 0) {
36 | this.connections.push({
37 | source_node: source,
38 | target_node: target,
39 | source_output: sourceOutput,
40 | target_input: targetInput
41 | });
42 | }
43 | exportWorkflow() {
44 | const workflow = {
45 | nodes: this.nodes,
46 | connections: { main: [] }
47 | };
48 | for (const conn of this.connections) {
49 | const connection = {
50 | node: conn.target_node,
51 | type: 'main',
52 | index: conn.target_input,
53 | sourceNode: conn.source_node,
54 | sourceIndex: conn.source_output
55 | };
56 | workflow.connections.main.push(connection);
57 | }
58 | return workflow;
59 | }
60 | }
61 |
62 | class N8NWorkflowServer {
63 | constructor() {
64 | this.n8nHost = process.env.N8N_HOST || 'http://localhost:5678';
65 | this.n8nApiKey = process.env.N8N_API_KEY || '';
66 |
67 | if (!this.n8nApiKey) {
68 | console.warn('N8N_API_KEY environment variable not set. API calls to n8n will likely fail.');
69 | }
70 |
71 | this.server = new index_js_1.Server({
72 | name: 'n8n-workflow-builder',
73 | version: '0.2.0'
74 | }, {
75 | capabilities: {
76 | resources: {},
77 | tools: {}
78 | }
79 | });
80 | this.setupToolHandlers();
81 | this.server.onerror = (error) => console.error('[MCP Error]', error);
82 | }
83 |
84 | async createWorkflow(workflowData) {
85 | try {
86 | console.log('Creating workflow with data:', JSON.stringify(workflowData, null, 2));
87 | const response = await axios.post(`${this.n8nHost}/api/v1/workflows`, workflowData, {
88 | headers: {
89 | 'X-N8N-API-KEY': this.n8nApiKey
90 | }
91 | });
92 | return response.data;
93 | }
94 | catch (error) {
95 | if (axios.isAxiosError(error)) {
96 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`);
97 | }
98 | throw error;
99 | }
100 | }
101 | async updateWorkflow(id, workflowData) {
102 | try {
103 | console.log(`Updating workflow ${id} with data:`, JSON.stringify(workflowData, null, 2));
104 | const response = await axios.put(`${this.n8nHost}/api/v1/workflows/${id}`, workflowData, {
105 | headers: {
106 | 'X-N8N-API-KEY': this.n8nApiKey
107 | }
108 | });
109 | return response.data;
110 | }
111 | catch (error) {
112 | if (axios.isAxiosError(error)) {
113 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`);
114 | }
115 | throw error;
116 | }
117 | }
118 | async activateWorkflow(id) {
119 | try {
120 | console.log(`Activating workflow ${id}`);
121 | const response = await axios.post(`${this.n8nHost}/api/v1/workflows/${id}/activate`, {}, {
122 | headers: {
123 | 'X-N8N-API-KEY': this.n8nApiKey
124 | }
125 | });
126 | return response.data;
127 | }
128 | catch (error) {
129 | if (axios.isAxiosError(error)) {
130 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`);
131 | }
132 | throw error;
133 | }
134 | }
135 | async deactivateWorkflow(id) {
136 | try {
137 | console.log(`Deactivating workflow ${id}`);
138 | const response = await axios.post(`${this.n8nHost}/api/v1/workflows/${id}/deactivate`, {}, {
139 | headers: {
140 | 'X-N8N-API-KEY': this.n8nApiKey
141 | }
142 | });
143 | return response.data;
144 | }
145 | catch (error) {
146 | if (axios.isAxiosError(error)) {
147 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`);
148 | }
149 | throw error;
150 | }
151 | }
152 | async getWorkflow(id) {
153 | try {
154 | console.log(`Getting workflow ${id}`);
155 | const response = await axios.get(`${this.n8nHost}/api/v1/workflows/${id}`, {
156 | headers: {
157 | 'X-N8N-API-KEY': this.n8nApiKey
158 | }
159 | });
160 | return response.data;
161 | }
162 | catch (error) {
163 | if (axios.isAxiosError(error)) {
164 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`);
165 | }
166 | throw error;
167 | }
168 | }
169 | async deleteWorkflow(id) {
170 | try {
171 | console.log(`Deleting workflow ${id}`);
172 | const response = await axios.delete(`${this.n8nHost}/api/v1/workflows/${id}`, {
173 | headers: {
174 | 'X-N8N-API-KEY': this.n8nApiKey
175 | }
176 | });
177 | return response.data;
178 | }
179 | catch (error) {
180 | if (axios.isAxiosError(error)) {
181 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`);
182 | }
183 | throw error;
184 | }
185 | }
186 | setupToolHandlers() {
187 | this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, () => __awaiter(this, void 0, void 0, function* () {
188 | return ({
189 | tools: [
190 | {
191 | name: 'create_workflow',
192 | description: 'Create an n8n workflow',
193 | inputSchema: {
194 | type: 'object',
195 | properties: {
196 | nodes: {
197 | type: 'array',
198 | items: {
199 | type: 'object',
200 | properties: {
201 | type: { type: 'string' },
202 | name: { type: 'string' },
203 | parameters: { type: 'object' }
204 | },
205 | required: ['type', 'name']
206 | }
207 | },
208 | connections: {
209 | type: 'array',
210 | items: {
211 | type: 'object',
212 | properties: {
213 | source: { type: 'string' },
214 | target: { type: 'string' },
215 | sourceOutput: { type: 'number', default: 0 },
216 | targetInput: { type: 'number', default: 0 }
217 | },
218 | required: ['source', 'target']
219 | }
220 | }
221 | },
222 | required: ['nodes']
223 | }
224 | },
225 | {
226 | name: 'create_workflow_and_activate',
227 | description: 'Create and activate an n8n workflow',
228 | inputSchema: {
229 | type: 'object',
230 | properties: {
231 | nodes: {
232 | type: 'array',
233 | items: {
234 | type: 'object',
235 | properties: {
236 | type: { type: 'string' },
237 | name: { type: 'string' },
238 | parameters: { type: 'object' }
239 | },
240 | required: ['type', 'name']
241 | }
242 | },
243 | connections: {
244 | type: 'array',
245 | items: {
246 | type: 'object',
247 | properties: {
248 | source: { type: 'string' },
249 | target: { type: 'string' },
250 | sourceOutput: { type: 'number', default: 0 },
251 | targetInput: { type: 'number', default: 0 }
252 | },
253 | required: ['source', 'target']
254 | }
255 | }
256 | },
257 | required: ['nodes']
258 | }
259 | },
260 | {
261 | name: 'update_workflow',
262 | description: 'Update an existing n8n workflow',
263 | inputSchema: {
264 | type: 'object',
265 | properties: {
266 | id: { type: 'string', description: 'The ID of the workflow to update' },
267 | nodes: {
268 | type: 'array',
269 | items: {
270 | type: 'object',
271 | properties: {
272 | type: { type: 'string' },
273 | name: { type: 'string' },
274 | parameters: { type: 'object' }
275 | },
276 | required: ['type', 'name']
277 | }
278 | },
279 | connections: {
280 | type: 'array',
281 | items: {
282 | type: 'object',
283 | properties: {
284 | source: { type: 'string' },
285 | target: { type: 'string' },
286 | sourceOutput: { type: 'number', default: 0 },
287 | targetInput: { type: 'number', default: 0 }
288 | },
289 | required: ['source', 'target']
290 | }
291 | }
292 | },
293 | required: ['id', 'nodes']
294 | }
295 | },
296 | {
297 | name: 'activate_workflow',
298 | description: 'Activate an n8n workflow',
299 | inputSchema: {
300 | type: 'object',
301 | properties: {
302 | id: { type: 'string', description: 'The ID of the workflow to activate' }
303 | },
304 | required: ['id']
305 | }
306 | },
307 | {
308 | name: 'deactivate_workflow',
309 | description: 'Deactivate an n8n workflow',
310 | inputSchema: {
311 | type: 'object',
312 | properties: {
313 | id: { type: 'string', description: 'The ID of the workflow to deactivate' }
314 | },
315 | required: ['id']
316 | }
317 | },
318 | {
319 | name: 'get_workflow',
320 | description: 'Get an n8n workflow',
321 | inputSchema: {
322 | type: 'object',
323 | properties: {
324 | id: { type: 'string', description: 'The ID of the workflow to get' }
325 | },
326 | required: ['id']
327 | }
328 | },
329 | {
330 | name: 'delete_workflow',
331 | description: 'Delete an n8n workflow',
332 | inputSchema: {
333 | type: 'object',
334 | properties: {
335 | id: { type: 'string', description: 'The ID of the workflow to delete' }
336 | },
337 | required: ['id']
338 | }
339 | }
340 | ]
341 | });
342 | }));
343 | this.server.setRequestHandler(types_js_1.CallToolRequestSchema, (request) => __awaiter(this, void 0, void 0, function* () {
344 | try {
345 | const builder = new N8NWorkflowBuilder();
346 | function isWorkflowSpec(obj) {
347 | return obj &&
348 | typeof obj === 'object' &&
349 | Array.isArray(obj.nodes) &&
350 | obj.nodes.every((node) => typeof node === 'object' &&
351 | typeof node.type === 'string' &&
352 | typeof node.name === 'string') &&
353 | (!obj.connections || (Array.isArray(obj.connections) &&
354 | obj.connections.every((conn) => typeof conn === 'object' &&
355 | typeof conn.source === 'string' &&
356 | typeof conn.target === 'string')));
357 | }
358 | const args = request.params.arguments;
359 | switch (request.params.name) {
360 | case 'create_workflow': {
361 | if (!isWorkflowSpec(args)) {
362 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Invalid workflow specification: must include nodes array with type and name properties');
363 | }
364 | const { nodes, connections } = args;
365 | for (const node of nodes) {
366 | builder.addNode(node.type, node.name, node.parameters || {});
367 | }
368 | for (const conn of connections || []) {
369 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput);
370 | }
371 | return {
372 | content: [{
373 | type: 'text',
374 | text: JSON.stringify(builder.exportWorkflow(), null, 2)
375 | }]
376 | };
377 | }
378 | case 'create_workflow_and_activate': {
379 | if (!isWorkflowSpec(args)) {
380 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Invalid workflow specification: must include nodes array with type and name properties');
381 | }
382 | const { nodes, connections } = args;
383 | for (const node of nodes) {
384 | builder.addNode(node.type, node.name, node.parameters || {});
385 | }
386 | for (const conn of connections || []) {
387 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput);
388 | }
389 | const workflowData = builder.exportWorkflow();
390 | const createdWorkflow = yield this.createWorkflow(workflowData);
391 | if (createdWorkflow && createdWorkflow.id) {
392 | yield this.activateWorkflow(createdWorkflow.id);
393 | }
394 | return {
395 | content: [{
396 | type: 'text',
397 | text: JSON.stringify(createdWorkflow, null, 2)
398 | }]
399 | };
400 | }
401 | case 'update_workflow': {
402 | if (!isWorkflowSpec(args)) {
403 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Invalid workflow specification: must include id, nodes array with type and name properties');
404 | }
405 | const { id, nodes, connections } = args;
406 | for (const node of nodes) {
407 | builder.addNode(node.type, node.name, node.parameters || {});
408 | }
409 | for (const conn of connections || []) {
410 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput);
411 | }
412 | const workflowData = builder.exportWorkflow();
413 | const updatedWorkflow = yield this.updateWorkflow(id, workflowData);
414 | return {
415 | content: [{
416 | type: 'text',
417 | text: JSON.stringify(updatedWorkflow, null, 2)
418 | }]
419 | };
420 | }
421 | case 'activate_workflow': {
422 | const { id } = args;
423 | if (typeof id !== 'string') {
424 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id');
425 | }
426 | const activatedWorkflow = yield this.activateWorkflow(id);
427 | return {
428 | content: [{
429 | type: 'text',
430 | text: JSON.stringify(activatedWorkflow, null, 2)
431 | }]
432 | };
433 | }
434 | case 'deactivate_workflow': {
435 | const { id } = args;
436 | if (typeof id !== 'string') {
437 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id');
438 | }
439 | const deactivatedWorkflow = yield this.deactivateWorkflow(id);
440 | return {
441 | content: [{
442 | type: 'text',
443 | text: JSON.stringify(deactivatedWorkflow, null, 2)
444 | }]
445 | };
446 | }
447 | case 'get_workflow': {
448 | const { id } = args;
449 | if (typeof id !== 'string') {
450 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id');
451 | }
452 | const workflow = yield this.getWorkflow(id);
453 | return {
454 | content: [{
455 | type: 'text',
456 | text: JSON.stringify(workflow, null, 2)
457 | }]
458 | };
459 | }
460 | case 'delete_workflow': {
461 | const { id } = args;
462 | if (typeof id !== 'string') {
463 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id');
464 | }
465 | const deletedWorkflow = yield this.deleteWorkflow(id);
466 | return {
467 | content: [{
468 | type: 'text',
469 | text: JSON.stringify(deletedWorkflow, null, 2)
470 | }]
471 | };
472 | }
473 | default:
474 | throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
475 | }
476 | }
477 | catch (error) {
478 | if (error instanceof types_js_1.McpError) {
479 | throw error;
480 | }
481 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Workflow operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
482 | }
483 | }));
484 | }
485 | run() {
486 | return __awaiter(this, void 0, void 0, function* () {
487 | const transport = new stdio_js_1.StdioServerTransport();
488 | yield this.server.connect(transport);
489 | console.error('N8N Workflow Builder MCP server running on stdio');
490 | });
491 | }
492 | }
493 | const server = new N8NWorkflowServer();
494 | server.run().catch(console.error);
495 |
```