#
tokens: 40432/50000 14/86 files (page 2/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 3. Use http://codebase.md/leonardsellem/n8n-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .babelrc
├── .env.example
├── .eslintrc.json
├── .github
│   └── workflows
│       ├── claude-code-review.yml
│       ├── claude.yml
│       ├── docker-publish.yml
│       └── release-package.yml
├── .gitignore
├── AGENTS.md
├── babel.config.cjs
├── CLAUDE.md
├── Dockerfile
├── docs
│   ├── .gitkeep
│   ├── api
│   │   ├── dynamic-resources.md
│   │   ├── execution-tools.md
│   │   ├── index.md
│   │   ├── static-resources.md
│   │   └── workflow-tools.md
│   ├── development
│   │   ├── architecture.md
│   │   ├── extending.md
│   │   ├── index.md
│   │   └── testing.md
│   ├── examples
│   │   ├── advanced-scenarios.md
│   │   ├── basic-examples.md
│   │   ├── index.md
│   │   └── integration-examples.md
│   ├── images
│   │   ├── architecture.png.placeholder
│   │   └── n8n-api-key.png.placeholder
│   ├── index.md
│   └── setup
│       ├── configuration.md
│       ├── index.md
│       ├── installation.md
│       └── troubleshooting.md
├── jest.config.cjs
├── LICENSE
├── manual_verify_update.mjs
├── n8n-openapi.yml
├── package-lock.json
├── package.json
├── README.md
├── requirements.txt
├── run-tests.js
├── smithery.yaml
├── src
│   ├── .gitkeep
│   ├── api
│   │   ├── client.ts
│   │   └── n8n-client.ts
│   ├── config
│   │   ├── environment.ts
│   │   └── server.ts
│   ├── errors
│   │   ├── error-codes.ts
│   │   └── index.ts
│   ├── index.ts
│   ├── resources
│   │   ├── dynamic
│   │   │   ├── execution.ts
│   │   │   └── workflow.ts
│   │   ├── index.ts
│   │   └── static
│   │       ├── execution-stats.ts
│   │       └── workflows.ts
│   ├── tools
│   │   ├── execution
│   │   │   ├── base-handler.ts
│   │   │   ├── delete.ts
│   │   │   ├── get.ts
│   │   │   ├── handler.ts
│   │   │   ├── index.ts
│   │   │   ├── list.ts
│   │   │   └── run.ts
│   │   └── workflow
│   │       ├── activate.ts
│   │       ├── base-handler.ts
│   │       ├── create.ts
│   │       ├── deactivate.ts
│   │       ├── delete.ts
│   │       ├── get.ts
│   │       ├── handler.ts
│   │       ├── index.ts
│   │       ├── list.ts
│   │       └── update.ts
│   ├── types
│   │   └── index.ts
│   └── utils
│       ├── execution-formatter.ts
│       └── resource-formatter.ts
├── tests
│   ├── jest-globals.d.ts
│   ├── mocks
│   │   ├── axios-mock.ts
│   │   └── n8n-fixtures.ts
│   ├── README.md
│   ├── test-setup.ts
│   ├── tsconfig.json
│   └── unit
│       ├── api
│       │   ├── client.test.ts.bak
│       │   └── simple-client.test.ts
│       ├── config
│       │   ├── environment.test.ts
│       │   ├── environment.test.ts.bak
│       │   └── simple-environment.test.ts
│       ├── resources
│       │   └── dynamic
│       │       └── workflow.test.ts
│       ├── tools
│       │   └── workflow
│       │       ├── list.test.ts.bak
│       │       └── simple-tool.test.ts
│       └── utils
│           ├── execution-formatter.test.ts
│           └── resource-formatter.test.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/docs/setup/troubleshooting.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Troubleshooting Guide
  2 | 
  3 | This guide addresses common issues you might encounter when setting up and using the n8n MCP Server.
  4 | 
  5 | ## Connection Issues
  6 | 
  7 | ### Cannot Connect to n8n API
  8 | 
  9 | **Symptoms:** 
 10 | - Error messages mentioning "Connection refused" or "Cannot connect to n8n API"
 11 | - Timeout errors when trying to use MCP tools
 12 | 
 13 | **Possible Solutions:**
 14 | 1. **Verify n8n is running:**
 15 |    - Ensure your n8n instance is running and accessible
 16 |    - Try accessing the n8n URL in a browser
 17 | 
 18 | 2. **Check n8n API URL:**
 19 |    - Verify the `N8N_API_URL` in your `.env` file
 20 |    - Make sure it includes the full path (e.g., `http://localhost:5678/api/v1`)
 21 |    - Check for typos or incorrect protocol (http vs https)
 22 | 
 23 | 3. **Network Configuration:**
 24 |    - If running on a different machine, ensure there are no firewall rules blocking access
 25 |    - Check if n8n is configured to allow remote connections
 26 | 
 27 | 4. **HTTPS/SSL Issues:**
 28 |    - If using HTTPS, ensure certificates are valid
 29 |    - For self-signed certificates, you may need to set up additional configuration
 30 | 
 31 | ### Authentication Failures
 32 | 
 33 | **Symptoms:**
 34 | - "Authentication failed" or "Invalid API key" messages
 35 | - 401 or 403 HTTP status codes
 36 | 
 37 | **Possible Solutions:**
 38 | 1. **Verify API Key:**
 39 |    - Check that the `N8N_API_KEY` in your `.env` file matches the one in n8n
 40 |    - Create a new API key if necessary
 41 | 
 42 | 2. **Check API Key Permissions:**
 43 |    - Ensure the API key has appropriate scopes/permissions
 44 |    - Required scopes: `workflow:read workflow:write workflow:execute`
 45 | 
 46 | 3. **n8n API Settings:**
 47 |    - Verify that API access is enabled in n8n settings
 48 |    - Check if there are IP restrictions on API access
 49 | 
 50 | ## MCP Server Issues
 51 | 
 52 | ### Server Crashes or Exits Unexpectedly
 53 | 
 54 | **Symptoms:**
 55 | - The MCP server stops running unexpectedly
 56 | - Error messages in logs or console output
 57 | 
 58 | **Possible Solutions:**
 59 | 1. **Check Node.js Version:**
 60 |    - Ensure you're using Node.js 20 or later
 61 |    - Check with `node --version`
 62 | 
 63 | 2. **Check Environment Variables:**
 64 |    - Ensure all required environment variables are set
 65 |    - Verify the format of the environment variables
 66 | 
 67 | 3. **View Debug Logs:**
 68 |    - Set `DEBUG=true` in your `.env` file
 69 |    - Check the console output for detailed error messages
 70 | 
 71 | 4. **Memory Issues:**
 72 |    - If running on a system with limited memory, increase available memory
 73 |    - Check for memory leaks or high consumption patterns
 74 | 
 75 | ### AI Assistant Cannot Communicate with MCP Server
 76 | 
 77 | **Symptoms:**
 78 | - AI assistant reports it cannot connect to the MCP server
 79 | - Tools are not available in the assistant interface
 80 | 
 81 | **Possible Solutions:**
 82 | 1. **Verify Server Registration:**
 83 |    - Ensure the server is properly registered with your AI assistant platform
 84 |    - Check the configuration settings for the MCP server in your assistant
 85 | 
 86 | 2. **Server Running Check:**
 87 |    - Verify the MCP server is running
 88 |    - Check that it was started with the correct environment
 89 | 
 90 | 3. **Restart Components:**
 91 |    - Restart the MCP server
 92 |    - Refresh the AI assistant interface
 93 |    - If using a managed AI assistant, check platform status
 94 | 
 95 | ## Tool-Specific Issues
 96 | 
 97 | ### Workflow Operations Fail
 98 | 
 99 | **Symptoms:**
100 | - Cannot list, create, or update workflows
101 | - Error messages about missing permissions
102 | 
103 | **Possible Solutions:**
104 | 1. **API Key Scope:**
105 |    - Ensure your API key has `workflow:read` and `workflow:write` permissions
106 |    - Create a new key with appropriate permissions if needed
107 | 
108 | 2. **n8n Version:**
109 |    - Check if your n8n version supports all the API endpoints being used
110 |    - Update n8n to the latest version if possible
111 | 
112 | 3. **Workflow Complexity:**
113 |    - Complex workflows with custom nodes may not work correctly
114 |    - Try with simpler workflows to isolate the issue
115 | 
116 | ### Execution Operations Fail
117 | 
118 | **Symptoms:**
119 | - Cannot execute workflows or retrieve execution data
120 | - Execution starts but fails to complete
121 | 
122 | **Possible Solutions:**
123 | 1. **API Key Scope:**
124 |    - Ensure your API key has the `workflow:execute` permission
125 |    - Create a new key with appropriate permissions if needed
126 | 
127 | 2. **Workflow Status:**
128 |    - Check if the target workflow is active
129 |    - Verify the workflow executes correctly in the n8n interface
130 | 
131 | 3. **Workflow Inputs:**
132 |    - Ensure all required inputs for workflow execution are provided
133 |    - Check the format of input data
134 | 
135 | ## Getting More Help
136 | 
137 | If you're still experiencing issues after trying these troubleshooting steps:
138 | 
139 | 1. **Check GitHub Issues:**
140 |    - Look for similar issues in the [GitHub repository](https://github.com/yourusername/n8n-mcp-server/issues)
141 |    - Create a new issue with detailed information about your problem
142 | 
143 | 2. **Submit Logs:**
144 |    - Enable debug logging with `DEBUG=true`
145 |    - Include relevant logs when seeking help
146 | 
147 | 3. **Community Support:**
148 |    - Ask in the n8n community forums
149 |    - Check MCP-related discussion groups
150 | 
```

--------------------------------------------------------------------------------
/src/tools/execution/run.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Run Execution via Webhook Tool Handler
  3 |  * 
  4 |  * This module provides a tool for running n8n workflows via webhooks.
  5 |  */
  6 | 
  7 | import axios from 'axios';
  8 | import { z } from 'zod';
  9 | import { ToolCallResult } from '../../types/index.js';
 10 | import { BaseExecutionToolHandler } from './base-handler.js';
 11 | import { N8nApiError } from '../../errors/index.js';
 12 | import { getEnvConfig } from '../../config/environment.js';
 13 | import { URL } from 'url';
 14 | 
 15 | /**
 16 |  * Webhook execution input schema
 17 |  */
 18 | const runWebhookSchema = z.object({
 19 |   workflowName: z.string().describe('Name of the workflow to execute (e.g., "hello-world")'),
 20 |   data: z.record(z.any()).optional().describe('Input data to pass to the webhook'),
 21 |   headers: z.record(z.string()).optional().describe('Additional headers to send with the request')
 22 | });
 23 | 
 24 | /**
 25 |  * Handler for the run_webhook tool
 26 |  */
 27 | export class RunWebhookHandler extends BaseExecutionToolHandler {
 28 |   /**
 29 |    * Tool definition for execution via webhook
 30 |    */
 31 |   public static readonly inputSchema = runWebhookSchema;
 32 | 
 33 |   /**
 34 |    * Extract N8N base URL from N8N API URL by removing /api/v1
 35 |    * @returns N8N base URL
 36 |    */
 37 |   private getN8nBaseUrl(): string {
 38 |     const config = getEnvConfig();
 39 |     const apiUrl = new URL(config.n8nApiUrl);
 40 |     
 41 |     // Remove /api/v1 if it exists in the path
 42 |     let path = apiUrl.pathname;
 43 |     if (path.endsWith('/api/v1') || path.endsWith('/api/v1/')) {
 44 |       path = path.replace(/\/api\/v1\/?$/, '');
 45 |     }
 46 |     
 47 |     // Create a new URL with the base path
 48 |     apiUrl.pathname = path;
 49 |     return apiUrl.toString();
 50 |   }
 51 | 
 52 |   /**
 53 |    * Validate and execute webhook call
 54 |    * 
 55 |    * @param args Tool arguments
 56 |    * @returns Tool call result
 57 |    */
 58 |   async execute(args: Record<string, any>): Promise<ToolCallResult> {
 59 |     return this.handleExecution(async (args) => {
 60 |       // Parse and validate arguments
 61 |       const params = runWebhookSchema.parse(args);
 62 |       
 63 |       // Get environment config for auth credentials
 64 |       const config = getEnvConfig();
 65 | 
 66 |       // Check if webhook credentials are provided, as they are required for this tool
 67 |       if (!config.n8nWebhookUsername || !config.n8nWebhookPassword) {
 68 |         throw new N8nApiError(
 69 |           'Webhook username and password are required for run_webhook tool. ' +
 70 |           'Please set N8N_WEBHOOK_USERNAME and N8N_WEBHOOK_PASSWORD environment variables.',
 71 |           400 // Bad Request, as it's a client-side configuration issue for this specific tool
 72 |         );
 73 |       }
 74 |       
 75 |       try {
 76 |         // Get the webhook URL with the proper prefix
 77 |         const baseUrl = this.getN8nBaseUrl();
 78 |         const webhookPath = `webhook/${params.workflowName}`;
 79 |         const webhookUrl = new URL(webhookPath, baseUrl).toString();
 80 |         
 81 |         // Prepare request config with basic auth from environment
 82 |         const requestConfig: any = {
 83 |           headers: {
 84 |             'Content-Type': 'application/json',
 85 |             ...(params.headers || {})
 86 |           },
 87 |           auth: {
 88 |             username: config.n8nWebhookUsername,
 89 |             password: config.n8nWebhookPassword
 90 |           }
 91 |         };
 92 | 
 93 |         // Make the request to the webhook
 94 |         const response = await axios.post(
 95 |           webhookUrl,
 96 |           params.data || {},
 97 |           requestConfig
 98 |         );
 99 | 
100 |         // Return the webhook response
101 |         return this.formatSuccess({
102 |           status: response.status,
103 |           statusText: response.statusText,
104 |           data: response.data
105 |         }, 'Webhook executed successfully');
106 |       } catch (error) {
107 |         // Handle error from the webhook request
108 |         if (axios.isAxiosError(error)) {
109 |           let errorMessage = `Webhook execution failed: ${error.message}`;
110 |           
111 |           if (error.response) {
112 |             errorMessage = `Webhook execution failed with status ${error.response.status}: ${error.response.statusText}`;
113 |             if (error.response.data) {
114 |               return this.formatError(new N8nApiError(
115 |                 `${errorMessage}\n\n${JSON.stringify(error.response.data, null, 2)}`,
116 |                 error.response.status
117 |               ));
118 |             }
119 |           }
120 |           
121 |           return this.formatError(new N8nApiError(errorMessage, error.response?.status || 500));
122 |         }
123 |         
124 |         throw error; // Re-throw non-axios errors for the handler to catch
125 |       }
126 |     }, args);
127 |   }
128 | }
129 | 
130 | /**
131 |  * Get the tool definition for run_webhook
132 |  * 
133 |  * @returns Tool definition object
134 |  */
135 | export function getRunWebhookToolDefinition() {
136 |   return {
137 |     name: 'run_webhook',
138 |     description: 'Execute a workflow via webhook with optional input data',
139 |     inputSchema: {
140 |       type: 'object',
141 |       properties: {
142 |         workflowName: {
143 |           type: 'string',
144 |           description: 'Name of the workflow to execute (e.g., "hello-world")'
145 |         },
146 |         data: {
147 |           type: 'object',
148 |           description: 'Input data to pass to the webhook'
149 |         },
150 |         headers: {
151 |           type: 'object',
152 |           description: 'Additional headers to send with the request'
153 |         }
154 |       },
155 |       required: ['workflowName']
156 |     }
157 |   };
158 | }
```

--------------------------------------------------------------------------------
/src/config/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Server Configuration
  3 |  * 
  4 |  * This module configures the MCP server with tools and resources
  5 |  * for n8n workflow management.
  6 |  */
  7 | 
  8 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  9 | import {
 10 |   CallToolRequestSchema,
 11 |   ListToolsRequestSchema
 12 | } from '@modelcontextprotocol/sdk/types.js';
 13 | import { getEnvConfig } from './environment.js';
 14 | import { setupWorkflowTools } from '../tools/workflow/index.js';
 15 | import { setupExecutionTools } from '../tools/execution/index.js';
 16 | import { setupResourceHandlers } from '../resources/index.js';
 17 | import { createApiService } from '../api/n8n-client.js';
 18 | 
 19 | // Import types
 20 | import { ToolCallResult } from '../types/index.js';
 21 | 
 22 | /**
 23 |  * Configure and return an MCP server instance with all tools and resources
 24 |  * 
 25 |  * @returns Configured MCP server instance
 26 |  */
 27 | export async function configureServer(): Promise<Server> {
 28 |   // Get validated environment configuration
 29 |   const envConfig = getEnvConfig();
 30 |   
 31 |   // Create n8n API service
 32 |   const apiService = createApiService(envConfig);
 33 |   
 34 |   // Verify n8n API connectivity
 35 |   try {
 36 |     console.error('Verifying n8n API connectivity...');
 37 |     await apiService.checkConnectivity();
 38 |     console.error(`Successfully connected to n8n API at ${envConfig.n8nApiUrl}`);
 39 |   } catch (error) {
 40 |     console.error('ERROR: Failed to connect to n8n API:', error instanceof Error ? error.message : error);
 41 |     throw error;
 42 |   }
 43 | 
 44 |   // Create server instance
 45 |   const server = new Server(
 46 |     {
 47 |       name: 'n8n-mcp-server',
 48 |       version: '0.1.0',
 49 |     },
 50 |     {
 51 |       capabilities: {
 52 |         resources: {},
 53 |         tools: {},
 54 |       },
 55 |     }
 56 |   );
 57 | 
 58 |   // Set up all request handlers
 59 |   setupToolListRequestHandler(server);
 60 |   setupToolCallRequestHandler(server);
 61 |   setupResourceHandlers(server, envConfig);
 62 | 
 63 |   return server;
 64 | }
 65 | 
 66 | /**
 67 |  * Set up the tool list request handler for the server
 68 |  * 
 69 |  * @param server MCP server instance
 70 |  */
 71 | function setupToolListRequestHandler(server: Server): void {
 72 |   server.setRequestHandler(ListToolsRequestSchema, async () => {
 73 |     // Combine tools from workflow and execution modules
 74 |     const workflowTools = await setupWorkflowTools();
 75 |     const executionTools = await setupExecutionTools();
 76 | 
 77 |     return {
 78 |       tools: [...workflowTools, ...executionTools],
 79 |     };
 80 |   });
 81 | }
 82 | 
 83 | /**
 84 |  * Set up the tool call request handler for the server
 85 |  * 
 86 |  * @param server MCP server instance
 87 |  */
 88 | function setupToolCallRequestHandler(server: Server): void {
 89 |   server.setRequestHandler(CallToolRequestSchema, async (request) => {
 90 |     const toolName = request.params.name;
 91 |     const args = request.params.arguments || {};
 92 | 
 93 |     let result: ToolCallResult;
 94 | 
 95 |     try {
 96 |       // Handle "prompts/list" as a special case, returning an empty success response
 97 |       // This is to address client calls for a method not central to n8n-mcp-server's direct n8n integration.
 98 |       if (toolName === 'prompts/list') {
 99 |         return {
100 |           content: [{ type: 'text', text: 'Prompts list acknowledged.' }], // Or an empty array: content: []
101 |           isError: false,
102 |         };
103 |       }
104 | 
105 |       // Import handlers
106 |       const { 
107 |         ListWorkflowsHandler, 
108 |         GetWorkflowHandler,
109 |         CreateWorkflowHandler,
110 |         UpdateWorkflowHandler,
111 |         DeleteWorkflowHandler,
112 |         ActivateWorkflowHandler,
113 |         DeactivateWorkflowHandler
114 |       } = await import('../tools/workflow/index.js');
115 |       
116 |       const {
117 |         ListExecutionsHandler,
118 |         GetExecutionHandler,
119 |         DeleteExecutionHandler,
120 |         RunWebhookHandler
121 |       } = await import('../tools/execution/index.js');
122 |       
123 |       // Route the tool call to the appropriate handler
124 |       if (toolName === 'list_workflows') {
125 |         const handler = new ListWorkflowsHandler();
126 |         result = await handler.execute(args);
127 |       } else if (toolName === 'get_workflow') {
128 |         const handler = new GetWorkflowHandler();
129 |         result = await handler.execute(args);
130 |       } else if (toolName === 'create_workflow') {
131 |         const handler = new CreateWorkflowHandler();
132 |         result = await handler.execute(args);
133 |       } else if (toolName === 'update_workflow') {
134 |         const handler = new UpdateWorkflowHandler();
135 |         result = await handler.execute(args);
136 |       } else if (toolName === 'delete_workflow') {
137 |         const handler = new DeleteWorkflowHandler();
138 |         result = await handler.execute(args);
139 |       } else if (toolName === 'activate_workflow') {
140 |         const handler = new ActivateWorkflowHandler();
141 |         result = await handler.execute(args);
142 |       } else if (toolName === 'deactivate_workflow') {
143 |         const handler = new DeactivateWorkflowHandler();
144 |         result = await handler.execute(args);
145 |       } else if (toolName === 'list_executions') {
146 |         const handler = new ListExecutionsHandler();
147 |         result = await handler.execute(args);
148 |       } else if (toolName === 'get_execution') {
149 |         const handler = new GetExecutionHandler();
150 |         result = await handler.execute(args);
151 |       } else if (toolName === 'delete_execution') {
152 |         const handler = new DeleteExecutionHandler();
153 |         result = await handler.execute(args);
154 |       } else if (toolName === 'run_webhook') {
155 |         const handler = new RunWebhookHandler();
156 |         result = await handler.execute(args);
157 |       } else {
158 |         throw new Error(`Unknown tool: ${toolName}`);
159 |       }
160 | 
161 |       // Converting to MCP SDK expected format
162 |       return {
163 |         content: result.content,
164 |         isError: result.isError,
165 |       };
166 |     } catch (error) {
167 |       console.error(`Error handling tool call to ${toolName}:`, error);
168 |       return {
169 |         content: [
170 |           {
171 |             type: 'text',
172 |             text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
173 |           },
174 |         ],
175 |         isError: true,
176 |       };
177 |     }
178 |   });
179 | }
180 | 
```

--------------------------------------------------------------------------------
/manual_verify_update.mjs:
--------------------------------------------------------------------------------

```
  1 | import { N8nApiClient } from './build/api/client.js';
  2 | import { getEnvConfig, loadEnvironmentVariables } from './build/config/environment.js';
  3 | import { N8nApiError } from './build/errors/index.js';
  4 | 
  5 | async function main() {
  6 |   console.log('Attempting to load environment configuration...');
  7 |   loadEnvironmentVariables(); // Load .env file if present and needed
  8 |   const config = getEnvConfig(); // Get validated config object
  9 | 
 10 |   if (!config.n8nApiUrl || !config.n8nApiKey) {
 11 |     console.error('Error: N8N_API_URL and/or N8N_API_KEY are not set.');
 12 |     console.error('Please set these environment variables to run this verification.');
 13 |     process.exit(1);
 14 |   }
 15 | 
 16 |   console.log(`N8N API URL: ${config.n8nApiUrl}`);
 17 |   console.log('N8N API Key: EXISTS (not printing value)');
 18 |   config.debug = true; // Enable debug logging for the client
 19 | 
 20 |   const client = new N8nApiClient(config);
 21 | 
 22 |   try {
 23 |     console.log('Checking connectivity...');
 24 |     await client.checkConnectivity();
 25 |     console.log('Connectivity check successful.');
 26 | 
 27 |     console.log('Fetching workflows to find one to update...');
 28 |     let workflows = await client.getWorkflows();
 29 | 
 30 |     if (!workflows || workflows.length === 0) {
 31 |       console.log('No workflows found. Cannot test update.');
 32 |       // If no workflows, try to create one, then update it.
 33 |       console.log('Attempting to create a dummy workflow for testing update...');
 34 |       const newWorkflowData = {
 35 |         name: 'Test Workflow for Update Verification',
 36 |         nodes: [
 37 |           {
 38 |             parameters: {},
 39 |             id: '0743771a-291a-4763-ab03-570546a05f70',
 40 |             name: 'When Webhook Called',
 41 |             type: 'n8n-nodes-base.webhook',
 42 |             typeVersion: 1,
 43 |             position: [480, 300],
 44 |             webhookId: 'test-webhook-id'
 45 |           }
 46 |         ],
 47 |         connections: {},
 48 |         active: false,
 49 |         settings: { executionOrder: 'v1' },
 50 |         tags: [] // Intentionally include tags to see if they are filtered
 51 |       };
 52 |       let createdWorkflow;
 53 |       try {
 54 |         createdWorkflow = await client.createWorkflow(newWorkflowData);
 55 |         console.log(`Successfully created workflow with ID: ${createdWorkflow.id}, Name: ${createdWorkflow.name}`);
 56 |         workflows.push(createdWorkflow); // Add to list to proceed with update
 57 |       } catch (createError) {
 58 |         console.error('Failed to create a dummy workflow:', createError);
 59 |         if (createError instanceof N8nApiError) {
 60 |           console.error(`N8nApiError Details: Status ${createError.status}, Message: ${createError.message}`);
 61 |           if (createError.cause) console.error('Cause:', createError.cause);
 62 |         }
 63 |         process.exit(1);
 64 |       }
 65 |     }
 66 | 
 67 |     if (!workflows || workflows.length === 0) {
 68 |         console.log('Still no workflows found after attempting creation. Cannot test update.');
 69 |         process.exit(0); // Exit gracefully, can't test.
 70 |     }
 71 | 
 72 |     const workflowToUpdate = workflows[0];
 73 |     const originalName = workflowToUpdate.name;
 74 |     const newName = `Updated - ${originalName} - ${Date.now()}`;
 75 | 
 76 |     console.log(`Attempting to update workflow ID: ${workflowToUpdate.id}, Original Name: "${originalName}"`);
 77 |     console.log(`New Name will be: "${newName}"`);
 78 | 
 79 |     // Construct the update payload. Include fields that should be stripped.
 80 |     const updatePayload = {
 81 |       ...workflowToUpdate, // Spread the existing workflow
 82 |       name: newName,       // Change the name
 83 |       // Explicitly include fields that should be removed by updateWorkflow
 84 |       id: workflowToUpdate.id,
 85 |       createdAt: workflowToUpdate.createdAt || '2023-01-01T00:00:00Z',
 86 |       updatedAt: workflowToUpdate.updatedAt || '2023-01-01T00:00:00Z',
 87 |       tags: workflowToUpdate.tags || [{ id: 'testtag', name: 'Test Tag' }]
 88 |     };
 89 | 
 90 |     // Remove nodes and connections if they are very large to avoid log clutter
 91 |     // and potential issues if they are not meant to be sent in full for simple updates.
 92 |     // The PR is about filtering metadata, not changing core workflow structure via update.
 93 |     delete updatePayload.nodes;
 94 |     delete updatePayload.connections;
 95 | 
 96 | 
 97 |     console.log('Workflow object before sending to updateWorkflow (with fields that should be stripped):', JSON.stringify(updatePayload, null, 2));
 98 | 
 99 |     const updatedWorkflow = await client.updateWorkflow(workflowToUpdate.id, updatePayload);
100 | 
101 |     if (updatedWorkflow.name === newName) {
102 |       console.log(`SUCCESS: Workflow updated successfully! New name: "${updatedWorkflow.name}"`);
103 |       console.log('Received updated workflow object:', JSON.stringify(updatedWorkflow, null, 2));
104 | 
105 |       // Optional: try to revert the name
106 |       try {
107 |         console.log(`Attempting to revert name for workflow ID: ${workflowToUpdate.id} to "${originalName}"`);
108 |         await client.updateWorkflow(workflowToUpdate.id, { name: originalName });
109 |         console.log(`Successfully reverted name to "${originalName}"`);
110 |       } catch (revertError) {
111 |         console.error('Failed to revert workflow name, but the main update test passed:', revertError);
112 |       }
113 | 
114 |     } else {
115 |       console.error(`FAILURE: Workflow name was not updated as expected. Expected: "${newName}", Got: "${updatedWorkflow.name}"`);
116 |       process.exit(1);
117 |     }
118 | 
119 |   } catch (error) {
120 |     console.error('Manual verification script failed:');
121 |     if (error instanceof N8nApiError) {
122 |       console.error(`N8nApiError Details: Status ${error.status}, Message: ${error.message}`);
123 |       if (error.cause) console.error('Cause:', error.cause);
124 |     } else if (error.isAxiosError) {
125 |       console.error('Axios Error:', error.message);
126 |       if (error.response) {
127 |         console.error('Response Status:', error.response.status);
128 |         console.error('Response Data:', JSON.stringify(error.response.data, null, 2));
129 |       }
130 |     } else {
131 |       console.error(error);
132 |     }
133 |     process.exit(1);
134 |   }
135 | }
136 | 
137 | main();
138 | 
```

--------------------------------------------------------------------------------
/docs/development/architecture.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Architecture
  2 | 
  3 | This document describes the architectural design of the n8n MCP Server.
  4 | 
  5 | ## Overview
  6 | 
  7 | The n8n MCP Server follows a layered architecture pattern that separates concerns and promotes maintainability. The main architectural layers are:
  8 | 
  9 | 1. **Transport Layer**: Handles communication with AI assistants via the Model Context Protocol
 10 | 2. **API Client Layer**: Interacts with the n8n API
 11 | 3. **Tools Layer**: Implements executable operations as MCP tools
 12 | 4. **Resources Layer**: Provides data access through URI-based resources
 13 | 5. **Configuration Layer**: Manages environment variables and server settings
 14 | 6. **Error Handling Layer**: Provides consistent error management and reporting
 15 | 
 16 | ## System Components
 17 | 
 18 | ![Architecture Diagram](../images/architecture.png.placeholder)
 19 | 
 20 | ### Entry Point
 21 | 
 22 | The server entry point is defined in `src/index.ts`. This file:
 23 | 
 24 | 1. Initializes the configuration from environment variables
 25 | 2. Creates and configures the MCP server instance
 26 | 3. Registers tool and resource handlers
 27 | 4. Connects to the transport layer (typically stdio)
 28 | 
 29 | ### Configuration
 30 | 
 31 | The configuration layer (`src/config/`) handles:
 32 | 
 33 | - Loading environment variables
 34 | - Validating required configuration
 35 | - Providing typed access to configuration values
 36 | 
 37 | The main configuration component is the `Environment` class, which validates and manages environment variables like `N8N_API_URL` and `N8N_API_KEY`.
 38 | 
 39 | ### API Client
 40 | 
 41 | The API client layer (`src/api/`) provides a clean interface for interacting with the n8n API. It includes:
 42 | 
 43 | - `N8nClient`: The main client that encapsulates communication with n8n
 44 | - API-specific functionality divided by resource type (workflows, executions)
 45 | - Authentication handling using the n8n API key
 46 | 
 47 | The client uses Axios for HTTP requests and includes error handling specific to the n8n API responses.
 48 | 
 49 | ### MCP Tools
 50 | 
 51 | The tools layer (`src/tools/`) implements the executable operations exposed to AI assistants. Each tool follows a common pattern:
 52 | 
 53 | 1. A tool definition that specifies name, description, and input schema
 54 | 2. A handler function that processes input parameters and executes the operation
 55 | 3. Error handling for validation and execution errors
 56 | 
 57 | Tools are categorized by resource type:
 58 | 
 59 | - Workflow tools: Create, list, update, delete, activate, and deactivate workflows
 60 | - Execution tools: Run, list, and manage workflow executions
 61 | 
 62 | Each tool is designed to be independently testable and maintains a clean separation of concerns.
 63 | 
 64 | ### MCP Resources
 65 | 
 66 | The resources layer (`src/resources/`) provides data access through URI-based templates. Resources are divided into two categories:
 67 | 
 68 | 1. **Static Resources** (`src/resources/static/`): Fixed resources like workflow listings
 69 | 2. **Dynamic Resources** (`src/resources/dynamic/`): Parameterized resources like specific workflow details
 70 | 
 71 | Each resource implements:
 72 | - URI pattern matching
 73 | - Content retrieval
 74 | - Error handling
 75 | - Response formatting
 76 | 
 77 | ### Error Handling
 78 | 
 79 | The error handling layer (`src/errors/`) provides consistent error management across the server. It includes:
 80 | 
 81 | - Custom error types that map to MCP error codes
 82 | - Error translation functions to convert n8n API errors to MCP errors
 83 | - Common error patterns and handling strategies
 84 | 
 85 | ## Data Flow
 86 | 
 87 | A typical data flow through the system:
 88 | 
 89 | 1. AI assistant sends a request via stdin to the MCP server
 90 | 2. Server routes the request to the appropriate handler based on the request type
 91 | 3. Handler validates input and delegates to the appropriate tool or resource
 92 | 4. Tool/resource uses the n8n API client to interact with n8n
 93 | 5. Response is processed, formatted, and returned via stdout
 94 | 6. AI assistant receives and processes the response
 95 | 
 96 | ## Key Design Principles
 97 | 
 98 | ### 1. Separation of Concerns
 99 | 
100 | Each component has a single responsibility, making the codebase easier to understand, test, and extend.
101 | 
102 | ### 2. Type Safety
103 | 
104 | TypeScript interfaces and types are used extensively to ensure type safety and provide better developer experience.
105 | 
106 | ### 3. Error Handling
107 | 
108 | Comprehensive error handling ensures that errors are caught at the appropriate level and translated into meaningful messages for AI assistants.
109 | 
110 | ### 4. Testability
111 | 
112 | The architecture supports unit testing by keeping components loosely coupled and maintaining clear boundaries between layers.
113 | 
114 | ### 5. Extensibility
115 | 
116 | New tools and resources can be added without modifying existing code, following the open-closed principle.
117 | 
118 | ## Implementation Patterns
119 | 
120 | ### Factory Pattern
121 | 
122 | Used for creating client instances and tool handlers based on configuration.
123 | 
124 | ### Adapter Pattern
125 | 
126 | The n8n API client adapts the n8n API to the internal representation used by the server.
127 | 
128 | ### Strategy Pattern
129 | 
130 | Different resource handlers implement a common interface but provide different strategies for retrieving and formatting data.
131 | 
132 | ### Decorator Pattern
133 | 
134 | Used to add cross-cutting concerns like logging and error handling to base functionality.
135 | 
136 | ## Core Files and Their Purposes
137 | 
138 | | File | Purpose |
139 | |------|---------|
140 | | `src/index.ts` | Main entry point, initializes and configures the server |
141 | | `src/config/environment.ts` | Manages environment variables and configuration |
142 | | `src/api/n8n-client.ts` | Main client for interacting with the n8n API |
143 | | `src/tools/workflow/handler.ts` | Handles workflow-related tool requests |
144 | | `src/tools/execution/handler.ts` | Handles execution-related tool requests |
145 | | `src/resources/index.ts` | Registers and manages resource handlers |
146 | | `src/resources/dynamic/workflow.ts` | Provides access to specific workflow resources |
147 | | `src/resources/static/workflows.ts` | Provides access to workflow listings |
148 | | `src/errors/index.ts` | Defines and manages error types and handling |
149 | 
150 | ## Extension Points
151 | 
152 | To extend the server with new capabilities:
153 | 
154 | 1. **Adding a new tool**: Create a new handler in the appropriate category under `src/tools/` and register it in the main server setup
155 | 2. **Adding a new resource**: Create a new resource handler in `src/resources/` and register it in the resource manager
156 | 3. **Supporting new n8n API features**: Extend the API client in `src/api/` to support new API endpoints or features
157 | 
158 | For detailed instructions on extending the server, see [Extending the Server](./extending.md).
159 | 
```

--------------------------------------------------------------------------------
/docs/api/dynamic-resources.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Dynamic Resources
  2 | 
  3 | This page documents the dynamic resources available in the n8n MCP Server.
  4 | 
  5 | ## Overview
  6 | 
  7 | Dynamic resources are parameterized URIs that allow access to specific n8n data based on identifiers such as workflow IDs or execution IDs. These resources follow the URI template format defined in RFC 6570, with parameters enclosed in curly braces.
  8 | 
  9 | ## Available Resource Templates
 10 | 
 11 | ### n8n://workflow/{id}
 12 | 
 13 | Provides detailed information about a specific workflow.
 14 | 
 15 | **URI Template:** `n8n://workflow/{id}`
 16 | 
 17 | **Parameters:**
 18 | - `id` (required): The ID of the workflow to retrieve
 19 | 
 20 | **Description:** Returns comprehensive information about a specific workflow, including its nodes, connections, and settings.
 21 | 
 22 | **Example Usage:**
 23 | 
 24 | ```javascript
 25 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://workflow/1234abc');
 26 | ```
 27 | 
 28 | **Response:**
 29 | 
 30 | ```javascript
 31 | {
 32 |   "workflow": {
 33 |     "id": "1234abc",
 34 |     "name": "Email Processing Workflow",
 35 |     "active": true,
 36 |     "createdAt": "2025-03-01T12:00:00.000Z",
 37 |     "updatedAt": "2025-03-02T14:30:00.000Z",
 38 |     "nodes": [
 39 |       {
 40 |         "id": "node1",
 41 |         "name": "Start",
 42 |         "type": "n8n-nodes-base.start",
 43 |         "position": [100, 200],
 44 |         "parameters": {}
 45 |       },
 46 |       {
 47 |         "id": "node2",
 48 |         "name": "Email Trigger",
 49 |         "type": "n8n-nodes-base.emailTrigger",
 50 |         "position": [300, 200],
 51 |         "parameters": {
 52 |           "inbox": "support",
 53 |           "domain": "example.com"
 54 |         }
 55 |       }
 56 |     ],
 57 |     "connections": {
 58 |       "node1": {
 59 |         "main": [
 60 |           [
 61 |             {
 62 |               "node": "node2",
 63 |               "type": "main",
 64 |               "index": 0
 65 |             }
 66 |           ]
 67 |         ]
 68 |       }
 69 |     },
 70 |     "settings": {
 71 |       "saveExecutionProgress": true,
 72 |       "saveManualExecutions": true,
 73 |       "timezone": "America/New_York"
 74 |     }
 75 |   }
 76 | }
 77 | ```
 78 | 
 79 | ### n8n://executions/{workflowId}
 80 | 
 81 | Provides a list of executions for a specific workflow.
 82 | 
 83 | **URI Template:** `n8n://executions/{workflowId}`
 84 | 
 85 | **Parameters:**
 86 | - `workflowId` (required): The ID of the workflow whose executions to retrieve
 87 | 
 88 | **Description:** Returns a list of execution records for the specified workflow, sorted by most recent first.
 89 | 
 90 | **Example Usage:**
 91 | 
 92 | ```javascript
 93 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://executions/1234abc');
 94 | ```
 95 | 
 96 | **Response:**
 97 | 
 98 | ```javascript
 99 | {
100 |   "executions": [
101 |     {
102 |       "id": "exec789",
103 |       "workflowId": "1234abc",
104 |       "status": "success",
105 |       "startedAt": "2025-03-12T16:30:00.000Z",
106 |       "finishedAt": "2025-03-12T16:30:05.000Z",
107 |       "mode": "manual"
108 |     },
109 |     {
110 |       "id": "exec456",
111 |       "workflowId": "1234abc",
112 |       "status": "error",
113 |       "startedAt": "2025-03-11T14:20:00.000Z",
114 |       "finishedAt": "2025-03-11T14:20:10.000Z",
115 |       "mode": "manual"
116 |     }
117 |   ],
118 |   "count": 2,
119 |   "pagination": {
120 |     "hasMore": false
121 |   }
122 | }
123 | ```
124 | 
125 | ### n8n://execution/{id}
126 | 
127 | Provides detailed information about a specific execution.
128 | 
129 | **URI Template:** `n8n://execution/{id}`
130 | 
131 | **Parameters:**
132 | - `id` (required): The ID of the execution to retrieve
133 | 
134 | **Description:** Returns comprehensive information about a specific execution, including its status, inputs, outputs, and execution path.
135 | 
136 | **Example Usage:**
137 | 
138 | ```javascript
139 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://execution/exec789');
140 | ```
141 | 
142 | **Response:**
143 | 
144 | ```javascript
145 | {
146 |   "execution": {
147 |     "id": "exec789",
148 |     "workflowId": "1234abc",
149 |     "workflowName": "Email Processing Workflow",
150 |     "status": "success",
151 |     "startedAt": "2025-03-12T16:30:00.000Z",
152 |     "finishedAt": "2025-03-12T16:30:05.000Z",
153 |     "mode": "manual",
154 |     "data": {
155 |       "resultData": {
156 |         "runData": {
157 |           "node1": [
158 |             {
159 |               "startTime": "2025-03-12T16:30:00.000Z",
160 |               "endTime": "2025-03-12T16:30:01.000Z",
161 |               "executionStatus": "success",
162 |               "data": {
163 |                 "json": {
164 |                   "started": true
165 |                 }
166 |               }
167 |             }
168 |           ],
169 |           "node2": [
170 |             {
171 |               "startTime": "2025-03-12T16:30:01.000Z",
172 |               "endTime": "2025-03-12T16:30:05.000Z",
173 |               "executionStatus": "success",
174 |               "data": {
175 |                 "json": {
176 |                   "subject": "Test Email",
177 |                   "body": "This is a test",
178 |                   "from": "[email protected]"
179 |                 }
180 |               }
181 |             }
182 |           ]
183 |         }
184 |       },
185 |       "executionData": {
186 |         "nodeExecutionOrder": ["node1", "node2"],
187 |         "waitingNodes": [],
188 |         "waitingExecutionData": []
189 |       }
190 |     }
191 |   }
192 | }
193 | ```
194 | 
195 | ### n8n://workflow/{id}/active
196 | 
197 | Provides information about whether a specific workflow is active.
198 | 
199 | **URI Template:** `n8n://workflow/{id}/active`
200 | 
201 | **Parameters:**
202 | - `id` (required): The ID of the workflow to check
203 | 
204 | **Description:** Returns the active status of a specific workflow.
205 | 
206 | **Example Usage:**
207 | 
208 | ```javascript
209 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://workflow/1234abc/active');
210 | ```
211 | 
212 | **Response:**
213 | 
214 | ```javascript
215 | {
216 |   "workflowId": "1234abc",
217 |   "active": true
218 | }
219 | ```
220 | 
221 | ## Content Types
222 | 
223 | All dynamic resources return JSON content with the MIME type `application/json`.
224 | 
225 | ## Error Handling
226 | 
227 | Dynamic resources can return the following errors:
228 | 
229 | | HTTP Status | Description |
230 | |-------------|-------------|
231 | | 400 | Bad Request - Invalid parameter in URI |
232 | | 401 | Unauthorized - Invalid or missing API key |
233 | | 403 | Forbidden - API key does not have permission to access this resource |
234 | | 404 | Not Found - The requested resource does not exist |
235 | | 500 | Internal Server Error - An unexpected error occurred on the n8n server |
236 | 
237 | ## Parameter Format
238 | 
239 | When using dynamic resources, parameters must be properly formatted:
240 | 
241 | 1. **Workflow IDs**: Must be valid n8n workflow IDs (typically alphanumeric)
242 | 2. **Execution IDs**: Must be valid n8n execution IDs (typically alphanumeric)
243 | 
244 | ## Best Practices
245 | 
246 | - Validate resource URIs before accessing them
247 | - Handle possible 404 errors when accessing resources by ID
248 | - Cache resource data when appropriate to reduce API calls
249 | - Use specific resources (like `n8n://workflow/{id}/active`) for single properties when you don't need the entire resource
250 | - Check workflow status before performing operations that require an active workflow
251 | 
```

--------------------------------------------------------------------------------
/docs/api/workflow-tools.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Workflow Tools
  2 | 
  3 | This page documents the tools available for managing n8n workflows.
  4 | 
  5 | ## Overview
  6 | 
  7 | Workflow tools allow AI assistants to manage n8n workflows, including creating, retrieving, updating, deleting, activating, and deactivating workflows. These tools provide a natural language interface to n8n's workflow management capabilities.
  8 | 
  9 | ## Available Tools
 10 | 
 11 | ### workflow_list
 12 | 
 13 | Lists all workflows with optional filtering.
 14 | 
 15 | **Input Schema:**
 16 | 
 17 | ```json
 18 | {
 19 |   "type": "object",
 20 |   "properties": {
 21 |     "active": {
 22 |       "type": "boolean",
 23 |       "description": "Filter workflows by active status"
 24 |     }
 25 |   },
 26 |   "required": []
 27 | }
 28 | ```
 29 | 
 30 | **Example Usage:**
 31 | 
 32 | ```javascript
 33 | // List all workflows
 34 | const result = await useWorkflowList({});
 35 | 
 36 | // List only active workflows
 37 | const activeWorkflows = await useWorkflowList({ active: true });
 38 | 
 39 | // List only inactive workflows
 40 | const inactiveWorkflows = await useWorkflowList({ active: false });
 41 | ```
 42 | 
 43 | **Response:**
 44 | 
 45 | ```javascript
 46 | [
 47 |   {
 48 |     "id": "1234abc",
 49 |     "name": "Test Workflow 1",
 50 |     "active": true,
 51 |     "createdAt": "2025-03-01T12:00:00.000Z",
 52 |     "updatedAt": "2025-03-02T14:30:00.000Z"
 53 |   },
 54 |   {
 55 |     "id": "5678def",
 56 |     "name": "Test Workflow 2",
 57 |     "active": false,
 58 |     "createdAt": "2025-03-01T12:00:00.000Z",
 59 |     "updatedAt": "2025-03-12T10:15:00.000Z"
 60 |   }
 61 | ]
 62 | ```
 63 | 
 64 | ### workflow_get
 65 | 
 66 | Retrieves a specific workflow by ID.
 67 | 
 68 | **Input Schema:**
 69 | 
 70 | ```json
 71 | {
 72 |   "type": "object",
 73 |   "properties": {
 74 |     "id": {
 75 |       "type": "string",
 76 |       "description": "The ID of the workflow to retrieve"
 77 |     }
 78 |   },
 79 |   "required": ["id"]
 80 | }
 81 | ```
 82 | 
 83 | **Example Usage:**
 84 | 
 85 | ```javascript
 86 | const workflow = await useWorkflowGet({ id: "1234abc" });
 87 | ```
 88 | 
 89 | **Response:**
 90 | 
 91 | ```javascript
 92 | {
 93 |   "id": "1234abc",
 94 |   "name": "Test Workflow 1",
 95 |   "active": true,
 96 |   "createdAt": "2025-03-01T12:00:00.000Z",
 97 |   "updatedAt": "2025-03-02T14:30:00.000Z",
 98 |   "nodes": [
 99 |     // Detailed node configuration
100 |   ],
101 |   "connections": {
102 |     // Connection configuration
103 |   },
104 |   "settings": {
105 |     // Workflow settings
106 |   }
107 | }
108 | ```
109 | 
110 | ### workflow_create
111 | 
112 | Creates a new workflow.
113 | 
114 | **Input Schema:**
115 | 
116 | ```json
117 | {
118 |   "type": "object",
119 |   "properties": {
120 |     "name": {
121 |       "type": "string",
122 |       "description": "Name of the workflow"
123 |     },
124 |     "nodes": {
125 |       "type": "array",
126 |       "description": "Array of node configurations"
127 |     },
128 |     "connections": {
129 |       "type": "object",
130 |       "description": "Connection configuration"
131 |     },
132 |     "active": {
133 |       "type": "boolean",
134 |       "description": "Whether the workflow should be active"
135 |     },
136 |     "settings": {
137 |       "type": "object",
138 |       "description": "Workflow settings"
139 |     }
140 |   },
141 |   "required": ["name"]
142 | }
143 | ```
144 | 
145 | **Example Usage:**
146 | 
147 | ```javascript
148 | const newWorkflow = await useWorkflowCreate({
149 |   name: "New Workflow",
150 |   active: true,
151 |   nodes: [
152 |     {
153 |       "name": "Start",
154 |       "type": "n8n-nodes-base.start",
155 |       "position": [100, 200],
156 |       "parameters": {}
157 |     }
158 |   ],
159 |   connections: {}
160 | });
161 | ```
162 | 
163 | **Response:**
164 | 
165 | ```javascript
166 | {
167 |   "id": "new123",
168 |   "name": "New Workflow",
169 |   "active": true,
170 |   "createdAt": "2025-03-12T15:30:00.000Z",
171 |   "updatedAt": "2025-03-12T15:30:00.000Z",
172 |   "nodes": [
173 |     {
174 |       "name": "Start",
175 |       "type": "n8n-nodes-base.start",
176 |       "position": [100, 200],
177 |       "parameters": {}
178 |     }
179 |   ],
180 |   "connections": {}
181 | }
182 | ```
183 | 
184 | ### workflow_update
185 | 
186 | Updates an existing workflow.
187 | 
188 | **Input Schema:**
189 | 
190 | ```json
191 | {
192 |   "type": "object",
193 |   "properties": {
194 |     "id": {
195 |       "type": "string",
196 |       "description": "ID of the workflow to update"
197 |     },
198 |     "name": {
199 |       "type": "string",
200 |       "description": "New name for the workflow"
201 |     },
202 |     "nodes": {
203 |       "type": "array",
204 |       "description": "Updated array of node configurations"
205 |     },
206 |     "connections": {
207 |       "type": "object",
208 |       "description": "Updated connection configuration"
209 |     },
210 |     "active": {
211 |       "type": "boolean",
212 |       "description": "Whether the workflow should be active"
213 |     },
214 |     "settings": {
215 |       "type": "object",
216 |       "description": "Updated workflow settings"
217 |     }
218 |   },
219 |   "required": ["id"]
220 | }
221 | ```
222 | 
223 | **Example Usage:**
224 | 
225 | ```javascript
226 | const updatedWorkflow = await useWorkflowUpdate({
227 |   id: "1234abc",
228 |   name: "Updated Workflow Name",
229 |   active: false
230 | });
231 | ```
232 | 
233 | **Response:**
234 | 
235 | ```javascript
236 | {
237 |   "id": "1234abc",
238 |   "name": "Updated Workflow Name",
239 |   "active": false,
240 |   "createdAt": "2025-03-01T12:00:00.000Z",
241 |   "updatedAt": "2025-03-12T15:45:00.000Z",
242 |   "nodes": [
243 |     // Existing node configuration
244 |   ],
245 |   "connections": {
246 |     // Existing connection configuration
247 |   }
248 | }
249 | ```
250 | 
251 | ### workflow_delete
252 | 
253 | Deletes a workflow.
254 | 
255 | **Input Schema:**
256 | 
257 | ```json
258 | {
259 |   "type": "object",
260 |   "properties": {
261 |     "id": {
262 |       "type": "string",
263 |       "description": "ID of the workflow to delete"
264 |     }
265 |   },
266 |   "required": ["id"]
267 | }
268 | ```
269 | 
270 | **Example Usage:**
271 | 
272 | ```javascript
273 | await useWorkflowDelete({ id: "1234abc" });
274 | ```
275 | 
276 | **Response:**
277 | 
278 | ```javascript
279 | {
280 |   "success": true
281 | }
282 | ```
283 | 
284 | ### workflow_activate
285 | 
286 | Activates a workflow.
287 | 
288 | **Input Schema:**
289 | 
290 | ```json
291 | {
292 |   "type": "object",
293 |   "properties": {
294 |     "id": {
295 |       "type": "string",
296 |       "description": "ID of the workflow to activate"
297 |     }
298 |   },
299 |   "required": ["id"]
300 | }
301 | ```
302 | 
303 | **Example Usage:**
304 | 
305 | ```javascript
306 | const activatedWorkflow = await useWorkflowActivate({ id: "1234abc" });
307 | ```
308 | 
309 | **Response:**
310 | 
311 | ```javascript
312 | {
313 |   "id": "1234abc",
314 |   "name": "Test Workflow 1",
315 |   "active": true,
316 |   "createdAt": "2025-03-01T12:00:00.000Z",
317 |   "updatedAt": "2025-03-12T16:00:00.000Z"
318 | }
319 | ```
320 | 
321 | ### workflow_deactivate
322 | 
323 | Deactivates a workflow.
324 | 
325 | **Input Schema:**
326 | 
327 | ```json
328 | {
329 |   "type": "object",
330 |   "properties": {
331 |     "id": {
332 |       "type": "string",
333 |       "description": "ID of the workflow to deactivate"
334 |     }
335 |   },
336 |   "required": ["id"]
337 | }
338 | ```
339 | 
340 | **Example Usage:**
341 | 
342 | ```javascript
343 | const deactivatedWorkflow = await useWorkflowDeactivate({ id: "1234abc" });
344 | ```
345 | 
346 | **Response:**
347 | 
348 | ```javascript
349 | {
350 |   "id": "1234abc",
351 |   "name": "Test Workflow 1",
352 |   "active": false,
353 |   "createdAt": "2025-03-01T12:00:00.000Z",
354 |   "updatedAt": "2025-03-12T16:15:00.000Z"
355 | }
356 | ```
357 | 
358 | ## Error Handling
359 | 
360 | All workflow tools can return the following errors:
361 | 
362 | | Error | Description |
363 | |-------|-------------|
364 | | Authentication Error | The provided API key is invalid or missing |
365 | | Not Found Error | The requested workflow does not exist |
366 | | Validation Error | The input parameters are invalid or incomplete |
367 | | Permission Error | The API key does not have permission to perform the operation |
368 | | Server Error | An unexpected error occurred on the n8n server |
369 | 
370 | ## Best Practices
371 | 
372 | - Use `workflow_list` to discover available workflows before performing operations
373 | - Validate workflow IDs before attempting to update or delete workflows
374 | - Check workflow status (active/inactive) before attempting activation/deactivation
375 | - Include only the necessary fields when updating workflows to avoid unintended changes
376 | 
```

--------------------------------------------------------------------------------
/docs/examples/basic-examples.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Basic Examples
  2 | 
  3 | This page provides basic examples of using the n8n MCP Server with AI assistants.
  4 | 
  5 | ## Listing Workflows
  6 | 
  7 | ### User Prompt
  8 | 
  9 | "Show me all the workflows in my n8n instance."
 10 | 
 11 | ### Assistant Actions
 12 | 
 13 | ```javascript
 14 | // The assistant uses the workflow_list tool
 15 | const result = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
 16 | 
 17 | // The assistant formats and presents the results
 18 | if (result.length === 0) {
 19 |   return "You don't have any workflows in your n8n instance yet.";
 20 | } else {
 21 |   let response = "Here are your workflows:\n\n";
 22 |   result.forEach(workflow => {
 23 |     response += `- ${workflow.name} (ID: ${workflow.id}) - ${workflow.active ? 'Active' : 'Inactive'}\n`;
 24 |   });
 25 |   return response;
 26 | }
 27 | ```
 28 | 
 29 | ## Getting Workflow Details
 30 | 
 31 | ### User Prompt
 32 | 
 33 | "Tell me about my 'Email Processing' workflow."
 34 | 
 35 | ### Assistant Actions
 36 | 
 37 | ```javascript
 38 | // First list all workflows to find the one with matching name
 39 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
 40 | 
 41 | // Find the workflow with a name containing "Email Processing"
 42 | const emailWorkflow = workflows.find(workflow => 
 43 |   workflow.name.toLowerCase().includes('email processing')
 44 | );
 45 | 
 46 | if (!emailWorkflow) {
 47 |   return "I couldn't find a workflow named 'Email Processing'. Would you like to see a list of all your workflows?";
 48 | }
 49 | 
 50 | // Get detailed information about the workflow
 51 | const details = await useMcpTool('n8n-mcp-server', 'workflow_get', {
 52 |   id: emailWorkflow.id
 53 | });
 54 | 
 55 | // Format and present the workflow details
 56 | return `
 57 | # Email Processing Workflow
 58 | 
 59 | **Status:** ${details.active ? 'Active' : 'Inactive'}
 60 | **Created:** ${new Date(details.createdAt).toLocaleString()}
 61 | **Last Updated:** ${new Date(details.updatedAt).toLocaleString()}
 62 | 
 63 | ## Nodes (${details.nodes.length})
 64 | ${details.nodes.map(node => `- ${node.name} (${node.type})`).join('\n')}
 65 | 
 66 | ## Settings
 67 | ${Object.entries(details.settings || {}).map(([key, value]) => `- ${key}: ${value}`).join('\n')}
 68 | `;
 69 | ```
 70 | 
 71 | ## Executing a Workflow
 72 | 
 73 | ### User Prompt
 74 | 
 75 | "Run my 'Data Sync' workflow."
 76 | 
 77 | ### Assistant Actions
 78 | 
 79 | ```javascript
 80 | // First list all workflows to find the one with matching name
 81 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
 82 | 
 83 | // Find the workflow with a name containing "Data Sync"
 84 | const syncWorkflow = workflows.find(workflow => 
 85 |   workflow.name.toLowerCase().includes('data sync')
 86 | );
 87 | 
 88 | if (!syncWorkflow) {
 89 |   return "I couldn't find a workflow named 'Data Sync'. Would you like to see a list of all your workflows?";
 90 | }
 91 | 
 92 | // Check if the workflow is active
 93 | if (!syncWorkflow.active) {
 94 |   return "The 'Data Sync' workflow is currently inactive. Would you like me to activate it before running it?";
 95 | }
 96 | 
 97 | // Execute the workflow
 98 | const execution = await useMcpTool('n8n-mcp-server', 'execution_run', {
 99 |   workflowId: syncWorkflow.id
100 | });
101 | 
102 | return `
103 | I've started the 'Data Sync' workflow. 
104 | 
105 | **Execution ID:** ${execution.executionId}
106 | **Status:** ${execution.status}
107 | **Started At:** ${new Date(execution.startedAt).toLocaleString()}
108 | 
109 | You can check the execution status later by asking me about it.
110 | `;
111 | ```
112 | 
113 | ## Checking Execution Status
114 | 
115 | ### User Prompt
116 | 
117 | "Check the status of my last workflow execution."
118 | 
119 | ### Assistant Actions
120 | 
121 | ```javascript
122 | // Get recent executions (this example assumes we've stored the last execution ID in context)
123 | // In a real scenario, you might need to list recent executions first
124 | const lastExecutionId = "exec123"; // In practice, this would come from context or a previous response
125 | 
126 | // Get execution details
127 | const execution = await useMcpTool('n8n-mcp-server', 'execution_get', {
128 |   executionId: lastExecutionId
129 | });
130 | 
131 | // Format and present the execution status
132 | let response = `
133 | # Execution Status for ${execution.workflowName}
134 | 
135 | **Status:** ${execution.status}
136 | **Started:** ${new Date(execution.startedAt).toLocaleString()}
137 | `;
138 | 
139 | if (execution.finishedAt) {
140 |   response += `**Finished:** ${new Date(execution.finishedAt).toLocaleString()}\n`;
141 |   response += `**Duration:** ${Math.round((new Date(execution.finishedAt) - new Date(execution.startedAt)) / 1000)} seconds\n`;
142 | }
143 | 
144 | response += "\n## Execution Path\n";
145 | execution.data.executionData.nodeExecutionOrder.forEach(nodeName => {
146 |   response += `- ${nodeName}\n`;
147 | });
148 | 
149 | return response;
150 | ```
151 | 
152 | ## Activating/Deactivating a Workflow
153 | 
154 | ### User Prompt
155 | 
156 | "Deactivate my 'Daily Report' workflow."
157 | 
158 | ### Assistant Actions
159 | 
160 | ```javascript
161 | // First list all workflows to find the one with matching name
162 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
163 | 
164 | // Find the workflow with a name containing "Daily Report"
165 | const reportWorkflow = workflows.find(workflow => 
166 |   workflow.name.toLowerCase().includes('daily report')
167 | );
168 | 
169 | if (!reportWorkflow) {
170 |   return "I couldn't find a workflow named 'Daily Report'. Would you like to see a list of all your workflows?";
171 | }
172 | 
173 | // Check if the workflow is already inactive
174 | if (!reportWorkflow.active) {
175 |   return "The 'Daily Report' workflow is already inactive.";
176 | }
177 | 
178 | // Deactivate the workflow
179 | const result = await useMcpTool('n8n-mcp-server', 'workflow_deactivate', {
180 |   id: reportWorkflow.id
181 | });
182 | 
183 | return `
184 | I've deactivated the 'Daily Report' workflow. 
185 | 
186 | It was last updated on ${new Date(result.updatedAt).toLocaleString()}.
187 | 
188 | You can reactivate it at any time by asking me to activate it.
189 | `;
190 | ```
191 | 
192 | ## Creating a Simple Workflow
193 | 
194 | ### User Prompt
195 | 
196 | "Create a new workflow that sends me a daily email reminder."
197 | 
198 | ### Assistant Actions
199 | 
200 | ```javascript
201 | // Create a new workflow
202 | const newWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
203 |   name: "Daily Email Reminder",
204 |   active: false, // Start inactive until it's configured properly
205 |   nodes: [
206 |     {
207 |       name: "Schedule Trigger",
208 |       type: "n8n-nodes-base.scheduleTrigger",
209 |       position: [100, 300],
210 |       parameters: {
211 |         rule: {
212 |           interval: [
213 |             {
214 |               field: "hours",
215 |               minuteInterval: 24
216 |             }
217 |           ]
218 |         }
219 |       }
220 |     },
221 |     {
222 |       name: "Send Email",
223 |       type: "n8n-nodes-base.emailSend",
224 |       position: [300, 300],
225 |       parameters: {
226 |         to: "{{$json.email}}",
227 |         subject: "Daily Reminder",
228 |         text: "This is your daily reminder!"
229 |       }
230 |     }
231 |   ],
232 |   connections: {
233 |     "Schedule Trigger": {
234 |       main: [
235 |         [
236 |           {
237 |             node: "Send Email",
238 |             type: "main",
239 |             index: 0
240 |           }
241 |         ]
242 |       ]
243 |     }
244 |   }
245 | });
246 | 
247 | return `
248 | I've created a new workflow called "Daily Email Reminder".
249 | 
250 | This workflow is currently **inactive** and needs configuration:
251 | 1. You'll need to enter your email address in the "Send Email" node
252 | 2. You might want to customize the schedule and email content
253 | 
254 | You can view and edit this workflow in the n8n interface (ID: ${newWorkflow.id}), and then ask me to activate it when you're ready.
255 | `;
256 | ```
257 | 
258 | These examples demonstrate the basic operations you can perform with the n8n MCP Server. For more complex scenarios, see the [Advanced Scenarios](./advanced-scenarios.md) page.
259 | 
```

--------------------------------------------------------------------------------
/docs/api/execution-tools.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Execution Tools
  2 | 
  3 | This page documents the tools available for managing n8n workflow executions.
  4 | 
  5 | ## Overview
  6 | 
  7 | Execution tools allow AI assistants to execute n8n workflows and manage execution records. These tools provide a natural language interface to n8n's execution capabilities, allowing workflows to be run, monitored, and their results accessed.
  8 | 
  9 | ## Available Tools
 10 | 
 11 | ### run_webhook
 12 | 
 13 | Executes a workflow via webhook with optional input data.
 14 | 
 15 | **Input Schema:**
 16 | 
 17 | ```json
 18 | {
 19 |   "type": "object",
 20 |   "properties": {
 21 |     "workflowName": {
 22 |       "type": "string",
 23 |       "description": "Name of the workflow to execute (e.g., \"hello-world\")"
 24 |     },
 25 |     "data": {
 26 |       "type": "object",
 27 |       "description": "Input data to pass to the webhook"
 28 |     },
 29 |     "headers": {
 30 |       "type": "object",
 31 |       "description": "Additional headers to send with the request"
 32 |     }
 33 |   },
 34 |   "required": ["workflowName"]
 35 | }
 36 | ```
 37 | 
 38 | **Example Usage:**
 39 | 
 40 | ```javascript
 41 | // Execute webhook with data
 42 | const webhookResult = await useRunWebhook({
 43 |   workflowName: "hello-world",
 44 |   data: {
 45 |     prompt: "Good morning!"
 46 |   }
 47 | });
 48 | 
 49 | // Execute webhook with additional headers
 50 | const webhookWithHeaders = await useRunWebhook({
 51 |   workflowName: "hello-world",
 52 |   data: {
 53 |     prompt: "Hello with custom header"
 54 |   },
 55 |   headers: {
 56 |     "X-Custom-Header": "CustomValue"
 57 |   }
 58 | });
 59 | ```
 60 | 
 61 | **Response:**
 62 | 
 63 | ```javascript
 64 | {
 65 |   "status": 200,
 66 |   "statusText": "OK",
 67 |   "data": {
 68 |     // Response data from the webhook
 69 |   }
 70 | }
 71 | ```
 72 | 
 73 | **Note:** 
 74 | - Authentication for the webhook is automatically handled using the environment variables `N8N_WEBHOOK_USERNAME` and `N8N_WEBHOOK_PASSWORD`.
 75 | - The tool automatically prefixes the `workflowName` with `webhook/` to create the full webhook path. For example, if you provide `hello-world` as the workflow name, the tool will call `{baseUrl}/webhook/hello-world`.
 76 | 
 77 | 
 78 | ### execution_run
 79 | 
 80 | Executes a workflow with optional input data.
 81 | 
 82 | **Input Schema:**
 83 | 
 84 | ```json
 85 | {
 86 |   "type": "object",
 87 |   "properties": {
 88 |     "workflowId": {
 89 |       "type": "string",
 90 |       "description": "ID of the workflow to execute"
 91 |     },
 92 |     "data": {
 93 |       "type": "object",
 94 |       "description": "Input data to pass to the workflow"
 95 |     },
 96 |     "waitForCompletion": {
 97 |       "type": "boolean",
 98 |       "description": "Whether to wait for the workflow to complete before returning",
 99 |       "default": false
100 |     }
101 |   },
102 |   "required": ["workflowId"]
103 | }
104 | ```
105 | 
106 | **Example Usage:**
107 | 
108 | ```javascript
109 | // Execute without waiting
110 | const execution = await useExecutionRun({
111 |   workflowId: "1234abc"
112 | });
113 | 
114 | // Execute with input data
115 | const executionWithData = await useExecutionRun({
116 |   workflowId: "1234abc",
117 |   data: {
118 |     firstName: "John",
119 |     lastName: "Doe",
120 |     email: "[email protected]"
121 |   }
122 | });
123 | 
124 | // Execute and wait for completion
125 | const completedExecution = await useExecutionRun({
126 |   workflowId: "1234abc",
127 |   waitForCompletion: true
128 | });
129 | ```
130 | 
131 | **Response (when waitForCompletion: false):**
132 | 
133 | ```javascript
134 | {
135 |   "executionId": "exec789",
136 |   "status": "running",
137 |   "startedAt": "2025-03-12T16:30:00.000Z"
138 | }
139 | ```
140 | 
141 | **Response (when waitForCompletion: true):**
142 | 
143 | ```javascript
144 | {
145 |   "executionId": "exec789",
146 |   "status": "success", // Or "error" if execution failed
147 |   "startedAt": "2025-03-12T16:30:00.000Z",
148 |   "finishedAt": "2025-03-12T16:30:05.000Z",
149 |   "data": {
150 |     // Output data from the workflow execution
151 |   }
152 | }
153 | ```
154 | 
155 | ### execution_get
156 | 
157 | Retrieves details of a specific execution.
158 | 
159 | **Input Schema:**
160 | 
161 | ```json
162 | {
163 |   "type": "object",
164 |   "properties": {
165 |     "executionId": {
166 |       "type": "string",
167 |       "description": "ID of the execution to retrieve"
168 |     }
169 |   },
170 |   "required": ["executionId"]
171 | }
172 | ```
173 | 
174 | **Example Usage:**
175 | 
176 | ```javascript
177 | const execution = await useExecutionGet({
178 |   executionId: "exec789"
179 | });
180 | ```
181 | 
182 | **Response:**
183 | 
184 | ```javascript
185 | {
186 |   "id": "exec789",
187 |   "workflowId": "1234abc",
188 |   "workflowName": "Test Workflow 1",
189 |   "status": "success", // Or "error", "running", "waiting", etc.
190 |   "startedAt": "2025-03-12T16:30:00.000Z",
191 |   "finishedAt": "2025-03-12T16:30:05.000Z",
192 |   "mode": "manual",
193 |   "data": {
194 |     "resultData": {
195 |       // Output data from the workflow execution
196 |     },
197 |     "executionData": {
198 |       // Detailed execution data including node inputs/outputs
199 |     },
200 |     "metadata": {
201 |       // Execution metadata
202 |     }
203 |   }
204 | }
205 | ```
206 | 
207 | ### execution_list
208 | 
209 | Lists executions for a specific workflow.
210 | 
211 | **Input Schema:**
212 | 
213 | ```json
214 | {
215 |   "type": "object",
216 |   "properties": {
217 |     "workflowId": {
218 |       "type": "string",
219 |       "description": "ID of the workflow to get executions for"
220 |     },
221 |     "limit": {
222 |       "type": "number",
223 |       "description": "Maximum number of executions to return",
224 |       "default": 20
225 |     },
226 |     "status": {
227 |       "type": "string",
228 |       "description": "Filter by execution status",
229 |       "enum": ["success", "error", "running", "waiting"]
230 |     }
231 |   },
232 |   "required": ["workflowId"]
233 | }
234 | ```
235 | 
236 | **Example Usage:**
237 | 
238 | ```javascript
239 | // List all executions for a workflow
240 | const executions = await useExecutionList({
241 |   workflowId: "1234abc"
242 | });
243 | 
244 | // List with limit
245 | const limitedExecutions = await useExecutionList({
246 |   workflowId: "1234abc",
247 |   limit: 5
248 | });
249 | 
250 | // List only successful executions
251 | const successfulExecutions = await useExecutionList({
252 |   workflowId: "1234abc",
253 |   status: "success"
254 | });
255 | ```
256 | 
257 | **Response:**
258 | 
259 | ```javascript
260 | [
261 |   {
262 |     "id": "exec789",
263 |     "workflowId": "1234abc",
264 |     "status": "success",
265 |     "startedAt": "2025-03-12T16:30:00.000Z",
266 |     "finishedAt": "2025-03-12T16:30:05.000Z",
267 |     "mode": "manual"
268 |   },
269 |   {
270 |     "id": "exec456",
271 |     "workflowId": "1234abc",
272 |     "status": "error",
273 |     "startedAt": "2025-03-11T14:20:00.000Z",
274 |     "finishedAt": "2025-03-11T14:20:10.000Z",
275 |     "mode": "manual"
276 |   }
277 | ]
278 | ```
279 | 
280 | ### execution_delete
281 | 
282 | Deletes an execution record.
283 | 
284 | **Input Schema:**
285 | 
286 | ```json
287 | {
288 |   "type": "object",
289 |   "properties": {
290 |     "executionId": {
291 |       "type": "string",
292 |       "description": "ID of the execution to delete"
293 |     }
294 |   },
295 |   "required": ["executionId"]
296 | }
297 | ```
298 | 
299 | **Example Usage:**
300 | 
301 | ```javascript
302 | await useExecutionDelete({
303 |   executionId: "exec789"
304 | });
305 | ```
306 | 
307 | **Response:**
308 | 
309 | ```javascript
310 | {
311 |   "success": true
312 | }
313 | ```
314 | 
315 | ### execution_stop
316 | 
317 | Stops a running execution.
318 | 
319 | **Input Schema:**
320 | 
321 | ```json
322 | {
323 |   "type": "object",
324 |   "properties": {
325 |     "executionId": {
326 |       "type": "string",
327 |       "description": "ID of the execution to stop"
328 |     }
329 |   },
330 |   "required": ["executionId"]
331 | }
332 | ```
333 | 
334 | **Example Usage:**
335 | 
336 | ```javascript
337 | await useExecutionStop({
338 |   executionId: "exec789"
339 | });
340 | ```
341 | 
342 | **Response:**
343 | 
344 | ```javascript
345 | {
346 |   "success": true,
347 |   "status": "cancelled",
348 |   "stoppedAt": "2025-03-12T16:32:00.000Z"
349 | }
350 | ```
351 | 
352 | ## Execution Status Codes
353 | 
354 | Executions can have the following status codes:
355 | 
356 | | Status | Description |
357 | |--------|-------------|
358 | | `running` | The execution is currently in progress |
359 | | `success` | The execution completed successfully |
360 | | `error` | The execution failed with an error |
361 | | `waiting` | The execution is waiting for a webhook or other event |
362 | | `cancelled` | The execution was manually stopped |
363 | 
364 | ## Error Handling
365 | 
366 | All execution tools can return the following errors:
367 | 
368 | | Error | Description |
369 | |-------|-------------|
370 | | Authentication Error | The provided API key is invalid or missing |
371 | | Not Found Error | The requested workflow or execution does not exist |
372 | | Validation Error | The input parameters are invalid or incomplete |
373 | | Permission Error | The API key does not have permission to perform the operation |
374 | | Server Error | An unexpected error occurred on the n8n server |
375 | 
376 | ## Best Practices
377 | 
378 | - Check if a workflow is active before attempting to execute it
379 | - Use `waitForCompletion: true` for short-running workflows, but be cautious with long-running workflows
380 | - Always handle potential errors when executing workflows
381 | - Filter executions by status to find problematic runs
382 | - Use execution IDs from `execution_run` responses to track workflow progress
383 | 
```

--------------------------------------------------------------------------------
/tests/unit/config/environment.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
  2 | import fs from 'fs';
  3 | import path from 'path';
  4 | import findConfig from 'find-config'; // Use the actual find-config
  5 | 
  6 | import { loadEnvironmentVariables } from '../../../src/config/environment';
  7 | import { ENV_VARS } from '../../../src/config/environment'; // To access defined var names
  8 | 
  9 | // Determine project root for placing dummy .env file
 10 | let projectRootDir: string | null = null;
 11 | let dummyEnvPath: string | null = null;
 12 | 
 13 | try {
 14 |   const packageJsonPath = findConfig('package.json');
 15 |   if (packageJsonPath) {
 16 |     projectRootDir = path.dirname(packageJsonPath);
 17 |     dummyEnvPath = path.resolve(projectRootDir, '.env.test_dummy'); // Use a distinct name
 18 |   } else {
 19 |     console.error("Could not find project root (package.json). Tests involving .env file might fail or be skipped.");
 20 |   }
 21 | } catch (e) {
 22 |   console.error("Error finding project root:", e);
 23 | }
 24 | 
 25 | 
 26 | const originalEnv = { ...process.env };
 27 | 
 28 | const clearTestEnvVars = () => {
 29 |   delete process.env[ENV_VARS.N8N_API_URL];
 30 |   delete process.env[ENV_VARS.N8N_API_KEY];
 31 |   delete process.env[ENV_VARS.N8N_WEBHOOK_USERNAME];
 32 |   delete process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD];
 33 | };
 34 | 
 35 | const saveEnvState = () => {
 36 |   return { ...process.env };
 37 | };
 38 | 
 39 | const DUMMY_ENV_CONTENT = `
 40 | ${ENV_VARS.N8N_API_URL}=http://dummyapi.com
 41 | ${ENV_VARS.N8N_API_KEY}=dummyapikey
 42 | ${ENV_VARS.N8N_WEBHOOK_USERNAME}=dummyuser
 43 | ${ENV_VARS.N8N_WEBHOOK_PASSWORD}=dummypassword
 44 | `;
 45 | 
 46 | describe('loadEnvironmentVariables', () => {
 47 |   beforeEach(() => {
 48 |     jest.resetModules(); // Reset module cache, critical for dotenv
 49 |     process.env = { ...originalEnv }; // Restore original env
 50 |     clearTestEnvVars(); // Clear our specific vars
 51 | 
 52 |     // Ensure dummy .env is clean before each test that might create it
 53 |     if (dummyEnvPath && fs.existsSync(dummyEnvPath)) {
 54 |       fs.unlinkSync(dummyEnvPath);
 55 |     }
 56 |   });
 57 | 
 58 |   afterEach(() => {
 59 |     // Restore original env
 60 |     process.env = { ...originalEnv };
 61 |     // Clean up dummy .env file if it exists after a test
 62 |     if (dummyEnvPath && fs.existsSync(dummyEnvPath)) {
 63 |       try {
 64 |         fs.unlinkSync(dummyEnvPath);
 65 |       } catch (e) {
 66 |         // In case the test itself deleted it, or it was never created.
 67 |       }
 68 |     }
 69 |   });
 70 | 
 71 |   // Test Case 1: All environment variables set
 72 |   test('should not change process.env if all required env vars are already set', () => {
 73 |     process.env[ENV_VARS.N8N_API_URL] = 'http://existingapi.com';
 74 |     process.env[ENV_VARS.N8N_API_KEY] = 'existingapikey';
 75 |     process.env[ENV_VARS.N8N_WEBHOOK_USERNAME] = 'existinguser';
 76 |     process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD] = 'existingpassword';
 77 | 
 78 |     const envStateBeforeLoad = saveEnvState();
 79 |     loadEnvironmentVariables(); // Should not load .env because vars are present
 80 |     
 81 |     expect(process.env).toEqual(envStateBeforeLoad);
 82 |   });
 83 | 
 84 |   // Test Case 2: No environment variables set, .env file exists
 85 |   test('should load vars from .env file if no required env vars are set and .env exists', () => {
 86 |     if (!projectRootDir || !dummyEnvPath) {
 87 |       console.warn("Skipping test: Project root not found, cannot create dummy .env file.");
 88 |       return; // or expect.hasAssertions() with a different path
 89 |     }
 90 |     
 91 |     // Pre-condition: specific vars are not set (cleared in beforeEach)
 92 |     expect(process.env[ENV_VARS.N8N_API_URL]).toBeUndefined();
 93 | 
 94 |     fs.writeFileSync(dummyEnvPath, DUMMY_ENV_CONTENT);
 95 | 
 96 |     // Temporarily point findConfig's "idea" of .env to our dummy .env by hijacking process.env for dotenv
 97 |     // This is tricky because loadEnvironmentVariables uses findConfig to locate package.json, then path.resolve for .env
 98 |     // The most straightforward way is to ensure findConfig returns our projectRootDir, and then dotenv loads our dummyEnvPath.
 99 |     // The current implementation of loadEnvironmentVariables is:
100 |     //   const projectRoot = findConfig('package.json');
101 |     //   if (projectRoot) {
102 |     //     const envPath = path.resolve(path.dirname(projectRoot), '.env'); <--- This is the key
103 |     //     dotenv.config({ path: envPath });
104 |     //   }
105 |     // So, for this test, we need to make `path.resolve` point to `dummyEnvPath` OR make the actual `.env` the dummy.
106 |     // Let's rename the actual .env if it exists, place our dummy, then restore.
107 |     // A simpler approach for testing: the function loads ".env". So we make our dummy file THE ".env".
108 |     
109 |     const actualEnvPath = path.resolve(projectRootDir, '.env');
110 |     let actualEnvRenamedPath: string | null = null;
111 |     if (fs.existsSync(actualEnvPath)) {
112 |       actualEnvRenamedPath = actualEnvPath + '.backup';
113 |       fs.renameSync(actualEnvPath, actualEnvRenamedPath);
114 |     }
115 | 
116 |     fs.writeFileSync(actualEnvPath, DUMMY_ENV_CONTENT); // Write our dummy content to the actual .env path
117 | 
118 |     try {
119 |       loadEnvironmentVariables();
120 | 
121 |       expect(process.env[ENV_VARS.N8N_API_URL]).toBe('http://dummyapi.com');
122 |       expect(process.env[ENV_VARS.N8N_API_KEY]).toBe('dummyapikey');
123 |       expect(process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]).toBe('dummyuser');
124 |       expect(process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]).toBe('dummypassword');
125 |     } finally {
126 |       // Clean up: remove our dummy .env and restore original .env if it was renamed
127 |       if (fs.existsSync(actualEnvPath)) {
128 |         fs.unlinkSync(actualEnvPath);
129 |       }
130 |       if (actualEnvRenamedPath && fs.existsSync(actualEnvRenamedPath)) {
131 |         fs.renameSync(actualEnvRenamedPath, actualEnvPath);
132 |       }
133 |     }
134 |   });
135 | 
136 |   // Test Case 3: No environment variables set, no .env file exists
137 |   test('should not change process.env if no required env vars are set and no .env file exists', () => {
138 |     if (!projectRootDir) {
139 |       console.warn("Skipping parts of test: Project root not found, .env file check might be unreliable.");
140 |       // We can still proceed as findConfig would return null or the .env file wouldn't be found
141 |     } else {
142 |       const actualEnvPath = path.resolve(projectRootDir, '.env');
143 |       if (fs.existsSync(actualEnvPath)) {
144 |         // This test requires no .env file, so if one exists (e.g. a real one for dev), this test is harder.
145 |         // For CI/isolated env, it should be fine. Here we assume it's okay if it doesn't exist.
146 |         // If it *does* exist, the test might reflect that it *was* loaded if not handled.
147 |         console.warn(`Warning: Test 'no .env file exists' running when an actual .env file is present at ${actualEnvPath}. This test assumes it won't be loaded or is empty.`);
148 |         // To be robust, we'd need to ensure it's not there, similar to Test Case 2's cleanup.
149 |         // For now, we assume `loadEnvironmentVariables` won't find one if `findConfig` fails or the file is empty/irrelevant.
150 |       }
151 |     }
152 |     
153 |     // Vars are cleared in beforeEach
154 |     const envStateBeforeLoad = saveEnvState();
155 |     loadEnvironmentVariables(); // Should not find a .env file to load (or findConfig returns null)
156 |     
157 |     expect(process.env[ENV_VARS.N8N_API_URL]).toBeUndefined();
158 |     expect(process.env[ENV_VARS.N8N_API_KEY]).toBeUndefined();
159 |     expect(process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]).toBeUndefined();
160 |     expect(process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]).toBeUndefined();
161 |     // Check if other env vars were not disturbed (more robust check)
162 |     expect(process.env).toEqual(envStateBeforeLoad);
163 |   });
164 | 
165 |   // Test Case 4: Some environment variables set
166 |   test('should not change process.env if some (but not all) required env vars are set', () => {
167 |     process.env[ENV_VARS.N8N_API_URL] = 'http://partialapi.com';
168 |     process.env[ENV_VARS.N8N_API_KEY] = 'partialapikey';
169 |     // N8N_WEBHOOK_USERNAME and N8N_WEBHOOK_PASSWORD are not set (cleared by beforeEach)
170 | 
171 |     const envStateBeforeLoad = saveEnvState();
172 |     loadEnvironmentVariables(); // Should not load .env because some vars are present
173 |     
174 |     expect(process.env).toEqual(envStateBeforeLoad);
175 |     expect(process.env[ENV_VARS.N8N_API_URL]).toBe('http://partialapi.com');
176 |     expect(process.env[ENV_VARS.N8N_API_KEY]).toBe('partialapikey');
177 |     expect(process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]).toBeUndefined();
178 |     expect(process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]).toBeUndefined();
179 |   });
180 | });
181 | 
```

--------------------------------------------------------------------------------
/src/api/client.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * n8n API Client
  3 |  * 
  4 |  * This module provides a client for interacting with the n8n API.
  5 |  */
  6 | 
  7 | import axios, { AxiosInstance } from 'axios';
  8 | import { EnvConfig } from '../config/environment.js';
  9 | import { handleAxiosError, N8nApiError } from '../errors/index.js';
 10 | 
 11 | /**
 12 |  * n8n API Client class for making requests to the n8n API
 13 |  */
 14 | export class N8nApiClient {
 15 |   private axiosInstance: AxiosInstance;
 16 |   private config: EnvConfig;
 17 | 
 18 |   /**
 19 |    * Create a new n8n API client
 20 |    * 
 21 |    * @param config Environment configuration
 22 |    */
 23 |   constructor(config: EnvConfig) {
 24 |     this.config = config;
 25 |     this.axiosInstance = axios.create({
 26 |       baseURL: config.n8nApiUrl,
 27 |       headers: {
 28 |         'X-N8N-API-KEY': config.n8nApiKey,
 29 |         'Accept': 'application/json',
 30 |       },
 31 |       timeout: 10000, // 10 seconds
 32 |     });
 33 | 
 34 |     // Add request debugging if debug mode is enabled
 35 |     if (config.debug) {
 36 |       this.axiosInstance.interceptors.request.use(request => {
 37 |         console.error(`[DEBUG] Request: ${request.method?.toUpperCase()} ${request.url}`);
 38 |         return request;
 39 |       });
 40 | 
 41 |       this.axiosInstance.interceptors.response.use(response => {
 42 |         console.error(`[DEBUG] Response: ${response.status} ${response.statusText}`);
 43 |         return response;
 44 |       });
 45 |     }
 46 |   }
 47 | 
 48 |   /**
 49 |    * Check connectivity to the n8n API
 50 |    * 
 51 |    * @returns Promise that resolves if connectivity check succeeds
 52 |    * @throws N8nApiError if connectivity check fails
 53 |    */
 54 |   async checkConnectivity(): Promise<void> {
 55 |     try {
 56 |       // Try to fetch health endpoint or workflows
 57 |       const response = await this.axiosInstance.get('/workflows');
 58 |       
 59 |       if (response.status !== 200) {
 60 |         throw new N8nApiError(
 61 |           'n8n API connectivity check failed',
 62 |           response.status
 63 |         );
 64 |       }
 65 |       
 66 |       if (this.config.debug) {
 67 |         console.error(`[DEBUG] Successfully connected to n8n API at ${this.config.n8nApiUrl}`);
 68 |         console.error(`[DEBUG] Found ${response.data.data?.length || 0} workflows`);
 69 |       }
 70 |     } catch (error) {
 71 |       throw handleAxiosError(error, 'Failed to connect to n8n API');
 72 |     }
 73 |   }
 74 | 
 75 |   /**
 76 |    * Get the axios instance for making custom requests
 77 |    * 
 78 |    * @returns Axios instance
 79 |    */
 80 |   getAxiosInstance(): AxiosInstance {
 81 |     return this.axiosInstance;
 82 |   }
 83 | 
 84 |   /**
 85 |    * Get all workflows from n8n
 86 |    * 
 87 |    * @returns Array of workflow objects
 88 |    */
 89 |   async getWorkflows(): Promise<any[]> {
 90 |     try {
 91 |       const response = await this.axiosInstance.get('/workflows');
 92 |       return response.data.data || [];
 93 |     } catch (error) {
 94 |       throw handleAxiosError(error, 'Failed to fetch workflows');
 95 |     }
 96 |   }
 97 | 
 98 |   /**
 99 |    * Get a specific workflow by ID
100 |    * 
101 |    * @param id Workflow ID
102 |    * @returns Workflow object
103 |    */
104 |   async getWorkflow(id: string): Promise<any> {
105 |     try {
106 |       const response = await this.axiosInstance.get(`/workflows/${id}`);
107 |       return response.data;
108 |     } catch (error) {
109 |       throw handleAxiosError(error, `Failed to fetch workflow ${id}`);
110 |     }
111 |   }
112 | 
113 |   /**
114 |    * Get all workflow executions
115 |    * 
116 |    * @returns Array of execution objects
117 |    */
118 |   async getExecutions(): Promise<any[]> {
119 |     try {
120 |       const response = await this.axiosInstance.get('/executions');
121 |       return response.data.data || [];
122 |     } catch (error) {
123 |       throw handleAxiosError(error, 'Failed to fetch executions');
124 |     }
125 |   }
126 | 
127 |   /**
128 |    * Get a specific execution by ID
129 |    * 
130 |    * @param id Execution ID
131 |    * @returns Execution object
132 |    */
133 |   async getExecution(id: string): Promise<any> {
134 |     try {
135 |       const response = await this.axiosInstance.get(`/executions/${id}`);
136 |       return response.data;
137 |     } catch (error) {
138 |       throw handleAxiosError(error, `Failed to fetch execution ${id}`);
139 |     }
140 |   }
141 | 
142 |   /**
143 |    * Execute a workflow by ID
144 |    * 
145 |    * @param id Workflow ID
146 |    * @param data Optional data to pass to the workflow
147 |    * @returns Execution result
148 |    */
149 |   async executeWorkflow(id: string, data?: Record<string, any>): Promise<any> {
150 |     try {
151 |       const response = await this.axiosInstance.post(`/workflows/${id}/execute`, data || {});
152 |       return response.data;
153 |     } catch (error) {
154 |       throw handleAxiosError(error, `Failed to execute workflow ${id}`);
155 |     }
156 |   }
157 | 
158 |   /**
159 |    * Create a new workflow
160 |    * 
161 |    * @param workflow Workflow object to create
162 |    * @returns Created workflow
163 |    */
164 |   async createWorkflow(workflow: Record<string, any>): Promise<any> {
165 |     try {
166 |       // Make sure settings property is present
167 |       if (!workflow.settings) {
168 |         workflow.settings = {
169 |           saveExecutionProgress: true,
170 |           saveManualExecutions: true,
171 |           saveDataErrorExecution: "all",
172 |           saveDataSuccessExecution: "all",
173 |           executionTimeout: 3600,
174 |           timezone: "UTC"
175 |         };
176 |       }
177 |       
178 |       // Remove read-only properties that cause issues
179 |       const workflowToCreate = { ...workflow };
180 |       delete workflowToCreate.active; // Remove active property as it's read-only
181 |       delete workflowToCreate.id; // Remove id property if it exists
182 |       delete workflowToCreate.createdAt; // Remove createdAt property if it exists
183 |       delete workflowToCreate.updatedAt; // Remove updatedAt property if it exists
184 |       delete workflowToCreate.tags; // Remove tags property as it's read-only
185 |       
186 |       // Log request for debugging
187 |       console.error('[DEBUG] Creating workflow with data:', JSON.stringify(workflowToCreate, null, 2));
188 |       
189 |       const response = await this.axiosInstance.post('/workflows', workflowToCreate);
190 |       return response.data;
191 |     } catch (error) {
192 |       console.error('[ERROR] Create workflow error:', error);
193 |       throw handleAxiosError(error, 'Failed to create workflow');
194 |     }
195 |   }
196 | 
197 |   /**
198 |    * Update an existing workflow
199 |    * 
200 |    * @param id Workflow ID
201 |    * @param workflow Updated workflow object
202 |    * @returns Updated workflow
203 |    */
204 |   async updateWorkflow(id: string, workflow: Record<string, any>): Promise<any> {
205 |     try {
206 |       // Remove read-only properties that cause issues with n8n API v1
207 |       // According to n8n API schema, only name, nodes, connections, settings, and staticData are allowed
208 |       const workflowToUpdate = { ...workflow };
209 |       delete workflowToUpdate.id; // Remove id property as it's read-only
210 |       delete workflowToUpdate.active; // Remove active property as it's read-only
211 |       delete workflowToUpdate.createdAt; // Remove createdAt property as it's read-only
212 |       delete workflowToUpdate.updatedAt; // Remove updatedAt property as it's read-only
213 |       delete workflowToUpdate.tags; // Remove tags property as it's read-only
214 | 
215 |       // Log request for debugging
216 |       if (this.config.debug) {
217 |         console.error('[DEBUG] Updating workflow with data:', JSON.stringify(workflowToUpdate, null, 2));
218 |       }
219 | 
220 |       const response = await this.axiosInstance.put(`/workflows/${id}`, workflowToUpdate);
221 |       return response.data;
222 |     } catch (error) {
223 |       throw handleAxiosError(error, `Failed to update workflow ${id}`);
224 |     }
225 |   }
226 | 
227 |   /**
228 |    * Delete a workflow
229 |    * 
230 |    * @param id Workflow ID
231 |    * @returns Deleted workflow
232 |    */
233 |   async deleteWorkflow(id: string): Promise<any> {
234 |     try {
235 |       const response = await this.axiosInstance.delete(`/workflows/${id}`);
236 |       return response.data;
237 |     } catch (error) {
238 |       throw handleAxiosError(error, `Failed to delete workflow ${id}`);
239 |     }
240 |   }
241 | 
242 |   /**
243 |    * Activate a workflow
244 |    * 
245 |    * @param id Workflow ID
246 |    * @returns Activated workflow
247 |    */
248 |   async activateWorkflow(id: string): Promise<any> {
249 |     try {
250 |       const response = await this.axiosInstance.post(`/workflows/${id}/activate`);
251 |       return response.data;
252 |     } catch (error) {
253 |       throw handleAxiosError(error, `Failed to activate workflow ${id}`);
254 |     }
255 |   }
256 | 
257 |   /**
258 |    * Deactivate a workflow
259 |    * 
260 |    * @param id Workflow ID
261 |    * @returns Deactivated workflow
262 |    */
263 |   async deactivateWorkflow(id: string): Promise<any> {
264 |     try {
265 |       const response = await this.axiosInstance.post(`/workflows/${id}/deactivate`);
266 |       return response.data;
267 |     } catch (error) {
268 |       throw handleAxiosError(error, `Failed to deactivate workflow ${id}`);
269 |     }
270 |   }
271 |   
272 |   /**
273 |    * Delete an execution
274 |    * 
275 |    * @param id Execution ID
276 |    * @returns Deleted execution or success message
277 |    */
278 |   async deleteExecution(id: string): Promise<any> {
279 |     try {
280 |       const response = await this.axiosInstance.delete(`/executions/${id}`);
281 |       return response.data;
282 |     } catch (error) {
283 |       throw handleAxiosError(error, `Failed to delete execution ${id}`);
284 |     }
285 |   }
286 | }
287 | 
288 | /**
289 |  * Create and return a configured n8n API client
290 |  * 
291 |  * @param config Environment configuration
292 |  * @returns n8n API client instance
293 |  */
294 | export function createApiClient(config: EnvConfig): N8nApiClient {
295 |   return new N8nApiClient(config);
296 | }
297 | 
```

