This is page 1 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/.gitkeep: -------------------------------------------------------------------------------- ``` 1 | ``` -------------------------------------------------------------------------------- /src/.gitkeep: -------------------------------------------------------------------------------- ``` 1 | ``` -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- ``` 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { "targets": { "node": "current" } }], 4 | "@babel/preset-typescript" 5 | ] 6 | } 7 | ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` 1 | # n8n MCP Server Environment Variables 2 | 3 | # Required: URL of the n8n API (e.g., http://localhost:5678/api/v1) 4 | N8N_API_URL=http://localhost:5678/api/v1 5 | 6 | # Required: API key for authenticating with n8n 7 | # Generate this in the n8n UI under Settings > API > API Keys 8 | N8N_API_KEY=your_n8n_api_key_here 9 | 10 | # Optional: Set to 'true' to enable debug logging 11 | DEBUG=false 12 | ``` -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "root": true, 11 | "rules": { 12 | "@typescript-eslint/no-explicit-any": "off", 13 | "@typescript-eslint/no-unused-vars": [ 14 | "error", 15 | { 16 | "argsIgnorePattern": "^_", 17 | "varsIgnorePattern": "^_" 18 | } 19 | ] 20 | } 21 | } 22 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Dependency directories 2 | node_modules/ 3 | coverage/ 4 | 5 | # Environment variables 6 | .env 7 | .env.local 8 | .env.development.local 9 | .env.test.local 10 | .env.production.local 11 | 12 | # Build outputs 13 | dist/ 14 | build/ 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # IDE - VSCode 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | 30 | # macOS 31 | .DS_Store 32 | 33 | # Misc 34 | .npm 35 | .eslintcache 36 | .yarn-integrity 37 | 38 | # CRCT System 39 | cline_docs/ 40 | strategy_tasks/ 41 | Previous_versions/ 42 | --output 43 | test-output-summary.md 44 | 45 | # Python 46 | __pycache__/ 47 | *.py[cod] 48 | *$py.class 49 | *.so 50 | .Python 51 | venv/ 52 | ENV/ 53 | env/ 54 | .env/ 55 | .venv/ 56 | *.egg-info/ 57 | dist/ 58 | build/ 59 | 60 | # Embeddings 61 | *.embedding 62 | embeddings/ 63 | mcp-config.json ``` -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Testing System for n8n MCP Server 2 | 3 | This directory contains the testing framework and tests for the n8n MCP Server project. The tests are organized in a hierarchical structure to match the project's architecture. 4 | 5 | ## Test Structure 6 | 7 | - **unit/**: Unit tests for individual components 8 | - **api/**: Tests for API clients and services 9 | - **config/**: Tests for configuration handling 10 | - **errors/**: Tests for error handling 11 | - **resources/**: Tests for MCP resource handlers 12 | - **dynamic/**: Tests for dynamic resource handlers 13 | - **static/**: Tests for static resource handlers 14 | - **tools/**: Tests for MCP tool handlers 15 | - **workflow/**: Tests for workflow-related tools 16 | - **execution/**: Tests for execution-related tools 17 | - **utils/**: Tests for utility functions 18 | 19 | - **integration/**: Integration tests for component interactions 20 | - Tests that verify multiple components work together correctly 21 | 22 | - **e2e/**: End-to-end tests for full server functionality 23 | - Tests that simulate real-world usage scenarios 24 | 25 | - **mocks/**: Mock data and utilities for testing 26 | - Reusable mock data and functions shared across tests 27 | 28 | ## Running Tests 29 | 30 | The project uses Jest as the test runner with ESM support. The following npm scripts are available: 31 | 32 | ```bash 33 | # Run all tests 34 | npm test 35 | 36 | # Run tests in watch mode (useful during development) 37 | npm run test:watch 38 | 39 | # Run tests with coverage report 40 | npm run test:coverage 41 | 42 | # Run specific test file(s) 43 | npm test -- tests/unit/api/client.test.ts 44 | 45 | # Run tests matching a specific pattern 46 | npm test -- -t "should format and return workflows" 47 | ``` 48 | 49 | ## Writing Tests 50 | 51 | ### Test File Naming Convention 52 | 53 | - All test files should end with `.test.ts` 54 | - Test files should be placed in the same directory structure as the source files they test 55 | 56 | ### Test Organization 57 | 58 | Each test file should follow this structure: 59 | 60 | ```typescript 61 | /** 62 | * Description of what's being tested 63 | */ 64 | 65 | import '@jest/globals'; 66 | import { ComponentToTest } from '../../../src/path/to/component.js'; 67 | // Import other dependencies and mocks 68 | 69 | // Mock dependencies 70 | jest.mock('../../../src/path/to/dependency.js'); 71 | 72 | describe('ComponentName', () => { 73 | // Setup and teardown 74 | beforeEach(() => { 75 | // Common setup 76 | }); 77 | 78 | afterEach(() => { 79 | // Common cleanup 80 | }); 81 | 82 | describe('methodName', () => { 83 | it('should do something specific', () => { 84 | // Arrange 85 | // ... 86 | 87 | // Act 88 | // ... 89 | 90 | // Assert 91 | expect(result).toBe(expectedValue); 92 | }); 93 | 94 | // More test cases... 95 | }); 96 | 97 | // More method tests... 98 | }); 99 | ``` 100 | 101 | ### Testing Utilities 102 | 103 | The project provides several testing utilities: 104 | 105 | - **test-setup.ts**: Common setup for all tests 106 | - **mocks/axios-mock.ts**: Utilities for mocking Axios HTTP requests 107 | - **mocks/n8n-fixtures.ts**: Mock data for n8n API responses 108 | 109 | ## Best Practices 110 | 111 | 1. **Isolation**: Each test should be independent and not rely on other tests 112 | 2. **Mock Dependencies**: External dependencies should be mocked 113 | 3. **Descriptive Names**: Use descriptive test and describe names 114 | 4. **Arrange-Act-Assert**: Structure your tests with clear sections 115 | 5. **Coverage**: Aim for high test coverage, especially for critical paths 116 | 6. **Readability**: Write clear, readable tests that serve as documentation 117 | 118 | ## Extending the Test Suite 119 | 120 | When adding new functionality to the project: 121 | 122 | 1. Create corresponding test files in the appropriate directory 123 | 2. Use existing mocks and utilities when possible 124 | 3. Create new mock data in `mocks/` for reusability 125 | 4. Update this README if you add new testing patterns or utilities 126 | 127 | ## Troubleshooting 128 | 129 | If you encounter issues running the tests: 130 | 131 | - Ensure you're using Node.js 20 or later 132 | - Run `npm install` to ensure all dependencies are installed 133 | - Check for ESM compatibility issues if importing CommonJS modules 134 | - Use `console.log` or `console.error` for debugging (removed in production) 135 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # n8n MCP Server 2 | 3 | [](https://badge.fury.io/js/%40leonardsellem%2Fn8n-mcp-server) 4 | 5 | A Model Context Protocol (MCP) server that allows AI assistants to interact with n8n workflows through natural language. 6 | 7 | ## Overview 8 | 9 | This project provides a Model Context Protocol (MCP) server that empowers AI assistants to seamlessly interact with n8n, a popular workflow automation tool. It acts as a bridge, enabling AI assistants to programmatically manage and control n8n workflows and executions using natural language commands. 10 | 11 | ## Installation 12 | 13 | ### Prerequisites 14 | 15 | - Node.js 20 or later 16 | - n8n instance with API access enabled 17 | 18 | ### Install from npm 19 | 20 | ```bash 21 | npm install -g @leonardsellem/n8n-mcp-server 22 | ``` 23 | 24 | ### Install from source 25 | 26 | ```bash 27 | # Clone the repository 28 | git clone https://github.com/leonardsellem/n8n-mcp-server.git 29 | cd n8n-mcp-server 30 | 31 | # Install dependencies 32 | npm install 33 | 34 | # Build the project 35 | npm run build 36 | 37 | # Optional: Install globally 38 | npm install -g . 39 | ``` 40 | 41 | ### Docker Installation 42 | 43 | You can also run the server using Docker: 44 | 45 | ```bash 46 | # Pull the image 47 | docker pull leonardsellem/n8n-mcp-server 48 | 49 | # Run the container with your n8n API configuration 50 | docker run -e N8N_API_URL=http://your-n8n:5678/api/v1 \ 51 | -e N8N_API_KEY=your_n8n_api_key \ 52 | -e N8N_WEBHOOK_USERNAME=username \ 53 | -e N8N_WEBHOOK_PASSWORD=password \ 54 | leonardsellem/n8n-mcp-server 55 | ``` 56 | 57 | ## Updating the Server 58 | 59 | How you update the server depends on how you initially installed it. 60 | 61 | ### 1. Installed globally via npm 62 | 63 | If you installed the server using `npm install -g @leonardsellem/n8n-mcp-server`: 64 | 65 | 1. Open your terminal or command prompt. 66 | 2. Run the following command to get the latest version: 67 | ```bash 68 | npm install -g @leonardsellem/n8n-mcp-server@latest 69 | ``` 70 | 3. If the server is currently running (e.g., as a background process or service), you'll need to restart it for the changes to take effect. 71 | 72 | ### 2. Installed from source 73 | 74 | If you cloned the repository and installed from source: 75 | 76 | 1. Open your terminal or command prompt. 77 | 2. Navigate to the directory where you cloned the project: 78 | ```bash 79 | cd path/to/n8n-mcp-server 80 | ``` 81 | 3. If you've made any local changes to the code that you want to keep, consider stashing them (optional): 82 | ```bash 83 | git stash 84 | ``` 85 | You can apply them later with `git stash pop`. 86 | 4. Pull the latest changes from the repository (assuming you are on the `main` branch): 87 | ```bash 88 | git pull origin main 89 | ``` 90 | If you are on a different branch, replace `main` with your branch name. 91 | 5. Install or update any changed dependencies: 92 | ```bash 93 | npm install 94 | ``` 95 | 6. Rebuild the project to include the latest updates: 96 | ```bash 97 | npm run build 98 | ``` 99 | 7. If you previously installed it globally from this source folder using `npm install -g .`, you might want to run this command again to update the global link: 100 | ```bash 101 | npm install -g . 102 | ``` 103 | 8. Restart the server. 104 | * If you run the server directly using a command like `node build/index.js` in your AI assistant's MCP configuration, ensure the path is still correct. Using `npm install -g .` and then `n8n-mcp-server` as the command should keep this consistent. 105 | 106 | ### 3. Using Docker 107 | 108 | If you are running the server using Docker: 109 | 110 | 1. Pull the latest image from Docker Hub: 111 | ```bash 112 | docker pull leonardsellem/n8n-mcp-server:latest 113 | ``` 114 | 2. Stop and remove your old container. You'll need your container's name or ID (you can find it using `docker ps`): 115 | ```bash 116 | docker stop <your_container_name_or_id> 117 | docker rm <your_container_name_or_id> 118 | ``` 119 | 3. Start a new container with the updated image. Use the same `docker run` command you used previously, including all your necessary environment variables (refer to the "Docker Installation" section for an example command). For instance: 120 | ```bash 121 | docker run -e N8N_API_URL=http://your-n8n:5678/api/v1 \ 122 | -e N8N_API_KEY=your_n8n_api_key \ 123 | -e N8N_WEBHOOK_USERNAME=username \ 124 | -e N8N_WEBHOOK_PASSWORD=password \ 125 | leonardsellem/n8n-mcp-server:latest 126 | ``` 127 | Ensure you use `:latest` or the specific version tag you intend to run. 128 | 129 | ## Configuration 130 | 131 | Create a `.env` file in the directory where you'll run the server, using `.env.example` as a template: 132 | 133 | ```bash 134 | cp .env.example .env 135 | ``` 136 | 137 | Configure the following environment variables: 138 | 139 | | Variable | Description | Example | 140 | |----------|-------------|---------| 141 | | `N8N_API_URL` | Full URL of the n8n API, including `/api/v1` | `http://localhost:5678/api/v1` | 142 | | `N8N_API_KEY` | API key for authenticating with n8n | `n8n_api_...` | 143 | | `N8N_WEBHOOK_USERNAME` | Username for webhook authentication (if using webhooks) | `username` | 144 | | `N8N_WEBHOOK_PASSWORD` | Password for webhook authentication | `password` | 145 | | `DEBUG` | Enable debug logging (optional) | `true` or `false` | 146 | 147 | ### Generating an n8n API Key 148 | 149 | 1. Open your n8n instance in a browser 150 | 2. Go to Settings > API > API Keys 151 | 3. Create a new API key with appropriate permissions 152 | 4. Copy the key to your `.env` file 153 | 154 | ## Usage 155 | 156 | ### Running the Server 157 | 158 | From the installation directory: 159 | 160 | ```bash 161 | n8n-mcp-server 162 | ``` 163 | 164 | Or if installed globally: 165 | 166 | ```bash 167 | n8n-mcp-server 168 | ``` 169 | 170 | ### Integrating with AI Assistants 171 | 172 | After building the server (`npm run build`), you need to configure your AI assistant (like VS Code with the Claude extension or the Claude Desktop app) to run it. This typically involves editing a JSON configuration file. 173 | 174 | **Example Configuration (e.g., in VS Code `settings.json` or Claude Desktop `claude_desktop_config.json`):** 175 | 176 | ```json 177 | { 178 | "mcpServers": { 179 | // Give your server a unique name 180 | "n8n-local": { 181 | // Use 'node' to execute the built JavaScript file 182 | "command": "node", 183 | // Provide the *absolute path* to the built index.js file 184 | "args": [ 185 | "/path/to/your/cloned/n8n-mcp-server/build/index.js" 186 | // On Windows, use double backslashes: 187 | // "C:\\path\\to\\your\\cloned\\n8n-mcp-server\\build\\index.js" 188 | ], 189 | // Environment variables needed by the server 190 | "env": { 191 | "N8N_API_URL": "http://your-n8n-instance:5678/api/v1", // Replace with your n8n URL 192 | "N8N_API_KEY": "YOUR_N8N_API_KEY", // Replace with your key 193 | // Add webhook credentials only if you plan to use webhook tools 194 | // "N8N_WEBHOOK_USERNAME": "your_webhook_user", 195 | // "N8N_WEBHOOK_PASSWORD": "your_webhook_password" 196 | }, 197 | // Ensure the server is enabled 198 | "disabled": false, 199 | // Default autoApprove settings 200 | "autoApprove": [] 201 | } 202 | // ... other servers might be configured here 203 | } 204 | } 205 | ``` 206 | 207 | **Key Points:** 208 | 209 | * Replace `/path/to/your/cloned/n8n-mcp-server/` with the actual absolute path where you cloned and built the repository. 210 | * Use the correct path separator for your operating system (forward slashes `/` for macOS/Linux, double backslashes `\\` for Windows). 211 | * Ensure you provide the correct `N8N_API_URL` (including `/api/v1`) and `N8N_API_KEY`. 212 | * The server needs to be built (`npm run build`) before the assistant can run the `build/index.js` file. 213 | 214 | ## Available Tools 215 | 216 | The server provides the following tools: 217 | 218 | ### Using Webhooks 219 | 220 | This MCP server supports executing workflows through n8n webhooks. To use this functionality: 221 | 222 | 1. Create a webhook-triggered workflow in n8n. 223 | 2. Set up Basic Authentication on your webhook node. 224 | 3. Use the `run_webhook` tool to trigger the workflow, passing just the workflow name. 225 | 226 | Example: 227 | ```javascript 228 | const result = await useRunWebhook({ 229 | workflowName: "hello-world", // Will call <n8n-url>/webhook/hello-world 230 | data: { 231 | prompt: "Hello from AI assistant!" 232 | } 233 | }); 234 | ``` 235 | 236 | The webhook authentication is handled automatically using the `N8N_WEBHOOK_USERNAME` and `N8N_WEBHOOK_PASSWORD` environment variables. 237 | 238 | ### Workflow Management 239 | 240 | - `workflow_list`: List all workflows 241 | - `workflow_get`: Get details of a specific workflow 242 | - `workflow_create`: Create a new workflow 243 | - `workflow_update`: Update an existing workflow 244 | - `workflow_delete`: Delete a workflow 245 | - `workflow_activate`: Activate a workflow 246 | - `workflow_deactivate`: Deactivate a workflow 247 | 248 | ### Execution Management 249 | 250 | - `execution_run`: Execute a workflow via the API 251 | - `run_webhook`: Execute a workflow via a webhook 252 | - `execution_get`: Get details of a specific execution 253 | - `execution_list`: List executions for a workflow 254 | - `execution_stop`: Stop a running execution 255 | 256 | ## Resources 257 | 258 | The server provides the following resources: 259 | 260 | - `n8n://workflows/list`: List of all workflows 261 | - `n8n://workflow/{id}`: Details of a specific workflow 262 | - `n8n://executions/{workflowId}`: List of executions for a workflow 263 | - `n8n://execution/{id}`: Details of a specific execution 264 | 265 | ## Roadmap 266 | 267 | The n8n MCP Server is a community-driven project, and its future direction will be shaped by your feedback and contributions! 268 | 269 | Currently, our roadmap is flexible and under continuous development. We believe in evolving the server based on the needs and ideas of our users. 270 | 271 | We encourage you to get involved in shaping the future of this tool: 272 | 273 | - **Suggest Features:** Have an idea for a new tool, resource, or improvement? 274 | - **Discuss Priorities:** Want to weigh in on what we should focus on next? 275 | 276 | Please share your thoughts, feature requests, and ideas by opening an issue on our [GitHub Issues page](https://github.com/leonardsellem/n8n-mcp-server/issues). Let's build a powerful tool for AI assistants together! 277 | 278 | ## Development 279 | 280 | ### Building 281 | 282 | ```bash 283 | npm run build 284 | ``` 285 | 286 | ### Running in Development Mode 287 | 288 | ```bash 289 | npm run dev 290 | ``` 291 | 292 | ### Testing 293 | 294 | ```bash 295 | npm test 296 | ``` 297 | 298 | ### Linting 299 | 300 | ```bash 301 | npm run lint 302 | ``` 303 | 304 | ## Contributing 305 | 306 | We welcome contributions from the community and are excited to see how you can help improve the n8n MCP Server! Whether you're fixing a bug, proposing a new feature, or improving documentation, your help is valued. 307 | 308 | ### Reporting Bugs 309 | 310 | If you encounter a bug, please report it by opening an issue on our [GitHub Issues page](https://github.com/leonardsellem/n8n-mcp-server/issues). 311 | 312 | When submitting a bug report, please include the following: 313 | 314 | - A clear and descriptive title. 315 | - A detailed description of the problem, including steps to reproduce the bug. 316 | - Information about your environment (e.g., Node.js version, n8n MCP Server version, operating system). 317 | - Any relevant error messages or screenshots. 318 | 319 | ### Suggesting Enhancements 320 | 321 | We're always looking for ways to make the server better. If you have an idea for an enhancement or a new feature, please open an issue on our [GitHub Issues page](https://github.com/leonardsellem/n8n-mcp-server/issues). 322 | 323 | Please provide: 324 | 325 | - A clear and descriptive title for your suggestion. 326 | - A detailed explanation of the proposed enhancement and why it would be beneficial. 327 | - Any potential use cases or examples. 328 | 329 | ### Submitting Pull Requests 330 | 331 | If you'd like to contribute code, please follow these steps: 332 | 333 | 1. **Fork the repository:** Create your own fork of the [n8n-mcp-server repository](https://github.com/leonardsellem/n8n-mcp-server). 334 | 2. **Create a branch:** Create a new branch in your fork for your changes (e.g., `git checkout -b feature/your-feature-name` or `bugfix/issue-number`). 335 | 3. **Make your changes:** Implement your feature or bug fix. 336 | * Ensure your code adheres to the existing coding style. (We use Prettier for formatting, which can be run with `npm run lint`). 337 | * Include tests for your changes if applicable. You can run tests using `npm test`. 338 | 4. **Commit your changes:** Write clear and concise commit messages. 339 | 5. **Push to your fork:** Push your changes to your forked repository. 340 | 6. **Open a Pull Request (PR):** Submit a PR to the `main` branch of the official `n8n-mcp-server` repository. 341 | * Provide a clear title and description for your PR, explaining the changes you've made and referencing any related issues. 342 | 343 | We'll review your PR as soon as possible and provide feedback. Thank you for your contribution! 344 | 345 | ## License 346 | 347 | [MIT](LICENSE) 348 | 349 | ## 🚀 Join Our Team: Call for Co-Maintainers! 350 | 351 | This project is a vibrant, community-driven tool actively used by AI enthusiasts and developers. Currently, it's maintained on a part-time basis by a passionate individual who isn't a seasoned engineer but is dedicated to bridging AI with workflow automation. To help this project flourish, ensure its long-term health, and keep up with its growing user base, we're looking for enthusiastic **co-maintainers** to join the team! 352 | 353 | ### Why Contribute? 354 | 355 | - **Learn and Grow:** Sharpen your skills in areas like TypeScript, Node.js, API integration, and AI tool development. 356 | - **Collaborate:** Work alongside other motivated developers and AI users. 357 | - **Make an Impact:** Directly shape the future of this project and help build a valuable tool for the AI community. 358 | - **Open Source:** Gain experience contributing to an open-source project. 359 | 360 | ### How You Can Help 361 | 362 | We welcome contributions in many forms! Here are some areas where you could make a big difference: 363 | 364 | - **Bug Fixing:** Help us identify and squash bugs to improve stability. 365 | - **Feature Development:** Implement new tools and functionalities based on user needs and your ideas. 366 | - **Documentation:** Improve our guides, examples, and API references to make the project more accessible. 367 | - **Testing:** Enhance our test suite (unit, integration) to ensure code quality and reliability. 368 | - **CI/CD:** Help streamline our development and deployment pipelines. 369 | - **Code Reviews:** Provide feedback on pull requests and help maintain code standards. 370 | - **Community Support:** Assist users with questions and help manage discussions. 371 | 372 | ### Get Involved! 373 | 374 | If you're excited about the intersection of AI and workflow automation, and you're looking for a rewarding open-source opportunity, we'd love to hear from you! 375 | 376 | **Ready to contribute?** 377 | 378 | 1. Check out our [GitHub Issues page](https://github.com/leonardsellem/n8n-mcp-server/issues) to find existing tasks, suggest new ideas, or express your interest in becoming a co-maintainer. 379 | 2. You can open an issue titled "Co-maintainer Application" to formally apply, or simply start contributing to existing issues. 380 | 3. Alternatively, feel free to reach out to the existing maintainers if you have questions. 381 | 382 | Let’s build the future of AI-powered workflow automation together! 🙌 383 | 384 | **Thanks to the community for the support!** 385 | [](https://www.star-history.com/#leonardsellem/n8n-mcp-server&Date) 386 | ``` -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- ```markdown 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Status 6 | 7 | This appears to be a new/empty repository for an n8n MCP (Model Context Protocol) server project. 8 | 9 | ## Getting Started 10 | 11 | When this project is initialized, common commands to look for: 12 | - `npm install` - Install dependencies 13 | - `npm run build` - Build the project 14 | - `npm run dev` - Run in development mode 15 | - `npm run test` - Run tests 16 | - `npm run lint` - Run linting 17 | 18 | ## Architecture Notes 19 | 20 | This will be populated once the project structure is established. Key areas to document: 21 | - MCP server implementation details 22 | - n8n integration patterns 23 | - Configuration management 24 | - API endpoints and protocols ``` -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- ```markdown 1 | # Guidelines for Coding Agents 2 | 3 | These rules apply to all automated contributors working on this project. 4 | 5 | ## Development 6 | - Use **TypeScript** and ES modules. Source files live under `src/`. 7 | - Place tests in the `tests/` directory mirroring the source structure. Test files must end with `.test.ts`. 8 | - Install dependencies with `npm install` and build with `npm run build` when required. 9 | - Format code using `npm run lint` and ensure all tests pass via `npm test` before committing. 10 | - Do **not** commit files ignored by `.gitignore` (e.g. `node_modules/`, `build/`, `.env`). 11 | - Follow existing patterns when adding new tools or resources as described in `docs/development`. 12 | 13 | ## Commit Messages 14 | - Use short messages in the form `type: description` (e.g. `feat: add webhook tool`, `fix: handle null id`). 15 | 16 | ## Pull Requests 17 | - Provide a concise summary of changes and reference related issues when opening a PR. 18 | - CI must pass before requesting review. 19 | 20 | ## Environment 21 | - Node.js 20 or later is required. 22 | 23 | ## Continuous Improvement 24 | - After completing a task, review this guide and update it with any lessons 25 | learned about the codebase, coding principles, or user preferences. 26 | ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml 1 | version: 1 2 | start: 3 | command: ["node", "build/index.js"] 4 | port: 8000 ``` -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "esModuleInterop": true, 6 | "rootDir": ".." 7 | }, 8 | "include": [ 9 | "**/*.ts", 10 | "**/*.tsx" 11 | ] 12 | } 13 | ``` -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- ``` 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' }, modules: false }], 4 | '@babel/preset-typescript', 5 | ], 6 | plugins: [ 7 | // No explicit CJS transform plugin 8 | ] 9 | }; ``` -------------------------------------------------------------------------------- /src/errors/error-codes.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Error Codes Module 3 | * 4 | * This module defines error codes used throughout the application. 5 | * These codes are compatible with the MCP SDK error handling system. 6 | */ 7 | 8 | // Numeric error codes for McpError 9 | export enum ErrorCode { 10 | InitializationError = 1000, 11 | AuthenticationError = 1001, 12 | NotFoundError = 1002, 13 | InvalidRequest = 1003, 14 | InternalError = 1004, 15 | NotImplemented = 1005, 16 | } 17 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "outDir": "build", 9 | "declaration": true, 10 | "sourceMap": true, 11 | "resolveJsonModule": true, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "rootDir": "src", 15 | "lib": [ 16 | "ES2020", 17 | "DOM" 18 | ], 19 | "types": [ 20 | "node" 21 | ] 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "build", 29 | "**/*.test.ts" 30 | ] 31 | } 32 | ``` -------------------------------------------------------------------------------- /tests/test-setup.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Global test setup for n8n MCP Server tests 3 | */ 4 | import { beforeEach, afterEach, jest } from '@jest/globals'; 5 | 6 | // Reset environment variables before each test 7 | beforeEach(() => { 8 | process.env = { 9 | ...process.env, 10 | NODE_ENV: 'test' 11 | }; 12 | }); 13 | 14 | // Clean up after each test 15 | afterEach(() => { 16 | jest.resetAllMocks(); 17 | jest.clearAllMocks(); 18 | }); 19 | 20 | export const mockEnv = (envVars: Record<string, string>) => { 21 | const originalEnv = process.env; 22 | 23 | beforeEach(() => { 24 | process.env = { 25 | ...originalEnv, 26 | ...envVars 27 | }; 28 | }); 29 | 30 | afterEach(() => { 31 | process.env = originalEnv; 32 | }); 33 | }; 34 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` 1 | astroid==3.3.8 2 | certifi==2025.1.31 3 | charset-normalizer==3.4.1 4 | colorama==0.4.6 5 | dill==0.3.9 6 | filelock==3.17.0 7 | fsspec==2025.2.0 8 | huggingface-hub==0.29.2 9 | idna==3.10 10 | isort==6.0.1 11 | Jinja2==3.1.6 12 | joblib==1.4.2 13 | MarkupSafe==3.0.2 14 | mccabe==0.7.0 15 | mpmath==1.3.0 16 | networkx==3.4.2 17 | numpy==2.2.3 18 | packaging==24.2 19 | pillow==11.1.0 20 | platformdirs==4.3.6 21 | pylint==3.3.4 22 | PyYAML==6.0.2 23 | regex==2024.11.6 24 | requests==2.32.3 25 | safetensors==0.5.3 26 | scikit-learn==1.6.1 27 | scipy==1.15.2 28 | sentence-transformers==3.4.1 29 | setuptools==75.8.2 30 | sympy==1.13.1 31 | threadpoolctl==3.5.0 32 | tokenizers==0.21.0 33 | tomlkit==0.13.2 34 | torch==2.6.0 35 | tqdm==4.67.1 36 | transformers==4.49.0 37 | typing_extensions==4.12.2 38 | urllib3==2.3.0 39 | ``` -------------------------------------------------------------------------------- /docs/setup/index.md: -------------------------------------------------------------------------------- ```markdown 1 | # Setup and Configuration 2 | 3 | This section covers everything you need to know to set up and configure the n8n MCP Server. 4 | 5 | ## Topics 6 | 7 | - [Installation](./installation.md): Instructions for installing the n8n MCP Server from npm or from source. 8 | - [Configuration](./configuration.md): Information on configuring the server, including environment variables and n8n API setup. 9 | - [Troubleshooting](./troubleshooting.md): Solutions to common issues you might encounter. 10 | 11 | ## Quick Start 12 | 13 | For a quick start, follow these steps: 14 | 15 | 1. Install the server: `npm install -g @leonardsellem/n8n-mcp-server` 16 | 2. Create a `.env` file with your n8n API URL and API key 17 | 3. Run the server: `n8n-mcp-server` 18 | 4. Register the server with your AI assistant platform 19 | ``` -------------------------------------------------------------------------------- /tests/unit/utils/resource-formatter.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Resource formatter utility tests 3 | */ 4 | 5 | import { describe, it, expect } from '@jest/globals'; 6 | import { formatResourceUri } from '../../../src/utils/resource-formatter.js'; 7 | 8 | describe('formatResourceUri', () => { 9 | it('appends "s" for singular resource types', () => { 10 | expect(formatResourceUri('workflow', '1')).toBe('n8n://workflows/1'); 11 | expect(formatResourceUri('execution', '2')).toBe('n8n://executions/2'); 12 | }); 13 | 14 | it('does not append "s" for already plural resource types', () => { 15 | expect(formatResourceUri('workflows', '3')).toBe('n8n://workflows/3'); 16 | expect(formatResourceUri('execution-stats', '4')).toBe('n8n://execution-stats/4'); 17 | }); 18 | 19 | it('returns URI without id when none is provided', () => { 20 | expect(formatResourceUri('workflow')).toBe('n8n://workflow'); 21 | expect(formatResourceUri('workflows')).toBe('n8n://workflows'); 22 | }); 23 | }); 24 | ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile 1 | # Stage 1: Build the application 2 | FROM node:20 AS builder 3 | 4 | WORKDIR /app 5 | 6 | # 1️⃣ Copy only dependency manifests first (keeps layer cache efficient) 7 | COPY package*.json ./ 8 | 9 | # 2️⃣ Install deps *without* running any scripts ➜ skips the automatic `prepare` 10 | RUN npm ci --ignore-scripts # dev + prod deps, no build yet 11 | 12 | # 3️⃣ Now bring in the full source tree (tsconfig.json, src/, …) 13 | COPY . . 14 | 15 | # 4️⃣ Build explicitly – everything is present now 16 | RUN npm run build 17 | 18 | # 5️⃣ Strip dev-dependencies; keeps runtime small 19 | RUN npm prune --omit=dev 20 | 21 | # Stage 2: Create the production image 22 | FROM node:20-slim 23 | 24 | WORKDIR /app 25 | 26 | # 6️⃣ Copy ready-to-run artefacts from builder 27 | COPY --from=builder /app/build ./build 28 | COPY --from=builder /app/node_modules ./node_modules 29 | COPY --from=builder /app/package*.json ./ 30 | 31 | # Set executable permissions for the binary 32 | RUN chmod +x build/index.js 33 | 34 | # Expose the port the app runs on 35 | EXPOSE 8000 36 | 37 | # Set the entrypoint to run the MCP server 38 | CMD ["node", "build/index.js"] 39 | ``` -------------------------------------------------------------------------------- /src/tools/execution/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Execution Tools Module 3 | * 4 | * This module provides MCP tools for interacting with n8n workflow executions. 5 | */ 6 | 7 | import { ToolDefinition } from '../../types/index.js'; 8 | import { getListExecutionsToolDefinition } from './list.js'; 9 | import { getGetExecutionToolDefinition } from './get.js'; 10 | import { getDeleteExecutionToolDefinition } from './delete.js'; 11 | import { getRunWebhookToolDefinition } from './run.js'; 12 | 13 | /** 14 | * Set up execution management tools 15 | * 16 | * @returns Array of execution tool definitions 17 | */ 18 | export async function setupExecutionTools(): Promise<ToolDefinition[]> { 19 | return [ 20 | getListExecutionsToolDefinition(), 21 | getGetExecutionToolDefinition(), 22 | getDeleteExecutionToolDefinition(), 23 | getRunWebhookToolDefinition() 24 | ]; 25 | } 26 | 27 | // Export execution tool handlers for use in the handler 28 | export { ListExecutionsHandler } from './list.js'; 29 | export { GetExecutionHandler } from './get.js'; 30 | export { DeleteExecutionHandler } from './delete.js'; 31 | export { RunWebhookHandler } from './run.js'; 32 | ``` -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Core Types Module 3 | * 4 | * This module provides type definitions used throughout the application 5 | * and bridges compatibility with the MCP SDK. 6 | */ 7 | 8 | // Tool definition for MCP tools 9 | export interface ToolDefinition { 10 | name: string; 11 | description: string; 12 | inputSchema: { 13 | type: string; 14 | properties: Record<string, any>; 15 | required?: string[]; 16 | }; 17 | } 18 | 19 | // Tool call result for MCP tool responses 20 | export interface ToolCallResult { 21 | content: Array<{ 22 | type: string; 23 | text: string; 24 | }>; 25 | isError?: boolean; 26 | } 27 | 28 | // Type for n8n workflow object 29 | export interface Workflow { 30 | id: string; 31 | name: string; 32 | active: boolean; 33 | nodes: any[]; 34 | connections: any; 35 | createdAt: string; 36 | updatedAt: string; 37 | [key: string]: any; 38 | } 39 | 40 | // Type for n8n execution object 41 | export interface Execution { 42 | id: string; 43 | workflowId: string; 44 | finished: boolean; 45 | mode: string; 46 | startedAt: string; 47 | stoppedAt: string; 48 | status: string; 49 | data: { 50 | resultData: { 51 | runData: any; 52 | }; 53 | }; 54 | [key: string]: any; 55 | } 56 | ``` -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- ```markdown 1 | # Usage Examples 2 | 3 | This section provides practical examples of using the n8n MCP Server with AI assistants. 4 | 5 | ## Overview 6 | 7 | The examples in this section demonstrate how AI assistants can interact with n8n workflows through the MCP server. They range from basic operations to complex integration scenarios. 8 | 9 | ## Examples Categories 10 | 11 | - [Basic Examples](./basic-examples.md): Simple examples covering fundamental operations like listing workflows, retrieving workflow details, and executing workflows. 12 | - [Advanced Scenarios](./advanced-scenarios.md): More complex examples showing how to chain operations, handle errors, and implement common workflow patterns. 13 | - [Integration Examples](./integration-examples.md): Examples of integrating the n8n MCP Server with different AI assistant platforms and other tools. 14 | 15 | ## How to Use These Examples 16 | 17 | The examples in this section show both: 18 | 19 | 1. **User Prompts**: What a user might ask an AI assistant to do 20 | 2. **Assistant Actions**: How the assistant would use the MCP tools and resources to accomplish the task 21 | 22 | You can use these examples as inspiration for your own interactions with the n8n MCP Server or as templates for building more complex workflows. 23 | ``` -------------------------------------------------------------------------------- /run-tests.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Test Runner Script 3 | * 4 | * This script provides a more reliable way to run Jest tests with proper 5 | * ESM support and error handling. 6 | */ 7 | 8 | import { spawn } from 'child_process'; 9 | import { fileURLToPath } from 'url'; 10 | import { dirname, resolve } from 'path'; 11 | 12 | // Get the directory of the current module 13 | const __filename = fileURLToPath(import.meta.url); 14 | const __dirname = dirname(__filename); 15 | 16 | // Set NODE_OPTIONS to ensure proper ESM support 17 | process.env.NODE_OPTIONS = '--experimental-vm-modules'; 18 | 19 | console.log('🧪 Running tests for n8n MCP Server...'); 20 | 21 | // Get command line arguments to pass to Jest 22 | const args = process.argv.slice(2); 23 | const jestArgs = ['--config', './jest.config.cjs', ...args]; 24 | 25 | // Spawn Jest process 26 | const jestProcess = spawn('node_modules/.bin/jest', jestArgs, { 27 | stdio: 'inherit', 28 | cwd: __dirname, 29 | env: { ...process.env, NODE_ENV: 'test' } 30 | }); 31 | 32 | // Handle process events 33 | jestProcess.on('error', (error) => { 34 | console.error('Error running tests:', error); 35 | process.exit(1); 36 | }); 37 | 38 | jestProcess.on('close', (code) => { 39 | if (code !== 0) { 40 | console.error(`Test process exited with code ${code}`); 41 | process.exit(code); 42 | } 43 | console.log('✅ Tests completed successfully'); 44 | }); 45 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- ```markdown 1 | # n8n MCP Server Documentation 2 | 3 | Welcome to the n8n MCP Server documentation. This documentation provides comprehensive information about setting up, configuring, and using the n8n MCP Server. 4 | 5 | ## Table of Contents 6 | 7 | - [Setup and Configuration](./setup/index.md) 8 | - [Installation](./setup/installation.md) 9 | - [Configuration](./setup/configuration.md) 10 | - [Troubleshooting](./setup/troubleshooting.md) 11 | 12 | - [API Reference](./api/index.md) 13 | - [Tools](./api/tools.md) 14 | - [Workflow Tools](./api/workflow-tools.md) 15 | - [Execution Tools](./api/execution-tools.md) 16 | - [Resources](./api/resources.md) 17 | - [Static Resources](./api/static-resources.md) 18 | - [Dynamic Resources](./api/dynamic-resources.md) 19 | 20 | - [Usage Examples](./examples/index.md) 21 | - [Basic Examples](./examples/basic-examples.md) 22 | - [Advanced Scenarios](./examples/advanced-scenarios.md) 23 | - [Integration Examples](./examples/integration-examples.md) 24 | 25 | - [Development](./development/index.md) 26 | - [Architecture](./development/architecture.md) 27 | - [Extending the Server](./development/extending.md) 28 | - [Testing](./development/testing.md) 29 | 30 | ## Quick Links 31 | 32 | - [GitHub Repository](https://github.com/yourusername/n8n-mcp-server) 33 | - [n8n Documentation](https://docs.n8n.io/) 34 | - [Model Context Protocol Documentation](https://modelcontextprotocol.github.io/) 35 | ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | /** 3 | * n8n MCP Server - Main Entry Point 4 | * 5 | * This file serves as the entry point for the n8n MCP Server, 6 | * which allows AI assistants to interact with n8n workflows through the MCP protocol. 7 | */ 8 | 9 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 10 | import { loadEnvironmentVariables } from './config/environment.js'; 11 | import { configureServer } from './config/server.js'; 12 | 13 | // Load environment variables 14 | loadEnvironmentVariables(); 15 | 16 | /** 17 | * Main function to start the n8n MCP Server 18 | */ 19 | async function main() { 20 | try { 21 | console.error('Starting n8n MCP Server...'); 22 | 23 | // Create and configure the MCP server 24 | const server = await configureServer(); 25 | 26 | // Set up error handling 27 | server.onerror = (error: unknown) => console.error('[MCP Error]', error); 28 | 29 | // Set up clean shutdown 30 | process.on('SIGINT', async () => { 31 | console.error('Shutting down n8n MCP Server...'); 32 | await server.close(); 33 | process.exit(0); 34 | }); 35 | 36 | // Connect to the server transport (stdio) 37 | const transport = new StdioServerTransport(); 38 | await server.connect(transport); 39 | 40 | console.error('n8n MCP Server running on stdio'); 41 | } catch (error) { 42 | console.error('Failed to start n8n MCP Server:', error); 43 | process.exit(1); 44 | } 45 | } 46 | 47 | // Start the server 48 | main().catch(console.error); 49 | ``` -------------------------------------------------------------------------------- /tests/unit/resources/dynamic/workflow.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simple test for URI Template functionality 3 | */ 4 | 5 | import { describe, it, expect } from '@jest/globals'; 6 | 7 | // Simple functions to test without complex imports 8 | function getWorkflowResourceTemplateUri() { 9 | return 'n8n://workflows/{id}'; 10 | } 11 | 12 | function extractWorkflowIdFromUri(uri: string): string | null { 13 | const regex = /^n8n:\/\/workflows\/([^/]+)$/; 14 | const match = uri.match(regex); 15 | return match ? match[1] : null; 16 | } 17 | 18 | describe('Workflow Resource URI Functions', () => { 19 | describe('getWorkflowResourceTemplateUri', () => { 20 | it('should return the correct URI template', () => { 21 | expect(getWorkflowResourceTemplateUri()).toBe('n8n://workflows/{id}'); 22 | }); 23 | }); 24 | 25 | describe('extractWorkflowIdFromUri', () => { 26 | it('should extract workflow ID from valid URI', () => { 27 | expect(extractWorkflowIdFromUri('n8n://workflows/123abc')).toBe('123abc'); 28 | expect(extractWorkflowIdFromUri('n8n://workflows/workflow-name-with-dashes')).toBe('workflow-name-with-dashes'); 29 | }); 30 | 31 | it('should return null for invalid URI formats', () => { 32 | expect(extractWorkflowIdFromUri('n8n://workflows/')).toBeNull(); 33 | expect(extractWorkflowIdFromUri('n8n://workflows')).toBeNull(); 34 | expect(extractWorkflowIdFromUri('n8n://workflow/123')).toBeNull(); 35 | expect(extractWorkflowIdFromUri('invalid://workflows/123')).toBeNull(); 36 | }); 37 | }); 38 | }); 39 | ``` -------------------------------------------------------------------------------- /.github/workflows/release-package.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Node.js Package 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build-and-publish: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write # Allow workflow to push to the repository 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | token: ${{ github.token }} # Uses the default GITHUB_TOKEN 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: '20' # Specify your desired Node.js version 23 | registry-url: 'https://registry.npmjs.org' # Point to npmjs.com 24 | - run: npm ci 25 | - run: npm run build # Add your build script here if you have one, otherwise remove this line 26 | - name: Bump version, commit, and push 27 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 28 | run: | 29 | git config --global user.name 'github-actions[bot]' 30 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 31 | npm version patch -m "chore: release %s" 32 | git push 33 | git push --tags 34 | env: 35 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} # NPM_TOKEN might be needed for npm version if it interacts with registry 36 | - name: Publish to npmjs.com 37 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 38 | run: npm publish 39 | env: 40 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} # Use the NPM_TOKEN secret 41 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/get.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Get Workflow Tool 3 | * 4 | * This tool retrieves a specific workflow from n8n by ID. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { N8nApiError } from '../../errors/index.js'; 10 | 11 | /** 12 | * Handler for the get_workflow tool 13 | */ 14 | export class GetWorkflowHandler extends BaseWorkflowToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments containing workflowId 19 | * @returns Workflow details 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async (args) => { 23 | const { workflowId } = args; 24 | 25 | if (!workflowId) { 26 | throw new N8nApiError('Missing required parameter: workflowId'); 27 | } 28 | 29 | const workflow = await this.apiService.getWorkflow(workflowId); 30 | 31 | return this.formatSuccess(workflow, `Retrieved workflow: ${workflow.name}`); 32 | }, args); 33 | } 34 | } 35 | 36 | /** 37 | * Get tool definition for the get_workflow tool 38 | * 39 | * @returns Tool definition 40 | */ 41 | export function getGetWorkflowToolDefinition(): ToolDefinition { 42 | return { 43 | name: 'get_workflow', 44 | description: 'Retrieve a specific workflow by ID', 45 | inputSchema: { 46 | type: 'object', 47 | properties: { 48 | workflowId: { 49 | type: 'string', 50 | description: 'ID of the workflow to retrieve', 51 | }, 52 | }, 53 | required: ['workflowId'], 54 | }, 55 | }; 56 | } 57 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Workflow Tools Module 3 | * 4 | * This module provides MCP tools for interacting with n8n workflows. 5 | */ 6 | 7 | import { ToolDefinition } from '../../types/index.js'; 8 | 9 | // Import tool definitions 10 | import { getListWorkflowsToolDefinition, ListWorkflowsHandler } from './list.js'; 11 | import { getGetWorkflowToolDefinition, GetWorkflowHandler } from './get.js'; 12 | import { getCreateWorkflowToolDefinition, CreateWorkflowHandler } from './create.js'; 13 | import { getUpdateWorkflowToolDefinition, UpdateWorkflowHandler } from './update.js'; 14 | import { getDeleteWorkflowToolDefinition, DeleteWorkflowHandler } from './delete.js'; 15 | import { getActivateWorkflowToolDefinition, ActivateWorkflowHandler } from './activate.js'; 16 | import { getDeactivateWorkflowToolDefinition, DeactivateWorkflowHandler } from './deactivate.js'; 17 | 18 | // Export handlers 19 | export { 20 | ListWorkflowsHandler, 21 | GetWorkflowHandler, 22 | CreateWorkflowHandler, 23 | UpdateWorkflowHandler, 24 | DeleteWorkflowHandler, 25 | ActivateWorkflowHandler, 26 | DeactivateWorkflowHandler, 27 | }; 28 | 29 | /** 30 | * Set up workflow management tools 31 | * 32 | * @returns Array of workflow tool definitions 33 | */ 34 | export async function setupWorkflowTools(): Promise<ToolDefinition[]> { 35 | return [ 36 | getListWorkflowsToolDefinition(), 37 | getGetWorkflowToolDefinition(), 38 | getCreateWorkflowToolDefinition(), 39 | getUpdateWorkflowToolDefinition(), 40 | getDeleteWorkflowToolDefinition(), 41 | getActivateWorkflowToolDefinition(), 42 | getDeactivateWorkflowToolDefinition(), 43 | ]; 44 | } 45 | ``` -------------------------------------------------------------------------------- /tests/unit/api/simple-client.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simple HTTP client tests without complex dependencies 3 | */ 4 | 5 | import { describe, it, expect } from '@jest/globals'; 6 | 7 | // Create a simple HTTP client class to test 8 | class SimpleHttpClient { 9 | constructor(private baseUrl: string, private apiKey: string) {} 10 | 11 | getBaseUrl(): string { 12 | return this.baseUrl; 13 | } 14 | 15 | getApiKey(): string { 16 | return this.apiKey; 17 | } 18 | 19 | buildAuthHeader(): Record<string, string> { 20 | return { 21 | 'X-N8N-API-KEY': this.apiKey 22 | }; 23 | } 24 | 25 | formatUrl(path: string): string { 26 | return `${this.baseUrl}${path.startsWith('/') ? path : '/' + path}`; 27 | } 28 | } 29 | 30 | describe('SimpleHttpClient', () => { 31 | it('should store baseUrl and apiKey properly', () => { 32 | const baseUrl = 'https://n8n.example.com/api/v1'; 33 | const apiKey = 'test-api-key'; 34 | const client = new SimpleHttpClient(baseUrl, apiKey); 35 | 36 | expect(client.getBaseUrl()).toBe(baseUrl); 37 | expect(client.getApiKey()).toBe(apiKey); 38 | }); 39 | 40 | it('should create proper auth headers', () => { 41 | const client = new SimpleHttpClient('https://n8n.example.com/api/v1', 'test-api-key'); 42 | const headers = client.buildAuthHeader(); 43 | 44 | expect(headers).toEqual({ 'X-N8N-API-KEY': 'test-api-key' }); 45 | }); 46 | 47 | it('should format URLs correctly', () => { 48 | const baseUrl = 'https://n8n.example.com/api/v1'; 49 | const client = new SimpleHttpClient(baseUrl, 'test-api-key'); 50 | 51 | expect(client.formatUrl('workflows')).toBe(`${baseUrl}/workflows`); 52 | expect(client.formatUrl('/workflows')).toBe(`${baseUrl}/workflows`); 53 | }); 54 | }); 55 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/list.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * List Workflows Tool 3 | * 4 | * This tool retrieves a list of workflows from n8n. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition, Workflow } from '../../types/index.js'; 9 | 10 | /** 11 | * Handler for the list_workflows tool 12 | */ 13 | export class ListWorkflowsHandler extends BaseWorkflowToolHandler { 14 | /** 15 | * Execute the tool 16 | * 17 | * @param args Tool arguments 18 | * @returns List of workflows 19 | */ 20 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 21 | return this.handleExecution(async () => { 22 | const workflows = await this.apiService.getWorkflows(); 23 | 24 | // Format the workflows for display 25 | const formattedWorkflows = workflows.map((workflow: Workflow) => ({ 26 | id: workflow.id, 27 | name: workflow.name, 28 | active: workflow.active, 29 | updatedAt: workflow.updatedAt, 30 | })); 31 | 32 | return this.formatSuccess( 33 | formattedWorkflows, 34 | `Found ${formattedWorkflows.length} workflow(s)` 35 | ); 36 | }, args); 37 | } 38 | } 39 | 40 | /** 41 | * Get tool definition for the list_workflows tool 42 | * 43 | * @returns Tool definition 44 | */ 45 | export function getListWorkflowsToolDefinition(): ToolDefinition { 46 | return { 47 | name: 'list_workflows', 48 | description: 'Retrieve a list of all workflows available in n8n', 49 | inputSchema: { 50 | type: 'object', 51 | properties: { 52 | active: { 53 | type: 'boolean', 54 | description: 'Optional filter to show only active or inactive workflows', 55 | }, 56 | }, 57 | required: [], 58 | }, 59 | }; 60 | } 61 | ``` -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- ``` 1 | module.exports = { 2 | // Use commonjs style export 3 | preset: 'ts-jest/presets/default-esm', // Using ESM preset for ts-jest 4 | testEnvironment: 'node', 5 | // transform: { // Removed to let ts-jest handle transformation via preset 6 | // '^.+\\.tsx?$': 'babel-jest', 7 | // }, 8 | // globals: { // Deprecated way to configure ts-jest 9 | // 'ts-jest': { 10 | // useESM: true, 11 | // } 12 | // }, 13 | transform: { 14 | '^.+\\.tsx?$': ['ts-jest', { 15 | useESM: true, 16 | tsconfig: 'tests/tsconfig.json', // Point to the tsconfig in the tests directory 17 | // babelConfig: true, // If babel.config.cjs is still needed for some features not in ts-jest 18 | }], 19 | }, 20 | // Allow src and test folders to resolve imports properly 21 | moduleNameMapper: { 22 | '^(\\.{1,2}/.*)\\.js$': '$1', 23 | // For ESM, Jest might need help resolving module paths if they are not fully specified 24 | // or if there are conditions in package.json exports. 25 | // Example: '(@modelcontextprotocol/sdk)(.*)': '<rootDir>/node_modules/$1/dist$2.js' 26 | // This line below is a guess, might need adjustment or might not be needed. 27 | // '^(@modelcontextprotocol/sdk/.*)\\.js$': '<rootDir>/node_modules/$1.js', 28 | }, 29 | // Handle the modelcontextprotocol SDK 30 | // Default is /node_modules/, so we want to NOT transform anything in node_modules 31 | transformIgnorePatterns: [ 32 | "node_modules/" 33 | ], 34 | collectCoverage: true, 35 | coverageDirectory: 'coverage', 36 | coverageReporters: ['text', 'lcov'], 37 | testMatch: ['**/tests/**/*.test.ts'], 38 | verbose: true, 39 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 40 | setupFilesAfterEnv: ['<rootDir>/tests/test-setup.ts'] 41 | }; 42 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/activate.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Activate Workflow Tool 3 | * 4 | * This tool activates an existing workflow in n8n. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { N8nApiError } from '../../errors/index.js'; 10 | 11 | /** 12 | * Handler for the activate_workflow tool 13 | */ 14 | export class ActivateWorkflowHandler extends BaseWorkflowToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments containing workflowId 19 | * @returns Activation confirmation 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async (args) => { 23 | const { workflowId } = args; 24 | 25 | if (!workflowId) { 26 | throw new N8nApiError('Missing required parameter: workflowId'); 27 | } 28 | 29 | // Activate the workflow 30 | const workflow = await this.apiService.activateWorkflow(workflowId); 31 | 32 | return this.formatSuccess( 33 | { 34 | id: workflow.id, 35 | name: workflow.name, 36 | active: workflow.active 37 | }, 38 | `Workflow "${workflow.name}" (ID: ${workflowId}) has been successfully activated` 39 | ); 40 | }, args); 41 | } 42 | } 43 | 44 | /** 45 | * Get tool definition for the activate_workflow tool 46 | * 47 | * @returns Tool definition 48 | */ 49 | export function getActivateWorkflowToolDefinition(): ToolDefinition { 50 | return { 51 | name: 'activate_workflow', 52 | description: 'Activate a workflow in n8n', 53 | inputSchema: { 54 | type: 'object', 55 | properties: { 56 | workflowId: { 57 | type: 'string', 58 | description: 'ID of the workflow to activate', 59 | }, 60 | }, 61 | required: ['workflowId'], 62 | }, 63 | }; 64 | } 65 | ``` -------------------------------------------------------------------------------- /src/tools/execution/handler.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Execution Tools Handler 3 | * 4 | * This module handles calls to execution-related tools. 5 | */ 6 | 7 | import { ToolCallResult } from '../../types/index.js'; 8 | import { McpError } from '@modelcontextprotocol/sdk/types.js'; 9 | import { ErrorCode } from '../../errors/error-codes.js'; 10 | import { getErrorMessage } from '../../errors/index.js'; 11 | import { 12 | ListExecutionsHandler, 13 | GetExecutionHandler, 14 | DeleteExecutionHandler, 15 | RunWebhookHandler 16 | } from './index.js'; 17 | 18 | /** 19 | * Handle execution tool calls 20 | * 21 | * @param toolName Name of the tool being called 22 | * @param args Arguments passed to the tool 23 | * @returns Tool call result 24 | */ 25 | export default async function executionHandler( 26 | toolName: string, 27 | args: Record<string, any> 28 | ): Promise<ToolCallResult> { 29 | try { 30 | // Route to the appropriate handler based on tool name 31 | switch (toolName) { 32 | case 'list_executions': 33 | return await new ListExecutionsHandler().execute(args); 34 | 35 | case 'get_execution': 36 | return await new GetExecutionHandler().execute(args); 37 | 38 | case 'delete_execution': 39 | return await new DeleteExecutionHandler().execute(args); 40 | 41 | case 'run_webhook': 42 | return await new RunWebhookHandler().execute(args); 43 | 44 | default: 45 | throw new McpError( 46 | ErrorCode.NotImplemented, 47 | `Unknown execution tool: '${toolName}'` 48 | ); 49 | } 50 | } catch (error) { 51 | // Get appropriate error message 52 | const errorMessage = getErrorMessage(error); 53 | 54 | return { 55 | content: [ 56 | { 57 | type: 'text', 58 | text: `Error executing execution tool: ${errorMessage}`, 59 | }, 60 | ], 61 | isError: true, 62 | }; 63 | } 64 | } 65 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/deactivate.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Deactivate Workflow Tool 3 | * 4 | * This tool deactivates an existing workflow in n8n. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { N8nApiError } from '../../errors/index.js'; 10 | 11 | /** 12 | * Handler for the deactivate_workflow tool 13 | */ 14 | export class DeactivateWorkflowHandler extends BaseWorkflowToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments containing workflowId 19 | * @returns Deactivation confirmation 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async (args) => { 23 | const { workflowId } = args; 24 | 25 | if (!workflowId) { 26 | throw new N8nApiError('Missing required parameter: workflowId'); 27 | } 28 | 29 | // Deactivate the workflow 30 | const workflow = await this.apiService.deactivateWorkflow(workflowId); 31 | 32 | return this.formatSuccess( 33 | { 34 | id: workflow.id, 35 | name: workflow.name, 36 | active: workflow.active 37 | }, 38 | `Workflow "${workflow.name}" (ID: ${workflowId}) has been successfully deactivated` 39 | ); 40 | }, args); 41 | } 42 | } 43 | 44 | /** 45 | * Get tool definition for the deactivate_workflow tool 46 | * 47 | * @returns Tool definition 48 | */ 49 | export function getDeactivateWorkflowToolDefinition(): ToolDefinition { 50 | return { 51 | name: 'deactivate_workflow', 52 | description: 'Deactivate a workflow in n8n', 53 | inputSchema: { 54 | type: 'object', 55 | properties: { 56 | workflowId: { 57 | type: 'string', 58 | description: 'ID of the workflow to deactivate', 59 | }, 60 | }, 61 | required: ['workflowId'], 62 | }, 63 | }; 64 | } 65 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/delete.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Delete Workflow Tool 3 | * 4 | * This tool deletes an existing workflow from n8n. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { N8nApiError } from '../../errors/index.js'; 10 | 11 | /** 12 | * Handler for the delete_workflow tool 13 | */ 14 | export class DeleteWorkflowHandler extends BaseWorkflowToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments containing workflowId 19 | * @returns Deletion confirmation 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async (args) => { 23 | const { workflowId } = args; 24 | 25 | if (!workflowId) { 26 | throw new N8nApiError('Missing required parameter: workflowId'); 27 | } 28 | 29 | // Get the workflow info first for the confirmation message 30 | const workflow = await this.apiService.getWorkflow(workflowId); 31 | const workflowName = workflow.name; 32 | 33 | // Delete the workflow 34 | await this.apiService.deleteWorkflow(workflowId); 35 | 36 | return this.formatSuccess( 37 | { id: workflowId }, 38 | `Workflow "${workflowName}" (ID: ${workflowId}) has been successfully deleted` 39 | ); 40 | }, args); 41 | } 42 | } 43 | 44 | /** 45 | * Get tool definition for the delete_workflow tool 46 | * 47 | * @returns Tool definition 48 | */ 49 | export function getDeleteWorkflowToolDefinition(): ToolDefinition { 50 | return { 51 | name: 'delete_workflow', 52 | description: 'Delete a workflow from n8n', 53 | inputSchema: { 54 | type: 'object', 55 | properties: { 56 | workflowId: { 57 | type: 'string', 58 | description: 'ID of the workflow to delete', 59 | }, 60 | }, 61 | required: ['workflowId'], 62 | }, 63 | }; 64 | } 65 | ``` -------------------------------------------------------------------------------- /docs/setup/installation.md: -------------------------------------------------------------------------------- ```markdown 1 | # Installation Guide 2 | 3 | This guide covers the installation process for the n8n MCP Server. 4 | 5 | ## Prerequisites 6 | 7 | - Before installing the n8n MCP Server, ensure you have: 8 | 9 | - Node.js 20 or later installed 10 | - An n8n instance running and accessible via HTTP/HTTPS 11 | - API access enabled on your n8n instance 12 | - An API key with appropriate permissions (see [Configuration](./configuration.md)) 13 | 14 | ## Option 1: Install from npm (Recommended) 15 | 16 | The easiest way to install the n8n MCP Server is from npm: 17 | 18 | ```bash 19 | npm install -g n8n-mcp-server 20 | ``` 21 | 22 | This will install the server globally, making the `n8n-mcp-server` command available in your terminal. 23 | 24 | ## Option 2: Install from Source 25 | 26 | For development purposes or to use the latest features, you can install from source: 27 | 28 | ```bash 29 | # Clone the repository 30 | git clone https://github.com/yourusername/n8n-mcp-server.git 31 | cd n8n-mcp-server 32 | 33 | # Install dependencies 34 | npm install 35 | 36 | # Build the project 37 | npm run build 38 | 39 | # Optional: Install globally 40 | npm install -g . 41 | ``` 42 | 43 | ## Verifying Installation 44 | 45 | Once installed, you can verify the installation by running: 46 | 47 | ```bash 48 | n8n-mcp-server --version 49 | ``` 50 | 51 | This should display the version number of the installed n8n MCP Server. 52 | 53 | ## Next Steps 54 | 55 | After installation, you'll need to: 56 | 57 | 1. [Configure the server](./configuration.md) by setting up environment variables 58 | 2. Run the server 59 | 3. Register the server with your AI assistant platform 60 | 61 | ## Upgrading 62 | 63 | To upgrade a global installation from npm: 64 | 65 | ```bash 66 | npm update -g n8n-mcp-server 67 | ``` 68 | 69 | To upgrade a source installation: 70 | 71 | ```bash 72 | # Navigate to the repository directory 73 | cd n8n-mcp-server 74 | 75 | # Pull the latest changes 76 | git pull 77 | 78 | # Install dependencies and rebuild 79 | npm install 80 | npm run build 81 | 82 | # If installed globally, reinstall 83 | npm install -g . 84 | ``` -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Build & Publish Docker image 2 | 3 | # ➊ Triggers – push to main OR new GitHub Release 4 | on: 5 | push: 6 | branches: [main] 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | build-and-push: 12 | runs-on: ubuntu-latest 13 | 14 | # ➋ The job needs these repo secrets (add in Settings ▸ Secrets ▸ Actions): 15 | # DOCKERHUB_USERNAME – e.g. 'leonardsellem' 16 | # DOCKERHUB_TOKEN – a Docker Hub access token / password 17 | env: 18 | IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/n8n-mcp-server 19 | 20 | steps: 21 | - name: Checkout repo 22 | uses: actions/checkout@v4 23 | 24 | # ➌ Enable multi-arch builds (amd64 + arm64) 25 | - uses: docker/setup-qemu-action@v3 26 | - uses: docker/setup-buildx-action@v3 27 | 28 | # ➍ Log in to Docker Hub 29 | - uses: docker/login-action@v3 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | 34 | # ➎ Generate convenient tags & labels (latest, sha-short, release tag …) 35 | - id: meta 36 | uses: docker/metadata-action@v5 37 | with: 38 | images: ${{ env.IMAGE_NAME }} 39 | tags: | 40 | type=sha,format=short 41 | type=ref,event=branch 42 | type=semver,pattern={{version}} 43 | type=raw,value=latest,enable={{is_default_branch}} 44 | 45 | # ➏ Build & push 46 | - uses: docker/build-push-action@v5 47 | with: 48 | context: . 49 | push: true 50 | platforms: linux/amd64,linux/arm64 51 | tags: ${{ steps.meta.outputs.tags }} 52 | labels: ${{ steps.meta.outputs.labels }} 53 | # optional build-cache (makes repeated builds faster) 54 | cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache 55 | cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max ``` -------------------------------------------------------------------------------- /src/tools/execution/delete.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Delete Execution Tool 3 | * 4 | * This tool deletes a specific workflow execution from n8n. 5 | */ 6 | 7 | import { BaseExecutionToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { McpError } from '@modelcontextprotocol/sdk/types.js'; 10 | import { ErrorCode } from '../../errors/error-codes.js'; 11 | 12 | /** 13 | * Handler for the delete_execution tool 14 | */ 15 | export class DeleteExecutionHandler extends BaseExecutionToolHandler { 16 | /** 17 | * Execute the tool 18 | * 19 | * @param args Tool arguments (executionId) 20 | * @returns Result of the deletion operation 21 | */ 22 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 23 | return this.handleExecution(async () => { 24 | // Validate required parameters 25 | if (!args.executionId) { 26 | throw new McpError( 27 | ErrorCode.InvalidRequest, 28 | 'Missing required parameter: executionId' 29 | ); 30 | } 31 | 32 | // Store execution ID for response message 33 | const executionId = args.executionId; 34 | 35 | // Delete the execution 36 | await this.apiService.deleteExecution(executionId); 37 | 38 | return this.formatSuccess( 39 | { id: executionId, deleted: true }, 40 | `Successfully deleted execution with ID: ${executionId}` 41 | ); 42 | }, args); 43 | } 44 | } 45 | 46 | /** 47 | * Get tool definition for the delete_execution tool 48 | * 49 | * @returns Tool definition 50 | */ 51 | export function getDeleteExecutionToolDefinition(): ToolDefinition { 52 | return { 53 | name: 'delete_execution', 54 | description: 'Delete a specific workflow execution from n8n', 55 | inputSchema: { 56 | type: 'object', 57 | properties: { 58 | executionId: { 59 | type: 'string', 60 | description: 'ID of the execution to delete', 61 | }, 62 | }, 63 | required: ['executionId'], 64 | }, 65 | }; 66 | } 67 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "@leonardsellem/n8n-mcp-server", 3 | "version": "0.1.8", 4 | "description": "Model Context Protocol (MCP) server for n8n workflow automation", 5 | "main": "build/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc && chmod +x build/index.js", 9 | "start": "node build/index.js", 10 | "dev": "tsc -w", 11 | "lint": "eslint --ext .ts src/", 12 | "test": "node --experimental-vm-modules run-tests.js", 13 | "test:watch": "node --experimental-vm-modules run-tests.js --watch", 14 | "test:coverage": "node --experimental-vm-modules run-tests.js --coverage", 15 | "prepare": "npm run build" 16 | }, 17 | "bin": { 18 | "n8n-mcp-server": "build/index.js" 19 | }, 20 | "keywords": [ 21 | "mcp", 22 | "n8n", 23 | "workflow", 24 | "automation", 25 | "ai" 26 | ], 27 | "author": "Leonard Sellem (https://sellem.me)", 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/leonardsellem/n8n-mcp-server.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/leonardsellem/n8n-mcp-server/issues" 35 | }, 36 | "homepage": "https://github.com/leonardsellem/n8n-mcp-server#readme", 37 | "files": [ 38 | "build", 39 | "README.md", 40 | "LICENSE", 41 | "package.json" 42 | ], 43 | "dependencies": { 44 | "@modelcontextprotocol/sdk": "^0.7.0", 45 | "axios": "^1.6.2", 46 | "dotenv": "^16.3.1", 47 | "find-config": "^1.0.0" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.26.10", 51 | "@babel/plugin-transform-modules-commonjs": "^7.26.3", 52 | "@babel/preset-env": "^7.26.9", 53 | "@babel/preset-typescript": "^7.26.0", 54 | "@types/find-config": "^1.0.4", 55 | "@types/jest": "^29.5.14", 56 | "@types/node": "^20.10.0", 57 | "@typescript-eslint/eslint-plugin": "^6.13.1", 58 | "@typescript-eslint/parser": "^6.13.1", 59 | "babel-jest": "^29.7.0", 60 | "eslint": "^8.54.0", 61 | "jest": "^29.7.0", 62 | "ts-jest": "^29.1.1", 63 | "typescript": "^5.3.2" 64 | }, 65 | "engines": { 66 | "node": ">=18.0.0" 67 | } 68 | } 69 | ``` -------------------------------------------------------------------------------- /src/tools/execution/get.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Get Execution Tool 3 | * 4 | * This tool retrieves detailed information about a specific workflow execution. 5 | */ 6 | 7 | import { BaseExecutionToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { McpError } from '@modelcontextprotocol/sdk/types.js'; 10 | import { ErrorCode } from '../../errors/error-codes.js'; 11 | import { formatExecutionDetails } from '../../utils/execution-formatter.js'; 12 | 13 | /** 14 | * Handler for the get_execution tool 15 | */ 16 | export class GetExecutionHandler extends BaseExecutionToolHandler { 17 | /** 18 | * Execute the tool 19 | * 20 | * @param args Tool arguments (executionId) 21 | * @returns Execution details 22 | */ 23 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 24 | return this.handleExecution(async () => { 25 | // Validate required parameters 26 | if (!args.executionId) { 27 | throw new McpError( 28 | ErrorCode.InvalidRequest, 29 | 'Missing required parameter: executionId' 30 | ); 31 | } 32 | 33 | // Get execution details 34 | const execution = await this.apiService.getExecution(args.executionId); 35 | 36 | // Format the execution for display 37 | const formattedExecution = formatExecutionDetails(execution); 38 | 39 | return this.formatSuccess( 40 | formattedExecution, 41 | `Execution Details for ID: ${args.executionId}` 42 | ); 43 | }, args); 44 | } 45 | 46 | } 47 | 48 | /** 49 | * Get tool definition for the get_execution tool 50 | * 51 | * @returns Tool definition 52 | */ 53 | export function getGetExecutionToolDefinition(): ToolDefinition { 54 | return { 55 | name: 'get_execution', 56 | description: 'Retrieve detailed information about a specific workflow execution', 57 | inputSchema: { 58 | type: 'object', 59 | properties: { 60 | executionId: { 61 | type: 'string', 62 | description: 'ID of the execution to retrieve', 63 | }, 64 | }, 65 | required: ['executionId'], 66 | }, 67 | }; 68 | } 69 | ``` -------------------------------------------------------------------------------- /src/resources/static/execution-stats.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Static Execution Statistics Resource Handler 3 | * 4 | * This module provides the MCP resource implementation for execution statistics. 5 | */ 6 | 7 | import { N8nApiService } from '../../api/n8n-client.js'; 8 | import { formatExecutionStats, formatResourceUri } from '../../utils/resource-formatter.js'; 9 | import { McpError, ErrorCode } from '../../errors/index.js'; 10 | 11 | /** 12 | * Get execution statistics resource data 13 | * 14 | * @param apiService n8n API service 15 | * @returns Formatted execution statistics resource data 16 | */ 17 | export async function getExecutionStatsResource(apiService: N8nApiService): Promise<string> { 18 | try { 19 | // Get executions from the API 20 | const executions = await apiService.getExecutions(); 21 | 22 | // Format the execution statistics 23 | const stats = formatExecutionStats(executions); 24 | 25 | // Add metadata about the resource 26 | const result = { 27 | resourceType: 'execution-stats', 28 | ...stats, 29 | _links: { 30 | self: formatResourceUri('execution-stats'), 31 | } 32 | }; 33 | 34 | return JSON.stringify(result, null, 2); 35 | } catch (error) { 36 | console.error('Error fetching execution statistics resource:', error); 37 | throw new McpError( 38 | ErrorCode.InternalError, 39 | `Failed to retrieve execution statistics: ${error instanceof Error ? error.message : 'Unknown error'}` 40 | ); 41 | } 42 | } 43 | 44 | /** 45 | * Get execution statistics resource URI 46 | * 47 | * @returns Formatted resource URI 48 | */ 49 | export function getExecutionStatsResourceUri(): string { 50 | return formatResourceUri('execution-stats'); 51 | } 52 | 53 | /** 54 | * Get execution statistics resource metadata 55 | * 56 | * @returns Resource metadata object 57 | */ 58 | export function getExecutionStatsResourceMetadata(): Record<string, any> { 59 | return { 60 | uri: getExecutionStatsResourceUri(), 61 | name: 'n8n Execution Statistics', 62 | mimeType: 'application/json', 63 | description: 'Summary statistics of workflow executions including success rates, average duration, and trends', 64 | }; 65 | } 66 | ``` -------------------------------------------------------------------------------- /src/resources/static/workflows.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Static Workflows Resource Handler 3 | * 4 | * This module provides the MCP resource implementation for listing all workflows. 5 | */ 6 | 7 | import { N8nApiService } from '../../api/n8n-client.js'; 8 | import { formatWorkflowSummary, formatResourceUri } from '../../utils/resource-formatter.js'; 9 | import { McpError, ErrorCode } from '../../errors/index.js'; 10 | 11 | /** 12 | * Get workflows resource data 13 | * 14 | * @param apiService n8n API service 15 | * @returns Formatted workflows resource data 16 | */ 17 | export async function getWorkflowsResource(apiService: N8nApiService): Promise<string> { 18 | try { 19 | // Get all workflows from the API 20 | const workflows = await apiService.getWorkflows(); 21 | 22 | // Format the workflows for resource consumption 23 | const formattedWorkflows = workflows.map(workflow => formatWorkflowSummary(workflow)); 24 | 25 | // Add metadata about the resource 26 | const result = { 27 | resourceType: 'workflows', 28 | count: formattedWorkflows.length, 29 | workflows: formattedWorkflows, 30 | _links: { 31 | self: formatResourceUri('workflows'), 32 | }, 33 | lastUpdated: new Date().toISOString(), 34 | }; 35 | 36 | return JSON.stringify(result, null, 2); 37 | } catch (error) { 38 | console.error('Error fetching workflows resource:', error); 39 | throw new McpError( 40 | ErrorCode.InternalError, 41 | `Failed to retrieve workflows: ${error instanceof Error ? error.message : 'Unknown error'}` 42 | ); 43 | } 44 | } 45 | 46 | /** 47 | * Get workflows resource URI 48 | * 49 | * @returns Formatted resource URI 50 | */ 51 | export function getWorkflowsResourceUri(): string { 52 | return formatResourceUri('workflows'); 53 | } 54 | 55 | /** 56 | * Get workflows resource metadata 57 | * 58 | * @returns Resource metadata object 59 | */ 60 | export function getWorkflowsResourceMetadata(): Record<string, any> { 61 | return { 62 | uri: getWorkflowsResourceUri(), 63 | name: 'n8n Workflows', 64 | mimeType: 'application/json', 65 | description: 'List of all workflows in the n8n instance with their basic information', 66 | }; 67 | } 68 | ``` -------------------------------------------------------------------------------- /tests/jest-globals.d.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Jest global type declarations 3 | * This file adds typings for Jest globals to reduce TypeScript errors in test files 4 | */ 5 | 6 | import '@jest/globals'; 7 | 8 | // Declare global Jest types explicitly to help TypeScript 9 | declare global { 10 | // Jest testing functions 11 | const describe: typeof import('@jest/globals').describe; 12 | const it: typeof import('@jest/globals').it; 13 | const test: typeof import('@jest/globals').test; 14 | const expect: typeof import('@jest/globals').expect; 15 | const beforeAll: typeof import('@jest/globals').beforeAll; 16 | const beforeEach: typeof import('@jest/globals').beforeEach; 17 | const afterAll: typeof import('@jest/globals').afterAll; 18 | const afterEach: typeof import('@jest/globals').afterEach; 19 | 20 | // Jest mock functionality 21 | const jest: typeof import('@jest/globals').jest; 22 | 23 | // Additional common helpers 24 | namespace jest { 25 | interface Mock<T = any, Y extends any[] = any[]> extends Function { 26 | new (...args: Y): T; 27 | (...args: Y): T; 28 | mockImplementation(fn: (...args: Y) => T): this; 29 | mockImplementationOnce(fn: (...args: Y) => T): this; 30 | mockReturnValue(value: T): this; 31 | mockReturnValueOnce(value: T): this; 32 | mockResolvedValue(value: T): this; 33 | mockResolvedValueOnce(value: T): this; 34 | mockRejectedValue(value: any): this; 35 | mockRejectedValueOnce(value: any): this; 36 | mockClear(): this; 37 | mockReset(): this; 38 | mockRestore(): this; 39 | mockName(name: string): this; 40 | getMockName(): string; 41 | mock: { 42 | calls: Y[]; 43 | instances: T[]; 44 | contexts: any[]; 45 | lastCall: Y; 46 | results: Array<{ type: string; value: T }>; 47 | }; 48 | } 49 | 50 | function fn<T = any, Y extends any[] = any[]>(): Mock<T, Y>; 51 | function fn<T = any, Y extends any[] = any[]>(implementation: (...args: Y) => T): Mock<T, Y>; 52 | 53 | function spyOn<T extends object, M extends keyof T>( 54 | object: T, 55 | method: M & string 56 | ): Mock<Required<T>[M]>; 57 | 58 | function mocked<T>(item: T, deep?: boolean): jest.Mocked<T>; 59 | } 60 | } 61 | 62 | export {}; 63 | ``` -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Claude Code 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: read 24 | issues: read 25 | id-token: write 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 1 31 | 32 | - name: Run Claude Code 33 | id: claude 34 | uses: anthropics/claude-code-action@beta 35 | with: 36 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} 37 | 38 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 39 | # model: "claude-opus-4-20250514" 40 | 41 | # Optional: Customize the trigger phrase (default: @claude) 42 | # trigger_phrase: "/claude" 43 | 44 | # Optional: Trigger when specific user is assigned to an issue 45 | # assignee_trigger: "claude-bot" 46 | 47 | # Optional: Allow Claude to run specific commands 48 | # allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" 49 | 50 | # Optional: Add custom instructions for Claude to customize its behavior for your project 51 | # custom_instructions: | 52 | # Follow our coding standards 53 | # Ensure all new code has tests 54 | # Use TypeScript for new files 55 | 56 | # Optional: Custom environment variables for Claude 57 | # claude_env: | 58 | # NODE_ENV: test 59 | 60 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/handler.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Workflow Tools Handler 3 | * 4 | * This module handles calls to workflow-related tools. 5 | */ 6 | 7 | import { ToolCallResult } from '../../types/index.js'; 8 | import { N8nApiError } from '../../errors/index.js'; 9 | import { 10 | ListWorkflowsHandler, 11 | GetWorkflowHandler, 12 | CreateWorkflowHandler, 13 | UpdateWorkflowHandler, 14 | DeleteWorkflowHandler, 15 | ActivateWorkflowHandler, 16 | DeactivateWorkflowHandler, 17 | } from './index.js'; 18 | 19 | /** 20 | * Handle workflow tool calls 21 | * 22 | * @param toolName Name of the tool being called 23 | * @param args Arguments passed to the tool 24 | * @returns Tool call result 25 | */ 26 | export default async function workflowHandler( 27 | toolName: string, 28 | args: Record<string, any> 29 | ): Promise<ToolCallResult> { 30 | try { 31 | // Route to the appropriate handler based on the tool name 32 | switch (toolName) { 33 | case 'list_workflows': 34 | return await new ListWorkflowsHandler().execute(args); 35 | 36 | case 'get_workflow': 37 | return await new GetWorkflowHandler().execute(args); 38 | 39 | case 'create_workflow': 40 | return await new CreateWorkflowHandler().execute(args); 41 | 42 | case 'update_workflow': 43 | return await new UpdateWorkflowHandler().execute(args); 44 | 45 | case 'delete_workflow': 46 | return await new DeleteWorkflowHandler().execute(args); 47 | 48 | case 'activate_workflow': 49 | return await new ActivateWorkflowHandler().execute(args); 50 | 51 | case 'deactivate_workflow': 52 | return await new DeactivateWorkflowHandler().execute(args); 53 | 54 | default: 55 | throw new N8nApiError(`Unknown workflow tool: ${toolName}`); 56 | } 57 | } catch (error) { 58 | if (error instanceof N8nApiError) { 59 | return { 60 | content: [ 61 | { 62 | type: 'text', 63 | text: error.message, 64 | }, 65 | ], 66 | isError: true, 67 | }; 68 | } 69 | 70 | // Handle unexpected errors 71 | const errorMessage = error instanceof Error 72 | ? error.message 73 | : 'Unknown error occurred'; 74 | 75 | return { 76 | content: [ 77 | { 78 | type: 'text', 79 | text: `Error executing workflow tool: ${errorMessage}`, 80 | }, 81 | ], 82 | isError: true, 83 | }; 84 | } 85 | } 86 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/base-handler.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Base Workflow Tool Handler 3 | * 4 | * This module provides a base handler for workflow-related tools. 5 | */ 6 | 7 | import { ToolCallResult } from '../../types/index.js'; 8 | import { N8nApiError } from '../../errors/index.js'; 9 | import { createApiService } from '../../api/n8n-client.js'; 10 | import { getEnvConfig } from '../../config/environment.js'; 11 | 12 | /** 13 | * Base class for workflow tool handlers 14 | */ 15 | export abstract class BaseWorkflowToolHandler { 16 | protected apiService = createApiService(getEnvConfig()); 17 | 18 | /** 19 | * Validate and execute the tool 20 | * 21 | * @param args Arguments passed to the tool 22 | * @returns Tool call result 23 | */ 24 | abstract execute(args: Record<string, any>): Promise<ToolCallResult>; 25 | 26 | /** 27 | * Format a successful response 28 | * 29 | * @param data Response data 30 | * @param message Optional success message 31 | * @returns Formatted success response 32 | */ 33 | protected formatSuccess(data: any, message?: string): ToolCallResult { 34 | const formattedData = typeof data === 'object' 35 | ? JSON.stringify(data, null, 2) 36 | : String(data); 37 | 38 | return { 39 | content: [ 40 | { 41 | type: 'text', 42 | text: message ? `${message}\n\n${formattedData}` : formattedData, 43 | }, 44 | ], 45 | }; 46 | } 47 | 48 | /** 49 | * Format an error response 50 | * 51 | * @param error Error object or message 52 | * @returns Formatted error response 53 | */ 54 | protected formatError(error: Error | string): ToolCallResult { 55 | const errorMessage = error instanceof Error ? error.message : error; 56 | 57 | return { 58 | content: [ 59 | { 60 | type: 'text', 61 | text: errorMessage, 62 | }, 63 | ], 64 | isError: true, 65 | }; 66 | } 67 | 68 | /** 69 | * Handle tool execution errors 70 | * 71 | * @param handler Function to execute 72 | * @param args Arguments to pass to the handler 73 | * @returns Tool call result 74 | */ 75 | protected async handleExecution( 76 | handler: (args: Record<string, any>) => Promise<ToolCallResult>, 77 | args: Record<string, any> 78 | ): Promise<ToolCallResult> { 79 | try { 80 | return await handler(args); 81 | } catch (error) { 82 | if (error instanceof N8nApiError) { 83 | return this.formatError(error.message); 84 | } 85 | 86 | const errorMessage = error instanceof Error 87 | ? error.message 88 | : 'Unknown error occurred'; 89 | 90 | return this.formatError(`Error executing workflow tool: ${errorMessage}`); 91 | } 92 | } 93 | } 94 | ``` -------------------------------------------------------------------------------- /src/tools/execution/base-handler.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Base Execution Tool Handler 3 | * 4 | * This module provides a base handler for execution-related tools. 5 | */ 6 | 7 | import { ToolCallResult } from '../../types/index.js'; 8 | import { N8nApiError } from '../../errors/index.js'; 9 | import { createApiService } from '../../api/n8n-client.js'; 10 | import { getEnvConfig } from '../../config/environment.js'; 11 | 12 | /** 13 | * Base class for execution tool handlers 14 | */ 15 | export abstract class BaseExecutionToolHandler { 16 | protected apiService = createApiService(getEnvConfig()); 17 | 18 | /** 19 | * Validate and execute the tool 20 | * 21 | * @param args Arguments passed to the tool 22 | * @returns Tool call result 23 | */ 24 | abstract execute(args: Record<string, any>): Promise<ToolCallResult>; 25 | 26 | /** 27 | * Format a successful response 28 | * 29 | * @param data Response data 30 | * @param message Optional success message 31 | * @returns Formatted success response 32 | */ 33 | protected formatSuccess(data: any, message?: string): ToolCallResult { 34 | const formattedData = typeof data === 'object' 35 | ? JSON.stringify(data, null, 2) 36 | : String(data); 37 | 38 | return { 39 | content: [ 40 | { 41 | type: 'text', 42 | text: message ? `${message}\n\n${formattedData}` : formattedData, 43 | }, 44 | ], 45 | }; 46 | } 47 | 48 | /** 49 | * Format an error response 50 | * 51 | * @param error Error object or message 52 | * @returns Formatted error response 53 | */ 54 | protected formatError(error: Error | string): ToolCallResult { 55 | const errorMessage = error instanceof Error ? error.message : error; 56 | 57 | return { 58 | content: [ 59 | { 60 | type: 'text', 61 | text: errorMessage, 62 | }, 63 | ], 64 | isError: true, 65 | }; 66 | } 67 | 68 | /** 69 | * Handle tool execution errors 70 | * 71 | * @param handler Function to execute 72 | * @param args Arguments to pass to the handler 73 | * @returns Tool call result 74 | */ 75 | protected async handleExecution( 76 | handler: (args: Record<string, any>) => Promise<ToolCallResult>, 77 | args: Record<string, any> 78 | ): Promise<ToolCallResult> { 79 | try { 80 | return await handler(args); 81 | } catch (error) { 82 | if (error instanceof N8nApiError) { 83 | return this.formatError(error.message); 84 | } 85 | 86 | const errorMessage = error instanceof Error 87 | ? error.message 88 | : 'Unknown error occurred'; 89 | 90 | return this.formatError(`Error executing execution tool: ${errorMessage}`); 91 | } 92 | } 93 | } 94 | ``` -------------------------------------------------------------------------------- /tests/unit/config/simple-environment.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simple environment configuration tests 3 | */ 4 | 5 | import { describe, it, expect } from '@jest/globals'; 6 | 7 | // Simple environment validation function to test 8 | function validateEnvironment(env: Record<string, string | undefined>): { 9 | n8nApiUrl: string; 10 | n8nApiKey: string; 11 | debug: boolean; 12 | } { 13 | // Check required variables 14 | if (!env.N8N_API_URL) { 15 | throw new Error('Missing required environment variable: N8N_API_URL'); 16 | } 17 | 18 | if (!env.N8N_API_KEY) { 19 | throw new Error('Missing required environment variable: N8N_API_KEY'); 20 | } 21 | 22 | // Validate URL format 23 | try { 24 | new URL(env.N8N_API_URL); 25 | } catch (error) { 26 | throw new Error(`Invalid URL format for N8N_API_URL: ${env.N8N_API_URL}`); 27 | } 28 | 29 | // Return parsed config 30 | return { 31 | n8nApiUrl: env.N8N_API_URL, 32 | n8nApiKey: env.N8N_API_KEY, 33 | debug: env.DEBUG?.toLowerCase() === 'true' 34 | }; 35 | } 36 | 37 | describe('Environment Configuration', () => { 38 | describe('validateEnvironment', () => { 39 | it('should return a valid config when all required variables are present', () => { 40 | const env = { 41 | N8N_API_URL: 'https://n8n.example.com/api/v1', 42 | N8N_API_KEY: 'test-api-key' 43 | }; 44 | 45 | const config = validateEnvironment(env); 46 | 47 | expect(config).toEqual({ 48 | n8nApiUrl: 'https://n8n.example.com/api/v1', 49 | n8nApiKey: 'test-api-key', 50 | debug: false 51 | }); 52 | }); 53 | 54 | it('should set debug to true when DEBUG=true', () => { 55 | const env = { 56 | N8N_API_URL: 'https://n8n.example.com/api/v1', 57 | N8N_API_KEY: 'test-api-key', 58 | DEBUG: 'true' 59 | }; 60 | 61 | const config = validateEnvironment(env); 62 | 63 | expect(config.debug).toBe(true); 64 | }); 65 | 66 | it('should throw an error when N8N_API_URL is missing', () => { 67 | const env = { 68 | N8N_API_KEY: 'test-api-key' 69 | }; 70 | 71 | expect(() => validateEnvironment(env)).toThrow( 72 | 'Missing required environment variable: N8N_API_URL' 73 | ); 74 | }); 75 | 76 | it('should throw an error when N8N_API_KEY is missing', () => { 77 | const env = { 78 | N8N_API_URL: 'https://n8n.example.com/api/v1' 79 | }; 80 | 81 | expect(() => validateEnvironment(env)).toThrow( 82 | 'Missing required environment variable: N8N_API_KEY' 83 | ); 84 | }); 85 | 86 | it('should throw an error when N8N_API_URL is not a valid URL', () => { 87 | const env = { 88 | N8N_API_URL: 'invalid-url', 89 | N8N_API_KEY: 'test-api-key' 90 | }; 91 | 92 | expect(() => validateEnvironment(env)).toThrow( 93 | 'Invalid URL format for N8N_API_URL: invalid-url' 94 | ); 95 | }); 96 | }); 97 | }); 98 | ``` -------------------------------------------------------------------------------- /docs/development/index.md: -------------------------------------------------------------------------------- ```markdown 1 | # Development Guide 2 | 3 | This section provides information for developers who want to understand, maintain, or extend the n8n MCP Server. 4 | 5 | ## Overview 6 | 7 | The n8n MCP Server is built with TypeScript and implements the Model Context Protocol (MCP) to provide AI assistants with access to n8n workflows and executions. This development guide covers the architecture, extension points, and testing procedures. 8 | 9 | ## Topics 10 | 11 | - [Architecture](./architecture.md): Overview of the codebase organization and design patterns 12 | - [Extending the Server](./extending.md): Guide to adding new tools and resources 13 | - [Testing](./testing.md): Information on testing procedures and writing tests 14 | 15 | ## Development Setup 16 | 17 | To set up a development environment: 18 | 19 | 1. Clone the repository: 20 | ```bash 21 | git clone https://github.com/yourusername/n8n-mcp-server.git 22 | cd n8n-mcp-server 23 | ``` 24 | 25 | 2. Install dependencies: 26 | ```bash 27 | npm install 28 | ``` 29 | 30 | 3. Create a `.env` file for local development: 31 | ```bash 32 | cp .env.example .env 33 | # Edit the .env file with your n8n API credentials 34 | ``` 35 | 36 | 4. Start the development server: 37 | ```bash 38 | npm run dev 39 | ``` 40 | 41 | This will compile the TypeScript code in watch mode, allowing you to make changes and see them take effect immediately. 42 | 43 | ## Project Structure 44 | 45 | The project follows a modular structure: 46 | 47 | ``` 48 | n8n-mcp-server/ 49 | ├── src/ # Source code 50 | │ ├── api/ # API client for n8n 51 | │ ├── config/ # Configuration and environment settings 52 | │ ├── errors/ # Error handling 53 | │ ├── resources/ # MCP resources implementation 54 | │ │ ├── static/ # Static resources 55 | │ │ └── dynamic/ # Dynamic (parameterized) resources 56 | │ ├── tools/ # MCP tools implementation 57 | │ │ ├── workflow/ # Workflow management tools 58 | │ │ └── execution/ # Execution management tools 59 | │ ├── types/ # TypeScript type definitions 60 | │ └── utils/ # Utility functions 61 | ├── tests/ # Test files 62 | │ ├── unit/ # Unit tests 63 | │ ├── integration/ # Integration tests 64 | │ └── e2e/ # End-to-end tests 65 | └── build/ # Compiled output 66 | ``` 67 | 68 | ## Build and Distribution 69 | 70 | To build the project for distribution: 71 | 72 | ```bash 73 | npm run build 74 | ``` 75 | 76 | This will compile the TypeScript code to JavaScript in the `build` directory and make the executable script file. 77 | 78 | ## Development Workflow 79 | 80 | 1. Create a feature branch for your changes 81 | 2. Make your changes and ensure tests pass 82 | 3. Update documentation as needed 83 | 4. Submit a pull request 84 | 85 | For more detailed instructions on specific development tasks, see the linked guides. 86 | ``` -------------------------------------------------------------------------------- /src/resources/dynamic/workflow.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Dynamic Workflow Resource Handler 3 | * 4 | * This module provides the MCP resource implementation for retrieving 5 | * detailed workflow information by ID. 6 | */ 7 | 8 | import { N8nApiService } from '../../api/n8n-client.js'; 9 | import { formatWorkflowDetails, formatResourceUri } from '../../utils/resource-formatter.js'; 10 | import { McpError, ErrorCode } from '../../errors/index.js'; 11 | 12 | /** 13 | * Get workflow resource data by ID 14 | * 15 | * @param apiService n8n API service 16 | * @param workflowId Workflow ID 17 | * @returns Formatted workflow resource data 18 | */ 19 | export async function getWorkflowResource(apiService: N8nApiService, workflowId: string): Promise<string> { 20 | try { 21 | // Get the specific workflow from the API 22 | const workflow = await apiService.getWorkflow(workflowId); 23 | 24 | // Format the workflow for resource consumption 25 | const formattedWorkflow = formatWorkflowDetails(workflow); 26 | 27 | // Add metadata about the resource 28 | const result = { 29 | resourceType: 'workflow', 30 | id: workflowId, 31 | ...formattedWorkflow, 32 | _links: { 33 | self: formatResourceUri('workflow', workflowId), 34 | // Include links to related resources 35 | executions: `n8n://executions?workflowId=${workflowId}`, 36 | }, 37 | lastUpdated: new Date().toISOString(), 38 | }; 39 | 40 | return JSON.stringify(result, null, 2); 41 | } catch (error) { 42 | console.error(`Error fetching workflow resource (ID: ${workflowId}):`, error); 43 | 44 | // Handle not found errors specifically 45 | if (error instanceof McpError && error.code === ErrorCode.NotFoundError) { 46 | throw error; 47 | } 48 | 49 | throw new McpError( 50 | ErrorCode.InternalError, 51 | `Failed to retrieve workflow (ID: ${workflowId}): ${error instanceof Error ? error.message : 'Unknown error'}` 52 | ); 53 | } 54 | } 55 | 56 | /** 57 | * Get workflow resource template URI 58 | * 59 | * @returns Formatted resource template URI 60 | */ 61 | export function getWorkflowResourceTemplateUri(): string { 62 | return 'n8n://workflows/{id}'; 63 | } 64 | 65 | /** 66 | * Get workflow resource template metadata 67 | * 68 | * @returns Resource template metadata object 69 | */ 70 | export function getWorkflowResourceTemplateMetadata(): Record<string, any> { 71 | return { 72 | uriTemplate: getWorkflowResourceTemplateUri(), 73 | name: 'n8n Workflow Details', 74 | mimeType: 'application/json', 75 | description: 'Detailed information about a specific n8n workflow including all nodes, connections, and settings', 76 | }; 77 | } 78 | 79 | /** 80 | * Extract workflow ID from resource URI 81 | * 82 | * @param uri Resource URI 83 | * @returns Workflow ID or null if URI format is invalid 84 | */ 85 | export function extractWorkflowIdFromUri(uri: string): string | null { 86 | const match = uri.match(/^n8n:\/\/workflows\/([^/]+)$/); 87 | return match ? match[1] : null; 88 | } 89 | ``` -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- ```markdown 1 | # API Reference 2 | 3 | This section provides a comprehensive reference for the n8n MCP Server API, including all available tools and resources. 4 | 5 | ## Overview 6 | 7 | The n8n MCP Server implements the Model Context Protocol (MCP) to provide AI assistants with access to n8n workflows and executions. The API is divided into two main categories: 8 | 9 | 1. **Tools**: Executable functions that can perform operations on n8n, such as creating workflows or starting executions. 10 | 2. **Resources**: Data sources that provide information about workflows and executions. 11 | 12 | ## API Architecture 13 | 14 | The n8n MCP Server follows a clean separation of concerns: 15 | 16 | - **Client Layer**: Handles communication with the n8n API 17 | - **Transport Layer**: Implements the MCP protocol for communication with AI assistants 18 | - **Tools Layer**: Exposes executable operations to AI assistants 19 | - **Resources Layer**: Provides data access through URI-based resources 20 | 21 | All API interactions are authenticated using the n8n API key configured in your environment. 22 | 23 | ## Available Tools 24 | 25 | The server provides tools for managing workflows and executions: 26 | 27 | - [Workflow Tools](./workflow-tools.md): Create, list, update, and delete workflows 28 | - [Execution Tools](./execution-tools.md): Execute workflows and manage workflow executions 29 | 30 | ## Available Resources 31 | 32 | The server provides resources for accessing workflow and execution data: 33 | 34 | - [Static Resources](./static-resources.md): Fixed resources like workflow listings or execution statistics 35 | - [Dynamic Resources](./dynamic-resources.md): Parameterized resources for specific workflows or executions 36 | 37 | ## Understanding Input Schemas 38 | 39 | Each tool has an input schema that defines the expected parameters. These schemas follow the JSON Schema format and are automatically provided to AI assistants to enable proper parameter validation and suggestion. 40 | 41 | Example input schema for the `workflow_get` tool: 42 | 43 | ```json 44 | { 45 | "type": "object", 46 | "properties": { 47 | "id": { 48 | "type": "string", 49 | "description": "The ID of the workflow to retrieve" 50 | } 51 | }, 52 | "required": ["id"] 53 | } 54 | ``` 55 | 56 | ## Error Handling 57 | 58 | All API operations can return errors in a standardized format. Common error scenarios include: 59 | 60 | - Authentication failures (invalid or missing API key) 61 | - Resource not found (workflow or execution doesn't exist) 62 | - Permission issues (API key doesn't have required permissions) 63 | - Input validation errors (missing or invalid parameters) 64 | 65 | Error responses include detailed messages to help troubleshoot issues. 66 | 67 | ## Next Steps 68 | 69 | Explore the detailed documentation for each category: 70 | 71 | - [Workflow Tools](./workflow-tools.md) 72 | - [Execution Tools](./execution-tools.md) 73 | - [Static Resources](./static-resources.md) 74 | - [Dynamic Resources](./dynamic-resources.md) 75 | ``` -------------------------------------------------------------------------------- /src/resources/dynamic/execution.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Dynamic Execution Resource Handler 3 | * 4 | * This module provides the MCP resource implementation for retrieving 5 | * detailed execution information by ID. 6 | */ 7 | 8 | import { N8nApiService } from '../../api/n8n-client.js'; 9 | import { formatExecutionDetails } from '../../utils/execution-formatter.js'; 10 | import { formatResourceUri } from '../../utils/resource-formatter.js'; 11 | import { McpError, ErrorCode } from '../../errors/index.js'; 12 | 13 | /** 14 | * Get execution resource data by ID 15 | * 16 | * @param apiService n8n API service 17 | * @param executionId Execution ID 18 | * @returns Formatted execution resource data 19 | */ 20 | export async function getExecutionResource(apiService: N8nApiService, executionId: string): Promise<string> { 21 | try { 22 | // Get the specific execution from the API 23 | const execution = await apiService.getExecution(executionId); 24 | 25 | // Format the execution for resource consumption 26 | const formattedExecution = formatExecutionDetails(execution); 27 | 28 | // Add metadata about the resource 29 | const result = { 30 | resourceType: 'execution', 31 | id: executionId, 32 | ...formattedExecution, 33 | _links: { 34 | self: formatResourceUri('execution', executionId), 35 | // Include link to related workflow 36 | workflow: `n8n://workflows/${execution.workflowId}`, 37 | }, 38 | lastUpdated: new Date().toISOString(), 39 | }; 40 | 41 | return JSON.stringify(result, null, 2); 42 | } catch (error) { 43 | console.error(`Error fetching execution resource (ID: ${executionId}):`, error); 44 | 45 | // Handle not found errors specifically 46 | if (error instanceof McpError && error.code === ErrorCode.NotFoundError) { 47 | throw error; 48 | } 49 | 50 | throw new McpError( 51 | ErrorCode.InternalError, 52 | `Failed to retrieve execution (ID: ${executionId}): ${error instanceof Error ? error.message : 'Unknown error'}` 53 | ); 54 | } 55 | } 56 | 57 | /** 58 | * Get execution resource template URI 59 | * 60 | * @returns Formatted resource template URI 61 | */ 62 | export function getExecutionResourceTemplateUri(): string { 63 | return 'n8n://executions/{id}'; 64 | } 65 | 66 | /** 67 | * Get execution resource template metadata 68 | * 69 | * @returns Resource template metadata object 70 | */ 71 | export function getExecutionResourceTemplateMetadata(): Record<string, any> { 72 | return { 73 | uriTemplate: getExecutionResourceTemplateUri(), 74 | name: 'n8n Execution Details', 75 | mimeType: 'application/json', 76 | description: 'Detailed information about a specific n8n workflow execution including node results and error information', 77 | }; 78 | } 79 | 80 | /** 81 | * Extract execution ID from resource URI 82 | * 83 | * @param uri Resource URI 84 | * @returns Execution ID or null if URI format is invalid 85 | */ 86 | export function extractExecutionIdFromUri(uri: string): string | null { 87 | const match = uri.match(/^n8n:\/\/executions\/([^/]+)$/); 88 | return match ? match[1] : null; 89 | } 90 | ``` -------------------------------------------------------------------------------- /tests/unit/tools/workflow/simple-tool.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Simple workflow tool tests without complex dependencies 3 | */ 4 | 5 | import { describe, it, expect } from '@jest/globals'; 6 | 7 | interface MockWorkflow { 8 | id: string; 9 | name: string; 10 | active: boolean; 11 | createdAt: string; 12 | updatedAt: string; 13 | nodes: any[]; 14 | } 15 | 16 | interface WorkflowFilter { 17 | active?: boolean; 18 | } 19 | 20 | // Mock workflow data 21 | const mockWorkflows: MockWorkflow[] = [ 22 | { 23 | id: '1234abc', 24 | name: 'Test Workflow 1', 25 | active: true, 26 | createdAt: '2025-03-01T12:00:00.000Z', 27 | updatedAt: '2025-03-02T14:30:00.000Z', 28 | nodes: [] 29 | }, 30 | { 31 | id: '5678def', 32 | name: 'Test Workflow 2', 33 | active: false, 34 | createdAt: '2025-03-01T12:00:00.000Z', 35 | updatedAt: '2025-03-12T10:15:00.000Z', 36 | nodes: [] 37 | } 38 | ]; 39 | 40 | // Simple function to test tool definition 41 | function getListWorkflowsToolDefinition() { 42 | return { 43 | name: 'list_workflows', 44 | description: 'List all workflows with optional filtering by status', 45 | inputSchema: { 46 | type: 'object', 47 | properties: { 48 | active: { 49 | type: 'boolean', 50 | description: 'Filter workflows by active status' 51 | } 52 | }, 53 | required: [] 54 | } 55 | }; 56 | } 57 | 58 | // Simple function to test workflow filtering 59 | function filterWorkflows(workflows: MockWorkflow[], filter: WorkflowFilter): MockWorkflow[] { 60 | if (filter && typeof filter.active === 'boolean') { 61 | return workflows.filter(workflow => workflow.active === filter.active); 62 | } 63 | return workflows; 64 | } 65 | 66 | describe('Workflow Tools', () => { 67 | describe('getListWorkflowsToolDefinition', () => { 68 | it('should return the correct tool definition', () => { 69 | const definition = getListWorkflowsToolDefinition(); 70 | 71 | expect(definition.name).toBe('list_workflows'); 72 | expect(definition.description).toBeTruthy(); 73 | expect(definition.inputSchema).toBeDefined(); 74 | expect(definition.inputSchema.properties).toHaveProperty('active'); 75 | expect(definition.inputSchema.required).toEqual([]); 76 | }); 77 | }); 78 | 79 | describe('filterWorkflows', () => { 80 | it('should return all workflows when no filter is provided', () => { 81 | const result = filterWorkflows(mockWorkflows, {}); 82 | 83 | expect(result).toHaveLength(2); 84 | expect(result).toEqual(mockWorkflows); 85 | }); 86 | 87 | it('should filter workflows by active status when active is true', () => { 88 | const result = filterWorkflows(mockWorkflows, { active: true }); 89 | 90 | expect(result).toHaveLength(1); 91 | expect(result[0].id).toBe('1234abc'); 92 | expect(result[0].active).toBe(true); 93 | }); 94 | 95 | it('should filter workflows by active status when active is false', () => { 96 | const result = filterWorkflows(mockWorkflows, { active: false }); 97 | 98 | expect(result).toHaveLength(1); 99 | expect(result[0].id).toBe('5678def'); 100 | expect(result[0].active).toBe(false); 101 | }); 102 | }); 103 | }); 104 | ``` -------------------------------------------------------------------------------- /.github/workflows/claude-code-review.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Claude Code Review 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | # Optional: Only run on specific file changes 7 | # paths: 8 | # - "src/**/*.ts" 9 | # - "src/**/*.tsx" 10 | # - "src/**/*.js" 11 | # - "src/**/*.jsx" 12 | 13 | jobs: 14 | claude-review: 15 | # Optional: Filter by PR author 16 | # if: | 17 | # github.event.pull_request.user.login == 'external-contributor' || 18 | # github.event.pull_request.user.login == 'new-developer' || 19 | # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' 20 | 21 | runs-on: ubuntu-latest 22 | permissions: 23 | contents: read 24 | pull-requests: read 25 | issues: read 26 | id-token: write 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 1 33 | 34 | - name: Run Claude Code Review 35 | id: claude-review 36 | uses: anthropics/claude-code-action@beta 37 | with: 38 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} 39 | 40 | # Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4) 41 | # model: "claude-opus-4-20250514" 42 | 43 | # Direct prompt for automated review (no @claude mention needed) 44 | direct_prompt: | 45 | Please review this pull request and provide feedback on: 46 | - Code quality and best practices 47 | - Potential bugs or issues 48 | - Performance considerations 49 | - Security concerns 50 | - Test coverage 51 | 52 | Be constructive and helpful in your feedback. 53 | 54 | # Optional: Customize review based on file types 55 | # direct_prompt: | 56 | # Review this PR focusing on: 57 | # - For TypeScript files: Type safety and proper interface usage 58 | # - For API endpoints: Security, input validation, and error handling 59 | # - For React components: Performance, accessibility, and best practices 60 | # - For tests: Coverage, edge cases, and test quality 61 | 62 | # Optional: Different prompts for different authors 63 | # direct_prompt: | 64 | # ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' && 65 | # 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' || 66 | # 'Please provide a thorough code review focusing on our coding standards and best practices.' }} 67 | 68 | # Optional: Add specific tools for running tests or linting 69 | # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" 70 | 71 | # Optional: Skip review for certain conditions 72 | # if: | 73 | # !contains(github.event.pull_request.title, '[skip-review]') && 74 | # !contains(github.event.pull_request.title, '[WIP]') 75 | 76 | ``` -------------------------------------------------------------------------------- /src/config/environment.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Environment Configuration 3 | * 4 | * This module handles loading and validating environment variables 5 | * required for connecting to the n8n API. 6 | */ 7 | 8 | import dotenv from 'dotenv'; 9 | import findConfig from 'find-config'; 10 | import path from 'path'; 11 | import { McpError } from '@modelcontextprotocol/sdk/types.js'; 12 | import { ErrorCode } from '../errors/error-codes.js'; 13 | 14 | // Environment variable names 15 | export const ENV_VARS = { 16 | N8N_API_URL: 'N8N_API_URL', 17 | N8N_API_KEY: 'N8N_API_KEY', 18 | N8N_WEBHOOK_USERNAME: 'N8N_WEBHOOK_USERNAME', 19 | N8N_WEBHOOK_PASSWORD: 'N8N_WEBHOOK_PASSWORD', 20 | DEBUG: 'DEBUG', 21 | }; 22 | 23 | // Interface for validated environment variables 24 | export interface EnvConfig { 25 | n8nApiUrl: string; 26 | n8nApiKey: string; 27 | n8nWebhookUsername?: string; // Made optional 28 | n8nWebhookPassword?: string; // Made optional 29 | debug: boolean; 30 | } 31 | 32 | /** 33 | * Load environment variables from .env file if present 34 | */ 35 | export function loadEnvironmentVariables(): void { 36 | const { 37 | N8N_API_URL, 38 | N8N_API_KEY, 39 | N8N_WEBHOOK_USERNAME, 40 | N8N_WEBHOOK_PASSWORD 41 | } = process.env; 42 | 43 | if ( 44 | !N8N_API_URL && 45 | !N8N_API_KEY && 46 | !N8N_WEBHOOK_USERNAME && 47 | !N8N_WEBHOOK_PASSWORD 48 | ) { 49 | const projectRoot = findConfig('package.json'); 50 | if (projectRoot) { 51 | const envPath = path.resolve(path.dirname(projectRoot), '.env'); 52 | dotenv.config({ path: envPath }); 53 | } 54 | } 55 | } 56 | 57 | /** 58 | * Validate and retrieve required environment variables 59 | * 60 | * @returns Validated environment configuration 61 | * @throws {McpError} If required environment variables are missing 62 | */ 63 | export function getEnvConfig(): EnvConfig { 64 | const n8nApiUrl = process.env[ENV_VARS.N8N_API_URL]; 65 | const n8nApiKey = process.env[ENV_VARS.N8N_API_KEY]; 66 | const n8nWebhookUsername = process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]; 67 | const n8nWebhookPassword = process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]; 68 | const debug = process.env[ENV_VARS.DEBUG]?.toLowerCase() === 'true'; 69 | 70 | // Validate required core environment variables 71 | if (!n8nApiUrl) { 72 | throw new McpError( 73 | ErrorCode.InitializationError, 74 | `Missing required environment variable: ${ENV_VARS.N8N_API_URL}` 75 | ); 76 | } 77 | 78 | if (!n8nApiKey) { 79 | throw new McpError( 80 | ErrorCode.InitializationError, 81 | `Missing required environment variable: ${ENV_VARS.N8N_API_KEY}` 82 | ); 83 | } 84 | 85 | // N8N_WEBHOOK_USERNAME and N8N_WEBHOOK_PASSWORD are now optional at startup. 86 | // Tools requiring them should perform checks at the point of use. 87 | 88 | // Validate URL format 89 | try { 90 | new URL(n8nApiUrl); 91 | } catch (error) { 92 | throw new McpError( 93 | ErrorCode.InitializationError, 94 | `Invalid URL format for ${ENV_VARS.N8N_API_URL}: ${n8nApiUrl}` 95 | ); 96 | } 97 | 98 | return { 99 | n8nApiUrl, 100 | n8nApiKey, 101 | n8nWebhookUsername: n8nWebhookUsername || undefined, // Ensure undefined if empty 102 | n8nWebhookPassword: n8nWebhookPassword || undefined, // Ensure undefined if empty 103 | debug, 104 | }; 105 | } 106 | ``` -------------------------------------------------------------------------------- /tests/mocks/axios-mock.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Axios mock utilities for n8n MCP Server tests 3 | */ 4 | 5 | import { AxiosRequestConfig, AxiosResponse } from 'axios'; 6 | 7 | export interface MockResponse { 8 | data: any; 9 | status: number; 10 | statusText: string; 11 | headers?: Record<string, string>; 12 | config?: AxiosRequestConfig; 13 | } 14 | 15 | export const createMockAxiosResponse = (options: Partial<MockResponse> = {}): AxiosResponse => { 16 | return { 17 | data: options.data ?? {}, 18 | status: options.status ?? 200, 19 | statusText: options.statusText ?? 'OK', 20 | headers: options.headers ?? {}, 21 | config: options.config ?? {}, 22 | } as AxiosResponse; 23 | }; 24 | 25 | /** 26 | * Create a mock axios instance for testing 27 | */ 28 | export const createMockAxiosInstance = () => { 29 | const mockRequests: Record<string, any[]> = {}; 30 | const mockResponses: Record<string, MockResponse[]> = {}; 31 | 32 | const mockInstance = { 33 | get: jest.fn(), 34 | post: jest.fn(), 35 | put: jest.fn(), 36 | delete: jest.fn(), 37 | interceptors: { 38 | request: { 39 | use: jest.fn(), 40 | }, 41 | response: { 42 | use: jest.fn(), 43 | }, 44 | }, 45 | defaults: {}, 46 | 47 | // Helper method to add mock response 48 | addMockResponse(method: string, url: string, response: MockResponse | Error) { 49 | if (!mockResponses[`${method}:${url}`]) { 50 | mockResponses[`${method}:${url}`] = []; 51 | } 52 | 53 | if (response instanceof Error) { 54 | mockResponses[`${method}:${url}`].push(response as any); 55 | } else { 56 | mockResponses[`${method}:${url}`].push(response); 57 | } 58 | }, 59 | 60 | // Helper method to get request history 61 | getRequestHistory(method: string, url: string) { 62 | return mockRequests[`${method}:${url}`] || []; 63 | }, 64 | 65 | // Reset all mocks 66 | reset() { 67 | Object.keys(mockRequests).forEach(key => { 68 | delete mockRequests[key]; 69 | }); 70 | 71 | Object.keys(mockResponses).forEach(key => { 72 | delete mockResponses[key]; 73 | }); 74 | 75 | mockInstance.get.mockReset(); 76 | mockInstance.post.mockReset(); 77 | mockInstance.put.mockReset(); 78 | mockInstance.delete.mockReset(); 79 | } 80 | }; 81 | 82 | // Setup method implementations 83 | ['get', 'post', 'put', 'delete'].forEach(method => { 84 | mockInstance[method].mockImplementation(async (url: string, data?: any) => { 85 | const requestKey = `${method}:${url}`; 86 | 87 | if (!mockRequests[requestKey]) { 88 | mockRequests[requestKey] = []; 89 | } 90 | 91 | mockRequests[requestKey].push(data); 92 | 93 | if (mockResponses[requestKey] && mockResponses[requestKey].length > 0) { 94 | const response = mockResponses[requestKey].shift(); 95 | 96 | if (response instanceof Error) { 97 | throw response; 98 | } 99 | 100 | return createMockAxiosResponse(response); 101 | } 102 | 103 | throw new Error(`No mock response defined for ${method.toUpperCase()} ${url}`); 104 | }); 105 | }); 106 | 107 | return mockInstance; 108 | }; 109 | 110 | export default { 111 | createMockAxiosResponse, 112 | createMockAxiosInstance, 113 | }; 114 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/create.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Create Workflow Tool 3 | * 4 | * This tool creates a new workflow in n8n. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { N8nApiError } from '../../errors/index.js'; 10 | 11 | /** 12 | * Handler for the create_workflow tool 13 | */ 14 | export class CreateWorkflowHandler extends BaseWorkflowToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments containing workflow details 19 | * @returns Created workflow information 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async (args) => { 23 | const { name, nodes, connections, active, tags } = args; 24 | 25 | if (!name) { 26 | throw new N8nApiError('Missing required parameter: name'); 27 | } 28 | 29 | // Validate nodes if provided 30 | if (nodes && !Array.isArray(nodes)) { 31 | throw new N8nApiError('Parameter "nodes" must be an array'); 32 | } 33 | 34 | // Validate connections if provided 35 | if (connections && typeof connections !== 'object') { 36 | throw new N8nApiError('Parameter "connections" must be an object'); 37 | } 38 | 39 | // Prepare workflow object 40 | const workflowData: Record<string, any> = { 41 | name, 42 | active: active === true, // Default to false if not specified 43 | }; 44 | 45 | // Add optional fields if provided 46 | if (nodes) workflowData.nodes = nodes; 47 | if (connections) workflowData.connections = connections; 48 | if (tags) workflowData.tags = tags; 49 | 50 | // Create the workflow 51 | const workflow = await this.apiService.createWorkflow(workflowData); 52 | 53 | return this.formatSuccess( 54 | { 55 | id: workflow.id, 56 | name: workflow.name, 57 | active: workflow.active 58 | }, 59 | `Workflow created successfully` 60 | ); 61 | }, args); 62 | } 63 | } 64 | 65 | /** 66 | * Get tool definition for the create_workflow tool 67 | * 68 | * @returns Tool definition 69 | */ 70 | export function getCreateWorkflowToolDefinition(): ToolDefinition { 71 | return { 72 | name: 'create_workflow', 73 | description: 'Create a new workflow in n8n', 74 | inputSchema: { 75 | type: 'object', 76 | properties: { 77 | name: { 78 | type: 'string', 79 | description: 'Name of the workflow', 80 | }, 81 | nodes: { 82 | type: 'array', 83 | description: 'Array of node objects that define the workflow', 84 | items: { 85 | type: 'object', 86 | }, 87 | }, 88 | connections: { 89 | type: 'object', 90 | description: 'Connection mappings between nodes', 91 | }, 92 | active: { 93 | type: 'boolean', 94 | description: 'Whether the workflow should be active upon creation', 95 | }, 96 | tags: { 97 | type: 'array', 98 | description: 'Tags to associate with the workflow', 99 | items: { 100 | type: 'string', 101 | }, 102 | }, 103 | }, 104 | required: ['name'], 105 | }, 106 | }; 107 | } 108 | ``` -------------------------------------------------------------------------------- /src/tools/execution/list.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * List Executions Tool 3 | * 4 | * This tool retrieves a list of workflow executions from n8n. 5 | */ 6 | 7 | import { BaseExecutionToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition, Execution } from '../../types/index.js'; 9 | import { formatExecutionSummary, summarizeExecutions } from '../../utils/execution-formatter.js'; 10 | 11 | /** 12 | * Handler for the list_executions tool 13 | */ 14 | export class ListExecutionsHandler extends BaseExecutionToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments (workflowId, status, limit, lastId) 19 | * @returns List of executions 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async () => { 23 | const executions = await this.apiService.getExecutions(); 24 | 25 | // Apply filters if provided 26 | let filteredExecutions = executions; 27 | 28 | // Filter by workflow ID if provided 29 | if (args.workflowId) { 30 | filteredExecutions = filteredExecutions.filter( 31 | (execution: Execution) => execution.workflowId === args.workflowId 32 | ); 33 | } 34 | 35 | // Filter by status if provided 36 | if (args.status) { 37 | filteredExecutions = filteredExecutions.filter( 38 | (execution: Execution) => execution.status === args.status 39 | ); 40 | } 41 | 42 | // Apply limit if provided 43 | const limit = args.limit && args.limit > 0 ? args.limit : filteredExecutions.length; 44 | filteredExecutions = filteredExecutions.slice(0, limit); 45 | 46 | // Format the executions for display 47 | const formattedExecutions = filteredExecutions.map((execution: Execution) => 48 | formatExecutionSummary(execution) 49 | ); 50 | 51 | // Generate summary if requested 52 | let summary = undefined; 53 | if (args.includeSummary) { 54 | summary = summarizeExecutions(executions); 55 | } 56 | 57 | // Prepare response data 58 | const responseData = { 59 | executions: formattedExecutions, 60 | summary: summary, 61 | total: formattedExecutions.length, 62 | filtered: args.workflowId || args.status ? true : false 63 | }; 64 | 65 | return this.formatSuccess( 66 | responseData, 67 | `Found ${formattedExecutions.length} execution(s)` 68 | ); 69 | }, args); 70 | } 71 | } 72 | 73 | /** 74 | * Get tool definition for the list_executions tool 75 | * 76 | * @returns Tool definition 77 | */ 78 | export function getListExecutionsToolDefinition(): ToolDefinition { 79 | return { 80 | name: 'list_executions', 81 | description: 'Retrieve a list of workflow executions from n8n', 82 | inputSchema: { 83 | type: 'object', 84 | properties: { 85 | workflowId: { 86 | type: 'string', 87 | description: 'Optional ID of workflow to filter executions by', 88 | }, 89 | status: { 90 | type: 'string', 91 | description: 'Optional status to filter by (success, error, waiting, or canceled)', 92 | }, 93 | limit: { 94 | type: 'number', 95 | description: 'Maximum number of executions to return', 96 | }, 97 | lastId: { 98 | type: 'string', 99 | description: 'ID of the last execution for pagination', 100 | }, 101 | includeSummary: { 102 | type: 'boolean', 103 | description: 'Include summary statistics about executions', 104 | }, 105 | }, 106 | required: [], 107 | }, 108 | }; 109 | } 110 | ``` -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Error Handling Module 3 | * 4 | * This module provides custom error classes and error handling utilities 5 | * for the n8n MCP Server. 6 | */ 7 | 8 | import { McpError as SdkMcpError } from '@modelcontextprotocol/sdk/types.js'; 9 | import { ErrorCode } from './error-codes.js'; 10 | 11 | // Re-export McpError from SDK 12 | export { McpError } from '@modelcontextprotocol/sdk/types.js'; 13 | // Re-export ErrorCode enum 14 | export { ErrorCode } from './error-codes.js'; 15 | 16 | /** 17 | * n8n API Error class for handling errors from the n8n API 18 | */ 19 | export class N8nApiError extends SdkMcpError { 20 | constructor(message: string, statusCode?: number, details?: unknown) { 21 | // Map HTTP status codes to appropriate MCP error codes 22 | let errorCode = ErrorCode.InternalError; 23 | 24 | if (statusCode) { 25 | if (statusCode === 401 || statusCode === 403) { 26 | errorCode = ErrorCode.AuthenticationError; 27 | } else if (statusCode === 404) { 28 | errorCode = ErrorCode.NotFoundError; 29 | } else if (statusCode >= 400 && statusCode < 500) { 30 | errorCode = ErrorCode.InvalidRequest; 31 | } 32 | } 33 | 34 | super(errorCode, formatErrorMessage(message, statusCode, details)); 35 | } 36 | } 37 | 38 | /** 39 | * Format an error message with status code and details 40 | */ 41 | function formatErrorMessage(message: string, statusCode?: number, details?: unknown): string { 42 | let formattedMessage = message; 43 | 44 | if (statusCode) { 45 | formattedMessage += ` (Status: ${statusCode})`; 46 | } 47 | 48 | if (details) { 49 | try { 50 | const detailsStr = typeof details === 'string' 51 | ? details 52 | : JSON.stringify(details, null, 2); 53 | formattedMessage += `\nDetails: ${detailsStr}`; 54 | } catch (error) { 55 | // Ignore JSON stringification errors 56 | } 57 | } 58 | 59 | return formattedMessage; 60 | } 61 | 62 | /** 63 | * Safely parse JSON response from n8n API 64 | * 65 | * @param text Text to parse as JSON 66 | * @returns Parsed JSON object or null if parsing fails 67 | */ 68 | export function safeJsonParse(text: string): any { 69 | try { 70 | return JSON.parse(text); 71 | } catch (error) { 72 | return null; 73 | } 74 | } 75 | 76 | /** 77 | * Handle axios errors and convert them to N8nApiError 78 | * 79 | * @param error Error object from axios 80 | * @param defaultMessage Default error message 81 | * @returns N8nApiError with appropriate details 82 | */ 83 | export function handleAxiosError(error: any, defaultMessage = 'n8n API request failed'): N8nApiError { 84 | // Handle axios error responses 85 | if (error.response) { 86 | const statusCode = error.response.status; 87 | const responseData = error.response.data; 88 | 89 | let errorMessage = defaultMessage; 90 | if (responseData && responseData.message) { 91 | errorMessage = responseData.message; 92 | } 93 | 94 | return new N8nApiError(errorMessage, statusCode, responseData); 95 | } 96 | 97 | // Handle request errors (e.g., network issues) 98 | if (error.request) { 99 | return new N8nApiError( 100 | 'Network error connecting to n8n API', 101 | undefined, 102 | error.message 103 | ); 104 | } 105 | 106 | // Handle other errors 107 | return new N8nApiError(error.message || defaultMessage); 108 | } 109 | 110 | /** 111 | * Extract a readable error message from an error object 112 | * 113 | * @param error Error object 114 | * @returns Readable error message 115 | */ 116 | export function getErrorMessage(error: unknown): string { 117 | if (error instanceof Error) { 118 | return error.message; 119 | } 120 | 121 | if (typeof error === 'string') { 122 | return error; 123 | } 124 | 125 | try { 126 | return JSON.stringify(error); 127 | } catch { 128 | return 'Unknown error'; 129 | } 130 | } 131 | ``` -------------------------------------------------------------------------------- /docs/setup/configuration.md: -------------------------------------------------------------------------------- ```markdown 1 | # Configuration Guide 2 | 3 | This guide provides detailed information on configuring the n8n MCP Server. 4 | 5 | ## Environment Variables 6 | 7 | The n8n MCP Server is configured using environment variables, which can be set in a `.env` file or directly in your environment. 8 | 9 | ### Required Variables 10 | 11 | | Variable | Description | Example | 12 | |----------|-------------|---------| 13 | | `N8N_API_URL` | URL of the n8n API | `http://localhost:5678/api/v1` | 14 | | `N8N_API_KEY` | API key for authenticating with n8n | `n8n_api_...` | 15 | 16 | ### Optional Variables 17 | 18 | | Variable | Description | Default | Example | 19 | |----------|-------------|---------|---------| 20 | | `DEBUG` | Enable debug logging | `false` | `true` or `false` | 21 | 22 | ## Creating a .env File 23 | 24 | The simplest way to configure the server is to create a `.env` file in the directory where you'll run the server: 25 | 26 | ```bash 27 | # Copy the example .env file 28 | cp .env.example .env 29 | 30 | # Edit the .env file with your settings 31 | nano .env # or use any text editor 32 | ``` 33 | 34 | Example `.env` file: 35 | 36 | ```env 37 | # n8n MCP Server Environment Variables 38 | 39 | # Required: URL of the n8n API 40 | N8N_API_URL=http://localhost:5678/api/v1 41 | 42 | # Required: API key for authenticating with n8n 43 | N8N_API_KEY=your_n8n_api_key_here 44 | 45 | # Optional: Set to 'true' to enable debug logging 46 | DEBUG=false 47 | ``` 48 | 49 | ## Generating an n8n API Key 50 | 51 | To use the n8n MCP Server, you need an API key from your n8n instance: 52 | 53 | 1. Open your n8n instance in a browser 54 | 2. Go to **Settings** > **API** > **API Keys** 55 | 3. Click **Create** to create a new API key 56 | 4. Set appropriate **Scope** (recommended: `workflow:read workflow:write workflow:execute`) 57 | 5. Copy the key to your `.env` file 58 | 59 |  60 | 61 | ## Server Connection Options 62 | 63 | By default, the n8n MCP Server listens on `stdin` and `stdout` for Model Context Protocol communications. This is the format expected by AI assistants using the MCP protocol. 64 | 65 | ## Configuring AI Assistants 66 | 67 | To use the n8n MCP Server with AI assistants, you need to register it with your AI assistant platform. The exact method depends on the platform you're using. 68 | 69 | ### Using the MCP Installer 70 | 71 | If you're using Claude or another assistant that supports the MCP Installer, you can register the server with: 72 | 73 | ```bash 74 | # Install the MCP Installer 75 | npx @anaisbetts/mcp-installer 76 | 77 | # Register the server (if installed globally) 78 | install_repo_mcp_server n8n-mcp-server 79 | 80 | # Or register from a local installation 81 | install_local_mcp_server path/to/n8n-mcp-server 82 | ``` 83 | 84 | ### Manual Configuration 85 | 86 | For platforms without an installer, you'll need to configure the connection according to the platform's documentation. Typically, this involves: 87 | 88 | 1. Specifying the path to the executable 89 | 2. Setting environment variables for the server 90 | 3. Configuring response formatting 91 | 92 | ## Verifying Configuration 93 | 94 | To verify your configuration: 95 | 96 | 1. Start the server 97 | 2. Open your AI assistant 98 | 3. Try a simple command like "List all workflows in n8n" 99 | 100 | If configured correctly, the assistant should be able to retrieve and display your workflows. 101 | 102 | ## Troubleshooting 103 | 104 | If you encounter issues with your configuration, check: 105 | 106 | - The `.env` file is in the correct location 107 | - The n8n API URL is accessible from where the server is running 108 | - The API key has the correct permissions 109 | - Any firewalls or network restrictions that might block connections 110 | 111 | For more specific issues, see the [Troubleshooting](./troubleshooting.md) guide. 112 | ``` -------------------------------------------------------------------------------- /tests/mocks/n8n-fixtures.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Mock fixtures for n8n API responses 3 | */ 4 | 5 | import { Workflow, Execution } from '../../src/types/index.js'; 6 | 7 | /** 8 | * Create a mock workflow for testing 9 | */ 10 | export const createMockWorkflow = (overrides: Partial<Workflow> = {}): Workflow => { 11 | const id = overrides.id ?? 'mock-workflow-1'; 12 | 13 | return { 14 | id, 15 | name: overrides.name ?? `Mock Workflow ${id}`, 16 | active: overrides.active ?? false, 17 | createdAt: overrides.createdAt ?? new Date().toISOString(), 18 | updatedAt: overrides.updatedAt ?? new Date().toISOString(), 19 | nodes: overrides.nodes ?? [ 20 | { 21 | id: 'start', 22 | name: 'Start', 23 | type: 'n8n-nodes-base.start', 24 | parameters: {}, 25 | position: [100, 300], 26 | }, 27 | ], 28 | connections: overrides.connections ?? {}, 29 | settings: overrides.settings ?? {}, 30 | staticData: overrides.staticData ?? null, 31 | pinData: overrides.pinData ?? {}, 32 | ...overrides, 33 | }; 34 | }; 35 | 36 | /** 37 | * Create multiple mock workflows 38 | */ 39 | export const createMockWorkflows = (count: number = 3): Workflow[] => { 40 | return Array.from({ length: count }, (_, i) => 41 | createMockWorkflow({ 42 | id: `mock-workflow-${i + 1}`, 43 | name: `Mock Workflow ${i + 1}`, 44 | active: i % 2 === 0, // Alternate active status 45 | }) 46 | ); 47 | }; 48 | 49 | /** 50 | * Create a mock execution for testing 51 | */ 52 | export const createMockExecution = (overrides: Partial<Execution> = {}): Execution => { 53 | const id = overrides.id ?? 'mock-execution-1'; 54 | const workflowId = overrides.workflowId ?? 'mock-workflow-1'; 55 | 56 | return { 57 | id, 58 | workflowId, 59 | finished: overrides.finished ?? true, 60 | mode: overrides.mode ?? 'manual', 61 | waitTill: overrides.waitTill ?? null, 62 | startedAt: overrides.startedAt ?? new Date().toISOString(), 63 | stoppedAt: overrides.stoppedAt ?? new Date().toISOString(), 64 | status: overrides.status ?? 'success', 65 | data: overrides.data ?? { 66 | resultData: { 67 | runData: {}, 68 | }, 69 | }, 70 | workflowData: overrides.workflowData ?? createMockWorkflow({ id: workflowId }), 71 | ...overrides, 72 | }; 73 | }; 74 | 75 | /** 76 | * Create multiple mock executions 77 | */ 78 | export const createMockExecutions = (count: number = 3): Execution[] => { 79 | return Array.from({ length: count }, (_, i) => 80 | createMockExecution({ 81 | id: `mock-execution-${i + 1}`, 82 | workflowId: `mock-workflow-${(i % 2) + 1}`, // Alternate between two workflows 83 | status: i % 3 === 0 ? 'success' : i % 3 === 1 ? 'error' : 'waiting', 84 | }) 85 | ); 86 | }; 87 | 88 | /** 89 | * Create mock n8n API responses 90 | */ 91 | export const mockApiResponses = { 92 | workflows: { 93 | list: { 94 | data: createMockWorkflows(), 95 | }, 96 | single: (id: string = 'mock-workflow-1') => createMockWorkflow({ id }), 97 | create: (workflow: Partial<Workflow> = {}) => createMockWorkflow(workflow), 98 | update: (id: string = 'mock-workflow-1', workflow: Partial<Workflow> = {}) => 99 | createMockWorkflow({ ...workflow, id }), 100 | delete: { success: true }, 101 | activate: (id: string = 'mock-workflow-1') => createMockWorkflow({ id, active: true }), 102 | deactivate: (id: string = 'mock-workflow-1') => createMockWorkflow({ id, active: false }), 103 | }, 104 | 105 | executions: { 106 | list: { 107 | data: createMockExecutions(), 108 | }, 109 | single: (id: string = 'mock-execution-1') => createMockExecution({ id }), 110 | delete: { success: true }, 111 | }, 112 | }; 113 | 114 | export default { 115 | createMockWorkflow, 116 | createMockWorkflows, 117 | createMockExecution, 118 | createMockExecutions, 119 | mockApiResponses, 120 | }; 121 | ``` -------------------------------------------------------------------------------- /src/api/n8n-client.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * n8n API Client Interface 3 | * 4 | * This module defines interfaces and types for the n8n API client. 5 | */ 6 | 7 | import { N8nApiClient } from './client.js'; 8 | import { EnvConfig } from '../config/environment.js'; 9 | import { Workflow, Execution } from '../types/index.js'; 10 | 11 | /** 12 | * n8n API service - provides functions for interacting with n8n API 13 | */ 14 | export class N8nApiService { 15 | private client: N8nApiClient; 16 | 17 | /** 18 | * Create a new n8n API service 19 | * 20 | * @param config Environment configuration 21 | */ 22 | constructor(config: EnvConfig) { 23 | this.client = new N8nApiClient(config); 24 | } 25 | 26 | /** 27 | * Check connectivity to the n8n API 28 | */ 29 | async checkConnectivity(): Promise<void> { 30 | return this.client.checkConnectivity(); 31 | } 32 | 33 | /** 34 | * Get all workflows from n8n 35 | * 36 | * @returns Array of workflow objects 37 | */ 38 | async getWorkflows(): Promise<Workflow[]> { 39 | return this.client.getWorkflows(); 40 | } 41 | 42 | /** 43 | * Get a specific workflow by ID 44 | * 45 | * @param id Workflow ID 46 | * @returns Workflow object 47 | */ 48 | async getWorkflow(id: string): Promise<Workflow> { 49 | return this.client.getWorkflow(id); 50 | } 51 | 52 | /** 53 | * Execute a workflow by ID 54 | * 55 | * @param id Workflow ID 56 | * @param data Optional data to pass to the workflow 57 | * @returns Execution result 58 | */ 59 | async executeWorkflow(id: string, data?: Record<string, any>): Promise<any> { 60 | return this.client.executeWorkflow(id, data); 61 | } 62 | 63 | /** 64 | * Create a new workflow 65 | * 66 | * @param workflow Workflow object to create 67 | * @returns Created workflow 68 | */ 69 | async createWorkflow(workflow: Record<string, any>): Promise<Workflow> { 70 | return this.client.createWorkflow(workflow); 71 | } 72 | 73 | /** 74 | * Update an existing workflow 75 | * 76 | * @param id Workflow ID 77 | * @param workflow Updated workflow object 78 | * @returns Updated workflow 79 | */ 80 | async updateWorkflow(id: string, workflow: Record<string, any>): Promise<Workflow> { 81 | return this.client.updateWorkflow(id, workflow); 82 | } 83 | 84 | /** 85 | * Delete a workflow 86 | * 87 | * @param id Workflow ID 88 | * @returns Deleted workflow or success message 89 | */ 90 | async deleteWorkflow(id: string): Promise<any> { 91 | return this.client.deleteWorkflow(id); 92 | } 93 | 94 | /** 95 | * Activate a workflow 96 | * 97 | * @param id Workflow ID 98 | * @returns Activated workflow 99 | */ 100 | async activateWorkflow(id: string): Promise<Workflow> { 101 | return this.client.activateWorkflow(id); 102 | } 103 | 104 | /** 105 | * Deactivate a workflow 106 | * 107 | * @param id Workflow ID 108 | * @returns Deactivated workflow 109 | */ 110 | async deactivateWorkflow(id: string): Promise<Workflow> { 111 | return this.client.deactivateWorkflow(id); 112 | } 113 | 114 | /** 115 | * Get all workflow executions 116 | * 117 | * @returns Array of execution objects 118 | */ 119 | async getExecutions(): Promise<Execution[]> { 120 | return this.client.getExecutions(); 121 | } 122 | 123 | /** 124 | * Get a specific execution by ID 125 | * 126 | * @param id Execution ID 127 | * @returns Execution object 128 | */ 129 | async getExecution(id: string): Promise<Execution> { 130 | return this.client.getExecution(id); 131 | } 132 | 133 | /** 134 | * Delete an execution 135 | * 136 | * @param id Execution ID 137 | * @returns Deleted execution or success message 138 | */ 139 | async deleteExecution(id: string): Promise<any> { 140 | return this.client.deleteExecution(id); 141 | } 142 | } 143 | 144 | /** 145 | * Create a new n8n API service 146 | * 147 | * @param config Environment configuration 148 | * @returns n8n API service 149 | */ 150 | export function createApiService(config: EnvConfig): N8nApiService { 151 | return new N8nApiService(config); 152 | } 153 | ``` -------------------------------------------------------------------------------- /tests/unit/utils/execution-formatter.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, it, expect } from '@jest/globals'; 2 | import { 3 | formatExecutionSummary, 4 | formatExecutionDetails, 5 | getStatusIndicator, 6 | summarizeExecutions 7 | } from '../../../src/utils/execution-formatter.js'; 8 | import { 9 | createMockExecution, 10 | createMockExecutions 11 | } from '../../mocks/n8n-fixtures.js'; 12 | 13 | 14 | describe('Execution Formatter Utilities', () => { 15 | describe('getStatusIndicator', () => { 16 | it('returns the correct emoji for known statuses', () => { 17 | expect(getStatusIndicator('success')).toBe('✅'); 18 | expect(getStatusIndicator('error')).toBe('❌'); 19 | expect(getStatusIndicator('waiting')).toBe('⏳'); 20 | expect(getStatusIndicator('canceled')).toBe('🛑'); 21 | }); 22 | 23 | it('returns a default emoji for unknown status', () => { 24 | expect(getStatusIndicator('unknown')).toBe('⏱️'); 25 | }); 26 | }); 27 | 28 | describe('formatExecutionSummary', () => { 29 | it('formats execution data into a summary', () => { 30 | const execution = createMockExecution({ 31 | id: 'exec1', 32 | workflowId: 'wf1', 33 | status: 'success', 34 | startedAt: '2025-01-01T00:00:00.000Z', 35 | stoppedAt: '2025-01-01T00:00:05.000Z' 36 | }); 37 | 38 | const summary = formatExecutionSummary(execution); 39 | 40 | expect(summary).toMatchObject({ 41 | id: 'exec1', 42 | workflowId: 'wf1', 43 | status: '✅ success', 44 | startedAt: '2025-01-01T00:00:00.000Z', 45 | stoppedAt: '2025-01-01T00:00:05.000Z', 46 | finished: true 47 | }); 48 | expect(summary.duration).toBe('5s'); 49 | }); 50 | 51 | it('marks execution as in progress when stoppedAt is missing', () => { 52 | const execution = createMockExecution({ 53 | stoppedAt: undefined as any, 54 | status: 'waiting' 55 | }); 56 | 57 | const summary = formatExecutionSummary(execution); 58 | expect(summary.stoppedAt).toBe('In progress'); 59 | }); 60 | }); 61 | 62 | describe('formatExecutionDetails', () => { 63 | it('includes node results when present', () => { 64 | const execution = createMockExecution({ 65 | data: { 66 | resultData: { 67 | runData: { 68 | MyNode: [ 69 | { 70 | status: 'success', 71 | data: { main: [[{ foo: 'bar' }]] } 72 | } 73 | ] 74 | } 75 | } 76 | }, 77 | status: 'success' 78 | }); 79 | 80 | const details = formatExecutionDetails(execution); 81 | expect(details.nodeResults.MyNode).toEqual({ 82 | status: 'success', 83 | items: 1, 84 | data: [{ foo: 'bar' }] 85 | }); 86 | }); 87 | 88 | it('adds error information when present', () => { 89 | const execution = createMockExecution({ 90 | data: { 91 | resultData: { 92 | runData: {}, 93 | error: { message: 'boom', stack: 'trace' } 94 | } as any 95 | }, 96 | status: 'error' 97 | }); 98 | 99 | const details = formatExecutionDetails(execution); 100 | expect(details.error).toEqual({ message: 'boom', stack: 'trace' }); 101 | }); 102 | }); 103 | 104 | describe('summarizeExecutions', () => { 105 | it('summarizes counts and percentages', () => { 106 | const executions = [ 107 | createMockExecution({ status: 'success' }), 108 | createMockExecution({ status: 'error' }), 109 | createMockExecution({ status: 'waiting' }), 110 | createMockExecution({ status: 'success' }) 111 | ]; 112 | 113 | const summary = summarizeExecutions(executions); 114 | 115 | expect(summary.total).toBe(4); 116 | const success = summary.byStatus.find((s: any) => s.status.includes('success')); 117 | const error = summary.byStatus.find((s: any) => s.status.includes('error')); 118 | expect(success.count).toBe(2); 119 | expect(error.count).toBe(1); 120 | expect(summary.successRate).toBe('50%'); 121 | }); 122 | }); 123 | }); 124 | ``` -------------------------------------------------------------------------------- /docs/api/static-resources.md: -------------------------------------------------------------------------------- ```markdown 1 | # Static Resources 2 | 3 | This page documents the static resources available in the n8n MCP Server. 4 | 5 | ## Overview 6 | 7 | Static resources provide access to fixed n8n data sources without requiring parameters in the URI. These resources are ideal for retrieving collections of data or summary information. 8 | 9 | ## Available Resources 10 | 11 | ### n8n://workflows/list 12 | 13 | Provides a list of all workflows in the n8n instance. 14 | 15 | **URI:** `n8n://workflows/list` 16 | 17 | **Description:** Returns a comprehensive list of all workflows with their basic metadata. 18 | 19 | **Example Usage:** 20 | 21 | ```javascript 22 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://workflows/list'); 23 | ``` 24 | 25 | **Response:** 26 | 27 | ```javascript 28 | { 29 | "workflows": [ 30 | { 31 | "id": "1234abc", 32 | "name": "Email Processing Workflow", 33 | "active": true, 34 | "createdAt": "2025-03-01T12:00:00.000Z", 35 | "updatedAt": "2025-03-02T14:30:00.000Z" 36 | }, 37 | { 38 | "id": "5678def", 39 | "name": "Data Sync Workflow", 40 | "active": false, 41 | "createdAt": "2025-03-01T12:00:00.000Z", 42 | "updatedAt": "2025-03-12T10:15:00.000Z" 43 | } 44 | ], 45 | "count": 2, 46 | "pagination": { 47 | "hasMore": false 48 | } 49 | } 50 | ``` 51 | 52 | ### n8n://execution-stats 53 | 54 | Provides aggregated statistics about workflow executions. 55 | 56 | **URI:** `n8n://execution-stats` 57 | 58 | **Description:** Returns summary statistics about workflow executions, including counts by status, average execution times, and recent trends. 59 | 60 | **Example Usage:** 61 | 62 | ```javascript 63 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://execution-stats'); 64 | ``` 65 | 66 | **Response:** 67 | 68 | ```javascript 69 | { 70 | "totalExecutions": 1250, 71 | "statusCounts": { 72 | "success": 1050, 73 | "error": 180, 74 | "cancelled": 20 75 | }, 76 | "averageExecutionTime": 3.5, // seconds 77 | "recentActivity": { 78 | "last24Hours": 125, 79 | "last7Days": 450 80 | }, 81 | "topWorkflows": [ 82 | { 83 | "id": "1234abc", 84 | "name": "Email Processing Workflow", 85 | "executionCount": 256 86 | }, 87 | { 88 | "id": "5678def", 89 | "name": "Data Sync Workflow", 90 | "executionCount": 198 91 | } 92 | ] 93 | } 94 | ``` 95 | 96 | ### n8n://health 97 | 98 | Provides health information about the n8n instance. 99 | 100 | **URI:** `n8n://health` 101 | 102 | **Description:** Returns health status information about the n8n instance including connection status, version, and basic metrics. 103 | 104 | **Example Usage:** 105 | 106 | ```javascript 107 | const resource = await accessMcpResource('n8n-mcp-server', 'n8n://health'); 108 | ``` 109 | 110 | **Response:** 111 | 112 | ```javascript 113 | { 114 | "status": "healthy", 115 | "n8nVersion": "1.5.0", 116 | "uptime": 259200, // seconds (3 days) 117 | "databaseStatus": "connected", 118 | "apiStatus": "operational", 119 | "memoryUsage": { 120 | "rss": "156MB", 121 | "heapTotal": "85MB", 122 | "heapUsed": "72MB" 123 | } 124 | } 125 | ``` 126 | 127 | ## Content Types 128 | 129 | All static resources return JSON content with the MIME type `application/json`. 130 | 131 | ## Authentication 132 | 133 | Access to static resources requires the same authentication as tools, using the configured n8n API key. If authentication fails, the resource will return an error. 134 | 135 | ## Error Handling 136 | 137 | Static resources can return the following errors: 138 | 139 | | HTTP Status | Description | 140 | |-------------|-------------| 141 | | 401 | Unauthorized - Invalid or missing API key | 142 | | 403 | Forbidden - API key does not have permission to access this resource | 143 | | 500 | Internal Server Error - An unexpected error occurred on the n8n server | 144 | 145 | ## Pagination 146 | 147 | Some resources that return large collections (like `n8n://workflows/list`) support pagination. The response includes a `pagination` object with information about whether more results are available. 148 | 149 | ## Best Practices 150 | 151 | - Use static resources for getting an overview of what's available in the n8n instance 152 | - Prefer static resources over tools when you only need to read data 153 | - Check the health resource before performing operations to ensure the n8n instance is operational 154 | - Use execution statistics to monitor the performance and reliability of your workflows 155 | ``` -------------------------------------------------------------------------------- /src/utils/resource-formatter.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Resource Formatter Utilities 3 | * 4 | * This module provides utility functions for formatting resource data 5 | * in a consistent, user-friendly manner for MCP resources. 6 | */ 7 | 8 | import { Workflow, Execution } from '../types/index.js'; 9 | 10 | /** 11 | * Format workflow summary for static resource listing 12 | * 13 | * @param workflow Workflow object 14 | * @returns Formatted workflow summary 15 | */ 16 | export function formatWorkflowSummary(workflow: Workflow): Record<string, any> { 17 | return { 18 | id: workflow.id, 19 | name: workflow.name, 20 | active: workflow.active, 21 | status: workflow.active ? '🟢 Active' : '⚪ Inactive', 22 | updatedAt: workflow.updatedAt, 23 | createdAt: workflow.createdAt, 24 | }; 25 | } 26 | 27 | /** 28 | * Format detailed workflow information for dynamic resources 29 | * 30 | * @param workflow Workflow object 31 | * @returns Formatted workflow details 32 | */ 33 | export function formatWorkflowDetails(workflow: Workflow): Record<string, any> { 34 | const summary = formatWorkflowSummary(workflow); 35 | 36 | // Add additional details 37 | return { 38 | ...summary, 39 | nodes: workflow.nodes.map(node => ({ 40 | id: node.id, 41 | name: node.name, 42 | type: node.type, 43 | position: node.position, 44 | parameters: node.parameters, 45 | })), 46 | connections: workflow.connections, 47 | staticData: workflow.staticData, 48 | settings: workflow.settings, 49 | tags: workflow.tags, 50 | // Exclude potentially sensitive or unnecessary information 51 | // like pinData or other internal fields 52 | }; 53 | } 54 | 55 | /** 56 | * Format execution statistics summary 57 | * 58 | * @param executions Array of execution objects 59 | * @returns Formatted execution statistics 60 | */ 61 | export function formatExecutionStats(executions: Execution[]): Record<string, any> { 62 | // Group executions by status 63 | const statusCounts: Record<string, number> = {}; 64 | executions.forEach(execution => { 65 | const status = execution.status || 'unknown'; 66 | statusCounts[status] = (statusCounts[status] || 0) + 1; 67 | }); 68 | 69 | // Calculate success rate 70 | const totalCount = executions.length; 71 | const successCount = statusCounts.success || 0; 72 | const successRate = totalCount > 0 ? Math.round((successCount / totalCount) * 100) : 0; 73 | 74 | // Calculate average execution time 75 | let totalDuration = 0; 76 | let completedCount = 0; 77 | 78 | executions.forEach(execution => { 79 | if (execution.startedAt && execution.stoppedAt) { 80 | const startedAt = new Date(execution.startedAt); 81 | const stoppedAt = new Date(execution.stoppedAt); 82 | const durationMs = stoppedAt.getTime() - startedAt.getTime(); 83 | 84 | totalDuration += durationMs; 85 | completedCount++; 86 | } 87 | }); 88 | 89 | const avgDurationMs = completedCount > 0 ? Math.round(totalDuration / completedCount) : 0; 90 | const avgDurationSec = Math.round(avgDurationMs / 1000); 91 | 92 | // Group executions by workflow 93 | const workflowExecutions: Record<string, number> = {}; 94 | executions.forEach(execution => { 95 | const workflowId = execution.workflowId; 96 | workflowExecutions[workflowId] = (workflowExecutions[workflowId] || 0) + 1; 97 | }); 98 | 99 | // Get top workflows by execution count 100 | const topWorkflows = Object.entries(workflowExecutions) 101 | .sort((a, b) => b[1] - a[1]) 102 | .slice(0, 5) 103 | .map(([workflowId, count]) => ({ 104 | workflowId, 105 | executionCount: count, 106 | percentage: totalCount > 0 ? Math.round((count / totalCount) * 100) : 0 107 | })); 108 | 109 | return { 110 | total: totalCount, 111 | byStatus: Object.entries(statusCounts).map(([status, count]) => ({ 112 | status, 113 | count, 114 | percentage: totalCount > 0 ? Math.round((count / totalCount) * 100) : 0 115 | })), 116 | successRate: `${successRate}%`, 117 | averageExecutionTime: completedCount > 0 ? `${avgDurationSec}s` : 'N/A', 118 | recentTrend: { 119 | // Recent executions trend - last 24 hours vs previous 24 hours 120 | // This is a placeholder - would need timestamp filtering logic 121 | changePercent: '0%', 122 | description: 'Stable execution rate' 123 | }, 124 | topWorkflows: topWorkflows, 125 | timeUpdated: new Date().toISOString() 126 | }; 127 | } 128 | 129 | /** 130 | * Format resource URI for n8n resources 131 | * 132 | * @param resourceType Type of resource (workflow or execution) 133 | * @param id Optional resource ID for specific resources 134 | * @returns Formatted resource URI 135 | */ 136 | export function formatResourceUri( 137 | resourceType: 'workflow' | 'execution' | 'workflows' | 'execution-stats', 138 | id?: string, 139 | ): string { 140 | if (id) { 141 | const base = ['workflow', 'execution'].includes(resourceType) 142 | ? `${resourceType}s` 143 | : resourceType; 144 | return `n8n://${base}/${id}`; 145 | } 146 | return `n8n://${resourceType}`; 147 | } 148 | ``` -------------------------------------------------------------------------------- /src/utils/execution-formatter.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Execution Formatter Utilities 3 | * 4 | * This module provides utility functions for formatting execution data 5 | * in a consistent, user-friendly manner. 6 | */ 7 | 8 | import { Execution } from '../types/index.js'; 9 | 10 | /** 11 | * Format basic execution information for display 12 | * 13 | * @param execution Execution object 14 | * @returns Formatted execution summary 15 | */ 16 | export function formatExecutionSummary(execution: Execution): Record<string, any> { 17 | // Calculate duration 18 | const startedAt = new Date(execution.startedAt); 19 | const stoppedAt = execution.stoppedAt ? new Date(execution.stoppedAt) : new Date(); 20 | const durationMs = stoppedAt.getTime() - startedAt.getTime(); 21 | const durationSeconds = Math.round(durationMs / 1000); 22 | 23 | // Create status indicator emoji 24 | const statusIndicator = getStatusIndicator(execution.status); 25 | 26 | return { 27 | id: execution.id, 28 | workflowId: execution.workflowId, 29 | status: `${statusIndicator} ${execution.status}`, 30 | startedAt: execution.startedAt, 31 | stoppedAt: execution.stoppedAt || 'In progress', 32 | duration: `${durationSeconds}s`, 33 | finished: execution.finished 34 | }; 35 | } 36 | 37 | /** 38 | * Format detailed execution information including node results 39 | * 40 | * @param execution Execution object 41 | * @returns Formatted execution details 42 | */ 43 | export function formatExecutionDetails(execution: Execution): Record<string, any> { 44 | const summary = formatExecutionSummary(execution); 45 | 46 | // Extract node results 47 | const nodeResults: Record<string, any> = {}; 48 | if (execution.data?.resultData?.runData) { 49 | for (const [nodeName, nodeData] of Object.entries(execution.data.resultData.runData)) { 50 | try { 51 | // Get the last output 52 | const lastOutput = Array.isArray(nodeData) && nodeData.length > 0 53 | ? nodeData[nodeData.length - 1] 54 | : null; 55 | 56 | if (lastOutput && lastOutput.data && Array.isArray(lastOutput.data.main)) { 57 | // Extract the output data 58 | const outputData = lastOutput.data.main.length > 0 59 | ? lastOutput.data.main[0] 60 | : []; 61 | 62 | nodeResults[nodeName] = { 63 | status: lastOutput.status, 64 | items: outputData.length, 65 | data: outputData.slice(0, 3), // Limit to first 3 items to avoid overwhelming response 66 | }; 67 | } 68 | } catch (error) { 69 | nodeResults[nodeName] = { error: 'Failed to parse node output' }; 70 | } 71 | } 72 | } 73 | 74 | // Add node results and error information to the summary 75 | return { 76 | ...summary, 77 | mode: execution.mode, 78 | nodeResults: nodeResults, 79 | // Include error information if present 80 | error: execution.data?.resultData && 'error' in execution.data.resultData 81 | ? { 82 | message: (execution.data.resultData as any).error?.message, 83 | stack: (execution.data.resultData as any).error?.stack, 84 | } 85 | : undefined, 86 | }; 87 | } 88 | 89 | /** 90 | * Get appropriate status indicator emoji based on execution status 91 | * 92 | * @param status Execution status string 93 | * @returns Status indicator emoji 94 | */ 95 | export function getStatusIndicator(status: string): string { 96 | switch (status) { 97 | case 'success': 98 | return '✅'; // Success 99 | case 'error': 100 | return '❌'; // Error 101 | case 'waiting': 102 | return '⏳'; // Waiting 103 | case 'canceled': 104 | return '🛑'; // Canceled 105 | default: 106 | return '⏱️'; // In progress or unknown 107 | } 108 | } 109 | 110 | /** 111 | * Summarize execution results for more compact display 112 | * 113 | * @param executions Array of execution objects 114 | * @param limit Maximum number of executions to include 115 | * @returns Summary of execution results 116 | */ 117 | export function summarizeExecutions(executions: Execution[], limit: number = 10): Record<string, any> { 118 | const limitedExecutions = executions.slice(0, limit); 119 | 120 | // Group executions by status 121 | const byStatus: Record<string, number> = {}; 122 | limitedExecutions.forEach(execution => { 123 | const status = execution.status || 'unknown'; 124 | byStatus[status] = (byStatus[status] || 0) + 1; 125 | }); 126 | 127 | // Calculate success rate 128 | const totalCount = limitedExecutions.length; 129 | const successCount = byStatus.success || 0; 130 | const successRate = totalCount > 0 ? Math.round((successCount / totalCount) * 100) : 0; 131 | 132 | return { 133 | total: totalCount, 134 | byStatus: Object.entries(byStatus).map(([status, count]) => ({ 135 | status: `${getStatusIndicator(status)} ${status}`, 136 | count, 137 | percentage: totalCount > 0 ? Math.round((count / totalCount) * 100) : 0 138 | })), 139 | successRate: `${successRate}%`, 140 | displayed: limitedExecutions.length, 141 | totalAvailable: executions.length 142 | }; 143 | } 144 | ``` -------------------------------------------------------------------------------- /src/tools/workflow/update.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Update Workflow Tool 3 | * 4 | * This tool updates an existing workflow in n8n. 5 | */ 6 | 7 | import { BaseWorkflowToolHandler } from './base-handler.js'; 8 | import { ToolCallResult, ToolDefinition } from '../../types/index.js'; 9 | import { N8nApiError } from '../../errors/index.js'; 10 | 11 | /** 12 | * Handler for the update_workflow tool 13 | */ 14 | export class UpdateWorkflowHandler extends BaseWorkflowToolHandler { 15 | /** 16 | * Execute the tool 17 | * 18 | * @param args Tool arguments containing workflow updates 19 | * @returns Updated workflow information 20 | */ 21 | async execute(args: Record<string, any>): Promise<ToolCallResult> { 22 | return this.handleExecution(async (args) => { 23 | const { workflowId, name, nodes, connections, active, tags } = args; 24 | 25 | if (!workflowId) { 26 | throw new N8nApiError('Missing required parameter: workflowId'); 27 | } 28 | 29 | // Validate nodes if provided 30 | if (nodes && !Array.isArray(nodes)) { 31 | throw new N8nApiError('Parameter "nodes" must be an array'); 32 | } 33 | 34 | // Validate connections if provided 35 | if (connections && typeof connections !== 'object') { 36 | throw new N8nApiError('Parameter "connections" must be an object'); 37 | } 38 | 39 | // Get the current workflow to update 40 | const currentWorkflow = await this.apiService.getWorkflow(workflowId); 41 | 42 | // Prepare update object with only allowed properties (per n8n API schema) 43 | const workflowData: Record<string, any> = { 44 | name: name !== undefined ? name : currentWorkflow.name, 45 | nodes: nodes !== undefined ? nodes : currentWorkflow.nodes, 46 | connections: connections !== undefined ? connections : currentWorkflow.connections, 47 | settings: currentWorkflow.settings 48 | }; 49 | 50 | // Add optional staticData if it exists 51 | if (currentWorkflow.staticData !== undefined) { 52 | workflowData.staticData = currentWorkflow.staticData; 53 | } 54 | 55 | // Note: active and tags are read-only properties and cannot be updated via this endpoint 56 | 57 | // Update the workflow 58 | const updatedWorkflow = await this.apiService.updateWorkflow(workflowId, workflowData); 59 | 60 | // Build a summary of changes 61 | const changesArray = []; 62 | if (name !== undefined && name !== currentWorkflow.name) changesArray.push(`name: "${currentWorkflow.name}" → "${name}"`); 63 | if (nodes !== undefined) changesArray.push('nodes updated'); 64 | if (connections !== undefined) changesArray.push('connections updated'); 65 | 66 | // Add warnings for read-only properties 67 | const warnings = []; 68 | if (active !== undefined) warnings.push('active (read-only, use activate/deactivate workflow tools)'); 69 | if (tags !== undefined) warnings.push('tags (read-only property)'); 70 | 71 | const changesSummary = changesArray.length > 0 72 | ? `Changes: ${changesArray.join(', ')}` 73 | : 'No changes were made'; 74 | 75 | const warningsSummary = warnings.length > 0 76 | ? ` Note: Ignored ${warnings.join(', ')}.` 77 | : ''; 78 | 79 | return this.formatSuccess( 80 | { 81 | id: updatedWorkflow.id, 82 | name: updatedWorkflow.name, 83 | active: updatedWorkflow.active 84 | }, 85 | `Workflow updated successfully. ${changesSummary}${warningsSummary}` 86 | ); 87 | }, args); 88 | } 89 | } 90 | 91 | /** 92 | * Get tool definition for the update_workflow tool 93 | * 94 | * @returns Tool definition 95 | */ 96 | export function getUpdateWorkflowToolDefinition(): ToolDefinition { 97 | return { 98 | name: 'update_workflow', 99 | description: 'Update an existing workflow in n8n', 100 | inputSchema: { 101 | type: 'object', 102 | properties: { 103 | workflowId: { 104 | type: 'string', 105 | description: 'ID of the workflow to update', 106 | }, 107 | name: { 108 | type: 'string', 109 | description: 'New name for the workflow', 110 | }, 111 | nodes: { 112 | type: 'array', 113 | description: 'Updated array of node objects that define the workflow', 114 | items: { 115 | type: 'object', 116 | }, 117 | }, 118 | connections: { 119 | type: 'object', 120 | description: 'Updated connection mappings between nodes', 121 | }, 122 | active: { 123 | type: 'boolean', 124 | description: 'Whether the workflow should be active (read-only: use activate/deactivate workflow tools instead)', 125 | }, 126 | tags: { 127 | type: 'array', 128 | description: 'Tags to associate with the workflow (read-only: cannot be updated via this endpoint)', 129 | items: { 130 | type: 'string', 131 | }, 132 | }, 133 | }, 134 | required: ['workflowId'], 135 | }, 136 | }; 137 | } 138 | ``` -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Resources Module 3 | * 4 | * This module provides MCP resource handlers for n8n workflows and executions. 5 | */ 6 | 7 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 8 | import { 9 | ListResourcesRequestSchema, 10 | ListResourceTemplatesRequestSchema, 11 | ReadResourceRequestSchema, 12 | } from '@modelcontextprotocol/sdk/types.js'; 13 | import { EnvConfig } from '../config/environment.js'; 14 | import { createApiService } from '../api/n8n-client.js'; 15 | import { McpError, ErrorCode } from '../errors/index.js'; 16 | 17 | // Import static resource handlers 18 | import { 19 | getWorkflowsResource, 20 | getWorkflowsResourceMetadata, 21 | getWorkflowsResourceUri, 22 | } from './static/workflows.js'; 23 | import { 24 | getExecutionStatsResource, 25 | getExecutionStatsResourceMetadata, 26 | getExecutionStatsResourceUri, 27 | } from './static/execution-stats.js'; 28 | 29 | // Import dynamic resource handlers 30 | import { 31 | getWorkflowResource, 32 | getWorkflowResourceTemplateMetadata, 33 | extractWorkflowIdFromUri, 34 | } from './dynamic/workflow.js'; 35 | import { 36 | getExecutionResource, 37 | getExecutionResourceTemplateMetadata, 38 | extractExecutionIdFromUri, 39 | } from './dynamic/execution.js'; 40 | 41 | /** 42 | * Set up resource handlers for the MCP server 43 | * 44 | * @param server MCP server instance 45 | * @param envConfig Environment configuration 46 | */ 47 | export function setupResourceHandlers(server: Server, envConfig: EnvConfig): void { 48 | // Set up static resources 49 | setupStaticResources(server, envConfig); 50 | 51 | // Set up dynamic resources 52 | setupDynamicResources(server, envConfig); 53 | } 54 | 55 | /** 56 | * Set up static resource handlers 57 | * 58 | * @param server MCP server instance 59 | * @param envConfig Environment configuration 60 | */ 61 | function setupStaticResources(server: Server, _envConfig: EnvConfig): void { 62 | server.setRequestHandler(ListResourcesRequestSchema, async () => { 63 | // Return all available static resources 64 | return { 65 | resources: [ 66 | getWorkflowsResourceMetadata(), 67 | getExecutionStatsResourceMetadata(), 68 | ], 69 | }; 70 | }); 71 | } 72 | 73 | /** 74 | * Set up dynamic resource handlers 75 | * 76 | * @param server MCP server instance 77 | * @param envConfig Environment configuration 78 | */ 79 | function setupDynamicResources(server: Server, envConfig: EnvConfig): void { 80 | const apiService = createApiService(envConfig); 81 | 82 | server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { 83 | // Return all available dynamic resource templates 84 | return { 85 | resourceTemplates: [ 86 | getWorkflowResourceTemplateMetadata(), 87 | getExecutionResourceTemplateMetadata(), 88 | ], 89 | }; 90 | }); 91 | 92 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => { 93 | const uri = request.params.uri; 94 | console.log(`Resource requested: ${uri}`); 95 | 96 | try { 97 | // Handle static resources 98 | if (uri === getWorkflowsResourceUri()) { 99 | const content = await getWorkflowsResource(apiService); 100 | return { 101 | contents: [ 102 | { 103 | uri, 104 | mimeType: 'application/json', 105 | text: content, 106 | }, 107 | ], 108 | }; 109 | } 110 | 111 | if (uri === getExecutionStatsResourceUri()) { 112 | const content = await getExecutionStatsResource(apiService); 113 | return { 114 | contents: [ 115 | { 116 | uri, 117 | mimeType: 'application/json', 118 | text: content, 119 | }, 120 | ], 121 | }; 122 | } 123 | 124 | // Handle dynamic resources 125 | const workflowId = extractWorkflowIdFromUri(uri); 126 | if (workflowId) { 127 | const content = await getWorkflowResource(apiService, workflowId); 128 | return { 129 | contents: [ 130 | { 131 | uri, 132 | mimeType: 'application/json', 133 | text: content, 134 | }, 135 | ], 136 | }; 137 | } 138 | 139 | const executionId = extractExecutionIdFromUri(uri); 140 | if (executionId) { 141 | const content = await getExecutionResource(apiService, executionId); 142 | return { 143 | contents: [ 144 | { 145 | uri, 146 | mimeType: 'application/json', 147 | text: content, 148 | }, 149 | ], 150 | }; 151 | } 152 | 153 | // If we get here, the URI isn't recognized 154 | throw new McpError( 155 | ErrorCode.NotFoundError, 156 | `Resource not found: ${uri}` 157 | ); 158 | } catch (error) { 159 | console.error(`Error retrieving resource (${uri}):`, error); 160 | 161 | // Pass through McpErrors 162 | if (error instanceof McpError) { 163 | throw error; 164 | } 165 | 166 | // Convert other errors to McpError 167 | throw new McpError( 168 | ErrorCode.InternalError, 169 | `Error retrieving resource: ${error instanceof Error ? error.message : 'Unknown error'}` 170 | ); 171 | } 172 | }); 173 | } 174 | ```