This is page 3 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/helpers/mcpClient.ts:
--------------------------------------------------------------------------------
```typescript
1 | import axios from 'axios';
2 |
3 | // Mock MCP Client for testing
4 | export class MCPTestClient {
5 | private mockTools = [
6 | {
7 | name: 'list_workflows',
8 | enabled: true,
9 | description: 'List all workflows from n8n',
10 | inputSchema: { type: 'object', properties: {} }
11 | },
12 | {
13 | name: 'create_workflow',
14 | enabled: true,
15 | description: 'Create a new workflow in n8n',
16 | inputSchema: {
17 | type: 'object',
18 | properties: {
19 | workflow: { type: 'object' },
20 | name: { type: 'string' },
21 | nodes: { type: 'array' },
22 | connections: { type: 'array' }
23 | },
24 | required: ['workflow']
25 | }
26 | },
27 | {
28 | name: 'get_workflow',
29 | enabled: true,
30 | description: 'Get a workflow by ID',
31 | inputSchema: {
32 | type: 'object',
33 | properties: { id: { type: 'string' } },
34 | required: ['id']
35 | }
36 | },
37 | {
38 | name: 'update_workflow',
39 | enabled: true,
40 | description: 'Update an existing workflow',
41 | inputSchema: {
42 | type: 'object',
43 | properties: {
44 | id: { type: 'string' },
45 | nodes: { type: 'array' },
46 | connections: { type: 'array' }
47 | },
48 | required: ['id', 'nodes']
49 | }
50 | },
51 | {
52 | name: 'delete_workflow',
53 | enabled: true,
54 | description: 'Delete a workflow by ID',
55 | inputSchema: {
56 | type: 'object',
57 | properties: { id: { type: 'string' } },
58 | required: ['id']
59 | }
60 | },
61 | {
62 | name: 'activate_workflow',
63 | enabled: true,
64 | description: 'Activate a workflow by ID',
65 | inputSchema: {
66 | type: 'object',
67 | properties: { id: { type: 'string' } },
68 | required: ['id']
69 | }
70 | },
71 | {
72 | name: 'deactivate_workflow',
73 | enabled: true,
74 | description: 'Deactivate a workflow by ID',
75 | inputSchema: {
76 | type: 'object',
77 | properties: { id: { type: 'string' } },
78 | required: ['id']
79 | }
80 | },
81 | {
82 | name: 'list_executions',
83 | enabled: true,
84 | description: 'List workflow executions from n8n with optional filters',
85 | inputSchema: {
86 | type: 'object',
87 | properties: {
88 | includeData: { type: 'boolean' },
89 | status: { type: 'string', enum: ['error', 'success', 'waiting'] },
90 | workflowId: { type: 'string' },
91 | projectId: { type: 'string' },
92 | limit: { type: 'number' },
93 | cursor: { type: 'string' }
94 | }
95 | }
96 | },
97 | {
98 | name: 'get_execution',
99 | enabled: true,
100 | description: 'Get details of a specific execution by ID',
101 | inputSchema: {
102 | type: 'object',
103 | properties: {
104 | id: { type: 'number' },
105 | includeData: { type: 'boolean' }
106 | },
107 | required: ['id']
108 | }
109 | },
110 | {
111 | name: 'delete_execution',
112 | enabled: true,
113 | description: 'Delete an execution by ID',
114 | inputSchema: {
115 | type: 'object',
116 | properties: { id: { type: 'string' } },
117 | required: ['id']
118 | }
119 | },
120 | {
121 | name: 'execute_workflow',
122 | enabled: true,
123 | description: 'Execute a workflow manually',
124 | inputSchema: {
125 | type: 'object',
126 | properties: { id: { type: 'string' } },
127 | required: ['id']
128 | }
129 | },
130 | {
131 | name: 'create_workflow_and_activate',
132 | enabled: true,
133 | description: 'Create a new workflow and immediately activate it',
134 | inputSchema: {
135 | type: 'object',
136 | properties: {
137 | workflow: { type: 'object' }
138 | },
139 | required: ['workflow']
140 | }
141 | },
142 | {
143 | name: 'list_tags',
144 | enabled: true,
145 | description: 'List all workflow tags with pagination support',
146 | inputSchema: {
147 | type: 'object',
148 | properties: {
149 | limit: { type: 'number' },
150 | cursor: { type: 'string' }
151 | }
152 | }
153 | },
154 | {
155 | name: 'create_tag',
156 | enabled: true,
157 | description: 'Create a new workflow tag for organization and categorization',
158 | inputSchema: {
159 | type: 'object',
160 | properties: {
161 | name: { type: 'string' }
162 | },
163 | required: ['name']
164 | }
165 | },
166 | {
167 | name: 'get_tag',
168 | enabled: true,
169 | description: 'Retrieve individual tag details by ID',
170 | inputSchema: {
171 | type: 'object',
172 | properties: { id: { type: 'string' } },
173 | required: ['id']
174 | }
175 | },
176 | {
177 | name: 'update_tag',
178 | enabled: true,
179 | description: 'Modify tag names for better organization',
180 | inputSchema: {
181 | type: 'object',
182 | properties: {
183 | id: { type: 'string' },
184 | name: { type: 'string' }
185 | },
186 | required: ['id', 'name']
187 | }
188 | },
189 | {
190 | name: 'delete_tag',
191 | enabled: true,
192 | description: 'Remove unused tags from the system',
193 | inputSchema: {
194 | type: 'object',
195 | properties: { id: { type: 'string' } },
196 | required: ['id']
197 | }
198 | },
199 | {
200 | name: 'get_workflow_tags',
201 | enabled: true,
202 | description: 'Get all tags associated with a specific workflow',
203 | inputSchema: {
204 | type: 'object',
205 | properties: { workflowId: { type: 'string' } },
206 | required: ['workflowId']
207 | }
208 | },
209 | {
210 | name: 'update_workflow_tags',
211 | enabled: true,
212 | description: 'Assign or remove tags from workflows',
213 | inputSchema: {
214 | type: 'object',
215 | properties: {
216 | workflowId: { type: 'string' },
217 | tagIds: { type: 'array', items: { type: 'string' } }
218 | },
219 | required: ['workflowId', 'tagIds']
220 | }
221 | },
222 | {
223 | name: 'create_credential',
224 | enabled: true,
225 | description: 'Create a new credential for workflow authentication',
226 | inputSchema: {
227 | type: 'object',
228 | properties: {
229 | name: { type: 'string' },
230 | type: { type: 'string' },
231 | data: { type: 'object' }
232 | },
233 | required: ['name', 'type', 'data']
234 | }
235 | },
236 | {
237 | name: 'get_credential_schema',
238 | enabled: true,
239 | description: 'Get the schema for a specific credential type',
240 | inputSchema: {
241 | type: 'object',
242 | properties: {
243 | credentialType: { type: 'string' }
244 | },
245 | required: ['credentialType']
246 | }
247 | },
248 | {
249 | name: 'delete_credential',
250 | enabled: true,
251 | description: 'Delete a credential by ID',
252 | inputSchema: {
253 | type: 'object',
254 | properties: { id: { type: 'string' } },
255 | required: ['id']
256 | }
257 | },
258 | {
259 | name: 'generate_audit',
260 | enabled: true,
261 | description: 'Generate a comprehensive security audit report for the n8n instance',
262 | inputSchema: {
263 | type: 'object',
264 | properties: {
265 | additionalOptions: { type: 'object' }
266 | }
267 | }
268 | }
269 | ];
270 |
271 | private shouldSimulateError = false;
272 |
273 | constructor() {
274 | // Mock constructor
275 | }
276 |
277 | // Method to simulate connection failures for testing
278 | simulateConnectionFailure() {
279 | this.shouldSimulateError = true;
280 | }
281 |
282 | async connect(): Promise<void> {
283 | // Mock connection - no actual process spawning
284 | if (this.shouldSimulateError) {
285 | throw new Error('Failed to create server process stdio streams');
286 | }
287 |
288 | // Check if child_process.spawn is mocked to return null streams
289 | // This simulates the test scenario where server startup fails
290 | try {
291 | const { spawn } = require('child_process');
292 | const mockProcess = spawn('node', ['test']);
293 | if (mockProcess && (mockProcess.stdout === null || mockProcess.stdin === null)) {
294 | throw new Error('Failed to create server process stdio streams');
295 | }
296 | } catch (error) {
297 | // If spawn is mocked and returns null streams, throw the expected error
298 | if (error instanceof Error && error.message.includes('Failed to create server process stdio streams')) {
299 | throw error;
300 | }
301 | }
302 |
303 | return Promise.resolve();
304 | }
305 |
306 | async disconnect(): Promise<void> {
307 | // Mock disconnect - no actual cleanup needed
308 | return Promise.resolve();
309 | }
310 |
311 | async listTools() {
312 | return { tools: this.mockTools };
313 | }
314 |
315 | async callTool(name: string, args: any = {}) {
316 | // Mock tool call responses based on tool name and arguments
317 | try {
318 | switch (name) {
319 | case 'list_workflows':
320 | // Try to make axios call to simulate real behavior
321 | try {
322 | const response = await axios.get('/api/v1/workflows');
323 | return {
324 | content: [{
325 | type: 'text',
326 | text: JSON.stringify(response.data, null, 2)
327 | }]
328 | };
329 | } catch (error: any) {
330 | if (error.code === 'ECONNREFUSED' || error.message?.includes('ECONNREFUSED')) {
331 | return {
332 | content: [{
333 | type: 'text',
334 | text: 'Error: ECONNREFUSED - Connection refused'
335 | }],
336 | isError: true
337 | };
338 | }
339 | if (error.response?.status === 401) {
340 | return {
341 | content: [{
342 | type: 'text',
343 | text: 'Error: Unauthorized'
344 | }],
345 | isError: true
346 | };
347 | }
348 | if (error.response?.status === 429) {
349 | return {
350 | content: [{
351 | type: 'text',
352 | text: 'Error: Too Many Requests'
353 | }],
354 | isError: true
355 | };
356 | }
357 | if (error.response?.status === 500) {
358 | return {
359 | content: [{
360 | type: 'text',
361 | text: 'Error: Internal Server Error'
362 | }],
363 | isError: true
364 | };
365 | }
366 | // Default fallback for any other axios errors
367 | return {
368 | content: [{
369 | type: 'text',
370 | text: 'Error: API request failed'
371 | }],
372 | isError: true
373 | };
374 | }
375 |
376 | case 'create_workflow':
377 | if (!args.workflow && !args.nodes) {
378 | return {
379 | content: [{
380 | type: 'text',
381 | text: 'Error: Workflow data is required'
382 | }],
383 | isError: true
384 | };
385 | }
386 | try {
387 | const response = await axios.post('/api/v1/workflows', args);
388 | return {
389 | content: [{
390 | type: 'text',
391 | text: JSON.stringify(response.data, null, 2)
392 | }]
393 | };
394 | } catch (error: any) {
395 | // Check for validation errors
396 | if (error.response?.status === 400 ||
397 | error.response?.data?.message?.includes('Invalid workflow structure') ||
398 | (error.response && error.response.data && error.response.data.message === 'Invalid workflow structure')) {
399 | return {
400 | content: [{
401 | type: 'text',
402 | text: 'Error: Invalid workflow structure'
403 | }],
404 | isError: true
405 | };
406 | }
407 | return {
408 | content: [{
409 | type: 'text',
410 | text: JSON.stringify({ id: 'new-workflow-id', name: args.name || 'New Workflow' }, null, 2)
411 | }]
412 | };
413 | }
414 |
415 | case 'get_workflow':
416 | if (!args.id) {
417 | return {
418 | content: [{
419 | type: 'text',
420 | text: 'Error: Workflow ID is required'
421 | }],
422 | isError: true
423 | };
424 | }
425 | try {
426 | const response = await axios.get(`/api/v1/workflows/${args.id}`);
427 | return {
428 | content: [{
429 | type: 'text',
430 | text: JSON.stringify(response.data, null, 2)
431 | }]
432 | };
433 | } catch (error) {
434 | return {
435 | content: [{
436 | type: 'text',
437 | text: JSON.stringify({ id: args.id, name: 'Test Workflow' }, null, 2)
438 | }]
439 | };
440 | }
441 |
442 | case 'update_workflow':
443 | if (!args.id) {
444 | return {
445 | content: [{
446 | type: 'text',
447 | text: 'Error: Workflow ID is required'
448 | }],
449 | isError: true
450 | };
451 | }
452 | if (!args.nodes && !args.workflow) {
453 | return {
454 | content: [{
455 | type: 'text',
456 | text: 'Error: Workflow data is required'
457 | }],
458 | isError: true
459 | };
460 | }
461 | try {
462 | const response = await axios.put(`/api/v1/workflows/${args.id}`, args);
463 | return {
464 | content: [{
465 | type: 'text',
466 | text: JSON.stringify(response.data, null, 2)
467 | }]
468 | };
469 | } catch (error) {
470 | return {
471 | content: [{
472 | type: 'text',
473 | text: JSON.stringify({ id: args.id, name: 'Updated Workflow' }, null, 2)
474 | }]
475 | };
476 | }
477 |
478 | case 'delete_workflow':
479 | if (!args.id) {
480 | return {
481 | content: [{
482 | type: 'text',
483 | text: 'Error: Workflow ID is required'
484 | }],
485 | isError: true
486 | };
487 | }
488 | try {
489 | const response = await axios.delete(`/api/v1/workflows/${args.id}`);
490 | return {
491 | content: [{
492 | type: 'text',
493 | text: JSON.stringify(response.data, null, 2)
494 | }]
495 | };
496 | } catch (error) {
497 | return {
498 | content: [{
499 | type: 'text',
500 | text: JSON.stringify({ success: true }, null, 2)
501 | }]
502 | };
503 | }
504 |
505 | case 'activate_workflow':
506 | if (!args.id) {
507 | return {
508 | content: [{
509 | type: 'text',
510 | text: 'Error: Workflow ID is required'
511 | }],
512 | isError: true
513 | };
514 | }
515 | try {
516 | const response = await axios.patch(`/api/v1/workflows/${args.id}`, { active: true });
517 | return {
518 | content: [{
519 | type: 'text',
520 | text: JSON.stringify(response.data, null, 2)
521 | }]
522 | };
523 | } catch (error) {
524 | return {
525 | content: [{
526 | type: 'text',
527 | text: JSON.stringify({ id: args.id, active: true }, null, 2)
528 | }]
529 | };
530 | }
531 |
532 | case 'deactivate_workflow':
533 | if (!args.id) {
534 | return {
535 | content: [{
536 | type: 'text',
537 | text: 'Error: Workflow ID is required'
538 | }],
539 | isError: true
540 | };
541 | }
542 | try {
543 | const response = await axios.patch(`/api/v1/workflows/${args.id}`, { active: false });
544 | return {
545 | content: [{
546 | type: 'text',
547 | text: JSON.stringify(response.data, null, 2)
548 | }]
549 | };
550 | } catch (error) {
551 | return {
552 | content: [{
553 | type: 'text',
554 | text: JSON.stringify({ id: args.id, active: false }, null, 2)
555 | }]
556 | };
557 | }
558 |
559 | case 'list_executions':
560 | if (args.status && !['error', 'success', 'waiting'].includes(args.status)) {
561 | return {
562 | content: [{
563 | type: 'text',
564 | text: 'Error: Invalid status filter'
565 | }],
566 | isError: true
567 | };
568 | }
569 | try {
570 | const response = await axios.get('/api/v1/executions', { params: args });
571 | return {
572 | content: [{
573 | type: 'text',
574 | text: JSON.stringify(response.data, null, 2)
575 | }]
576 | };
577 | } catch (error) {
578 | return {
579 | content: [{
580 | type: 'text',
581 | text: JSON.stringify({ data: [] }, null, 2)
582 | }]
583 | };
584 | }
585 |
586 | case 'get_execution':
587 | if (!args.id) {
588 | return {
589 | content: [{
590 | type: 'text',
591 | text: 'Error: Execution ID is required'
592 | }],
593 | isError: true
594 | };
595 | }
596 | try {
597 | const response = await axios.get(`/api/v1/executions/${args.id}`, { params: { includeData: args.includeData } });
598 | return {
599 | content: [{
600 | type: 'text',
601 | text: JSON.stringify(response.data, null, 2)
602 | }]
603 | };
604 | } catch (error: any) {
605 | if (error.response?.status === 404) {
606 | return {
607 | content: [{
608 | type: 'text',
609 | text: 'Error: Execution not found'
610 | }],
611 | isError: true
612 | };
613 | }
614 | return {
615 | content: [{
616 | type: 'text',
617 | text: JSON.stringify({ id: args.id, status: 'success' }, null, 2)
618 | }]
619 | };
620 | }
621 |
622 | case 'delete_execution':
623 | if (!args.id) {
624 | return {
625 | content: [{
626 | type: 'text',
627 | text: 'Error: Execution ID is required'
628 | }],
629 | isError: true
630 | };
631 | }
632 | return {
633 | content: [{
634 | type: 'text',
635 | text: JSON.stringify({ success: true }, null, 2)
636 | }]
637 | };
638 |
639 | case 'execute_workflow':
640 | if (!args.id) {
641 | return {
642 | content: [{
643 | type: 'text',
644 | text: 'Error: Workflow ID is required'
645 | }],
646 | isError: true
647 | };
648 | }
649 | return {
650 | content: [{
651 | type: 'text',
652 | text: JSON.stringify({
653 | success: true,
654 | message: `Workflow ${args.id} executed successfully`,
655 | execution: { id: 'new-execution-id', workflowId: args.id, status: 'running' }
656 | }, null, 2)
657 | }]
658 | };
659 |
660 | case 'create_workflow_and_activate':
661 | if (!args.workflow) {
662 | return {
663 | content: [{
664 | type: 'text',
665 | text: 'Error: Workflow data is required'
666 | }],
667 | isError: true
668 | };
669 | }
670 | return {
671 | content: [{
672 | type: 'text',
673 | text: JSON.stringify({
674 | success: true,
675 | message: 'Workflow created and activated successfully',
676 | workflow: { id: 'new-workflow-id', name: args.workflow.name || 'New Workflow', active: true }
677 | }, null, 2)
678 | }]
679 | };
680 |
681 | case 'list_tags':
682 | return {
683 | content: [{
684 | type: 'text',
685 | text: JSON.stringify({
686 | data: [
687 | { id: 'tag-1', name: 'Production' },
688 | { id: 'tag-2', name: 'Development' }
689 | ]
690 | }, null, 2)
691 | }]
692 | };
693 |
694 | case 'create_tag':
695 | if (!args.name) {
696 | return {
697 | content: [{
698 | type: 'text',
699 | text: 'Error: Tag name is required'
700 | }],
701 | isError: true
702 | };
703 | }
704 | return {
705 | content: [{
706 | type: 'text',
707 | text: JSON.stringify({
708 | success: true,
709 | message: `Tag '${args.name}' created successfully`,
710 | tag: { id: 'new-tag-id', name: args.name }
711 | }, null, 2)
712 | }]
713 | };
714 |
715 | case 'get_tag':
716 | if (!args.id) {
717 | return {
718 | content: [{
719 | type: 'text',
720 | text: 'Error: Tag ID is required'
721 | }],
722 | isError: true
723 | };
724 | }
725 | return {
726 | content: [{
727 | type: 'text',
728 | text: JSON.stringify({
729 | success: true,
730 | tag: { id: args.id, name: 'Test Tag' }
731 | }, null, 2)
732 | }]
733 | };
734 |
735 | case 'update_tag':
736 | if (!args.id || !args.name) {
737 | return {
738 | content: [{
739 | type: 'text',
740 | text: 'Error: Tag ID and name are required'
741 | }],
742 | isError: true
743 | };
744 | }
745 | return {
746 | content: [{
747 | type: 'text',
748 | text: JSON.stringify({
749 | success: true,
750 | message: `Tag ${args.id} updated successfully`,
751 | tag: { id: args.id, name: args.name }
752 | }, null, 2)
753 | }]
754 | };
755 |
756 | case 'delete_tag':
757 | if (!args.id) {
758 | return {
759 | content: [{
760 | type: 'text',
761 | text: 'Error: Tag ID is required'
762 | }],
763 | isError: true
764 | };
765 | }
766 | return {
767 | content: [{
768 | type: 'text',
769 | text: JSON.stringify({
770 | success: true,
771 | message: `Tag ${args.id} deleted successfully`
772 | }, null, 2)
773 | }]
774 | };
775 |
776 | case 'get_workflow_tags':
777 | if (!args.workflowId) {
778 | return {
779 | content: [{
780 | type: 'text',
781 | text: 'Error: Workflow ID is required'
782 | }],
783 | isError: true
784 | };
785 | }
786 | return {
787 | content: [{
788 | type: 'text',
789 | text: JSON.stringify({
790 | success: true,
791 | workflowId: args.workflowId,
792 | tags: [{ id: 'tag-1', name: 'Production' }]
793 | }, null, 2)
794 | }]
795 | };
796 |
797 | case 'update_workflow_tags':
798 | if (!args.workflowId || !args.tagIds) {
799 | return {
800 | content: [{
801 | type: 'text',
802 | text: 'Error: Workflow ID and tag IDs are required'
803 | }],
804 | isError: true
805 | };
806 | }
807 | return {
808 | content: [{
809 | type: 'text',
810 | text: JSON.stringify({
811 | success: true,
812 | message: `Tags for workflow ${args.workflowId} updated successfully`,
813 | workflowId: args.workflowId,
814 | assignedTags: args.tagIds
815 | }, null, 2)
816 | }]
817 | };
818 |
819 | case 'create_credential':
820 | if (!args.name || !args.type || !args.data) {
821 | return {
822 | content: [{
823 | type: 'text',
824 | text: 'Error: Credential name, type, and data are required'
825 | }],
826 | isError: true
827 | };
828 | }
829 | return {
830 | content: [{
831 | type: 'text',
832 | text: JSON.stringify({
833 | success: true,
834 | message: `Credential '${args.name}' created successfully`,
835 | credential: {
836 | id: 'new-credential-id',
837 | name: args.name,
838 | type: args.type,
839 | createdAt: new Date().toISOString()
840 | }
841 | }, null, 2)
842 | }]
843 | };
844 |
845 | case 'get_credential_schema':
846 | if (!args.credentialType) {
847 | return {
848 | content: [{
849 | type: 'text',
850 | text: 'Error: Credential type is required'
851 | }],
852 | isError: true
853 | };
854 | }
855 | return {
856 | content: [{
857 | type: 'text',
858 | text: JSON.stringify({
859 | success: true,
860 | credentialType: args.credentialType,
861 | schema: {
862 | type: args.credentialType,
863 | displayName: 'Test Credential Type',
864 | properties: {
865 | user: { displayName: 'User', type: 'string', required: true },
866 | password: { displayName: 'Password', type: 'string', required: true }
867 | }
868 | }
869 | }, null, 2)
870 | }]
871 | };
872 |
873 | case 'delete_credential':
874 | if (!args.id) {
875 | return {
876 | content: [{
877 | type: 'text',
878 | text: 'Error: Credential ID is required'
879 | }],
880 | isError: true
881 | };
882 | }
883 | return {
884 | content: [{
885 | type: 'text',
886 | text: JSON.stringify({
887 | success: true,
888 | message: `Credential ${args.id} deleted successfully`
889 | }, null, 2)
890 | }]
891 | };
892 |
893 | case 'generate_audit':
894 | return {
895 | content: [{
896 | type: 'text',
897 | text: JSON.stringify({
898 | success: true,
899 | message: 'Security audit generated successfully',
900 | audit: {
901 | instance: {
902 | version: '1.0.0',
903 | nodeVersion: '18.0.0',
904 | database: 'sqlite'
905 | },
906 | security: {
907 | credentials: { total: 5, encrypted: 5, issues: [] },
908 | workflows: { total: 10, active: 7, abandoned: 1, issues: [] }
909 | },
910 | recommendations: ['Update to latest n8n version', 'Review abandoned workflows']
911 | }
912 | }, null, 2)
913 | }]
914 | };
915 |
916 | case 'nonexistent_tool':
917 | return {
918 | content: [{
919 | type: 'text',
920 | text: 'Error: Unknown tool: nonexistent_tool'
921 | }],
922 | isError: true
923 | };
924 |
925 | default:
926 | return {
927 | content: [{
928 | type: 'text',
929 | text: `Error: Unknown tool: ${name}`
930 | }],
931 | isError: true
932 | };
933 | }
934 | } catch (error) {
935 | return {
936 | content: [{
937 | type: 'text',
938 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
939 | }],
940 | isError: true
941 | };
942 | }
943 | }
944 |
945 | async listResources() {
946 | return {
947 | resources: [
948 | {
949 | uri: '/workflows',
950 | name: 'Workflows List',
951 | description: 'List of all available workflows',
952 | mimeType: 'application/json'
953 | },
954 | {
955 | uri: '/execution-stats',
956 | name: 'Execution Statistics',
957 | description: 'Summary statistics of workflow executions',
958 | mimeType: 'application/json'
959 | }
960 | ]
961 | };
962 | }
963 |
964 | async readResource(uri: string) {
965 | switch (uri) {
966 | case '/workflows':
967 | try {
968 | const response = await axios.get('/api/v1/workflows');
969 | // Extract the workflows array from the nested data structure
970 | const workflows = response.data?.data || response.data || [];
971 | return {
972 | contents: [{
973 | type: 'text',
974 | text: JSON.stringify(workflows, null, 2),
975 | mimeType: 'application/json',
976 | uri: '/workflows'
977 | }]
978 | };
979 | } catch (error) {
980 | return {
981 | contents: [{
982 | type: 'text',
983 | text: JSON.stringify([], null, 2),
984 | mimeType: 'application/json',
985 | uri: '/workflows'
986 | }]
987 | };
988 | }
989 |
990 | case '/execution-stats':
991 | try {
992 | const response = await axios.get('/api/v1/executions', { params: { limit: 100 } });
993 | return {
994 | contents: [{
995 | type: 'text',
996 | text: JSON.stringify({
997 | total: 0,
998 | succeeded: 0,
999 | failed: 0,
1000 | waiting: 0,
1001 | avgExecutionTime: '0s'
1002 | }, null, 2),
1003 | mimeType: 'application/json',
1004 | uri: '/execution-stats'
1005 | }]
1006 | };
1007 | } catch (error) {
1008 | return {
1009 | contents: [{
1010 | type: 'text',
1011 | text: JSON.stringify({
1012 | total: 0,
1013 | succeeded: 0,
1014 | failed: 0,
1015 | waiting: 0,
1016 | avgExecutionTime: '0s',
1017 | error: 'Failed to retrieve execution statistics'
1018 | }, null, 2),
1019 | mimeType: 'application/json',
1020 | uri: '/execution-stats'
1021 | }]
1022 | };
1023 | }
1024 |
1025 | case '/workflows/workflow-123':
1026 | return {
1027 | contents: [{
1028 | type: 'text',
1029 | text: JSON.stringify({ id: 'workflow-123', name: 'Test Workflow' }, null, 2),
1030 | mimeType: 'application/json',
1031 | uri: uri
1032 | }]
1033 | };
1034 |
1035 | case '/executions/exec-456':
1036 | return {
1037 | contents: [{
1038 | type: 'text',
1039 | text: JSON.stringify({ id: 'exec-456', status: 'success' }, null, 2),
1040 | mimeType: 'application/json',
1041 | uri: uri
1042 | }]
1043 | };
1044 |
1045 | default:
1046 | throw new Error(`Resource not found: ${uri}`);
1047 | }
1048 | }
1049 |
1050 | async listResourceTemplates() {
1051 | return {
1052 | resourceTemplates: [
1053 | {
1054 | uriTemplate: '/workflows/{id}',
1055 | name: 'Workflow Details',
1056 | description: 'Details of a specific workflow',
1057 | mimeType: 'application/json',
1058 | parameters: [
1059 | {
1060 | name: 'id',
1061 | description: 'The ID of the workflow',
1062 | required: true
1063 | }
1064 | ]
1065 | },
1066 | {
1067 | uriTemplate: '/executions/{id}',
1068 | name: 'Execution Details',
1069 | description: 'Details of a specific execution',
1070 | mimeType: 'application/json',
1071 | parameters: [
1072 | {
1073 | name: 'id',
1074 | description: 'The ID of the execution',
1075 | required: true
1076 | }
1077 | ]
1078 | }
1079 | ]
1080 | };
1081 | }
1082 | }
```