--------------------------------------------------------------------------------
/docs/development/testing.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Testing
  2 | 
  3 | This document describes the testing approach for the n8n MCP Server and provides guidelines for writing effective tests.
  4 | 
  5 | ## Overview
  6 | 
  7 | The n8n MCP Server uses Jest as its testing framework and follows a multi-level testing approach:
  8 | 
  9 | 1. **Unit Tests**: Test individual components in isolation
 10 | 2. **Integration Tests**: Test interactions between components
 11 | 3. **End-to-End Tests**: Test the entire system as a whole
 12 | 
 13 | Tests are organized in the `tests/` directory, with a structure that mirrors the `src/` directory.
 14 | 
 15 | ## Running Tests
 16 | 
 17 | ### Running All Tests
 18 | 
 19 | To run all tests:
 20 | 
 21 | ```bash
 22 | npm test
 23 | ```
 24 | 
 25 | This command runs all tests and outputs a summary of the results.
 26 | 
 27 | ### Running Tests with Coverage
 28 | 
 29 | To run tests with coverage reporting:
 30 | 
 31 | ```bash
 32 | npm run test:coverage
 33 | ```
 34 | 
 35 | This generates coverage reports in the `coverage/` directory, including HTML reports that you can view in a browser.
 36 | 
 37 | ### Running Tests in Watch Mode
 38 | 
 39 | During development, you can run tests in watch mode, which will automatically rerun tests when files change:
 40 | 
 41 | ```bash
 42 | npm run test:watch
 43 | ```
 44 | 
 45 | ### Running Specific Tests
 46 | 
 47 | To run tests in a specific file or directory:
 48 | 
 49 | ```bash
 50 | npx jest path/to/test-file.test.ts
 51 | ```
 52 | 
 53 | Or to run tests matching a specific pattern:
 54 | 
 55 | ```bash
 56 | npx jest -t "test pattern"
 57 | ```
 58 | 
 59 | ## Test Structure
 60 | 
 61 | Tests are organized into the following directories:
 62 | 
 63 | - `tests/unit/`: Unit tests for individual components
 64 | - `tests/integration/`: Integration tests that test interactions between components
 65 | - `tests/e2e/`: End-to-end tests that test the entire system
 66 | - `tests/mocks/`: Shared test fixtures and mocks
 67 | 
 68 | ### Unit Tests
 69 | 
 70 | Unit tests are organized in a structure that mirrors the `src/` directory. For example:
 71 | 
 72 | - `src/api/n8n-client.ts` has a corresponding test at `tests/unit/api/n8n-client.test.ts`
 73 | - `src/tools/workflow/list.ts` has a corresponding test at `tests/unit/tools/workflow/list.test.ts`
 74 | 
 75 | ### Integration Tests
 76 | 
 77 | Integration tests focus on testing interactions between components, such as:
 78 | 
 79 | - Testing that tools correctly use the API client
 80 | - Testing that resources correctly format data from the API
 81 | 
 82 | ### End-to-End Tests
 83 | 
 84 | End-to-end tests test the entire system, from the transport layer to the API client and back.
 85 | 
 86 | ## Writing Effective Tests
 87 | 
 88 | ### Unit Test Example
 89 | 
 90 | Here's an example of a unit test for a workflow tool:
 91 | 
 92 | ```typescript
 93 | // tests/unit/tools/workflow/list.test.ts
 94 | import { describe, it, expect, jest } from '@jest/globals';
 95 | import { getListWorkflowsToolDefinition, handleListWorkflows } from '../../../../src/tools/workflow/list.js';
 96 | import { N8nClient } from '../../../../src/api/n8n-client.js';
 97 | 
 98 | // Mock data
 99 | const mockWorkflows = [
100 |   {
101 |     id: '1234abc',
102 |     name: 'Test Workflow 1',
103 |     active: true,
104 |     createdAt: '2025-03-01T12:00:00.000Z',
105 |     updatedAt: '2025-03-02T14:30:00.000Z'
106 |   },
107 |   {
108 |     id: '5678def',
109 |     name: 'Test Workflow 2',
110 |     active: false,
111 |     createdAt: '2025-03-01T12:00:00.000Z',
112 |     updatedAt: '2025-03-12T10:15:00.000Z'
113 |   }
114 | ];
115 | 
116 | describe('Workflow List Tool', () => {
117 |   describe('getListWorkflowsToolDefinition', () => {
118 |     it('should return the correct tool definition', () => {
119 |       const definition = getListWorkflowsToolDefinition();
120 |       
121 |       expect(definition.name).toBe('workflow_list');
122 |       expect(definition.description).toBeTruthy();
123 |       expect(definition.inputSchema).toBeDefined();
124 |       expect(definition.inputSchema.properties).toHaveProperty('active');
125 |       expect(definition.inputSchema.required).toEqual([]);
126 |     });
127 |   });
128 |   
129 |   describe('handleListWorkflows', () => {
130 |     it('should return all workflows when no filter is provided', async () => {
131 |       // Mock the API client
132 |       const mockClient = {
133 |         getWorkflows: jest.fn().mockResolvedValue(mockWorkflows)
134 |       };
135 |       
136 |       const result = await handleListWorkflows(mockClient as unknown as N8nClient, {});
137 |       
138 |       expect(mockClient.getWorkflows).toHaveBeenCalledWith(undefined);
139 |       expect(result.isError).toBeFalsy();
140 |       
141 |       // Parse the JSON text to check the content
142 |       const content = JSON.parse(result.content[0].text);
143 |       expect(content).toHaveLength(2);
144 |       expect(content[0].id).toBe('1234abc');
145 |       expect(content[1].id).toBe('5678def');
146 |     });
147 |     
148 |     it('should filter workflows by active status', async () => {
149 |       // Mock the API client
150 |       const mockClient = {
151 |         getWorkflows: jest.fn().mockResolvedValue(mockWorkflows)
152 |       };
153 |       
154 |       const result = await handleListWorkflows(mockClient as unknown as N8nClient, { active: true });
155 |       
156 |       expect(mockClient.getWorkflows).toHaveBeenCalledWith(true);
157 |       expect(result.isError).toBeFalsy();
158 |       
159 |       // Parse the JSON text to check the content
160 |       const content = JSON.parse(result.content[0].text);
161 |       expect(content).toHaveLength(2);
162 |     });
163 |     
164 |     it('should handle API errors', async () => {
165 |       // Mock the API client to throw an error
166 |       const mockClient = {
167 |         getWorkflows: jest.fn().mockRejectedValue(new Error('API error'))
168 |       };
169 |       
170 |       const result = await handleListWorkflows(mockClient as unknown as N8nClient, {});
171 |       
172 |       expect(result.isError).toBeTruthy();
173 |       expect(result.content[0].text).toContain('API error');
174 |     });
175 |   });
176 | });
177 | ```
178 | 
179 | ### Integration Test Example
180 | 
181 | Here's an example of an integration test that tests the interaction between a resource handler and the API client:
182 | 
183 | ```typescript
184 | // tests/integration/resources/static/workflows.test.ts
185 | import { describe, it, expect, jest } from '@jest/globals';
186 | import { handleWorkflowsRequest, WORKFLOWS_URI } from '../../../../src/resources/static/workflows.js';
187 | import { N8nClient } from '../../../../src/api/n8n-client.js';
188 | 
189 | // Mock data
190 | const mockWorkflows = [
191 |   {
192 |     id: '1234abc',
193 |     name: 'Test Workflow 1',
194 |     active: true,
195 |     createdAt: '2025-03-01T12:00:00.000Z',
196 |     updatedAt: '2025-03-02T14:30:00.000Z'
197 |   },
198 |   {
199 |     id: '5678def',
200 |     name: 'Test Workflow 2',
201 |     active: false,
202 |     createdAt: '2025-03-01T12:00:00.000Z',
203 |     updatedAt: '2025-03-12T10:15:00.000Z'
204 |   }
205 | ];
206 | 
207 | describe('Workflows Resource Handler', () => {
208 |   it('should return a properly formatted response', async () => {
209 |     // Mock the API client
210 |     const mockClient = {
211 |       getWorkflows: jest.fn().mockResolvedValue(mockWorkflows)
212 |     };
213 |     
214 |     const response = await handleWorkflowsRequest(mockClient as unknown as N8nClient);
215 |     
216 |     expect(mockClient.getWorkflows).toHaveBeenCalled();
217 |     expect(response.contents).toHaveLength(1);
218 |     expect(response.contents[0].uri).toBe(WORKFLOWS_URI);
219 |     expect(response.contents[0].mimeType).toBe('application/json');
220 |     
221 |     // Parse the JSON text to check the content
222 |     const content = JSON.parse(response.contents[0].text);
223 |     expect(content).toHaveProperty('workflows');
224 |     expect(content.workflows).toHaveLength(2);
225 |     expect(content.count).toBe(2);
226 |     expect(content.workflows[0].id).toBe('1234abc');
227 |   });
228 |   
229 |   it('should handle API errors', async () => {
230 |     // Mock the API client to throw an error
231 |     const mockClient = {
232 |       getWorkflows: jest.fn().mockRejectedValue(new Error('API error'))
233 |     };
234 |     
235 |     await expect(handleWorkflowsRequest(mockClient as unknown as N8nClient))
236 |       .rejects
237 |       .toThrow('Failed to retrieve workflows');
238 |   });
239 | });
240 | ```
241 | 
242 | ### End-to-End Test Example
243 | 
244 | Here's an example of an end-to-end test that tests the entire system:
245 | 
246 | ```typescript
247 | // tests/e2e/workflow-operations.test.ts
248 | import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
249 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
250 | import { MemoryServerTransport } from '@modelcontextprotocol/sdk/server/memory.js';
251 | import { createServer } from '../../src/index.js';
252 | 
253 | describe('End-to-End Workflow Operations', () => {
254 |   let server: Server;
255 |   let transport: MemoryServerTransport;
256 |   
257 |   beforeAll(async () => {
258 |     // Mock the environment
259 |     process.env.N8N_API_URL = 'http://localhost:5678/api/v1';
260 |     process.env.N8N_API_KEY = 'test-api-key';
261 |     
262 |     // Create the server with a memory transport
263 |     transport = new MemoryServerTransport();
264 |     server = await createServer(transport);
265 |   });
266 |   
267 |   afterAll(async () => {
268 |     await server.close();
269 |   });
270 |   
271 |   it('should list workflows', async () => {
272 |     // Send a request to list workflows
273 |     const response = await transport.sendRequest({
274 |       jsonrpc: '2.0',
275 |       id: '1',
276 |       method: 'callTool',
277 |       params: {
278 |         name: 'workflow_list',
279 |         arguments: {}
280 |       }
281 |     });
282 |     
283 |     expect(response.result).toBeDefined();
284 |     expect(response.result.content).toHaveLength(1);
285 |     expect(response.result.content[0].type).toBe('text');
286 |     
287 |     // Parse the JSON text to check the content
288 |     const content = JSON.parse(response.result.content[0].text);
289 |     expect(Array.isArray(content)).toBe(true);
290 |   });
291 |   
292 |   it('should retrieve a workflow by ID', async () => {
293 |     // Send a request to get a workflow
294 |     const response = await transport.sendRequest({
295 |       jsonrpc: '2.0',
296 |       id: '2',
297 |       method: 'callTool',
298 |       params: {
299 |         name: 'workflow_get',
300 |         arguments: {
301 |           id: '1234abc'
302 |         }
303 |       }
304 |     });
305 |     
306 |     expect(response.result).toBeDefined();
307 |     expect(response.result.content).toHaveLength(1);
308 |     expect(response.result.content[0].type).toBe('text');
309 |     
310 |     // Parse the JSON text to check the content
311 |     const content = JSON.parse(response.result.content[0].text);
312 |     expect(content).toHaveProperty('id');
313 |     expect(content.id).toBe('1234abc');
314 |   });
315 | });
316 | ```
317 | 
318 | ## Test Fixtures and Mocks
319 | 
320 | To avoid duplication and improve test maintainability, common test fixtures and mocks are stored in the `tests/mocks/` directory.
321 | 
322 | ### Axios Mock
323 | 
324 | The Axios HTTP client is mocked using `axios-mock-adapter` to simulate HTTP responses without making actual API calls:
325 | 
326 | ```typescript
327 | // tests/mocks/axios-mock.ts
328 | import axios from 'axios';
329 | import MockAdapter from 'axios-mock-adapter';
330 | 
331 | // Create a new instance of the mock adapter
332 | export const axiosMock = new MockAdapter(axios);
333 | 
334 | // Helper function to reset the mock adapter before each test
335 | export function resetAxiosMock() {
336 |   axiosMock.reset();
337 | }
338 | ```
339 | 
340 | ### n8n API Fixtures
341 | 
342 | Common fixtures for n8n API responses are stored in a shared file:
343 | 
344 | ```typescript
345 | // tests/mocks/n8n-fixtures.ts
346 | export const mockWorkflows = [
347 |   {
348 |     id: '1234abc',
349 |     name: 'Test Workflow 1',
350 |     active: true,
351 |     createdAt: '2025-03-01T12:00:00.000Z',
352 |     updatedAt: '2025-03-02T14:30:00.000Z',
353 |     nodes: [
354 |       {
355 |         id: 'node1',
356 |         name: 'Start',
357 |         type: 'n8n-nodes-base.start',
358 |         position: [100, 200],
359 |         parameters: {}
360 |       }
361 |     ],
362 |     connections: {}
363 |   },
364 |   {
365 |     id: '5678def',
366 |     name: 'Test Workflow 2',
367 |     active: false,
368 |     createdAt: '2025-03-01T12:00:00.000Z',
369 |     updatedAt: '2025-03-12T10:15:00.000Z',
370 |     nodes: [],
371 |     connections: {}
372 |   }
373 | ];
374 | 
375 | export const mockExecutions = [
376 |   {
377 |     id: 'exec123',
378 |     workflowId: '1234abc',
379 |     workflowName: 'Test Workflow 1',
380 |     status: 'success',
381 |     startedAt: '2025-03-10T15:00:00.000Z',
382 |     finishedAt: '2025-03-10T15:01:00.000Z',
383 |     mode: 'manual'
384 |   },
385 |   {
386 |     id: 'exec456',
387 |     workflowId: '1234abc',
388 |     workflowName: 'Test Workflow 1',
389 |     status: 'error',
390 |     startedAt: '2025-03-09T12:00:00.000Z',
391 |     finishedAt: '2025-03-09T12:00:10.000Z',
392 |     mode: 'manual'
393 |   }
394 | ];
395 | ```
396 | 
397 | ## Test Environment
398 | 
399 | The test environment is configured in `jest.config.js` and `babel.config.js`. Key configurations include:
400 | 
401 | - TypeScript support via Babel
402 | - ES module support
403 | - Coverage reporting
404 | 
405 | The `tests/test-setup.ts` file contains global setup code that runs before tests:
406 | 
407 | ```typescript
408 | // tests/test-setup.ts
409 | import { jest } from '@jest/globals';
410 | import { resetAxiosMock } from './mocks/axios-mock';
411 | 
412 | // Reset mocks before each test
413 | beforeEach(() => {
414 |   jest.clearAllMocks();
415 |   resetAxiosMock();
416 | });
417 | ```
418 | 
419 | ## Best Practices
420 | 
421 | ### General Testing Guidelines
422 | 
423 | 1. **Write tests first**: Follow a test-driven development (TDD) approach when possible.
424 | 2. **Test behavior, not implementation**: Focus on what a component does, not how it's implemented.
425 | 3. **Keep tests simple**: Each test should test one behavior or aspect of functionality.
426 | 4. **Use descriptive test names**: Test names should describe the expected behavior.
427 | 5. **Follow the AAA pattern**: Arrange, Act, Assert (setup, execute, verify).
428 | 
429 | ### Mocking Best Practices
430 | 
431 | 1. **Mock dependencies, not the unit under test**: Only mock external dependencies, not the code you're testing.
432 | 2. **Use the minimum viable mock**: Only mock the methods and behavior needed for the test.
433 | 3. **Ensure mock behavior is realistic**: Mocks should behave similarly to the real implementation.
434 | 4. **Verify interactions with mocks**: Use `expect(mock).toHaveBeenCalled()` to verify interactions.
435 | 
436 | ### Error Testing Best Practices
437 | 
438 | 1. **Test error cases**: Don't just test the happy path; test error handling too.
439 | 2. **Simulate errors with mocks**: Use mocks to simulate error scenarios.
440 | 3. **Verify error messages**: Ensure error messages are helpful and descriptive.
441 | 
442 | ### Performance Testing Considerations
443 | 
444 | 1. **Monitor test performance**: Slow tests can slow down development.
445 | 2. **Use test timeout values wisely**: Set appropriate timeout values for async tests.
446 | 3. **Minimize redundant setup**: Use `beforeEach` and `beforeAll` to avoid redundant setup.
447 | 
448 | ## Continuous Integration
449 | 
450 | Tests are run automatically in CI environments on pull requests and commits to the main branch. The CI configuration ensures tests pass before code can be merged.
451 | 
452 | ### CI Test Requirements
453 | 
454 | - All tests must pass
455 | - Test coverage must not decrease
456 | - Linting checks must pass
457 | 
458 | ## Debugging Tests
459 | 
460 | ### Console Output
461 | 
462 | You can use `console.log()` statements in your tests to debug issues:
463 | 
464 | ```typescript
465 | it('should do something', () => {
466 |   const result = doSomething();
467 |   console.log('Result:', result);
468 |   expect(result).toBe(expectedValue);
469 | });
470 | ```
471 | 
472 | When running tests with Jest, console output will be displayed for failing tests by default.
473 | 
474 | ### Using the Debugger
475 | 
476 | You can also use the Node.js debugger with Jest:
477 | 
478 | ```bash
479 | node --inspect-brk node_modules/.bin/jest --runInBand path/to/test
480 | ```
481 | 
482 | Then connect to the debugger with Chrome DevTools or VS Code.
483 | 
484 | ## Conclusion
485 | 
486 | Thorough testing is essential for maintaining a reliable and robust n8n MCP Server. By following these guidelines and examples, you can write effective tests that help ensure your code works as expected and catches issues early.
487 | 
```

--------------------------------------------------------------------------------
/docs/development/extending.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Extending the Server
  2 | 
  3 | This guide explains how to extend the n8n MCP Server with new functionality.
  4 | 
  5 | ## Overview
  6 | 
  7 | The n8n MCP Server is designed to be extensible, allowing developers to add new tools and resources without modifying existing code. This extensibility makes it easy to support new n8n features or customize the server for specific use cases.
  8 | 
  9 | ## Adding a New Tool
 10 | 
 11 | Tools in the MCP server represent executable operations that AI assistants can use. To add a new tool, follow these steps:
 12 | 
 13 | ### 1. Define the Tool Interface
 14 | 
 15 | Create a new TypeScript interface that defines the input parameters for your tool:
 16 | 
 17 | ```typescript
 18 | // src/types/tools/my-tool.ts
 19 | export interface MyToolParams {
 20 |   param1: string;
 21 |   param2?: number; // Optional parameter
 22 | }
 23 | ```
 24 | 
 25 | ### 2. Create the Tool Handler
 26 | 
 27 | Create a new file for your tool in the appropriate category under `src/tools/`:
 28 | 
 29 | ```typescript
 30 | // src/tools/category/my-tool.ts
 31 | import { ToolCallResponse, ToolDefinition } from '@modelcontextprotocol/sdk/types.js';
 32 | import { N8nClient } from '../../api/n8n-client.js';
 33 | import { MyToolParams } from '../../types/tools/my-tool.js';
 34 | 
 35 | // Define the tool
 36 | export function getMyToolDefinition(): ToolDefinition {
 37 |   return {
 38 |     name: 'my_tool',
 39 |     description: 'Description of what my tool does',
 40 |     inputSchema: {
 41 |       type: 'object',
 42 |       properties: {
 43 |         param1: {
 44 |           type: 'string',
 45 |           description: 'Description of param1'
 46 |         },
 47 |         param2: {
 48 |           type: 'number',
 49 |           description: 'Description of param2'
 50 |         }
 51 |       },
 52 |       required: ['param1']
 53 |     }
 54 |   };
 55 | }
 56 | 
 57 | // Implement the tool handler
 58 | export async function handleMyTool(
 59 |   client: N8nClient,
 60 |   params: MyToolParams
 61 | ): Promise<ToolCallResponse> {
 62 |   try {
 63 |     // Implement the tool logic here
 64 |     // Use the N8nClient to interact with n8n
 65 | 
 66 |     // Return the response
 67 |     return {
 68 |       content: [
 69 |         {
 70 |           type: 'text',
 71 |           text: 'Result of the operation'
 72 |         }
 73 |       ]
 74 |     };
 75 |   } catch (error) {
 76 |     // Handle errors
 77 |     return {
 78 |       content: [
 79 |         {
 80 |           type: 'text',
 81 |           text: `Error: ${error.message}`
 82 |         }
 83 |       ],
 84 |       isError: true
 85 |     };
 86 |   }
 87 | }
 88 | ```
 89 | 
 90 | ### 3. Register the Tool in the Handler
 91 | 
 92 | Update the main handler file for your tool category (e.g., `src/tools/category/handler.ts`):
 93 | 
 94 | ```typescript
 95 | // src/tools/category/handler.ts
 96 | import { getMyToolDefinition, handleMyTool } from './my-tool.js';
 97 | 
 98 | // Add your tool to the tools object
 99 | export const categoryTools = {
100 |   // ... existing tools
101 |   my_tool: {
102 |     definition: getMyToolDefinition,
103 |     handler: handleMyTool
104 |   }
105 | };
106 | ```
107 | 
108 | ### 4. Add Handler to Main Server
109 | 
110 | Update the main tool handler registration in `src/index.ts`:
111 | 
112 | ```typescript
113 | // src/index.ts
114 | import { categoryTools } from './tools/category/handler.js';
115 | 
116 | // In the server initialization
117 | const server = new Server(
118 |   {
119 |     name: 'n8n-mcp-server',
120 |     version: '0.1.0'
121 |   },
122 |   {
123 |     capabilities: {
124 |       tools: {
125 |         // ... existing categories
126 |         category: true
127 |       }
128 |     }
129 |   }
130 | );
131 | 
132 | // Register tool handlers
133 | Object.entries(categoryTools).forEach(([name, { definition, handler }]) => {
134 |   server.setToolHandler(definition(), async (request) => {
135 |     return await handler(client, request.params.arguments as any);
136 |   });
137 | });
138 | ```
139 | 
140 | ### 5. Add Unit Tests
141 | 
142 | Create unit tests for your new tool:
143 | 
144 | ```typescript
145 | // tests/unit/tools/category/my-tool.test.ts
146 | import { describe, it, expect, jest } from '@jest/globals';
147 | import { getMyToolDefinition, handleMyTool } from '../../../../src/tools/category/my-tool.js';
148 | 
149 | describe('My Tool', () => {
150 |   describe('getMyToolDefinition', () => {
151 |     it('should return the correct tool definition', () => {
152 |       const definition = getMyToolDefinition();
153 |       
154 |       expect(definition.name).toBe('my_tool');
155 |       expect(definition.description).toBeTruthy();
156 |       expect(definition.inputSchema).toBeDefined();
157 |       expect(definition.inputSchema.properties).toHaveProperty('param1');
158 |       expect(definition.inputSchema.required).toEqual(['param1']);
159 |     });
160 |   });
161 |   
162 |   describe('handleMyTool', () => {
163 |     it('should handle valid parameters', async () => {
164 |       const mockClient = {
165 |         // Mock the necessary client methods
166 |       };
167 |       
168 |       const result = await handleMyTool(mockClient as any, {
169 |         param1: 'test value'
170 |       });
171 |       
172 |       expect(result.isError).toBeFalsy();
173 |       expect(result.content[0].text).toBeTruthy();
174 |     });
175 |     
176 |     it('should handle errors properly', async () => {
177 |       const mockClient = {
178 |         // Mock client that throws an error
179 |         someMethod: jest.fn().mockRejectedValue(new Error('Test error'))
180 |       };
181 |       
182 |       const result = await handleMyTool(mockClient as any, {
183 |         param1: 'test value'
184 |       });
185 |       
186 |       expect(result.isError).toBeTruthy();
187 |       expect(result.content[0].text).toContain('Error');
188 |     });
189 |   });
190 | });
191 | ```
192 | 
193 | ## Adding a New Resource
194 | 
195 | Resources in the MCP server provide data access through URI-based templates. To add a new resource, follow these steps:
196 | 
197 | ### 1. Create a Static Resource (No Parameters)
198 | 
199 | For a resource that doesn't require parameters:
200 | 
201 | ```typescript
202 | // src/resources/static/my-resource.ts
203 | import { McpError, ReadResourceResponse } from '@modelcontextprotocol/sdk/types.js';
204 | import { ErrorCode } from '../../errors/error-codes.js';
205 | import { N8nClient } from '../../api/n8n-client.js';
206 | 
207 | export const MY_RESOURCE_URI = 'n8n://my-resource';
208 | 
209 | export async function handleMyResourceRequest(
210 |   client: N8nClient
211 | ): Promise<ReadResourceResponse> {
212 |   try {
213 |     // Implement the resource logic
214 |     // Use the N8nClient to interact with n8n
215 |     
216 |     // Return the response
217 |     return {
218 |       contents: [
219 |         {
220 |           uri: MY_RESOURCE_URI,
221 |           mimeType: 'application/json',
222 |           text: JSON.stringify(
223 |             {
224 |               // Resource data
225 |               property1: 'value1',
226 |               property2: 'value2'
227 |             },
228 |             null,
229 |             2
230 |           )
231 |         }
232 |       ]
233 |     };
234 |   } catch (error) {
235 |     throw new McpError(
236 |       ErrorCode.InternalError,
237 |       `Failed to retrieve resource: ${error.message}`
238 |     );
239 |   }
240 | }
241 | ```
242 | 
243 | ### 2. Create a Dynamic Resource (With Parameters)
244 | 
245 | For a resource that requires parameters:
246 | 
247 | ```typescript
248 | // src/resources/dynamic/my-resource.ts
249 | import { McpError, ReadResourceResponse } from '@modelcontextprotocol/sdk/types.js';
250 | import { ErrorCode } from '../../errors/error-codes.js';
251 | import { N8nClient } from '../../api/n8n-client.js';
252 | 
253 | export const MY_RESOURCE_URI_TEMPLATE = 'n8n://my-resource/{id}';
254 | 
255 | export function matchMyResourceUri(uri: string): { id: string } | null {
256 |   const match = uri.match(/^n8n:\/\/my-resource\/([^/]+)$/);
257 |   if (!match) return null;
258 |   
259 |   return {
260 |     id: decodeURIComponent(match[1])
261 |   };
262 | }
263 | 
264 | export async function handleMyResourceRequest(
265 |   client: N8nClient,
266 |   uri: string
267 | ): Promise<ReadResourceResponse> {
268 |   const params = matchMyResourceUri(uri);
269 |   if (!params) {
270 |     throw new McpError(
271 |       ErrorCode.InvalidRequest,
272 |       `Invalid URI format: ${uri}`
273 |     );
274 |   }
275 |   
276 |   try {
277 |     // Implement the resource logic using params.id
278 |     // Use the N8nClient to interact with n8n
279 |     
280 |     // Return the response
281 |     return {
282 |       contents: [
283 |         {
284 |           uri,
285 |           mimeType: 'application/json',
286 |           text: JSON.stringify(
287 |             {
288 |               // Resource data with the specific ID
289 |               id: params.id,
290 |               property1: 'value1',
291 |               property2: 'value2'
292 |             },
293 |             null,
294 |             2
295 |           )
296 |         }
297 |       ]
298 |     };
299 |   } catch (error) {
300 |     throw new McpError(
301 |       ErrorCode.InternalError,
302 |       `Failed to retrieve resource: ${error.message}`
303 |     );
304 |   }
305 | }
306 | ```
307 | 
308 | ### 3. Register Resources in the Handler Files
309 | 
310 | Update the resource handler registration:
311 | 
312 | #### For Static Resources
313 | 
314 | ```typescript
315 | // src/resources/static/index.ts
316 | import { MY_RESOURCE_URI, handleMyResourceRequest } from './my-resource.js';
317 | 
318 | export const staticResources = {
319 |   // ... existing static resources
320 |   [MY_RESOURCE_URI]: handleMyResourceRequest
321 | };
322 | ```
323 | 
324 | #### For Dynamic Resources
325 | 
326 | ```typescript
327 | // src/resources/dynamic/index.ts
328 | import { MY_RESOURCE_URI_TEMPLATE, matchMyResourceUri, handleMyResourceRequest } from './my-resource.js';
329 | 
330 | export const dynamicResourceMatchers = [
331 |   // ... existing dynamic resource matchers
332 |   {
333 |     uriTemplate: MY_RESOURCE_URI_TEMPLATE,
334 |     match: matchMyResourceUri,
335 |     handler: handleMyResourceRequest
336 |   }
337 | ];
338 | ```
339 | 
340 | ### 4. Add Resource Listings
341 | 
342 | Update the resource listing functions:
343 | 
344 | ```typescript
345 | // src/resources/index.ts
346 | // Update the resource templates listing
347 | export function getResourceTemplates() {
348 |   return [
349 |     // ... existing templates
350 |     {
351 |       uriTemplate: MY_RESOURCE_URI_TEMPLATE,
352 |       name: 'My Resource',
353 |       description: 'Description of my resource'
354 |     }
355 |   ];
356 | }
357 | 
358 | // Update the static resources listing
359 | export function getStaticResources() {
360 |   return [
361 |     // ... existing resources
362 |     {
363 |       uri: MY_RESOURCE_URI,
364 |       name: 'My Resource List',
365 |       description: 'List of all my resources'
366 |     }
367 |   ];
368 | }
369 | ```
370 | 
371 | ### 5. Add Unit Tests
372 | 
373 | Create tests for your new resource:
374 | 
375 | ```typescript
376 | // tests/unit/resources/static/my-resource.test.ts
377 | // or
378 | // tests/unit/resources/dynamic/my-resource.test.ts
379 | import { describe, it, expect, jest } from '@jest/globals';
380 | import {
381 |   MY_RESOURCE_URI,
382 |   handleMyResourceRequest
383 | } from '../../../../src/resources/static/my-resource.js';
384 | 
385 | describe('My Resource', () => {
386 |   it('should return resource data', async () => {
387 |     const mockClient = {
388 |       // Mock the necessary client methods
389 |     };
390 |     
391 |     const response = await handleMyResourceRequest(mockClient as any);
392 |     
393 |     expect(response.contents).toHaveLength(1);
394 |     expect(response.contents[0].uri).toBe(MY_RESOURCE_URI);
395 |     expect(response.contents[0].mimeType).toBe('application/json');
396 |     
397 |     const data = JSON.parse(response.contents[0].text);
398 |     expect(data).toHaveProperty('property1');
399 |     expect(data).toHaveProperty('property2');
400 |   });
401 |   
402 |   it('should handle errors properly', async () => {
403 |     const mockClient = {
404 |       // Mock client that throws an error
405 |       someMethod: jest.fn().mockRejectedValue(new Error('Test error'))
406 |     };
407 |     
408 |     await expect(handleMyResourceRequest(mockClient as any))
409 |       .rejects
410 |       .toThrow('Failed to retrieve resource');
411 |   });
412 | });
413 | ```
414 | 
415 | ## Extending the API Client
416 | 
417 | If you need to add support for new n8n API features, extend the N8nClient class:
418 | 
419 | ### 1. Add New Methods to the Client
420 | 
421 | ```typescript
422 | // src/api/n8n-client.ts
423 | export class N8nClient {
424 |   // ... existing methods
425 |   
426 |   // Add new methods
427 |   async myNewApiMethod(param1: string): Promise<any> {
428 |     try {
429 |       const response = await this.httpClient.get(`/endpoint/${param1}`);
430 |       return response.data;
431 |     } catch (error) {
432 |       this.handleApiError(error);
433 |     }
434 |   }
435 | }
436 | ```
437 | 
438 | ### 2. Add Type Definitions
439 | 
440 | ```typescript
441 | // src/types/api.ts
442 | // Add types for API responses and requests
443 | export interface MyApiResponse {
444 |   id: string;
445 |   name: string;
446 |   // Other properties
447 | }
448 | 
449 | export interface MyApiRequest {
450 |   param1: string;
451 |   param2?: number;
452 | }
453 | ```
454 | 
455 | ### 3. Add Tests for the New API Methods
456 | 
457 | ```typescript
458 | // tests/unit/api/n8n-client.test.ts
459 | describe('N8nClient', () => {
460 |   // ... existing tests
461 |   
462 |   describe('myNewApiMethod', () => {
463 |     it('should call the correct API endpoint', async () => {
464 |       // Set up mock Axios
465 |       axiosMock.onGet('/endpoint/test').reply(200, {
466 |         id: '123',
467 |         name: 'Test'
468 |       });
469 |       
470 |       const client = new N8nClient({
471 |         apiUrl: 'http://localhost:5678/api/v1',
472 |         apiKey: 'test-api-key'
473 |       });
474 |       
475 |       const result = await client.myNewApiMethod('test');
476 |       
477 |       expect(result).toEqual({
478 |         id: '123',
479 |         name: 'Test'
480 |       });
481 |     });
482 |     
483 |     it('should handle errors correctly', async () => {
484 |       // Set up mock Axios
485 |       axiosMock.onGet('/endpoint/test').reply(404, {
486 |         message: 'Not found'
487 |       });
488 |       
489 |       const client = new N8nClient({
490 |         apiUrl: 'http://localhost:5678/api/v1',
491 |         apiKey: 'test-api-key'
492 |       });
493 |       
494 |       await expect(client.myNewApiMethod('test'))
495 |         .rejects
496 |         .toThrow('Resource not found');
497 |     });
498 |   });
499 | });
500 | ```
501 | 
502 | ## Best Practices for Extensions
503 | 
504 | 1. **Follow the Existing Patterns**: Try to follow the patterns already established in the codebase.
505 | 2. **Type Safety**: Use TypeScript types and interfaces to ensure type safety.
506 | 3. **Error Handling**: Implement comprehensive error handling in all extensions.
507 | 4. **Testing**: Write thorough tests for all new functionality.
508 | 5. **Documentation**: Document your extensions, including JSDoc comments for all public methods.
509 | 6. **Backward Compatibility**: Ensure that your extensions don't break existing functionality.
510 | 
511 | ## Example: Adding Support for n8n Tags
512 | 
513 | Here's a complete example of adding support for n8n tags:
514 | 
515 | ### API Client Extension
516 | 
517 | ```typescript
518 | // src/api/n8n-client.ts
519 | export class N8nClient {
520 |   // ... existing methods
521 |   
522 |   // Add tag methods
523 |   async getTags(): Promise<Tag[]> {
524 |     try {
525 |       const response = await this.httpClient.get('/tags');
526 |       return response.data;
527 |     } catch (error) {
528 |       this.handleApiError(error);
529 |     }
530 |   }
531 |   
532 |   async createTag(data: CreateTagRequest): Promise<Tag> {
533 |     try {
534 |       const response = await this.httpClient.post('/tags', data);
535 |       return response.data;
536 |     } catch (error) {
537 |       this.handleApiError(error);
538 |     }
539 |   }
540 |   
541 |   async deleteTag(id: string): Promise<void> {
542 |     try {
543 |       await this.httpClient.delete(`/tags/${id}`);
544 |     } catch (error) {
545 |       this.handleApiError(error);
546 |     }
547 |   }
548 | }
549 | ```
550 | 
551 | ### Type Definitions
552 | 
553 | ```typescript
554 | // src/types/api.ts
555 | export interface Tag {
556 |   id: string;
557 |   name: string;
558 |   createdAt: string;
559 |   updatedAt: string;
560 | }
561 | 
562 | export interface CreateTagRequest {
563 |   name: string;
564 | }
565 | ```
566 | 
567 | ### Tool Implementations
568 | 
569 | ```typescript
570 | // src/tools/tag/list.ts
571 | export function getTagListToolDefinition(): ToolDefinition {
572 |   return {
573 |     name: 'tag_list',
574 |     description: 'List all tags in n8n',
575 |     inputSchema: {
576 |       type: 'object',
577 |       properties: {},
578 |       required: []
579 |     }
580 |   };
581 | }
582 | 
583 | export async function handleTagList(
584 |   client: N8nClient,
585 |   params: any
586 | ): Promise<ToolCallResponse> {
587 |   try {
588 |     const tags = await client.getTags();
589 |     
590 |     return {
591 |       content: [
592 |         {
593 |           type: 'text',
594 |           text: JSON.stringify(tags, null, 2)
595 |         }
596 |       ]
597 |     };
598 |   } catch (error) {
599 |     return {
600 |       content: [
601 |         {
602 |           type: 'text',
603 |           text: `Error listing tags: ${error.message}`
604 |         }
605 |       ],
606 |       isError: true
607 |     };
608 |   }
609 | }
610 | ```
611 | 
612 | ### Resource Implementation
613 | 
614 | ```typescript
615 | // src/resources/static/tags.ts
616 | export const TAGS_URI = 'n8n://tags';
617 | 
618 | export async function handleTagsRequest(
619 |   client: N8nClient
620 | ): Promise<ReadResourceResponse> {
621 |   try {
622 |     const tags = await client.getTags();
623 |     
624 |     return {
625 |       contents: [
626 |         {
627 |           uri: TAGS_URI,
628 |           mimeType: 'application/json',
629 |           text: JSON.stringify(
630 |             {
631 |               tags,
632 |               count: tags.length
633 |             },
634 |             null,
635 |             2
636 |           )
637 |         }
638 |       ]
639 |     };
640 |   } catch (error) {
641 |     throw new McpError(
642 |       ErrorCode.InternalError,
643 |       `Failed to retrieve tags: ${error.message}`
644 |     );
645 |   }
646 | }
647 | ```
648 | 
649 | ### Integration
650 | 
651 | Register the new tools and resources in the appropriate handler files, and update the main server initialization to include them.
652 | 
653 | By following these patterns, you can extend the n8n MCP Server to support any n8n feature or add custom functionality tailored to your specific needs.
654 | 
```

--------------------------------------------------------------------------------
/docs/examples/advanced-scenarios.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Advanced Scenarios
  2 | 
  3 | This page demonstrates more complex examples of using the n8n MCP Server with AI assistants.
  4 | 
  5 | ## Workflow Analysis and Optimization
  6 | 
  7 | ### User Prompt
  8 | 
  9 | "Analyze my workflows and suggest optimizations."
 10 | 
 11 | ### Assistant Actions
 12 | 
 13 | ```javascript
 14 | // Get all workflows
 15 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
 16 | 
 17 | // Get execution statistics
 18 | const stats = await accessMcpResource('n8n-mcp-server', 'n8n://execution-stats');
 19 | 
 20 | // Analyze each workflow in detail
 21 | const analysisResults = [];
 22 | 
 23 | for (const workflow of workflows) {
 24 |   // Get detailed workflow information
 25 |   const details = await useMcpTool('n8n-mcp-server', 'workflow_get', {
 26 |     id: workflow.id
 27 |   });
 28 |   
 29 |   // Get recent executions
 30 |   const executions = await useMcpTool('n8n-mcp-server', 'execution_list', {
 31 |     workflowId: workflow.id,
 32 |     limit: 10
 33 |   });
 34 |   
 35 |   // Analyze workflow structure
 36 |   const nodeCount = details.nodes.length;
 37 |   const nodeTypes = new Set(details.nodes.map(node => node.type));
 38 |   
 39 |   // Check for error patterns in executions
 40 |   const errorRate = executions.length > 0 
 41 |     ? executions.filter(e => e.status === "error").length / executions.length 
 42 |     : 0;
 43 |   
 44 |   // Check execution times
 45 |   const executionTimes = executions
 46 |     .filter(e => e.status === "success" && e.finishedAt && e.startedAt)
 47 |     .map(e => new Date(e.finishedAt) - new Date(e.startedAt));
 48 |   
 49 |   const avgExecutionTime = executionTimes.length > 0
 50 |     ? executionTimes.reduce((sum, time) => sum + time, 0) / executionTimes.length / 1000
 51 |     : 0;
 52 |   
 53 |   // Generate optimization suggestions
 54 |   const suggestions = [];
 55 |   
 56 |   if (nodeCount > 15) {
 57 |     suggestions.push("Consider splitting into multiple workflows for better maintainability");
 58 |   }
 59 |   
 60 |   if (errorRate > 0.2) {
 61 |     suggestions.push("High error rate detected - review error handling and input validation");
 62 |   }
 63 |   
 64 |   if (avgExecutionTime > 30) {
 65 |     suggestions.push("Long execution time - check for bottlenecks or inefficient operations");
 66 |   }
 67 |   
 68 |   if (nodeTypes.has("n8n-nodes-base.httpRequest") && !nodeTypes.has("n8n-nodes-base.errorTrigger")) {
 69 |     suggestions.push("HTTP requests without error handling - consider adding error triggers");
 70 |   }
 71 |   
 72 |   analysisResults.push({
 73 |     id: workflow.id,
 74 |     name: workflow.name,
 75 |     nodeCount,
 76 |     errorRate,
 77 |     avgExecutionTime,
 78 |     suggestions
 79 |   });
 80 | }
 81 | 
 82 | // Format and present the analysis
 83 | let response = "# Workflow Analysis and Optimization Suggestions\n\n";
 84 | 
 85 | analysisResults.forEach(result => {
 86 |   response += `## ${result.name}\n\n`;
 87 |   response += `**Nodes:** ${result.nodeCount}\n`;
 88 |   response += `**Error Rate:** ${(result.errorRate * 100).toFixed(1)}%\n`;
 89 |   response += `**Avg. Execution Time:** ${result.avgExecutionTime.toFixed(1)} seconds\n\n`;
 90 |   
 91 |   if (result.suggestions.length > 0) {
 92 |     response += "**Optimization Suggestions:**\n";
 93 |     result.suggestions.forEach(suggestion => {
 94 |       response += `- ${suggestion}\n`;
 95 |     });
 96 |   } else {
 97 |     response += "No specific optimization suggestions for this workflow.\n";
 98 |   }
 99 |   
100 |   response += "\n";
101 | });
102 | 
103 | return response;
104 | ```
105 | 
106 | ## Scheduled Workflow Management
107 | 
108 | ### User Prompt
109 | 
110 | "Update all my scheduled workflows to run one hour later."
111 | 
112 | ### Assistant Actions
113 | 
114 | ```javascript
115 | // Get all workflows
116 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
117 | 
118 | // Filter for scheduled workflows (those with a Schedule node)
119 | const scheduledWorkflows = [];
120 | 
121 | for (const workflow of workflows) {
122 |   // Get detailed workflow information
123 |   const details = await useMcpTool('n8n-mcp-server', 'workflow_get', {
124 |     id: workflow.id
125 |   });
126 |   
127 |   // Look for Schedule Trigger nodes
128 |   const scheduleNodes = details.nodes.filter(node => 
129 |     node.type === "n8n-nodes-base.scheduleTrigger" || 
130 |     node.type === "n8n-nodes-base.cron"
131 |   );
132 |   
133 |   if (scheduleNodes.length > 0) {
134 |     scheduledWorkflows.push({
135 |       workflow: details,
136 |       scheduleNodes
137 |     });
138 |   }
139 | }
140 | 
141 | if (scheduledWorkflows.length === 0) {
142 |   return "I couldn't find any workflows with scheduled triggers.";
143 | }
144 | 
145 | // Process each scheduled workflow
146 | const results = [];
147 | 
148 | for (const { workflow, scheduleNodes } of scheduledWorkflows) {
149 |   // Create a copy of the workflow for modification
150 |   const updatedWorkflow = { ...workflow };
151 |   
152 |   // Update each schedule node
153 |   for (const scheduleNode of scheduleNodes) {
154 |     const nodeIndex = updatedWorkflow.nodes.findIndex(n => n.id === scheduleNode.id);
155 |     
156 |     if (nodeIndex === -1) continue;
157 |     
158 |     // Copy the node for modification
159 |     const updatedNode = { ...updatedWorkflow.nodes[nodeIndex] };
160 |     
161 |     // Handle different types of schedule configurations
162 |     if (updatedNode.type === "n8n-nodes-base.scheduleTrigger") {
163 |       if (updatedNode.parameters.cronExpression) {
164 |         // Modify cron expression to run 1 hour later
165 |         const cronParts = updatedNode.parameters.cronExpression.split(' ');
166 |         if (cronParts.length === 5) {
167 |           // Standard cron format: minute hour day month dayOfWeek
168 |           const hour = parseInt(cronParts[1], 10);
169 |           cronParts[1] = ((hour + 1) % 24).toString();
170 |           updatedNode.parameters.cronExpression = cronParts.join(' ');
171 |         }
172 |       } else if (updatedNode.parameters.timeToRepeat) {
173 |         // Handle specific time scheduling
174 |         const time = updatedNode.parameters.timeToRepeat;
175 |         if (time && time.split(':').length === 2) {
176 |           const [hours, minutes] = time.split(':').map(part => parseInt(part, 10));
177 |           const newHours = (hours + 1) % 24;
178 |           updatedNode.parameters.timeToRepeat = `${newHours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
179 |         }
180 |       }
181 |     } else if (updatedNode.type === "n8n-nodes-base.cron") {
182 |       // Similar handling for cron node
183 |       if (updatedNode.parameters.cronExpression) {
184 |         const cronParts = updatedNode.parameters.cronExpression.split(' ');
185 |         if (cronParts.length === 5) {
186 |           const hour = parseInt(cronParts[1], 10);
187 |           cronParts[1] = ((hour + 1) % 24).toString();
188 |           updatedNode.parameters.cronExpression = cronParts.join(' ');
189 |         }
190 |       }
191 |     }
192 |     
193 |     // Update the node in the workflow
194 |     updatedWorkflow.nodes[nodeIndex] = updatedNode;
195 |   }
196 |   
197 |   // Save the updated workflow
198 |   const result = await useMcpTool('n8n-mcp-server', 'workflow_update', {
199 |     id: workflow.id,
200 |     nodes: updatedWorkflow.nodes,
201 |     connections: updatedWorkflow.connections
202 |   });
203 |   
204 |   results.push({
205 |     name: workflow.name,
206 |     updated: result.updatedAt
207 |   });
208 | }
209 | 
210 | // Format and present the results
211 | let response = "# Updated Scheduled Workflows\n\n";
212 | response += "I've updated the following workflows to run one hour later:\n\n";
213 | 
214 | results.forEach(result => {
215 |   response += `- **${result.name}** (Updated: ${new Date(result.updated).toLocaleString()})\n`;
216 | });
217 | 
218 | return response;
219 | ```
220 | 
221 | ## Error Handling and Notification System
222 | 
223 | ### User Prompt
224 | 
225 | "Set up a notification system for any workflow that fails."
226 | 
227 | ### Assistant Actions
228 | 
229 | ```javascript
230 | // This scenario demonstrates creating a monitoring workflow that checks for failures
231 | // and sends notifications
232 | 
233 | // First, check if a monitoring workflow already exists
234 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
235 | const existingMonitor = workflows.find(workflow => 
236 |   workflow.name.toLowerCase().includes('workflow monitor') || 
237 |   workflow.name.toLowerCase().includes('error notification')
238 | );
239 | 
240 | if (existingMonitor) {
241 |   return `You already have a monitoring workflow: "${existingMonitor.name}" (ID: ${existingMonitor.id}). Would you like me to update it instead?`;
242 | }
243 | 
244 | // Create a new monitoring workflow
245 | const monitorWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
246 |   name: "Workflow Error Notification System",
247 |   active: false, // Start inactive until configured
248 |   nodes: [
249 |     {
250 |       name: "Schedule Trigger",
251 |       type: "n8n-nodes-base.scheduleTrigger",
252 |       position: [100, 300],
253 |       parameters: {
254 |         cronExpression: "*/15 * * * *" // Run every 15 minutes
255 |       }
256 |     },
257 |     {
258 |       name: "Get Failed Executions",
259 |       type: "n8n-nodes-base.n8n",
260 |       position: [300, 300],
261 |       parameters: {
262 |         resource: "execution",
263 |         operation: "getAll",
264 |         filters: {
265 |           status: "error",
266 |           // Look for executions in the last 15 minutes
267 |           finished: {
268 |             $gt: "={{$now.minus({ minutes: 15 }).toISOString()}}"
269 |           }
270 |         }
271 |       }
272 |     },
273 |     {
274 |       name: "Filter Empty",
275 |       type: "n8n-nodes-base.filter",
276 |       position: [500, 300],
277 |       parameters: {
278 |         conditions: {
279 |           boolean: [
280 |             {
281 |               value1: "={{ $json.length > 0 }}",
282 |               operation: "equal",
283 |               value2: true
284 |             }
285 |           ]
286 |         }
287 |       }
288 |     },
289 |     {
290 |       name: "Format Notification",
291 |       type: "n8n-nodes-base.function",
292 |       position: [700, 300],
293 |       parameters: {
294 |         functionCode: `
295 | // Function to format error notifications
296 | const executions = items;
297 | const now = new Date();
298 | 
299 | // Group by workflow
300 | const workflowErrors = {};
301 | for (const execution of executions) {
302 |   const workflowId = execution.workflowId;
303 |   const workflowName = execution.workflowData.name;
304 |   
305 |   if (!workflowErrors[workflowId]) {
306 |     workflowErrors[workflowId] = {
307 |       name: workflowName,
308 |       errors: []
309 |     };
310 |   }
311 |   
312 |   workflowErrors[workflowId].errors.push({
313 |     id: execution.id,
314 |     time: execution.finished,
315 |     error: execution.error?.message || "Unknown error"
316 |   });
317 | }
318 | 
319 | // Create notification text
320 | let notificationText = "⚠️ Workflow Error Alert ⚠️\\n\\n";
321 | notificationText += "The following workflows have failed:\\n\\n";
322 | 
323 | for (const [workflowId, data] of Object.entries(workflowErrors)) {
324 |   notificationText += \`👉 \${data.name} (ID: \${workflowId})\\n\`;
325 |   notificationText += \`   Failed executions: \${data.errors.length}\\n\`;
326 |   
327 |   // Add details about each failure
328 |   data.errors.forEach(error => {
329 |     const time = new Date(error.time).toLocaleString();
330 |     notificationText += \`   - \${time}: \${error.error}\\n\`;
331 |   });
332 |   
333 |   notificationText += "\\n";
334 | }
335 | 
336 | notificationText += "Check your n8n dashboard for more details.";
337 | 
338 | return [{
339 |   json: {
340 |     text: notificationText,
341 |     subject: \`n8n Alert: \${Object.keys(workflowErrors).length} Workflow(s) Failed\`,
342 |     timestamp: now.toISOString()
343 |   }
344 | }];
345 | `
346 |       }
347 |     },
348 |     {
349 |       name: "Send Email",
350 |       type: "n8n-nodes-base.emailSend",
351 |       position: [900, 300],
352 |       parameters: {
353 |         to: "{{$env.EMAIL_RECIPIENT}}", // Will need to be configured
354 |         subject: "{{$json.subject}}",
355 |         text: "{{$json.text}}"
356 |       }
357 |     }
358 |   ],
359 |   connections: {
360 |     "Schedule Trigger": {
361 |       main: [
362 |         [
363 |           {
364 |             node: "Get Failed Executions",
365 |             type: "main",
366 |             index: 0
367 |           }
368 |         ]
369 |       ]
370 |     },
371 |     "Get Failed Executions": {
372 |       main: [
373 |         [
374 |           {
375 |             node: "Filter Empty",
376 |             type: "main",
377 |             index: 0
378 |           }
379 |         ]
380 |       ]
381 |     },
382 |     "Filter Empty": {
383 |       main: [
384 |         [
385 |           {
386 |             node: "Format Notification",
387 |             type: "main",
388 |             index: 0
389 |           }
390 |         ]
391 |       ]
392 |     },
393 |     "Format Notification": {
394 |       main: [
395 |         [
396 |           {
397 |             node: "Send Email",
398 |             type: "main",
399 |             index: 0
400 |           }
401 |         ]
402 |       ]
403 |     }
404 |   }
405 | });
406 | 
407 | return `
408 | # Workflow Error Notification System Created
409 | 
410 | I've created a new workflow that will monitor for failed executions and send email notifications.
411 | 
412 | **Workflow ID:** ${monitorWorkflow.id}
413 | 
414 | ## Configuration Needed:
415 | 1. The workflow is currently **inactive** - you'll need to activate it once configured
416 | 2. Set up the email configuration in n8n settings
417 | 3. Configure the "Send Email" node with your recipient email address
418 | 
419 | ## How It Works:
420 | - Runs every 15 minutes
421 | - Checks for any workflows that failed in the last 15 minutes
422 | - Groups failures by workflow and formats a notification
423 | - Sends an email with details about the failures
424 | 
425 | You can customize the notification format, schedule, and notification method by editing the workflow in the n8n interface.
426 | `;
427 | ```
428 | 
429 | ## Data Migration Between Workflows
430 | 
431 | ### User Prompt
432 | 
433 | "Help me move data from my 'Legacy CRM' workflow to my 'New CRM' workflow."
434 | 
435 | ### Assistant Actions
436 | 
437 | ```javascript
438 | // Get all workflows
439 | const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
440 | 
441 | // Find the source and target workflows
442 | const legacyWorkflow = workflows.find(w => w.name.toLowerCase().includes('legacy crm'));
443 | const newWorkflow = workflows.find(w => w.name.toLowerCase().includes('new crm'));
444 | 
445 | if (!legacyWorkflow) {
446 |   return "I couldn't find a workflow with 'Legacy CRM' in the name. Please check the exact name of your source workflow.";
447 | }
448 | 
449 | if (!newWorkflow) {
450 |   return "I couldn't find a workflow with 'New CRM' in the name. Please check the exact name of your target workflow.";
451 | }
452 | 
453 | // Create a data migration workflow
454 | const migrationWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
455 |   name: "Data Migration: Legacy CRM to New CRM",
456 |   active: false, // Start inactive until confirmed
457 |   nodes: [
458 |     {
459 |       name: "Manual Trigger",
460 |       type: "n8n-nodes-base.manualTrigger",
461 |       position: [100, 300],
462 |       parameters: {}
463 |     },
464 |     {
465 |       name: "Execute Legacy Workflow",
466 |       type: "n8n-nodes-base.executeWorkflow",
467 |       position: [300, 300],
468 |       parameters: {
469 |         workflowId: legacyWorkflow.id,
470 |         options: {
471 |           includeData: true
472 |         }
473 |       }
474 |     },
475 |     {
476 |       name: "Transform Data",
477 |       type: "n8n-nodes-base.function",
478 |       position: [500, 300],
479 |       parameters: {
480 |         functionCode: `
481 | // This is a placeholder transformation function that you'll need to customize
482 | // based on the actual data structure of your workflows
483 | 
484 | const legacyData = items;
485 | const transformedItems = [];
486 | 
487 | // Example transformation (modify based on your data structures)
488 | for (const item of legacyData) {
489 |   transformedItems.push({
490 |     json: {
491 |       // Map legacy fields to new fields
492 |       customer_id: item.json.id,
493 |       customer_name: item.json.fullName || \`\${item.json.firstName || ''} \${item.json.lastName || ''}\`.trim(),
494 |       email: item.json.emailAddress || item.json.email,
495 |       phone: item.json.phoneNumber || item.json.phone,
496 |       notes: item.json.comments || item.json.notes || '',
497 |       // Add migration metadata
498 |       migrated_from_legacy: true,
499 |       migration_date: new Date().toISOString()
500 |     }
501 |   });
502 | }
503 | 
504 | return transformedItems;
505 | `
506 |       }
507 |     },
508 |     {
509 |       name: "Execute New Workflow",
510 |       type: "n8n-nodes-base.executeWorkflow",
511 |       position: [700, 300],
512 |       parameters: {
513 |         workflowId: newWorkflow.id,
514 |         options: {
515 |           includeData: true
516 |         }
517 |       }
518 |     },
519 |     {
520 |       name: "Migration Summary",
521 |       type: "n8n-nodes-base.function",
522 |       position: [900, 300],
523 |       parameters: {
524 |         functionCode: `
525 | // Create a summary of the migration
526 | const results = items;
527 | const totalItems = items.length;
528 | const successItems = items.filter(item => !item.json.error).length;
529 | const errorItems = totalItems - successItems;
530 | 
531 | return [
532 |   {
533 |     json: {
534 |       summary: "Migration Complete",
535 |       total_records: totalItems,
536 |       successful_records: successItems,
537 |       failed_records: errorItems,
538 |       completion_time: new Date().toISOString()
539 |     }
540 |   }
541 | ];
542 | `
543 |       }
544 |     }
545 |   ],
546 |   connections: {
547 |     "Manual Trigger": {
548 |       main: [
549 |         [
550 |           {
551 |             node: "Execute Legacy Workflow",
552 |             type: "main",
553 |             index: 0
554 |           }
555 |         ]
556 |       ]
557 |     },
558 |     "Execute Legacy Workflow": {
559 |       main: [
560 |         [
561 |           {
562 |             node: "Transform Data",
563 |             type: "main",
564 |             index: 0
565 |           }
566 |         ]
567 |       ]
568 |     },
569 |     "Transform Data": {
570 |       main: [
571 |         [
572 |           {
573 |             node: "Execute New Workflow",
574 |             type: "main",
575 |             index: 0
576 |           }
577 |         ]
578 |       ]
579 |     },
580 |     "Execute New Workflow": {
581 |       main: [
582 |         [
583 |           {
584 |             node: "Migration Summary",
585 |             type: "main",
586 |             index: 0
587 |           }
588 |         ]
589 |       ]
590 |     }
591 |   }
592 | });
593 | 
594 | return `
595 | # Data Migration Workflow Created
596 | 
597 | I've created a new workflow to migrate data from "${legacyWorkflow.name}" to "${newWorkflow.name}".
598 | 
599 | **Migration Workflow ID:** ${migrationWorkflow.id}
600 | 
601 | ## Important Notes:
602 | 1. The workflow is currently **inactive** - activate it only when you're ready to perform the migration
603 | 2. The data transformation is a placeholder - you'll need to edit the "Transform Data" function node to map fields correctly based on your specific data structures
604 | 3. This is a one-time migration workflow - run it manually when you're ready to migrate the data
605 | 
606 | ## Next Steps:
607 | 1. Open the workflow in the n8n interface
608 | 2. Edit the "Transform Data" function to correctly map your data fields
609 | 3. Test the workflow with a small sample if possible
610 | 4. Activate and run the workflow to perform the migration
611 | 5. Check the migration summary for results
612 | 
613 | Would you like me to help you customize the data transformation based on the specific fields in your CRM workflows?
614 | `;
615 | ```
616 | 
617 | These examples demonstrate more advanced usage patterns for the n8n MCP Server. For integration with other systems, see the [Integration Examples](./integration-examples.md) page.
618 | 
```
Page 2/3FirstPrevNextLast