├── .gitignore ├── LICENSE ├── README.md ├── dist └── index.js ├── docs └── openapi.yml ├── package.json ├── smithery.yaml ├── src ├── config │ └── constants.ts ├── index.cjs ├── index.ts ├── sdk-schemas.ts ├── services │ ├── n8nApi.ts │ └── workflowBuilder.ts ├── types │ ├── api.ts │ ├── execution.ts │ ├── node.ts │ ├── sdk.d.ts │ └── workflow.ts └── utils │ ├── positioning.ts │ └── validation.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | package-lock.json 7 | 8 | # Environment variables 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Build files 16 | /build 17 | /dist 18 | 19 | # Logs 20 | logs 21 | *.log 22 | 23 | # OS specific files 24 | .DS_Store 25 | .DS_Store? 26 | ._* 27 | .Spotlight-V100 28 | .Trashes 29 | ehthumbs.db 30 | Thumbs.db 31 | 32 | # IDE files 33 | .idea/ 34 | .vscode/ 35 | *.swp 36 | *.swo -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Makafeli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/makafeli-n8n-workflow-builder-badge.png)](https://mseep.ai/app/makafeli-n8n-workflow-builder) 2 | 3 | # n8n Workflow Builder MCP Server 4 | 5 | This project provides an MCP server for managing n8n workflows. It offers functionality to list, create, update, delete, activate, and deactivate workflows through a set of defined tools. 6 | 7 | **Important:** 8 | This version exclusively supports **npm** for package management and running the server. (npx support will be reintroduced in a future update.) 9 | 10 | ## Requirements 11 | 12 | - Node.js (v14+ recommended) 13 | - npm 14 | 15 | ## Extensive Installation Guide 16 | 17 | ### 1. Clone the Repository 18 | 19 | Clone the repository from your preferred source. For example: 20 | 21 | ```bash 22 | git clone https://github.com/makafeli/n8n-workflow-builder.git 23 | ``` 24 | 25 | Then, navigate to the project directory: 26 | 27 | ```bash 28 | cd /root/n8n-workflow-builder 29 | ``` 30 | 31 | ### 2. Install Dependencies 32 | 33 | Install the necessary dependencies using npm: 34 | 35 | ```bash 36 | npm install 37 | ``` 38 | 39 | This command will download and install all required packages as defined in the `package.json` file. 40 | 41 | ### 3. Build and Test 42 | 43 | For testing and getting the server online, use the following commands: 44 | 45 | - **Build the project:** 46 | This compiles the TypeScript files and produces the executable JavaScript in the `build` directory. 47 | 48 | ```bash 49 | npm run build 50 | ``` 51 | 52 | - **Start the MCP Server:** 53 | Launch the server using: 54 | 55 | ```bash 56 | npm start 57 | ``` 58 | 59 | The server will start and connect via stdio. You can check the console to see messages confirming that the server has started correctly. 60 | 61 | ### 4. Deployment 62 | 63 | For testing purposes and to get the server online, use the build and start commands mentioned above. This basic workflow (install, build, start) is currently the recommended method. 64 | 65 | ### 5. Additional Configuration 66 | 67 | Server configuration for [Cline](https://cline.bot) is managed via the `cline_mcp_settings.json` file. Ensure that the following environment variables are correctly set: 68 | 69 | - `N8N_HOST`: Your n8n API host URL. 70 | - `N8N_API_KEY`: Your n8n API key. 71 | 72 | Example configuration in `cline_mcp_settings.json`: 73 | 74 | ```json 75 | { 76 | "n8n-workflow-builder": { 77 | "command": "node", 78 | "args": ["/root/n8n-workflow-builder/build/index.js"], 79 | "env": { 80 | "N8N_HOST": "https://n8n.io/api/v1/", 81 | "N8N_API_KEY": "YOUR_N8N_API_KEY_HERE" 82 | }, 83 | "disabled": false, 84 | "alwaysAllow": [ 85 | "create_workflow", 86 | "create_workflow_and_activate", 87 | "update_workflow", 88 | "activate_workflow", 89 | "deactivate_workflow", 90 | "get_workflow", 91 | "delete_workflow" 92 | ], 93 | "autoApprove": [] 94 | } 95 | } 96 | ``` 97 | 98 | ## Available Features 99 | 100 | ### MCP Tools 101 | 102 | The following tools are defined in the server and can be accessed through your MCP client: 103 | 104 | #### Workflow Management 105 | - **list_workflows**: Lists all workflows from n8n. 106 | - **create_workflow**: Creates a new workflow in n8n. 107 | - **get_workflow**: Retrieves a workflow by its ID. 108 | - **update_workflow**: Updates an existing workflow. 109 | - **delete_workflow**: Deletes a workflow by its ID. 110 | - **activate_workflow**: Activates a workflow by its ID. 111 | - **deactivate_workflow**: Deactivates a workflow by its ID. 112 | 113 | #### Execution Management 114 | - **list_executions**: Lists all workflow executions with optional filters. 115 | - **get_execution**: Retrieves details of a specific execution by its ID. 116 | - **delete_execution**: Deletes an execution by its ID. 117 | 118 | ### MCP Resources 119 | 120 | The server also provides the following resources for more efficient context access: 121 | 122 | #### Static Resources 123 | - **/workflows**: List of all available workflows in the n8n instance 124 | - **/execution-stats**: Summary statistics about workflow executions 125 | 126 | #### Dynamic Resource Templates 127 | - **/workflows/{id}**: Detailed information about a specific workflow 128 | - **/executions/{id}**: Detailed information about a specific execution 129 | 130 | ## Troubleshooting 131 | 132 | - Ensure you are using npm (this version does not support npx). 133 | - If you encounter any issues, try cleaning the build directory and rebuilding: 134 | ```bash 135 | npm run clean && npm run build 136 | ``` 137 | - Verify that your environment variables in `cline_mcp_settings.json` are correct. 138 | 139 | ## Future Enhancements 140 | 141 | - Reintroduction of npx support. 142 | - Additional tools and workflow features. 143 | - Further enhancements to deployment and scaling. 144 | 145 | ## License 146 | 147 | This project is licensed under the MIT License. 148 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 12 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 13 | import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 14 | class N8NWorkflowBuilder { 15 | constructor() { 16 | this.nodes = []; 17 | this.connections = []; 18 | this.nextPosition = { x: 100, y: 100 }; 19 | } 20 | addNode(nodeType, name, parameters) { 21 | const node = { 22 | type: nodeType, 23 | name: name, 24 | parameters: parameters, 25 | position: Object.assign({}, this.nextPosition) 26 | }; 27 | this.nodes.push(node); 28 | this.nextPosition.x += 200; 29 | return name; 30 | } 31 | connectNodes(source, target, sourceOutput = 0, targetInput = 0) { 32 | this.connections.push({ 33 | source_node: source, 34 | target_node: target, 35 | source_output: sourceOutput, 36 | target_input: targetInput 37 | }); 38 | } 39 | exportWorkflow() { 40 | const workflow = { 41 | nodes: this.nodes, 42 | connections: { main: [] } 43 | }; 44 | for (const conn of this.connections) { 45 | const connection = { 46 | node: conn.target_node, 47 | type: 'main', 48 | index: conn.target_input, 49 | sourceNode: conn.source_node, 50 | sourceIndex: conn.source_output 51 | }; 52 | workflow.connections.main.push(connection); 53 | } 54 | return workflow; 55 | } 56 | } 57 | class N8NWorkflowServer { 58 | constructor() { 59 | this.server = new Server({ 60 | name: 'n8n-workflow-builder', 61 | version: '0.1.0' 62 | }, { 63 | capabilities: { 64 | resources: {}, 65 | tools: {} 66 | } 67 | }); 68 | this.setupToolHandlers(); 69 | this.server.onerror = (error) => console.error('[MCP Error]', error); 70 | } 71 | setupToolHandlers() { 72 | this.server.setRequestHandler(ListToolsRequestSchema, () => __awaiter(this, void 0, void 0, function* () { 73 | return ({ 74 | tools: [{ 75 | name: 'create_workflow', 76 | description: 'Create and configure n8n workflows programmatically', 77 | inputSchema: { 78 | type: 'object', 79 | properties: { 80 | nodes: { 81 | type: 'array', 82 | items: { 83 | type: 'object', 84 | properties: { 85 | type: { type: 'string' }, 86 | name: { type: 'string' }, 87 | parameters: { type: 'object' } 88 | }, 89 | required: ['type', 'name'] 90 | } 91 | }, 92 | connections: { 93 | type: 'array', 94 | items: { 95 | type: 'object', 96 | properties: { 97 | source: { type: 'string' }, 98 | target: { type: 'string' }, 99 | sourceOutput: { type: 'number', default: 0 }, 100 | targetInput: { type: 'number', default: 0 } 101 | }, 102 | required: ['source', 'target'] 103 | } 104 | } 105 | }, 106 | required: ['nodes'] 107 | } 108 | }] 109 | }); 110 | })); 111 | this.server.setRequestHandler(CallToolRequestSchema, (request) => __awaiter(this, void 0, void 0, function* () { 112 | if (request.params.name !== 'create_workflow') { 113 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); 114 | } 115 | try { 116 | const builder = new N8NWorkflowBuilder(); 117 | function isWorkflowSpec(obj) { 118 | return obj && 119 | typeof obj === 'object' && 120 | Array.isArray(obj.nodes) && 121 | obj.nodes.every((node) => typeof node === 'object' && 122 | typeof node.type === 'string' && 123 | typeof node.name === 'string') && 124 | (!obj.connections || (Array.isArray(obj.connections) && 125 | obj.connections.every((conn) => typeof conn === 'object' && 126 | typeof conn.source === 'string' && 127 | typeof conn.target === 'string'))); 128 | } 129 | const args = request.params.arguments; 130 | if (!isWorkflowSpec(args)) { 131 | throw new McpError(ErrorCode.InvalidParams, 'Invalid workflow specification: must include nodes array with type and name properties'); 132 | } 133 | const { nodes, connections } = args; 134 | for (const node of nodes) { 135 | builder.addNode(node.type, node.name, node.parameters || {}); 136 | } 137 | for (const conn of connections || []) { 138 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput); 139 | } 140 | return { 141 | content: [{ 142 | type: 'text', 143 | text: JSON.stringify(builder.exportWorkflow(), null, 2) 144 | }] 145 | }; 146 | } 147 | catch (error) { 148 | throw new McpError(ErrorCode.InternalError, `Workflow creation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); 149 | } 150 | })); 151 | } 152 | run() { 153 | return __awaiter(this, void 0, void 0, function* () { 154 | const transport = new StdioServerTransport(); 155 | yield this.server.connect(transport); 156 | console.error('N8N Workflow Builder MCP server running on stdio'); 157 | }); 158 | } 159 | } 160 | const server = new N8NWorkflowServer(); 161 | server.run().catch(console.error); 162 | -------------------------------------------------------------------------------- /docs/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: n8n Public API 4 | description: n8n Public API 5 | termsOfService: https://n8n.io/legal/terms 6 | contact: 7 | email: hello@n8n.io 8 | license: 9 | name: Sustainable Use License 10 | url: https://github.com/n8n-io/n8n/blob/master/LICENSE.md 11 | version: 1.1.1 12 | servers: 13 | - url: /api/v1 14 | security: 15 | - ApiKeyAuth: [] 16 | tags: 17 | - name: User 18 | description: Operations about users 19 | - name: Audit 20 | description: Operations about security audit 21 | - name: Execution 22 | description: Operations about executions 23 | - name: Workflow 24 | description: Operations about workflows 25 | - name: Credential 26 | description: Operations about credentials 27 | - name: Tags 28 | description: Operations about tags 29 | - name: SourceControl 30 | description: Operations about source control 31 | - name: Variables 32 | description: Operations about variables 33 | - name: Projects 34 | description: Operations about projects 35 | externalDocs: 36 | description: n8n API documentation 37 | url: https://docs.n8n.io/api/ 38 | paths: 39 | /audit: 40 | post: 41 | x-eov-operation-id: generateAudit 42 | x-eov-operation-handler: v1/handlers/audit/audit.handler 43 | tags: 44 | - Audit 45 | summary: Generate an audit 46 | description: Generate a security audit for your n8n instance. 47 | requestBody: 48 | required: false 49 | content: 50 | application/json: 51 | schema: 52 | type: object 53 | properties: 54 | additionalOptions: 55 | type: object 56 | properties: 57 | daysAbandonedWorkflow: 58 | type: integer 59 | description: Days for a workflow to be considered abandoned if not executed 60 | categories: 61 | type: array 62 | items: 63 | type: string 64 | enum: 65 | - credentials 66 | - database 67 | - nodes 68 | - filesystem 69 | - instance 70 | responses: 71 | '200': 72 | description: Operation successful. 73 | content: 74 | application/json: 75 | schema: 76 | $ref: '#/components/schemas/audit' 77 | '401': 78 | $ref: '#/components/responses/unauthorized' 79 | '500': 80 | description: Internal server error. 81 | /credentials: 82 | post: 83 | x-eov-operation-id: createCredential 84 | x-eov-operation-handler: v1/handlers/credentials/credentials.handler 85 | tags: 86 | - Credential 87 | summary: Create a credential 88 | description: Creates a credential that can be used by nodes of the specified type. 89 | requestBody: 90 | description: Credential to be created. 91 | required: true 92 | content: 93 | application/json: 94 | schema: 95 | $ref: '#/components/schemas/credential' 96 | responses: 97 | '200': 98 | description: Operation successful. 99 | content: 100 | application/json: 101 | schema: 102 | $ref: '#/components/schemas/create-credential-response' 103 | '401': 104 | $ref: '#/components/responses/unauthorized' 105 | '415': 106 | description: Unsupported media type. 107 | /credentials/{id}: 108 | delete: 109 | x-eov-operation-id: deleteCredential 110 | x-eov-operation-handler: v1/handlers/credentials/credentials.handler 111 | tags: 112 | - Credential 113 | summary: Delete credential by ID 114 | description: Deletes a credential from your instance. You must be the owner of the credentials 115 | operationId: deleteCredential 116 | parameters: 117 | - name: id 118 | in: path 119 | description: The credential ID that needs to be deleted 120 | required: true 121 | schema: 122 | type: string 123 | responses: 124 | '200': 125 | description: Operation successful. 126 | content: 127 | application/json: 128 | schema: 129 | $ref: '#/components/schemas/credential' 130 | '401': 131 | $ref: '#/components/responses/unauthorized' 132 | '404': 133 | $ref: '#/components/responses/notFound' 134 | /credentials/schema/{credentialTypeName}: 135 | get: 136 | x-eov-operation-id: getCredentialType 137 | x-eov-operation-handler: v1/handlers/credentials/credentials.handler 138 | tags: 139 | - Credential 140 | summary: Show credential data schema 141 | parameters: 142 | - name: credentialTypeName 143 | in: path 144 | description: The credential type name that you want to get the schema for 145 | required: true 146 | schema: 147 | type: string 148 | responses: 149 | '200': 150 | description: Operation successful. 151 | content: 152 | application/json: 153 | schema: 154 | type: object 155 | examples: 156 | freshdeskApi: 157 | value: 158 | additionalProperties: false 159 | type: object 160 | properties: 161 | apiKey: 162 | type: string 163 | domain: 164 | type: string 165 | required: 166 | - apiKey 167 | - domain 168 | slackOAuth2Api: 169 | value: 170 | additionalProperties: false 171 | type: object 172 | properties: 173 | clientId: 174 | type: string 175 | clientSecret: 176 | type: string 177 | required: 178 | - clientId 179 | - clientSecret 180 | '401': 181 | $ref: '#/components/responses/unauthorized' 182 | '404': 183 | $ref: '#/components/responses/notFound' 184 | /executions: 185 | get: 186 | x-eov-operation-id: getExecutions 187 | x-eov-operation-handler: v1/handlers/executions/executions.handler 188 | tags: 189 | - Execution 190 | summary: Retrieve all executions 191 | description: Retrieve all executions from your instance. 192 | parameters: 193 | - $ref: '#/components/parameters/includeData' 194 | - name: status 195 | in: query 196 | description: Status to filter the executions by. 197 | required: false 198 | schema: 199 | type: string 200 | enum: 201 | - error 202 | - success 203 | - waiting 204 | - name: workflowId 205 | in: query 206 | description: Workflow to filter the executions by. 207 | required: false 208 | schema: 209 | type: string 210 | example: '1000' 211 | - name: projectId 212 | in: query 213 | required: false 214 | explode: false 215 | allowReserved: true 216 | schema: 217 | type: string 218 | example: VmwOO9HeTEj20kxM 219 | - $ref: '#/components/parameters/limit' 220 | - $ref: '#/components/parameters/cursor' 221 | responses: 222 | '200': 223 | description: Operation successful. 224 | content: 225 | application/json: 226 | schema: 227 | $ref: '#/components/schemas/executionList' 228 | '401': 229 | $ref: '#/components/responses/unauthorized' 230 | '404': 231 | $ref: '#/components/responses/notFound' 232 | /executions/{id}: 233 | get: 234 | x-eov-operation-id: getExecution 235 | x-eov-operation-handler: v1/handlers/executions/executions.handler 236 | tags: 237 | - Execution 238 | summary: Retrieve an execution 239 | description: Retrieve an execution from your instance. 240 | parameters: 241 | - $ref: '#/components/parameters/executionId' 242 | - $ref: '#/components/parameters/includeData' 243 | responses: 244 | '200': 245 | description: Operation successful. 246 | content: 247 | application/json: 248 | schema: 249 | $ref: '#/components/schemas/execution' 250 | '401': 251 | $ref: '#/components/responses/unauthorized' 252 | '404': 253 | $ref: '#/components/responses/notFound' 254 | delete: 255 | x-eov-operation-id: deleteExecution 256 | x-eov-operation-handler: v1/handlers/executions/executions.handler 257 | tags: 258 | - Execution 259 | summary: Delete an execution 260 | description: Deletes an execution from your instance. 261 | parameters: 262 | - $ref: '#/components/parameters/executionId' 263 | responses: 264 | '200': 265 | description: Operation successful. 266 | content: 267 | application/json: 268 | schema: 269 | $ref: '#/components/schemas/execution' 270 | '401': 271 | $ref: '#/components/responses/unauthorized' 272 | '404': 273 | $ref: '#/components/responses/notFound' 274 | /tags: 275 | post: 276 | x-eov-operation-id: createTag 277 | x-eov-operation-handler: v1/handlers/tags/tags.handler 278 | tags: 279 | - Tags 280 | summary: Create a tag 281 | description: Create a tag in your instance. 282 | requestBody: 283 | description: Created tag object. 284 | content: 285 | application/json: 286 | schema: 287 | $ref: '#/components/schemas/tag' 288 | required: true 289 | responses: 290 | '201': 291 | description: A tag object 292 | content: 293 | application/json: 294 | schema: 295 | $ref: '#/components/schemas/tag' 296 | '400': 297 | $ref: '#/components/responses/badRequest' 298 | '401': 299 | $ref: '#/components/responses/unauthorized' 300 | '409': 301 | $ref: '#/components/responses/conflict' 302 | get: 303 | x-eov-operation-id: getTags 304 | x-eov-operation-handler: v1/handlers/tags/tags.handler 305 | tags: 306 | - Tags 307 | summary: Retrieve all tags 308 | description: Retrieve all tags from your instance. 309 | parameters: 310 | - $ref: '#/components/parameters/limit' 311 | - $ref: '#/components/parameters/cursor' 312 | responses: 313 | '200': 314 | description: Operation successful. 315 | content: 316 | application/json: 317 | schema: 318 | $ref: '#/components/schemas/tagList' 319 | '401': 320 | $ref: '#/components/responses/unauthorized' 321 | /tags/{id}: 322 | get: 323 | x-eov-operation-id: getTag 324 | x-eov-operation-handler: v1/handlers/tags/tags.handler 325 | tags: 326 | - Tags 327 | summary: Retrieves a tag 328 | description: Retrieves a tag. 329 | parameters: 330 | - $ref: '#/components/parameters/tagId' 331 | responses: 332 | '200': 333 | description: Operation successful. 334 | content: 335 | application/json: 336 | schema: 337 | $ref: '#/components/schemas/tag' 338 | '401': 339 | $ref: '#/components/responses/unauthorized' 340 | '404': 341 | $ref: '#/components/responses/notFound' 342 | delete: 343 | x-eov-operation-id: deleteTag 344 | x-eov-operation-handler: v1/handlers/tags/tags.handler 345 | tags: 346 | - Tags 347 | summary: Delete a tag 348 | description: Deletes a tag. 349 | parameters: 350 | - $ref: '#/components/parameters/tagId' 351 | responses: 352 | '200': 353 | description: Operation successful. 354 | content: 355 | application/json: 356 | schema: 357 | $ref: '#/components/schemas/tag' 358 | '401': 359 | $ref: '#/components/responses/unauthorized' 360 | '403': 361 | $ref: '#/components/responses/forbidden' 362 | '404': 363 | $ref: '#/components/responses/notFound' 364 | put: 365 | x-eov-operation-id: updateTag 366 | x-eov-operation-handler: v1/handlers/tags/tags.handler 367 | tags: 368 | - Tags 369 | summary: Update a tag 370 | description: Update a tag. 371 | parameters: 372 | - $ref: '#/components/parameters/tagId' 373 | requestBody: 374 | description: Updated tag object. 375 | content: 376 | application/json: 377 | schema: 378 | $ref: '#/components/schemas/tag' 379 | required: true 380 | responses: 381 | '200': 382 | description: Tag object 383 | content: 384 | application/json: 385 | schema: 386 | $ref: '#/components/schemas/tag' 387 | '400': 388 | $ref: '#/components/responses/badRequest' 389 | '401': 390 | $ref: '#/components/responses/unauthorized' 391 | '404': 392 | $ref: '#/components/responses/notFound' 393 | '409': 394 | $ref: '#/components/responses/conflict' 395 | /workflows: 396 | post: 397 | x-eov-operation-id: createWorkflow 398 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 399 | tags: 400 | - Workflow 401 | summary: Create a workflow 402 | description: Create a workflow in your instance. 403 | requestBody: 404 | description: Created workflow object. 405 | content: 406 | application/json: 407 | schema: 408 | $ref: '#/components/schemas/workflow' 409 | required: true 410 | responses: 411 | '200': 412 | description: A workflow object 413 | content: 414 | application/json: 415 | schema: 416 | $ref: '#/components/schemas/workflow' 417 | '400': 418 | $ref: '#/components/responses/badRequest' 419 | '401': 420 | $ref: '#/components/responses/unauthorized' 421 | get: 422 | x-eov-operation-id: getWorkflows 423 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 424 | tags: 425 | - Workflow 426 | summary: Retrieve all workflows 427 | description: Retrieve all workflows from your instance. 428 | parameters: 429 | - name: active 430 | in: query 431 | schema: 432 | type: boolean 433 | example: true 434 | - name: tags 435 | in: query 436 | required: false 437 | explode: false 438 | allowReserved: true 439 | schema: 440 | type: string 441 | example: test,production 442 | - name: name 443 | in: query 444 | required: false 445 | explode: false 446 | allowReserved: true 447 | schema: 448 | type: string 449 | example: My Workflow 450 | - name: projectId 451 | in: query 452 | required: false 453 | explode: false 454 | allowReserved: true 455 | schema: 456 | type: string 457 | example: VmwOO9HeTEj20kxM 458 | - name: excludePinnedData 459 | in: query 460 | required: false 461 | description: Set this to avoid retrieving pinned data 462 | schema: 463 | type: boolean 464 | example: true 465 | - $ref: '#/components/parameters/limit' 466 | - $ref: '#/components/parameters/cursor' 467 | responses: 468 | '200': 469 | description: Operation successful. 470 | content: 471 | application/json: 472 | schema: 473 | $ref: '#/components/schemas/workflowList' 474 | '401': 475 | $ref: '#/components/responses/unauthorized' 476 | /workflows/{id}: 477 | get: 478 | x-eov-operation-id: getWorkflow 479 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 480 | tags: 481 | - Workflow 482 | summary: Retrieves a workflow 483 | description: Retrieves a workflow. 484 | parameters: 485 | - name: excludePinnedData 486 | in: query 487 | required: false 488 | description: Set this to avoid retrieving pinned data 489 | schema: 490 | type: boolean 491 | example: true 492 | - $ref: '#/components/parameters/workflowId' 493 | responses: 494 | '200': 495 | description: Operation successful. 496 | content: 497 | application/json: 498 | schema: 499 | $ref: '#/components/schemas/workflow' 500 | '401': 501 | $ref: '#/components/responses/unauthorized' 502 | '404': 503 | $ref: '#/components/responses/notFound' 504 | delete: 505 | x-eov-operation-id: deleteWorkflow 506 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 507 | tags: 508 | - Workflow 509 | summary: Delete a workflow 510 | description: Deletes a workflow. 511 | parameters: 512 | - $ref: '#/components/parameters/workflowId' 513 | responses: 514 | '200': 515 | description: Operation successful. 516 | content: 517 | application/json: 518 | schema: 519 | $ref: '#/components/schemas/workflow' 520 | '401': 521 | $ref: '#/components/responses/unauthorized' 522 | '404': 523 | $ref: '#/components/responses/notFound' 524 | put: 525 | x-eov-operation-id: updateWorkflow 526 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 527 | tags: 528 | - Workflow 529 | summary: Update a workflow 530 | description: Update a workflow. 531 | parameters: 532 | - $ref: '#/components/parameters/workflowId' 533 | requestBody: 534 | description: Updated workflow object. 535 | content: 536 | application/json: 537 | schema: 538 | $ref: '#/components/schemas/workflow' 539 | required: true 540 | responses: 541 | '200': 542 | description: Workflow object 543 | content: 544 | application/json: 545 | schema: 546 | $ref: '#/components/schemas/workflow' 547 | '400': 548 | $ref: '#/components/responses/badRequest' 549 | '401': 550 | $ref: '#/components/responses/unauthorized' 551 | '404': 552 | $ref: '#/components/responses/notFound' 553 | /workflows/{id}/activate: 554 | post: 555 | x-eov-operation-id: activateWorkflow 556 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 557 | tags: 558 | - Workflow 559 | summary: Activate a workflow 560 | description: Active a workflow. 561 | parameters: 562 | - $ref: '#/components/parameters/workflowId' 563 | responses: 564 | '200': 565 | description: Workflow object 566 | content: 567 | application/json: 568 | schema: 569 | $ref: '#/components/schemas/workflow' 570 | '401': 571 | $ref: '#/components/responses/unauthorized' 572 | '404': 573 | $ref: '#/components/responses/notFound' 574 | /workflows/{id}/deactivate: 575 | post: 576 | x-eov-operation-id: deactivateWorkflow 577 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 578 | tags: 579 | - Workflow 580 | summary: Deactivate a workflow 581 | description: Deactivate a workflow. 582 | parameters: 583 | - $ref: '#/components/parameters/workflowId' 584 | responses: 585 | '200': 586 | description: Workflow object 587 | content: 588 | application/json: 589 | schema: 590 | $ref: '#/components/schemas/workflow' 591 | '401': 592 | $ref: '#/components/responses/unauthorized' 593 | '404': 594 | $ref: '#/components/responses/notFound' 595 | /workflows/{id}/transfer: 596 | put: 597 | x-eov-operation-id: transferWorkflow 598 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 599 | tags: 600 | - Workflow 601 | summary: Transfer a workflow to another project. 602 | description: Transfer a workflow to another project. 603 | parameters: 604 | - $ref: '#/components/parameters/workflowId' 605 | requestBody: 606 | description: Destination project information for the workflow transfer. 607 | content: 608 | application/json: 609 | schema: 610 | type: object 611 | properties: 612 | destinationProjectId: 613 | type: string 614 | description: The ID of the project to transfer the workflow to. 615 | required: 616 | - destinationProjectId 617 | required: true 618 | responses: 619 | '200': 620 | description: Operation successful. 621 | '400': 622 | $ref: '#/components/responses/badRequest' 623 | '401': 624 | $ref: '#/components/responses/unauthorized' 625 | '404': 626 | $ref: '#/components/responses/notFound' 627 | /credentials/{id}/transfer: 628 | put: 629 | x-eov-operation-id: transferCredential 630 | x-eov-operation-handler: v1/handlers/credentials/credentials.handler 631 | tags: 632 | - Workflow 633 | summary: Transfer a credential to another project. 634 | description: Transfer a credential to another project. 635 | parameters: 636 | - $ref: '#/components/parameters/credentialId' 637 | requestBody: 638 | description: Destination project for the credential transfer. 639 | content: 640 | application/json: 641 | schema: 642 | type: object 643 | properties: 644 | destinationProjectId: 645 | type: string 646 | description: The ID of the project to transfer the credential to. 647 | required: 648 | - destinationProjectId 649 | required: true 650 | responses: 651 | '200': 652 | description: Operation successful. 653 | '400': 654 | $ref: '#/components/responses/badRequest' 655 | '401': 656 | $ref: '#/components/responses/unauthorized' 657 | '404': 658 | $ref: '#/components/responses/notFound' 659 | /workflows/{id}/tags: 660 | get: 661 | x-eov-operation-id: getWorkflowTags 662 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 663 | tags: 664 | - Workflow 665 | summary: Get workflow tags 666 | description: Get workflow tags. 667 | parameters: 668 | - $ref: '#/components/parameters/workflowId' 669 | responses: 670 | '200': 671 | description: List of tags 672 | content: 673 | application/json: 674 | schema: 675 | $ref: '#/components/schemas/workflowTags' 676 | '400': 677 | $ref: '#/components/responses/badRequest' 678 | '401': 679 | $ref: '#/components/responses/unauthorized' 680 | '404': 681 | $ref: '#/components/responses/notFound' 682 | put: 683 | x-eov-operation-id: updateWorkflowTags 684 | x-eov-operation-handler: v1/handlers/workflows/workflows.handler 685 | tags: 686 | - Workflow 687 | summary: Update tags of a workflow 688 | description: Update tags of a workflow. 689 | parameters: 690 | - $ref: '#/components/parameters/workflowId' 691 | requestBody: 692 | description: List of tags 693 | content: 694 | application/json: 695 | schema: 696 | $ref: '#/components/schemas/tagIds' 697 | required: true 698 | responses: 699 | '200': 700 | description: List of tags after add the tag 701 | content: 702 | application/json: 703 | schema: 704 | $ref: '#/components/schemas/workflowTags' 705 | '400': 706 | $ref: '#/components/responses/badRequest' 707 | '401': 708 | $ref: '#/components/responses/unauthorized' 709 | '404': 710 | $ref: '#/components/responses/notFound' 711 | /users: 712 | get: 713 | x-eov-operation-id: getUsers 714 | x-eov-operation-handler: v1/handlers/users/users.handler.ee 715 | tags: 716 | - User 717 | summary: Retrieve all users 718 | description: Retrieve all users from your instance. Only available for the instance owner. 719 | parameters: 720 | - $ref: '#/components/parameters/limit' 721 | - $ref: '#/components/parameters/cursor' 722 | - $ref: '#/components/parameters/includeRole' 723 | - name: projectId 724 | in: query 725 | required: false 726 | explode: false 727 | allowReserved: true 728 | schema: 729 | type: string 730 | example: VmwOO9HeTEj20kxM 731 | responses: 732 | '200': 733 | description: Operation successful. 734 | content: 735 | application/json: 736 | schema: 737 | $ref: '#/components/schemas/userList' 738 | '401': 739 | $ref: '#/components/responses/unauthorized' 740 | post: 741 | x-eov-operation-id: createUser 742 | x-eov-operation-handler: v1/handlers/users/users.handler.ee 743 | tags: 744 | - User 745 | summary: Create multiple users 746 | description: Create one or more users. 747 | requestBody: 748 | description: Array of users to be created. 749 | required: true 750 | content: 751 | application/json: 752 | schema: 753 | type: array 754 | items: 755 | type: object 756 | properties: 757 | email: 758 | type: string 759 | format: email 760 | role: 761 | type: string 762 | enum: 763 | - global:admin 764 | - global:member 765 | required: 766 | - email 767 | responses: 768 | '200': 769 | description: Operation successful. 770 | content: 771 | application/json: 772 | schema: 773 | type: object 774 | properties: 775 | user: 776 | type: object 777 | properties: 778 | id: 779 | type: string 780 | email: 781 | type: string 782 | inviteAcceptUrl: 783 | type: string 784 | emailSent: 785 | type: boolean 786 | error: 787 | type: string 788 | '401': 789 | $ref: '#/components/responses/unauthorized' 790 | '403': 791 | $ref: '#/components/responses/forbidden' 792 | /users/{id}: 793 | get: 794 | x-eov-operation-id: getUser 795 | x-eov-operation-handler: v1/handlers/users/users.handler.ee 796 | tags: 797 | - User 798 | summary: Get user by ID/Email 799 | description: Retrieve a user from your instance. Only available for the instance owner. 800 | parameters: 801 | - $ref: '#/components/parameters/userIdentifier' 802 | - $ref: '#/components/parameters/includeRole' 803 | responses: 804 | '200': 805 | description: Operation successful. 806 | content: 807 | application/json: 808 | schema: 809 | $ref: '#/components/schemas/user' 810 | '401': 811 | $ref: '#/components/responses/unauthorized' 812 | delete: 813 | x-eov-operation-id: deleteUser 814 | x-eov-operation-handler: v1/handlers/users/users.handler.ee 815 | tags: 816 | - User 817 | summary: Delete a user 818 | description: Delete a user from your instance. 819 | parameters: 820 | - $ref: '#/components/parameters/userIdentifier' 821 | responses: 822 | '204': 823 | description: Operation successful. 824 | '401': 825 | $ref: '#/components/responses/unauthorized' 826 | '403': 827 | $ref: '#/components/responses/forbidden' 828 | '404': 829 | $ref: '#/components/responses/notFound' 830 | /users/{id}/role: 831 | patch: 832 | x-eov-operation-id: changeRole 833 | x-eov-operation-handler: v1/handlers/users/users.handler.ee 834 | tags: 835 | - User 836 | summary: Change a user's global role 837 | description: Change a user's global role 838 | parameters: 839 | - $ref: '#/components/parameters/userIdentifier' 840 | requestBody: 841 | description: New role for the user 842 | required: true 843 | content: 844 | application/json: 845 | schema: 846 | type: object 847 | properties: 848 | newRoleName: 849 | type: string 850 | enum: 851 | - global:admin 852 | - global:member 853 | required: 854 | - newRoleName 855 | responses: 856 | '200': 857 | description: Operation successful. 858 | '401': 859 | $ref: '#/components/responses/unauthorized' 860 | '403': 861 | $ref: '#/components/responses/forbidden' 862 | '404': 863 | $ref: '#/components/responses/notFound' 864 | /source-control/pull: 865 | post: 866 | x-eov-operation-id: pull 867 | x-eov-operation-handler: v1/handlers/source-control/source-control.handler 868 | tags: 869 | - SourceControl 870 | summary: Pull changes from the remote repository 871 | description: Requires the Source Control feature to be licensed and connected to a repository. 872 | requestBody: 873 | description: Pull options 874 | required: true 875 | content: 876 | application/json: 877 | schema: 878 | $ref: '#/components/schemas/pull' 879 | responses: 880 | '200': 881 | description: Import result 882 | content: 883 | application/json: 884 | schema: 885 | $ref: '#/components/schemas/importResult' 886 | '400': 887 | $ref: '#/components/responses/badRequest' 888 | '409': 889 | $ref: '#/components/responses/conflict' 890 | /variables: 891 | post: 892 | x-eov-operation-id: createVariable 893 | x-eov-operation-handler: v1/handlers/variables/variables.handler 894 | tags: 895 | - Variables 896 | summary: Create a variable 897 | description: Create a variable in your instance. 898 | requestBody: 899 | description: Payload for variable to create. 900 | content: 901 | application/json: 902 | schema: 903 | $ref: '#/components/schemas/variable' 904 | required: true 905 | responses: 906 | '201': 907 | description: Operation successful. 908 | '400': 909 | $ref: '#/components/responses/badRequest' 910 | '401': 911 | $ref: '#/components/responses/unauthorized' 912 | get: 913 | x-eov-operation-id: getVariables 914 | x-eov-operation-handler: v1/handlers/variables/variables.handler 915 | tags: 916 | - Variables 917 | summary: Retrieve variables 918 | description: Retrieve variables from your instance. 919 | parameters: 920 | - $ref: '#/components/parameters/limit' 921 | - $ref: '#/components/parameters/cursor' 922 | responses: 923 | '200': 924 | description: Operation successful. 925 | content: 926 | application/json: 927 | schema: 928 | $ref: '#/components/schemas/variableList' 929 | '401': 930 | $ref: '#/components/responses/unauthorized' 931 | /variables/{id}: 932 | delete: 933 | x-eov-operation-id: deleteVariable 934 | x-eov-operation-handler: v1/handlers/variables/variables.handler 935 | tags: 936 | - Variables 937 | summary: Delete a variable 938 | description: Delete a variable from your instance. 939 | parameters: 940 | - $ref: '#/components/parameters/variableId' 941 | responses: 942 | '204': 943 | description: Operation successful. 944 | '401': 945 | $ref: '#/components/responses/unauthorized' 946 | '404': 947 | $ref: '#/components/responses/notFound' 948 | /projects: 949 | post: 950 | x-eov-operation-id: createProject 951 | x-eov-operation-handler: v1/handlers/projects/projects.handler 952 | tags: 953 | - Projects 954 | summary: Create a project 955 | description: Create a project in your instance. 956 | requestBody: 957 | description: Payload for project to create. 958 | content: 959 | application/json: 960 | schema: 961 | $ref: '#/components/schemas/project' 962 | required: true 963 | responses: 964 | '201': 965 | description: Operation successful. 966 | '400': 967 | $ref: '#/components/responses/badRequest' 968 | '401': 969 | $ref: '#/components/responses/unauthorized' 970 | get: 971 | x-eov-operation-id: getProjects 972 | x-eov-operation-handler: v1/handlers/projects/projects.handler 973 | tags: 974 | - Projects 975 | summary: Retrieve projects 976 | description: Retrieve projects from your instance. 977 | parameters: 978 | - $ref: '#/components/parameters/limit' 979 | - $ref: '#/components/parameters/cursor' 980 | responses: 981 | '200': 982 | description: Operation successful. 983 | content: 984 | application/json: 985 | schema: 986 | $ref: '#/components/schemas/projectList' 987 | '401': 988 | $ref: '#/components/responses/unauthorized' 989 | /projects/{projectId}: 990 | delete: 991 | x-eov-operation-id: deleteProject 992 | x-eov-operation-handler: v1/handlers/projects/projects.handler 993 | tags: 994 | - Projects 995 | summary: Delete a project 996 | description: Delete a project from your instance. 997 | parameters: 998 | - $ref: '#/components/parameters/projectId' 999 | responses: 1000 | '204': 1001 | description: Operation successful. 1002 | '401': 1003 | $ref: '#/components/responses/unauthorized' 1004 | '403': 1005 | $ref: '#/components/responses/forbidden' 1006 | '404': 1007 | $ref: '#/components/responses/notFound' 1008 | put: 1009 | x-eov-operation-id: updateProject 1010 | x-eov-operation-handler: v1/handlers/projects/projects.handler 1011 | tags: 1012 | - Project 1013 | summary: Update a project 1014 | description: Update a project. 1015 | requestBody: 1016 | description: Updated project object. 1017 | content: 1018 | application/json: 1019 | schema: 1020 | $ref: '#/components/schemas/project' 1021 | required: true 1022 | responses: 1023 | '204': 1024 | description: Operation successful. 1025 | '400': 1026 | $ref: '#/components/responses/badRequest' 1027 | '401': 1028 | $ref: '#/components/responses/unauthorized' 1029 | '403': 1030 | $ref: '#/components/responses/forbidden' 1031 | '404': 1032 | $ref: '#/components/responses/notFound' 1033 | components: 1034 | schemas: 1035 | audit: 1036 | type: object 1037 | properties: 1038 | Credentials Risk Report: 1039 | type: object 1040 | example: 1041 | risk: credentials 1042 | sections: 1043 | - title: Credentials not used in any workflow 1044 | description: These credentials are not used in any workflow. Keeping unused credentials in your instance is an unneeded security risk. 1045 | recommendation: Consider deleting these credentials if you no longer need them. 1046 | location: 1047 | - kind: credential 1048 | id: '1' 1049 | name: My Test Account 1050 | Database Risk Report: 1051 | type: object 1052 | example: 1053 | risk: database 1054 | sections: 1055 | - title: Expressions in "Execute Query" fields in SQL nodes 1056 | description: This SQL node has an expression in the "Query" field of an "Execute Query" operation. Building a SQL query with an expression may lead to a SQL injection attack. 1057 | recommendation: Consider using the "Query Parameters" field to pass parameters to the query 1058 | or validating the input of the expression in the "Query" field.: null 1059 | location: 1060 | - kind: node 1061 | workflowId: '1' 1062 | workflowName: My Workflow 1063 | nodeId: 51eb5852-ce0b-4806-b4ff-e41322a4041a 1064 | nodeName: MySQL 1065 | nodeType: n8n-nodes-base.mySql 1066 | Filesystem Risk Report: 1067 | type: object 1068 | example: 1069 | risk: filesystem 1070 | sections: 1071 | - title: Nodes that interact with the filesystem 1072 | description: This node reads from and writes to any accessible file in the host filesystem. Sensitive file content may be manipulated through a node operation. 1073 | recommendation: Consider protecting any sensitive files in the host filesystem 1074 | or refactoring the workflow so that it does not require host filesystem interaction.: null 1075 | location: 1076 | - kind: node 1077 | workflowId: '1' 1078 | workflowName: My Workflow 1079 | nodeId: 51eb5852-ce0b-4806-b4ff-e41322a4041a 1080 | nodeName: Ready Binary file 1081 | nodeType: n8n-nodes-base.readBinaryFile 1082 | Nodes Risk Report: 1083 | type: object 1084 | example: 1085 | risk: nodes 1086 | sections: 1087 | - title: Community nodes 1088 | description: This node is sourced from the community. Community nodes are not vetted by the n8n team and have full access to the host system. 1089 | recommendation: Consider reviewing the source code in any community nodes installed in this n8n instance 1090 | and uninstalling any community nodes no longer used.: null 1091 | location: 1092 | - kind: community 1093 | nodeType: n8n-nodes-test.test 1094 | packageUrl: https://www.npmjs.com/package/n8n-nodes-test 1095 | Instance Risk Report: 1096 | type: object 1097 | example: 1098 | risk: execution 1099 | sections: 1100 | - title: Unprotected webhooks in instance 1101 | description: These webhook nodes have the "Authentication" field set to "None" and are not directly connected to a node to validate the payload. Every unprotected webhook allows your workflow to be called by any third party who knows the webhook URL. 1102 | recommendation: Consider setting the "Authentication" field to an option other than "None" 1103 | or validating the payload with one of the following nodes.: null 1104 | location: 1105 | - kind: community 1106 | nodeType: n8n-nodes-test.test 1107 | packageUrl: https://www.npmjs.com/package/n8n-nodes-test 1108 | credential: 1109 | required: 1110 | - name 1111 | - type 1112 | - data 1113 | type: object 1114 | properties: 1115 | id: 1116 | type: string 1117 | readOnly: true 1118 | example: R2DjclaysHbqn778 1119 | name: 1120 | type: string 1121 | example: Joe's Github Credentials 1122 | type: 1123 | type: string 1124 | example: github 1125 | data: 1126 | type: object 1127 | writeOnly: true 1128 | example: 1129 | token: ada612vad6fa5df4adf5a5dsf4389adsf76da7s 1130 | createdAt: 1131 | type: string 1132 | format: date-time 1133 | readOnly: true 1134 | example: '2022-04-29T11:02:29.842Z' 1135 | updatedAt: 1136 | type: string 1137 | format: date-time 1138 | readOnly: true 1139 | example: '2022-04-29T11:02:29.842Z' 1140 | create-credential-response: 1141 | required: 1142 | - id 1143 | - name 1144 | - type 1145 | - createdAt 1146 | - updatedAt 1147 | type: object 1148 | properties: 1149 | id: 1150 | type: string 1151 | readOnly: true 1152 | example: vHxaz5UaCghVYl9C 1153 | name: 1154 | type: string 1155 | example: John's Github account 1156 | type: 1157 | type: string 1158 | example: github 1159 | createdAt: 1160 | type: string 1161 | format: date-time 1162 | readOnly: true 1163 | example: '2022-04-29T11:02:29.842Z' 1164 | updatedAt: 1165 | type: string 1166 | format: date-time 1167 | readOnly: true 1168 | example: '2022-04-29T11:02:29.842Z' 1169 | execution: 1170 | type: object 1171 | properties: 1172 | id: 1173 | type: number 1174 | example: 1000 1175 | data: 1176 | type: object 1177 | finished: 1178 | type: boolean 1179 | example: true 1180 | mode: 1181 | type: string 1182 | enum: 1183 | - cli 1184 | - error 1185 | - integrated 1186 | - internal 1187 | - manual 1188 | - retry 1189 | - trigger 1190 | - webhook 1191 | retryOf: 1192 | type: number 1193 | nullable: true 1194 | retrySuccessId: 1195 | type: number 1196 | nullable: true 1197 | example: '2' 1198 | startedAt: 1199 | type: string 1200 | format: date-time 1201 | stoppedAt: 1202 | type: string 1203 | format: date-time 1204 | workflowId: 1205 | type: number 1206 | example: '1000' 1207 | waitTill: 1208 | type: string 1209 | nullable: true 1210 | format: date-time 1211 | customData: 1212 | type: object 1213 | executionList: 1214 | type: object 1215 | properties: 1216 | data: 1217 | type: array 1218 | items: 1219 | $ref: '#/components/schemas/execution' 1220 | nextCursor: 1221 | type: string 1222 | description: Paginate through executions by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. 1223 | nullable: true 1224 | example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA 1225 | tag: 1226 | type: object 1227 | additionalProperties: false 1228 | required: 1229 | - name 1230 | properties: 1231 | id: 1232 | type: string 1233 | readOnly: true 1234 | example: 2tUt1wbLX592XDdX 1235 | name: 1236 | type: string 1237 | example: Production 1238 | createdAt: 1239 | type: string 1240 | format: date-time 1241 | readOnly: true 1242 | updatedAt: 1243 | type: string 1244 | format: date-time 1245 | readOnly: true 1246 | tagList: 1247 | type: object 1248 | properties: 1249 | data: 1250 | type: array 1251 | items: 1252 | $ref: '#/components/schemas/tag' 1253 | nextCursor: 1254 | type: string 1255 | description: Paginate through tags by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. 1256 | nullable: true 1257 | example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA 1258 | node: 1259 | type: object 1260 | additionalProperties: false 1261 | properties: 1262 | id: 1263 | type: string 1264 | example: 0f5532f9-36ba-4bef-86c7-30d607400b15 1265 | name: 1266 | type: string 1267 | example: Jira 1268 | webhookId: 1269 | type: string 1270 | disabled: 1271 | type: boolean 1272 | notesInFlow: 1273 | type: boolean 1274 | notes: 1275 | type: string 1276 | type: 1277 | type: string 1278 | example: n8n-nodes-base.Jira 1279 | typeVersion: 1280 | type: number 1281 | example: 1 1282 | executeOnce: 1283 | type: boolean 1284 | example: false 1285 | alwaysOutputData: 1286 | type: boolean 1287 | example: false 1288 | retryOnFail: 1289 | type: boolean 1290 | example: false 1291 | maxTries: 1292 | type: number 1293 | waitBetweenTries: 1294 | type: number 1295 | continueOnFail: 1296 | type: boolean 1297 | example: false 1298 | description: use onError instead 1299 | deprecated: true 1300 | onError: 1301 | type: string 1302 | example: stopWorkflow 1303 | position: 1304 | type: array 1305 | items: 1306 | type: number 1307 | example: 1308 | - -100 1309 | - 80 1310 | parameters: 1311 | type: object 1312 | example: 1313 | additionalProperties: {} 1314 | credentials: 1315 | type: object 1316 | example: 1317 | jiraSoftwareCloudApi: 1318 | id: '35' 1319 | name: jiraApi 1320 | createdAt: 1321 | type: string 1322 | format: date-time 1323 | readOnly: true 1324 | updatedAt: 1325 | type: string 1326 | format: date-time 1327 | readOnly: true 1328 | workflowSettings: 1329 | type: object 1330 | additionalProperties: false 1331 | properties: 1332 | saveExecutionProgress: 1333 | type: boolean 1334 | saveManualExecutions: 1335 | type: boolean 1336 | saveDataErrorExecution: 1337 | type: string 1338 | enum: 1339 | - all 1340 | - none 1341 | saveDataSuccessExecution: 1342 | type: string 1343 | enum: 1344 | - all 1345 | - none 1346 | executionTimeout: 1347 | type: number 1348 | example: 3600 1349 | maxLength: 3600 1350 | errorWorkflow: 1351 | type: string 1352 | example: VzqKEW0ShTXA5vPj 1353 | description: The ID of the workflow that contains the error trigger node. 1354 | timezone: 1355 | type: string 1356 | example: America/New_York 1357 | executionOrder: 1358 | type: string 1359 | example: v1 1360 | workflow: 1361 | type: object 1362 | additionalProperties: false 1363 | required: 1364 | - name 1365 | - nodes 1366 | - connections 1367 | - settings 1368 | properties: 1369 | id: 1370 | type: string 1371 | readOnly: true 1372 | example: 2tUt1wbLX592XDdX 1373 | name: 1374 | type: string 1375 | example: Workflow 1 1376 | active: 1377 | type: boolean 1378 | readOnly: true 1379 | createdAt: 1380 | type: string 1381 | format: date-time 1382 | readOnly: true 1383 | updatedAt: 1384 | type: string 1385 | format: date-time 1386 | readOnly: true 1387 | nodes: 1388 | type: array 1389 | items: 1390 | $ref: '#/components/schemas/node' 1391 | connections: 1392 | type: object 1393 | example: 1394 | main: 1395 | - node: Jira 1396 | type: main 1397 | index: 0 1398 | settings: 1399 | $ref: '#/components/schemas/workflowSettings' 1400 | staticData: 1401 | example: 1402 | lastId: 1 1403 | anyOf: 1404 | - type: string 1405 | format: jsonString 1406 | nullable: true 1407 | - type: object 1408 | nullable: true 1409 | tags: 1410 | type: array 1411 | items: 1412 | $ref: '#/components/schemas/tag' 1413 | readOnly: true 1414 | workflowList: 1415 | type: object 1416 | properties: 1417 | data: 1418 | type: array 1419 | items: 1420 | $ref: '#/components/schemas/workflow' 1421 | nextCursor: 1422 | type: string 1423 | description: Paginate through workflows by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. 1424 | nullable: true 1425 | example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA 1426 | workflowTags: 1427 | type: array 1428 | items: 1429 | $ref: '#/components/schemas/tag' 1430 | tagIds: 1431 | type: array 1432 | items: 1433 | type: object 1434 | additionalProperties: false 1435 | required: 1436 | - id 1437 | properties: 1438 | id: 1439 | type: string 1440 | example: 2tUt1wbLX592XDdX 1441 | user: 1442 | required: 1443 | - email 1444 | type: object 1445 | properties: 1446 | id: 1447 | type: string 1448 | readOnly: true 1449 | example: 123e4567-e89b-12d3-a456-426614174000 1450 | email: 1451 | type: string 1452 | format: email 1453 | example: john.doe@company.com 1454 | firstName: 1455 | maxLength: 32 1456 | type: string 1457 | description: User's first name 1458 | readOnly: true 1459 | example: john 1460 | lastName: 1461 | maxLength: 32 1462 | type: string 1463 | description: User's last name 1464 | readOnly: true 1465 | example: Doe 1466 | isPending: 1467 | type: boolean 1468 | description: Whether the user finished setting up their account in response to the invitation (true) or not (false). 1469 | readOnly: true 1470 | createdAt: 1471 | type: string 1472 | description: Time the user was created. 1473 | format: date-time 1474 | readOnly: true 1475 | updatedAt: 1476 | type: string 1477 | description: Last time the user was updated. 1478 | format: date-time 1479 | readOnly: true 1480 | role: 1481 | type: string 1482 | example: owner 1483 | readOnly: true 1484 | userList: 1485 | type: object 1486 | properties: 1487 | data: 1488 | type: array 1489 | items: 1490 | $ref: '#/components/schemas/user' 1491 | nextCursor: 1492 | type: string 1493 | description: Paginate through users by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. 1494 | nullable: true 1495 | example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA 1496 | pull: 1497 | type: object 1498 | properties: 1499 | force: 1500 | type: boolean 1501 | example: true 1502 | variables: 1503 | type: object 1504 | example: 1505 | foo: bar 1506 | importResult: 1507 | type: object 1508 | additionalProperties: true 1509 | properties: 1510 | variables: 1511 | type: object 1512 | properties: 1513 | added: 1514 | type: array 1515 | items: 1516 | type: string 1517 | changed: 1518 | type: array 1519 | items: 1520 | type: string 1521 | credentials: 1522 | type: array 1523 | items: 1524 | type: object 1525 | properties: 1526 | id: 1527 | type: string 1528 | name: 1529 | type: string 1530 | type: 1531 | type: string 1532 | workflows: 1533 | type: array 1534 | items: 1535 | type: object 1536 | properties: 1537 | id: 1538 | type: string 1539 | name: 1540 | type: string 1541 | tags: 1542 | type: object 1543 | properties: 1544 | tags: 1545 | type: array 1546 | items: 1547 | type: object 1548 | properties: 1549 | id: 1550 | type: string 1551 | name: 1552 | type: string 1553 | mappings: 1554 | type: array 1555 | items: 1556 | type: object 1557 | properties: 1558 | workflowId: 1559 | type: string 1560 | tagId: 1561 | type: string 1562 | variable: 1563 | type: object 1564 | additionalProperties: false 1565 | required: 1566 | - key 1567 | - value 1568 | properties: 1569 | id: 1570 | type: string 1571 | readOnly: true 1572 | key: 1573 | type: string 1574 | value: 1575 | type: string 1576 | example: test 1577 | type: 1578 | type: string 1579 | readOnly: true 1580 | variableList: 1581 | type: object 1582 | properties: 1583 | data: 1584 | type: array 1585 | items: 1586 | $ref: '#/components/schemas/variable' 1587 | nextCursor: 1588 | type: string 1589 | description: Paginate through variables by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. 1590 | nullable: true 1591 | example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA 1592 | project: 1593 | type: object 1594 | additionalProperties: false 1595 | required: 1596 | - name 1597 | properties: 1598 | id: 1599 | type: string 1600 | readOnly: true 1601 | name: 1602 | type: string 1603 | type: 1604 | type: string 1605 | readOnly: true 1606 | projectList: 1607 | type: object 1608 | properties: 1609 | data: 1610 | type: array 1611 | items: 1612 | $ref: '#/components/schemas/project' 1613 | nextCursor: 1614 | type: string 1615 | description: Paginate through projects by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. 1616 | nullable: true 1617 | example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA 1618 | error: 1619 | required: 1620 | - message 1621 | type: object 1622 | properties: 1623 | code: 1624 | type: string 1625 | message: 1626 | type: string 1627 | description: 1628 | type: string 1629 | role: 1630 | readOnly: true 1631 | type: object 1632 | properties: 1633 | id: 1634 | type: number 1635 | readOnly: true 1636 | example: 1 1637 | name: 1638 | type: string 1639 | example: owner 1640 | readOnly: true 1641 | scope: 1642 | type: string 1643 | readOnly: true 1644 | example: global 1645 | createdAt: 1646 | type: string 1647 | description: Time the role was created. 1648 | format: date-time 1649 | readOnly: true 1650 | updatedAt: 1651 | type: string 1652 | description: Last time the role was updated. 1653 | format: date-time 1654 | readOnly: true 1655 | credentialType: 1656 | type: object 1657 | properties: 1658 | displayName: 1659 | type: string 1660 | readOnly: true 1661 | example: Email 1662 | name: 1663 | type: string 1664 | readOnly: true 1665 | example: email 1666 | type: 1667 | type: string 1668 | readOnly: true 1669 | example: string 1670 | default: 1671 | type: string 1672 | readOnly: true 1673 | example: string 1674 | Error: 1675 | $ref: '#/components/schemas/error' 1676 | Role: 1677 | $ref: '#/components/schemas/role' 1678 | Execution: 1679 | $ref: '#/components/schemas/execution' 1680 | Node: 1681 | $ref: '#/components/schemas/node' 1682 | Tag: 1683 | $ref: '#/components/schemas/tag' 1684 | Workflow: 1685 | $ref: '#/components/schemas/workflow' 1686 | WorkflowSettings: 1687 | $ref: '#/components/schemas/workflowSettings' 1688 | ExecutionList: 1689 | $ref: '#/components/schemas/executionList' 1690 | WorkflowList: 1691 | $ref: '#/components/schemas/workflowList' 1692 | Credential: 1693 | $ref: '#/components/schemas/credential' 1694 | CredentialType: 1695 | $ref: '#/components/schemas/credentialType' 1696 | Audit: 1697 | $ref: '#/components/schemas/audit' 1698 | Pull: 1699 | $ref: '#/components/schemas/pull' 1700 | ImportResult: 1701 | $ref: '#/components/schemas/importResult' 1702 | UserList: 1703 | $ref: '#/components/schemas/userList' 1704 | User: 1705 | $ref: '#/components/schemas/user' 1706 | responses: 1707 | unauthorized: 1708 | description: Unauthorized 1709 | notFound: 1710 | description: The specified resource was not found. 1711 | badRequest: 1712 | description: The request is invalid or provides malformed data. 1713 | conflict: 1714 | description: Conflict 1715 | forbidden: 1716 | description: Forbidden 1717 | NotFound: 1718 | $ref: '#/components/responses/notFound' 1719 | Unauthorized: 1720 | $ref: '#/components/responses/unauthorized' 1721 | BadRequest: 1722 | $ref: '#/components/responses/badRequest' 1723 | Conflict: 1724 | $ref: '#/components/responses/conflict' 1725 | Forbidden: 1726 | $ref: '#/components/responses/forbidden' 1727 | parameters: 1728 | includeData: 1729 | name: includeData 1730 | in: query 1731 | description: Whether or not to include the execution's detailed data. 1732 | required: false 1733 | schema: 1734 | type: boolean 1735 | limit: 1736 | name: limit 1737 | in: query 1738 | description: The maximum number of items to return. 1739 | required: false 1740 | schema: 1741 | type: number 1742 | example: 100 1743 | default: 100 1744 | maximum: 250 1745 | cursor: 1746 | name: cursor 1747 | in: query 1748 | description: Paginate by setting the cursor parameter to the nextCursor attribute returned by the previous request's response. Default value fetches the first "page" of the collection. See pagination for more detail. 1749 | required: false 1750 | style: form 1751 | schema: 1752 | type: string 1753 | executionId: 1754 | name: id 1755 | in: path 1756 | description: The ID of the execution. 1757 | required: true 1758 | schema: 1759 | type: number 1760 | tagId: 1761 | name: id 1762 | in: path 1763 | description: The ID of the tag. 1764 | required: true 1765 | schema: 1766 | type: string 1767 | workflowId: 1768 | name: id 1769 | in: path 1770 | description: The ID of the workflow. 1771 | required: true 1772 | schema: 1773 | type: string 1774 | credentialId: 1775 | name: id 1776 | in: path 1777 | description: The ID of the credential. 1778 | required: true 1779 | schema: 1780 | type: string 1781 | includeRole: 1782 | name: includeRole 1783 | in: query 1784 | description: Whether to include the user's role or not. 1785 | required: false 1786 | schema: 1787 | type: boolean 1788 | example: true 1789 | default: false 1790 | userIdentifier: 1791 | name: id 1792 | in: path 1793 | description: The ID or email of the user. 1794 | required: true 1795 | schema: 1796 | type: string 1797 | format: identifier 1798 | variableId: 1799 | name: id 1800 | in: path 1801 | description: The ID of the variable. 1802 | required: true 1803 | schema: 1804 | type: string 1805 | projectId: 1806 | name: projectId 1807 | in: path 1808 | description: The ID of the project. 1809 | required: true 1810 | schema: 1811 | type: string 1812 | Cursor: 1813 | $ref: '#/components/parameters/cursor' 1814 | Limit: 1815 | $ref: '#/components/parameters/limit' 1816 | ExecutionId: 1817 | $ref: '#/components/parameters/executionId' 1818 | WorkflowId: 1819 | $ref: '#/components/parameters/workflowId' 1820 | TagId: 1821 | $ref: '#/components/parameters/tagId' 1822 | IncludeData: 1823 | $ref: '#/components/parameters/includeData' 1824 | UserIdentifier: 1825 | $ref: '#/components/parameters/userIdentifier' 1826 | IncludeRole: 1827 | $ref: '#/components/parameters/includeRole' 1828 | securitySchemes: 1829 | ApiKeyAuth: 1830 | type: apiKey 1831 | in: header 1832 | name: X-N8N-API-KEY 1833 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@makafeli/n8n-workflow-builder", 3 | "version": "0.3.0", 4 | "scripts": { 5 | "clean": "rm -rf build", 6 | "build": "tsc", 7 | "dev": "tsc -w", 8 | "start": "node build/index.js" 9 | }, 10 | "dependencies": { 11 | "@modelcontextprotocol/sdk": "^1.4.1", 12 | "axios": "^1.7.9" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^5.0.0", 16 | "@types/node": "^20.0.0" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | }, 21 | "bin": { 22 | "n8n-workflow-builder": "build/index.js" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | name: n8n-workflow-builder 2 | displayName: n8n Workflow Builder 3 | description: An MCP server for programmatically creating and managing n8n workflows. 4 | category: productivity 5 | publisher: makafeli 6 | repository: https://github.com/makafeli/n8n-workflow-builder 7 | license: MIT 8 | keywords: 9 | - n8n 10 | - workflow 11 | - automation 12 | - mcp 13 | - model context protocol 14 | - server 15 | - api 16 | install: 17 | - npx: 18 | package: "@makafeli/n8n-workflow-builder" 19 | command: n8n-workflow-builder 20 | args: [] 21 | env:{} 22 | -------------------------------------------------------------------------------- /src/config/constants.ts: -------------------------------------------------------------------------------- 1 | export const N8N_HOST = process.env.N8N_HOST || ''; 2 | export const N8N_API_KEY = process.env.N8N_API_KEY || ''; 3 | -------------------------------------------------------------------------------- /src/index.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 4 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 5 | return new (P || (P = Promise))(function (resolve, reject) { 6 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 7 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 8 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 9 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 10 | }); 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); 14 | const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); 15 | const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); 16 | const axios = require('axios'); // Use require for CommonJS 17 | 18 | class N8NWorkflowBuilder { 19 | constructor() { 20 | this.nodes = []; 21 | this.connections = []; 22 | this.nextPosition = { x: 100, y: 100 }; 23 | } 24 | addNode(nodeType, name, parameters) { 25 | const node = { 26 | type: nodeType, 27 | name: name, 28 | parameters: parameters, 29 | position: Object.assign({}, this.nextPosition) 30 | }; 31 | this.nodes.push(node); 32 | this.nextPosition.x += 200; 33 | return name; 34 | } 35 | connectNodes(source, target, sourceOutput = 0, targetInput = 0) { 36 | this.connections.push({ 37 | source_node: source, 38 | target_node: target, 39 | source_output: sourceOutput, 40 | target_input: targetInput 41 | }); 42 | } 43 | exportWorkflow() { 44 | const workflow = { 45 | nodes: this.nodes, 46 | connections: { main: [] } 47 | }; 48 | for (const conn of this.connections) { 49 | const connection = { 50 | node: conn.target_node, 51 | type: 'main', 52 | index: conn.target_input, 53 | sourceNode: conn.source_node, 54 | sourceIndex: conn.source_output 55 | }; 56 | workflow.connections.main.push(connection); 57 | } 58 | return workflow; 59 | } 60 | } 61 | 62 | class N8NWorkflowServer { 63 | constructor() { 64 | this.n8nHost = process.env.N8N_HOST || 'http://localhost:5678'; 65 | this.n8nApiKey = process.env.N8N_API_KEY || ''; 66 | 67 | if (!this.n8nApiKey) { 68 | console.warn('N8N_API_KEY environment variable not set. API calls to n8n will likely fail.'); 69 | } 70 | 71 | this.server = new index_js_1.Server({ 72 | name: 'n8n-workflow-builder', 73 | version: '0.2.0' 74 | }, { 75 | capabilities: { 76 | resources: {}, 77 | tools: {} 78 | } 79 | }); 80 | this.setupToolHandlers(); 81 | this.server.onerror = (error) => console.error('[MCP Error]', error); 82 | } 83 | 84 | async createWorkflow(workflowData) { 85 | try { 86 | console.log('Creating workflow with data:', JSON.stringify(workflowData, null, 2)); 87 | const response = await axios.post(`${this.n8nHost}/api/v1/workflows`, workflowData, { 88 | headers: { 89 | 'X-N8N-API-KEY': this.n8nApiKey 90 | } 91 | }); 92 | return response.data; 93 | } 94 | catch (error) { 95 | if (axios.isAxiosError(error)) { 96 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`); 97 | } 98 | throw error; 99 | } 100 | } 101 | async updateWorkflow(id, workflowData) { 102 | try { 103 | console.log(`Updating workflow ${id} with data:`, JSON.stringify(workflowData, null, 2)); 104 | const response = await axios.put(`${this.n8nHost}/api/v1/workflows/${id}`, workflowData, { 105 | headers: { 106 | 'X-N8N-API-KEY': this.n8nApiKey 107 | } 108 | }); 109 | return response.data; 110 | } 111 | catch (error) { 112 | if (axios.isAxiosError(error)) { 113 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`); 114 | } 115 | throw error; 116 | } 117 | } 118 | async activateWorkflow(id) { 119 | try { 120 | console.log(`Activating workflow ${id}`); 121 | const response = await axios.post(`${this.n8nHost}/api/v1/workflows/${id}/activate`, {}, { 122 | headers: { 123 | 'X-N8N-API-KEY': this.n8nApiKey 124 | } 125 | }); 126 | return response.data; 127 | } 128 | catch (error) { 129 | if (axios.isAxiosError(error)) { 130 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`); 131 | } 132 | throw error; 133 | } 134 | } 135 | async deactivateWorkflow(id) { 136 | try { 137 | console.log(`Deactivating workflow ${id}`); 138 | const response = await axios.post(`${this.n8nHost}/api/v1/workflows/${id}/deactivate`, {}, { 139 | headers: { 140 | 'X-N8N-API-KEY': this.n8nApiKey 141 | } 142 | }); 143 | return response.data; 144 | } 145 | catch (error) { 146 | if (axios.isAxiosError(error)) { 147 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`); 148 | } 149 | throw error; 150 | } 151 | } 152 | async getWorkflow(id) { 153 | try { 154 | console.log(`Getting workflow ${id}`); 155 | const response = await axios.get(`${this.n8nHost}/api/v1/workflows/${id}`, { 156 | headers: { 157 | 'X-N8N-API-KEY': this.n8nApiKey 158 | } 159 | }); 160 | return response.data; 161 | } 162 | catch (error) { 163 | if (axios.isAxiosError(error)) { 164 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`); 165 | } 166 | throw error; 167 | } 168 | } 169 | async deleteWorkflow(id) { 170 | try { 171 | console.log(`Deleting workflow ${id}`); 172 | const response = await axios.delete(`${this.n8nHost}/api/v1/workflows/${id}`, { 173 | headers: { 174 | 'X-N8N-API-KEY': this.n8nApiKey 175 | } 176 | }); 177 | return response.data; 178 | } 179 | catch (error) { 180 | if (axios.isAxiosError(error)) { 181 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `n8n API Error: ${error.response?.data?.message || error.message}`); 182 | } 183 | throw error; 184 | } 185 | } 186 | setupToolHandlers() { 187 | this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, () => __awaiter(this, void 0, void 0, function* () { 188 | return ({ 189 | tools: [ 190 | { 191 | name: 'create_workflow', 192 | description: 'Create an n8n workflow', 193 | inputSchema: { 194 | type: 'object', 195 | properties: { 196 | nodes: { 197 | type: 'array', 198 | items: { 199 | type: 'object', 200 | properties: { 201 | type: { type: 'string' }, 202 | name: { type: 'string' }, 203 | parameters: { type: 'object' } 204 | }, 205 | required: ['type', 'name'] 206 | } 207 | }, 208 | connections: { 209 | type: 'array', 210 | items: { 211 | type: 'object', 212 | properties: { 213 | source: { type: 'string' }, 214 | target: { type: 'string' }, 215 | sourceOutput: { type: 'number', default: 0 }, 216 | targetInput: { type: 'number', default: 0 } 217 | }, 218 | required: ['source', 'target'] 219 | } 220 | } 221 | }, 222 | required: ['nodes'] 223 | } 224 | }, 225 | { 226 | name: 'create_workflow_and_activate', 227 | description: 'Create and activate an n8n workflow', 228 | inputSchema: { 229 | type: 'object', 230 | properties: { 231 | nodes: { 232 | type: 'array', 233 | items: { 234 | type: 'object', 235 | properties: { 236 | type: { type: 'string' }, 237 | name: { type: 'string' }, 238 | parameters: { type: 'object' } 239 | }, 240 | required: ['type', 'name'] 241 | } 242 | }, 243 | connections: { 244 | type: 'array', 245 | items: { 246 | type: 'object', 247 | properties: { 248 | source: { type: 'string' }, 249 | target: { type: 'string' }, 250 | sourceOutput: { type: 'number', default: 0 }, 251 | targetInput: { type: 'number', default: 0 } 252 | }, 253 | required: ['source', 'target'] 254 | } 255 | } 256 | }, 257 | required: ['nodes'] 258 | } 259 | }, 260 | { 261 | name: 'update_workflow', 262 | description: 'Update an existing n8n workflow', 263 | inputSchema: { 264 | type: 'object', 265 | properties: { 266 | id: { type: 'string', description: 'The ID of the workflow to update' }, 267 | nodes: { 268 | type: 'array', 269 | items: { 270 | type: 'object', 271 | properties: { 272 | type: { type: 'string' }, 273 | name: { type: 'string' }, 274 | parameters: { type: 'object' } 275 | }, 276 | required: ['type', 'name'] 277 | } 278 | }, 279 | connections: { 280 | type: 'array', 281 | items: { 282 | type: 'object', 283 | properties: { 284 | source: { type: 'string' }, 285 | target: { type: 'string' }, 286 | sourceOutput: { type: 'number', default: 0 }, 287 | targetInput: { type: 'number', default: 0 } 288 | }, 289 | required: ['source', 'target'] 290 | } 291 | } 292 | }, 293 | required: ['id', 'nodes'] 294 | } 295 | }, 296 | { 297 | name: 'activate_workflow', 298 | description: 'Activate an n8n workflow', 299 | inputSchema: { 300 | type: 'object', 301 | properties: { 302 | id: { type: 'string', description: 'The ID of the workflow to activate' } 303 | }, 304 | required: ['id'] 305 | } 306 | }, 307 | { 308 | name: 'deactivate_workflow', 309 | description: 'Deactivate an n8n workflow', 310 | inputSchema: { 311 | type: 'object', 312 | properties: { 313 | id: { type: 'string', description: 'The ID of the workflow to deactivate' } 314 | }, 315 | required: ['id'] 316 | } 317 | }, 318 | { 319 | name: 'get_workflow', 320 | description: 'Get an n8n workflow', 321 | inputSchema: { 322 | type: 'object', 323 | properties: { 324 | id: { type: 'string', description: 'The ID of the workflow to get' } 325 | }, 326 | required: ['id'] 327 | } 328 | }, 329 | { 330 | name: 'delete_workflow', 331 | description: 'Delete an n8n workflow', 332 | inputSchema: { 333 | type: 'object', 334 | properties: { 335 | id: { type: 'string', description: 'The ID of the workflow to delete' } 336 | }, 337 | required: ['id'] 338 | } 339 | } 340 | ] 341 | }); 342 | })); 343 | this.server.setRequestHandler(types_js_1.CallToolRequestSchema, (request) => __awaiter(this, void 0, void 0, function* () { 344 | try { 345 | const builder = new N8NWorkflowBuilder(); 346 | function isWorkflowSpec(obj) { 347 | return obj && 348 | typeof obj === 'object' && 349 | Array.isArray(obj.nodes) && 350 | obj.nodes.every((node) => typeof node === 'object' && 351 | typeof node.type === 'string' && 352 | typeof node.name === 'string') && 353 | (!obj.connections || (Array.isArray(obj.connections) && 354 | obj.connections.every((conn) => typeof conn === 'object' && 355 | typeof conn.source === 'string' && 356 | typeof conn.target === 'string'))); 357 | } 358 | const args = request.params.arguments; 359 | switch (request.params.name) { 360 | case 'create_workflow': { 361 | if (!isWorkflowSpec(args)) { 362 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Invalid workflow specification: must include nodes array with type and name properties'); 363 | } 364 | const { nodes, connections } = args; 365 | for (const node of nodes) { 366 | builder.addNode(node.type, node.name, node.parameters || {}); 367 | } 368 | for (const conn of connections || []) { 369 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput); 370 | } 371 | return { 372 | content: [{ 373 | type: 'text', 374 | text: JSON.stringify(builder.exportWorkflow(), null, 2) 375 | }] 376 | }; 377 | } 378 | case 'create_workflow_and_activate': { 379 | if (!isWorkflowSpec(args)) { 380 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Invalid workflow specification: must include nodes array with type and name properties'); 381 | } 382 | const { nodes, connections } = args; 383 | for (const node of nodes) { 384 | builder.addNode(node.type, node.name, node.parameters || {}); 385 | } 386 | for (const conn of connections || []) { 387 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput); 388 | } 389 | const workflowData = builder.exportWorkflow(); 390 | const createdWorkflow = yield this.createWorkflow(workflowData); 391 | if (createdWorkflow && createdWorkflow.id) { 392 | yield this.activateWorkflow(createdWorkflow.id); 393 | } 394 | return { 395 | content: [{ 396 | type: 'text', 397 | text: JSON.stringify(createdWorkflow, null, 2) 398 | }] 399 | }; 400 | } 401 | case 'update_workflow': { 402 | if (!isWorkflowSpec(args)) { 403 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Invalid workflow specification: must include id, nodes array with type and name properties'); 404 | } 405 | const { id, nodes, connections } = args; 406 | for (const node of nodes) { 407 | builder.addNode(node.type, node.name, node.parameters || {}); 408 | } 409 | for (const conn of connections || []) { 410 | builder.connectNodes(conn.source, conn.target, conn.sourceOutput, conn.targetInput); 411 | } 412 | const workflowData = builder.exportWorkflow(); 413 | const updatedWorkflow = yield this.updateWorkflow(id, workflowData); 414 | return { 415 | content: [{ 416 | type: 'text', 417 | text: JSON.stringify(updatedWorkflow, null, 2) 418 | }] 419 | }; 420 | } 421 | case 'activate_workflow': { 422 | const { id } = args; 423 | if (typeof id !== 'string') { 424 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id'); 425 | } 426 | const activatedWorkflow = yield this.activateWorkflow(id); 427 | return { 428 | content: [{ 429 | type: 'text', 430 | text: JSON.stringify(activatedWorkflow, null, 2) 431 | }] 432 | }; 433 | } 434 | case 'deactivate_workflow': { 435 | const { id } = args; 436 | if (typeof id !== 'string') { 437 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id'); 438 | } 439 | const deactivatedWorkflow = yield this.deactivateWorkflow(id); 440 | return { 441 | content: [{ 442 | type: 'text', 443 | text: JSON.stringify(deactivatedWorkflow, null, 2) 444 | }] 445 | }; 446 | } 447 | case 'get_workflow': { 448 | const { id } = args; 449 | if (typeof id !== 'string') { 450 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id'); 451 | } 452 | const workflow = yield this.getWorkflow(id); 453 | return { 454 | content: [{ 455 | type: 'text', 456 | text: JSON.stringify(workflow, null, 2) 457 | }] 458 | }; 459 | } 460 | case 'delete_workflow': { 461 | const { id } = args; 462 | if (typeof id !== 'string') { 463 | throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'Missing workflow id'); 464 | } 465 | const deletedWorkflow = yield this.deleteWorkflow(id); 466 | return { 467 | content: [{ 468 | type: 'text', 469 | text: JSON.stringify(deletedWorkflow, null, 2) 470 | }] 471 | }; 472 | } 473 | default: 474 | throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`); 475 | } 476 | } 477 | catch (error) { 478 | if (error instanceof types_js_1.McpError) { 479 | throw error; 480 | } 481 | throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Workflow operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); 482 | } 483 | })); 484 | } 485 | run() { 486 | return __awaiter(this, void 0, void 0, function* () { 487 | const transport = new stdio_js_1.StdioServerTransport(); 488 | yield this.server.connect(transport); 489 | console.error('N8N Workflow Builder MCP server running on stdio'); 490 | }); 491 | } 492 | } 493 | const server = new N8NWorkflowServer(); 494 | server.run().catch(console.error); 495 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 5 | import { 6 | CallToolRequestSchema, 7 | ListToolsRequestSchema, 8 | ListResourcesRequestSchema, 9 | ReadResourceRequestSchema, 10 | ListResourceTemplatesRequestSchema 11 | } from './sdk-schemas'; 12 | import * as n8nApi from './services/n8nApi'; 13 | import { WorkflowBuilder } from './services/workflowBuilder'; 14 | import { validateWorkflowSpec } from './utils/validation'; 15 | 16 | console.log("ListToolsRequestSchema:", ListToolsRequestSchema); 17 | console.log("CallToolRequestSchema:", CallToolRequestSchema); 18 | 19 | if (!ListToolsRequestSchema) { 20 | console.error("ListToolsRequestSchema is undefined!"); 21 | } 22 | 23 | if (!CallToolRequestSchema) { 24 | console.error("CallToolRequestSchema is undefined!"); 25 | } 26 | 27 | class N8NWorkflowServer { 28 | private server: InstanceType; 29 | 30 | constructor() { 31 | this.server = new Server( 32 | { name: 'n8n-workflow-builder', version: '0.3.0' }, 33 | { capabilities: { tools: {}, resources: {} } } 34 | ); 35 | this.setupToolHandlers(); 36 | this.setupResourceHandlers(); 37 | this.server.onerror = (error: any) => console.error('[MCP Error]', error); 38 | } 39 | 40 | private setupResourceHandlers() { 41 | // List available resources 42 | this.server.setRequestHandler(ListResourcesRequestSchema, async () => { 43 | console.log("listResources handler invoked"); 44 | return { 45 | resources: [ 46 | { 47 | uri: '/workflows', 48 | name: 'Workflows List', 49 | description: 'List of all available workflows', 50 | mimeType: 'application/json' 51 | }, 52 | { 53 | uri: '/execution-stats', 54 | name: 'Execution Statistics', 55 | description: 'Summary statistics of workflow executions', 56 | mimeType: 'application/json' 57 | } 58 | ] 59 | }; 60 | }); 61 | 62 | // List resource templates 63 | this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => { 64 | console.log("listResourceTemplates handler invoked"); 65 | return { 66 | templates: [ 67 | { 68 | uriTemplate: '/workflows/{id}', 69 | name: 'Workflow Details', 70 | description: 'Details of a specific workflow', 71 | mimeType: 'application/json', 72 | parameters: [ 73 | { 74 | name: 'id', 75 | description: 'The ID of the workflow', 76 | required: true 77 | } 78 | ] 79 | }, 80 | { 81 | uriTemplate: '/executions/{id}', 82 | name: 'Execution Details', 83 | description: 'Details of a specific execution', 84 | mimeType: 'application/json', 85 | parameters: [ 86 | { 87 | name: 'id', 88 | description: 'The ID of the execution', 89 | required: true 90 | } 91 | ] 92 | } 93 | ] 94 | }; 95 | }); 96 | 97 | // Read a specific resource 98 | this.server.setRequestHandler(ReadResourceRequestSchema, async (request: any) => { 99 | const { uri } = request.params; 100 | console.log(`readResource handler invoked for URI: ${uri}`); 101 | 102 | // Static resources 103 | if (uri === '/workflows') { 104 | const workflows = await n8nApi.listWorkflows(); 105 | return { 106 | contents: [{ 107 | type: 'text', 108 | text: JSON.stringify(workflows, null, 2), 109 | mimeType: 'application/json', 110 | uri: '/workflows' 111 | }] 112 | }; 113 | } 114 | 115 | if (uri === '/execution-stats') { 116 | try { 117 | const executions = await n8nApi.listExecutions({ limit: 100 }); 118 | 119 | // Calculate statistics 120 | const total = executions.data.length; 121 | const succeeded = executions.data.filter(exec => exec.finished && exec.mode !== 'error').length; 122 | const failed = executions.data.filter(exec => exec.mode === 'error').length; 123 | const waiting = executions.data.filter(exec => !exec.finished).length; 124 | 125 | // Calculate average execution time for finished executions 126 | let totalTimeMs = 0; 127 | let finishedCount = 0; 128 | for (const exec of executions.data) { 129 | if (exec.finished && exec.startedAt && exec.stoppedAt) { 130 | const startTime = new Date(exec.startedAt).getTime(); 131 | const endTime = new Date(exec.stoppedAt).getTime(); 132 | totalTimeMs += (endTime - startTime); 133 | finishedCount++; 134 | } 135 | } 136 | 137 | const avgExecutionTimeMs = finishedCount > 0 ? totalTimeMs / finishedCount : 0; 138 | const avgExecutionTime = `${(avgExecutionTimeMs / 1000).toFixed(2)}s`; 139 | 140 | return { 141 | contents: [{ 142 | type: 'text', 143 | text: JSON.stringify({ 144 | total, 145 | succeeded, 146 | failed, 147 | waiting, 148 | avgExecutionTime 149 | }, null, 2), 150 | mimeType: 'application/json', 151 | uri: '/execution-stats' 152 | }] 153 | }; 154 | } catch (error) { 155 | console.error('Error generating execution stats:', error); 156 | return { 157 | contents: [{ 158 | type: 'text', 159 | text: JSON.stringify({ 160 | total: 0, 161 | succeeded: 0, 162 | failed: 0, 163 | waiting: 0, 164 | avgExecutionTime: '0s', 165 | error: 'Failed to retrieve execution statistics' 166 | }, null, 2), 167 | mimeType: 'application/json', 168 | uri: '/execution-stats' 169 | }] 170 | }; 171 | } 172 | } 173 | 174 | 175 | // Dynamic resource template matching 176 | const workflowMatch = uri.match(/^\/workflows\/(.+)$/); 177 | if (workflowMatch) { 178 | const id = workflowMatch[1]; 179 | try { 180 | const workflow = await n8nApi.getWorkflow(id); 181 | return { 182 | contents: [{ 183 | type: 'text', 184 | text: JSON.stringify(workflow, null, 2), 185 | mimeType: 'application/json', 186 | uri: uri 187 | }] 188 | }; 189 | } catch (error) { 190 | throw new McpError(ErrorCode.InvalidParams, `Workflow with ID ${id} not found`); 191 | } 192 | } 193 | 194 | const executionMatch = uri.match(/^\/executions\/(.+)$/); 195 | if (executionMatch) { 196 | const id = parseInt(executionMatch[1], 10); 197 | if (isNaN(id)) { 198 | throw new McpError(ErrorCode.InvalidParams, 'Execution ID must be a number'); 199 | } 200 | 201 | try { 202 | const execution = await n8nApi.getExecution(id, true); 203 | return { 204 | contents: [{ 205 | type: 'text', 206 | text: JSON.stringify(execution, null, 2), 207 | mimeType: 'application/json', 208 | uri: uri 209 | }] 210 | }; 211 | } catch (error) { 212 | throw new McpError(ErrorCode.InvalidParams, `Execution with ID ${id} not found`); 213 | } 214 | } 215 | 216 | throw new McpError(ErrorCode.InvalidParams, `Resource not found: ${uri}`); 217 | }); 218 | } 219 | 220 | private setupToolHandlers() { 221 | // Register available tools using the local schemas and return an array of tool definitions. 222 | this.server.setRequestHandler(ListToolsRequestSchema, async (req: any) => { 223 | console.log("listTools handler invoked with request:", req); 224 | return { 225 | tools: [ 226 | // Workflow Tools 227 | { 228 | name: 'list_workflows', 229 | enabled: true, 230 | description: 'List all workflows from n8n', 231 | inputSchema: { type: 'object', properties: {} } 232 | }, 233 | { 234 | name: 'create_workflow', 235 | enabled: true, 236 | description: 'Create a new workflow in n8n', 237 | inputSchema: { 238 | type: 'object', 239 | properties: { 240 | name: { type: 'string' }, 241 | nodes: { 242 | type: 'array', 243 | items: { 244 | type: 'object', 245 | properties: { 246 | type: { type: 'string' }, 247 | name: { type: 'string' }, 248 | parameters: { type: 'object' } 249 | }, 250 | required: ['type', 'name'] 251 | } 252 | }, 253 | connections: { 254 | type: 'array', 255 | items: { 256 | type: 'object', 257 | properties: { 258 | source: { type: 'string' }, 259 | target: { type: 'string' }, 260 | sourceOutput: { type: 'number', default: 0 }, 261 | targetInput: { type: 'number', default: 0 } 262 | }, 263 | required: ['source', 'target'] 264 | } 265 | } 266 | }, 267 | required: ['nodes'] 268 | } 269 | }, 270 | { 271 | name: 'get_workflow', 272 | enabled: true, 273 | description: 'Get a workflow by ID', 274 | inputSchema: { 275 | type: 'object', 276 | properties: { id: { type: 'string' } }, 277 | required: ['id'] 278 | } 279 | }, 280 | { 281 | name: 'update_workflow', 282 | enabled: true, 283 | description: 'Update an existing workflow', 284 | inputSchema: { 285 | type: 'object', 286 | properties: { 287 | id: { type: 'string' }, 288 | nodes: { type: 'array' }, 289 | connections: { type: 'array' } 290 | }, 291 | required: ['id', 'nodes'] 292 | } 293 | }, 294 | { 295 | name: 'delete_workflow', 296 | enabled: true, 297 | description: 'Delete a workflow by ID', 298 | inputSchema: { 299 | type: 'object', 300 | properties: { id: { type: 'string' } }, 301 | required: ['id'] 302 | } 303 | }, 304 | { 305 | name: 'activate_workflow', 306 | enabled: true, 307 | description: 'Activate a workflow by ID', 308 | inputSchema: { 309 | type: 'object', 310 | properties: { id: { type: 'string' } }, 311 | required: ['id'] 312 | } 313 | }, 314 | { 315 | name: 'deactivate_workflow', 316 | enabled: true, 317 | description: 'Deactivate a workflow by ID', 318 | inputSchema: { 319 | type: 'object', 320 | properties: { id: { type: 'string' } }, 321 | required: ['id'] 322 | } 323 | }, 324 | 325 | // Execution Tools 326 | { 327 | name: 'list_executions', 328 | enabled: true, 329 | description: 'List all executions from n8n with optional filters', 330 | inputSchema: { 331 | type: 'object', 332 | properties: { 333 | includeData: { type: 'boolean' }, 334 | status: { 335 | type: 'string', 336 | enum: ['error', 'success', 'waiting'] 337 | }, 338 | workflowId: { type: 'string' }, 339 | projectId: { type: 'string' }, 340 | limit: { type: 'number' }, 341 | cursor: { type: 'string' } 342 | } 343 | } 344 | }, 345 | { 346 | name: 'get_execution', 347 | enabled: true, 348 | description: 'Get details of a specific execution by ID', 349 | inputSchema: { 350 | type: 'object', 351 | properties: { 352 | id: { type: 'number' }, 353 | includeData: { type: 'boolean' } 354 | }, 355 | required: ['id'] 356 | } 357 | }, 358 | { 359 | name: 'delete_execution', 360 | enabled: true, 361 | description: 'Delete an execution by ID', 362 | inputSchema: { 363 | type: 'object', 364 | properties: { 365 | id: { type: 'number' } 366 | }, 367 | required: ['id'] 368 | } 369 | } 370 | ] 371 | }; 372 | }); 373 | 374 | this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => { 375 | console.log("callTool handler invoked with request:", request); 376 | 377 | try { 378 | const { name, arguments: args } = request.params; 379 | 380 | switch (name) { 381 | // Workflow Tools 382 | case 'list_workflows': 383 | const workflows = await n8nApi.listWorkflows(); 384 | return { 385 | content: [{ 386 | type: 'text', 387 | text: JSON.stringify(workflows, null, 2) 388 | }] 389 | }; 390 | 391 | case 'create_workflow': 392 | if (!args.name) { 393 | args.name = 'New Workflow'; 394 | } 395 | 396 | const workflowSpec = validateWorkflowSpec({ 397 | name: args.name, 398 | nodes: args.nodes as any[], 399 | connections: args.connections || [] 400 | }); 401 | 402 | const createdWorkflow = await n8nApi.createWorkflow(workflowSpec); 403 | return { 404 | content: [{ 405 | type: 'text', 406 | text: JSON.stringify(createdWorkflow, null, 2) 407 | }] 408 | }; 409 | 410 | case 'get_workflow': 411 | if (!args.id) { 412 | throw new McpError(ErrorCode.InvalidParams, 'Workflow ID is required'); 413 | } 414 | 415 | const workflow = await n8nApi.getWorkflow(args.id); 416 | return { 417 | content: [{ 418 | type: 'text', 419 | text: JSON.stringify(workflow, null, 2) 420 | }] 421 | }; 422 | 423 | case 'update_workflow': 424 | if (!args.id) { 425 | throw new McpError(ErrorCode.InvalidParams, 'Workflow ID is required'); 426 | } 427 | 428 | const updatedWorkflowSpec = validateWorkflowSpec({ 429 | nodes: args.nodes as any[], 430 | connections: args.connections || [] 431 | }); 432 | 433 | const updatedWorkflow = await n8nApi.updateWorkflow(args.id, updatedWorkflowSpec); 434 | return { 435 | content: [{ 436 | type: 'text', 437 | text: JSON.stringify(updatedWorkflow, null, 2) 438 | }] 439 | }; 440 | 441 | case 'delete_workflow': 442 | if (!args.id) { 443 | throw new McpError(ErrorCode.InvalidParams, 'Workflow ID is required'); 444 | } 445 | 446 | const deleteResult = await n8nApi.deleteWorkflow(args.id); 447 | return { 448 | content: [{ 449 | type: 'text', 450 | text: JSON.stringify(deleteResult, null, 2) 451 | }] 452 | }; 453 | 454 | case 'activate_workflow': 455 | if (!args.id) { 456 | throw new McpError(ErrorCode.InvalidParams, 'Workflow ID is required'); 457 | } 458 | 459 | const activatedWorkflow = await n8nApi.activateWorkflow(args.id); 460 | return { 461 | content: [{ 462 | type: 'text', 463 | text: JSON.stringify(activatedWorkflow, null, 2) 464 | }] 465 | }; 466 | 467 | case 'deactivate_workflow': 468 | if (!args.id) { 469 | throw new McpError(ErrorCode.InvalidParams, 'Workflow ID is required'); 470 | } 471 | 472 | const deactivatedWorkflow = await n8nApi.deactivateWorkflow(args.id); 473 | return { 474 | content: [{ 475 | type: 'text', 476 | text: JSON.stringify(deactivatedWorkflow, null, 2) 477 | }] 478 | }; 479 | 480 | // Execution Tools 481 | case 'list_executions': 482 | const executions = await n8nApi.listExecutions({ 483 | includeData: args.includeData, 484 | status: args.status, 485 | workflowId: args.workflowId, 486 | projectId: args.projectId, 487 | limit: args.limit, 488 | cursor: args.cursor 489 | }); 490 | return { 491 | content: [{ 492 | type: 'text', 493 | text: JSON.stringify(executions, null, 2) 494 | }] 495 | }; 496 | 497 | case 'get_execution': 498 | if (!args.id) { 499 | throw new McpError(ErrorCode.InvalidParams, 'Execution ID is required'); 500 | } 501 | 502 | const execution = await n8nApi.getExecution(args.id, args.includeData); 503 | return { 504 | content: [{ 505 | type: 'text', 506 | text: JSON.stringify(execution, null, 2) 507 | }] 508 | }; 509 | 510 | case 'delete_execution': 511 | if (!args.id) { 512 | throw new McpError(ErrorCode.InvalidParams, 'Execution ID is required'); 513 | } 514 | 515 | const deletedExecution = await n8nApi.deleteExecution(args.id); 516 | return { 517 | content: [{ 518 | type: 'text', 519 | text: JSON.stringify(deletedExecution, null, 2) 520 | }] 521 | }; 522 | 523 | default: 524 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); 525 | } 526 | } catch (error) { 527 | console.error('Error handling tool call:', error); 528 | 529 | if (error instanceof McpError) { 530 | throw error; 531 | } 532 | 533 | return { 534 | content: [{ 535 | type: 'text', 536 | text: `Error: ${error instanceof Error ? error.message : String(error)}` 537 | }], 538 | isError: true 539 | }; 540 | } 541 | }); 542 | } 543 | 544 | async run() { 545 | const transport = new StdioServerTransport(); 546 | await this.server.connect(transport); 547 | console.error('N8N Workflow Builder MCP server running on stdio'); 548 | } 549 | } 550 | 551 | const server = new N8NWorkflowServer(); 552 | server.run().catch(console.error); 553 | -------------------------------------------------------------------------------- /src/sdk-schemas.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ListToolsRequestSchema as MCPListToolsRequestSchema, 3 | CallToolRequestSchema as MCPCallToolRequestSchema, 4 | ListResourcesRequestSchema as MCPListResourcesRequestSchema, 5 | ReadResourceRequestSchema as MCPReadResourceRequestSchema, 6 | ListResourceTemplatesRequestSchema as MCPListResourceTemplatesRequestSchema 7 | } from '@modelcontextprotocol/sdk/types.js'; 8 | 9 | export const ListToolsRequestSchema = MCPListToolsRequestSchema; 10 | export const CallToolRequestSchema = MCPCallToolRequestSchema; 11 | export const ListResourcesRequestSchema = MCPListResourcesRequestSchema; 12 | export const ReadResourceRequestSchema = MCPReadResourceRequestSchema; 13 | export const ListResourceTemplatesRequestSchema = MCPListResourceTemplatesRequestSchema; 14 | -------------------------------------------------------------------------------- /src/services/n8nApi.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { N8N_HOST, N8N_API_KEY } from '../config/constants'; 3 | import { WorkflowSpec } from '../types/workflow'; 4 | import { ExecutionListOptions } from '../types/execution'; 5 | import { N8NWorkflowResponse, N8NExecutionResponse, N8NExecutionListResponse } from '../types/api'; 6 | 7 | const api = axios.create({ 8 | baseURL: N8N_HOST, 9 | headers: { 10 | 'Content-Type': 'application/json', 11 | 'X-N8N-API-KEY': N8N_API_KEY 12 | } 13 | }); 14 | 15 | // Log the API configuration for debugging 16 | console.log('N8N API Configuration:'); 17 | console.log('Host:', N8N_HOST); 18 | console.log('API Key:', N8N_API_KEY ? '****' + N8N_API_KEY.slice(-4) : 'Not set'); 19 | 20 | /** 21 | * Helper function to handle API errors consistently 22 | * @param context Description of the operation that failed 23 | * @param error The error that was thrown 24 | */ 25 | function handleApiError(context: string, error: unknown): never { 26 | console.error(`Error ${context}:`, error); 27 | if (axios.isAxiosError(error)) { 28 | console.error('Request URL:', error.config?.url); 29 | console.error('Response status:', error.response?.status); 30 | console.error('Response data:', error.response?.data); 31 | } 32 | throw error; 33 | } 34 | 35 | /** 36 | * Helper function to build a URL with query parameters 37 | * @param basePath The base API path 38 | * @param params An object containing the query parameters 39 | * @returns The complete URL with query parameters 40 | */ 41 | function buildUrl(basePath: string, params: Record = {}): string { 42 | const urlParams = new URLSearchParams(); 43 | 44 | for (const [key, value] of Object.entries(params)) { 45 | if (value !== undefined) { 46 | urlParams.append(key, value.toString()); 47 | } 48 | } 49 | 50 | const queryString = urlParams.toString(); 51 | return `${basePath}${queryString ? '?' + queryString : ''}`; 52 | } 53 | 54 | export async function createWorkflow(workflow: WorkflowSpec): Promise { 55 | try { 56 | console.log('Creating workflow'); 57 | const response = await api.post('/workflows', workflow); 58 | console.log('Response:', response.status, response.statusText); 59 | return response.data; 60 | } catch (error) { 61 | return handleApiError('creating workflow', error); 62 | } 63 | } 64 | 65 | export async function getWorkflow(id: string): Promise { 66 | try { 67 | console.log(`Getting workflow with ID: ${id}`); 68 | const response = await api.get(`/workflows/${id}`); 69 | console.log('Response:', response.status, response.statusText); 70 | return response.data; 71 | } catch (error) { 72 | return handleApiError(`getting workflow with ID ${id}`, error); 73 | } 74 | } 75 | 76 | export async function updateWorkflow(id: string, workflow: WorkflowSpec): Promise { 77 | try { 78 | console.log(`Updating workflow with ID: ${id}`); 79 | const response = await api.put(`/workflows/${id}`, workflow); 80 | console.log('Response:', response.status, response.statusText); 81 | return response.data; 82 | } catch (error) { 83 | return handleApiError(`updating workflow with ID ${id}`, error); 84 | } 85 | } 86 | 87 | export async function deleteWorkflow(id: string): Promise { 88 | try { 89 | console.log(`Deleting workflow with ID: ${id}`); 90 | const response = await api.delete(`/workflows/${id}`); 91 | console.log('Response:', response.status, response.statusText); 92 | return response.data; 93 | } catch (error) { 94 | return handleApiError(`deleting workflow with ID ${id}`, error); 95 | } 96 | } 97 | 98 | export async function activateWorkflow(id: string): Promise { 99 | try { 100 | console.log(`Activating workflow with ID: ${id}`); 101 | const response = await api.patch(`/workflows/${id}/activate`, {}); 102 | console.log('Response:', response.status, response.statusText); 103 | return response.data; 104 | } catch (error) { 105 | return handleApiError(`activating workflow with ID ${id}`, error); 106 | } 107 | } 108 | 109 | export async function deactivateWorkflow(id: string): Promise { 110 | try { 111 | console.log(`Deactivating workflow with ID: ${id}`); 112 | const response = await api.patch(`/workflows/${id}/deactivate`, {}); 113 | console.log('Response:', response.status, response.statusText); 114 | return response.data; 115 | } catch (error) { 116 | return handleApiError(`deactivating workflow with ID ${id}`, error); 117 | } 118 | } 119 | 120 | export async function listWorkflows(): Promise { 121 | try { 122 | console.log('Listing workflows from:', `${N8N_HOST}`); 123 | const response = await api.get('/workflows'); 124 | console.log('Response:', response.status, response.statusText); 125 | return response.data; 126 | } catch (error) { 127 | return handleApiError('listing workflows', error); 128 | } 129 | } 130 | 131 | // Execution API Methods 132 | 133 | /** 134 | * List workflow executions with optional filtering 135 | * 136 | * @param options Filtering and pagination options 137 | * @returns A paginated list of executions 138 | * 139 | * Pagination: This endpoint uses cursor-based pagination. To retrieve the next page: 140 | * 1. Check if the response contains a nextCursor property 141 | * 2. If present, use it in the next request as the cursor parameter 142 | * 3. Continue until nextCursor is no longer returned 143 | */ 144 | export async function listExecutions(options: ExecutionListOptions = {}): Promise { 145 | try { 146 | console.log('Listing executions'); 147 | 148 | const url = buildUrl('/executions', options); 149 | 150 | console.log(`Fetching executions from: ${url}`); 151 | const response = await api.get(url); 152 | console.log('Response:', response.status, response.statusText); 153 | return response.data; 154 | } catch (error) { 155 | return handleApiError('listing executions', error); 156 | } 157 | } 158 | 159 | /** 160 | * Get details of a specific execution 161 | * 162 | * @param id The execution ID to retrieve 163 | * @param includeData Whether to include the full execution data (may be large) 164 | * @returns The execution details 165 | */ 166 | export async function getExecution(id: number, includeData?: boolean): Promise { 167 | try { 168 | console.log(`Getting execution with ID: ${id}`); 169 | const url = buildUrl(`/executions/${id}`, includeData ? { includeData: true } : {}); 170 | const response = await api.get(url); 171 | console.log('Response:', response.status, response.statusText); 172 | return response.data; 173 | } catch (error) { 174 | return handleApiError(`getting execution with ID ${id}`, error); 175 | } 176 | } 177 | 178 | /** 179 | * Delete an execution by ID 180 | * 181 | * @param id The execution ID to delete 182 | * @returns The response from the deletion operation 183 | */ 184 | export async function deleteExecution(id: number): Promise { 185 | try { 186 | console.log(`Deleting execution with ID: ${id}`); 187 | const response = await api.delete(`/executions/${id}`); 188 | console.log('Response:', response.status, response.statusText); 189 | return response.data; 190 | } catch (error) { 191 | return handleApiError(`deleting execution with ID ${id}`, error); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/services/workflowBuilder.ts: -------------------------------------------------------------------------------- 1 | import { WorkflowSpec, WorkflowNode, WorkflowConnection } from '../types/workflow'; 2 | import { calculateNextPosition } from '../utils/positioning'; 3 | 4 | export class WorkflowBuilder { 5 | private nodes: WorkflowNode[] = []; 6 | private connections: WorkflowConnection[] = []; 7 | private nextPosition = { x: 100, y: 100 }; 8 | 9 | addNode(node: WorkflowNode): WorkflowNode { 10 | if (!node.position) { 11 | node.position = { ...this.nextPosition }; 12 | this.nextPosition = calculateNextPosition(this.nextPosition); 13 | } 14 | this.nodes.push(node); 15 | return node; 16 | } 17 | 18 | connectNodes(connection: WorkflowConnection) { 19 | this.connections.push(connection); 20 | } 21 | 22 | exportWorkflow(): WorkflowSpec { 23 | return { 24 | nodes: this.nodes, 25 | connections: this.connections 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/types/api.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionData, ExecutionMode } from './execution'; 2 | 3 | export interface N8NWorkflowResponse { 4 | id: string; 5 | name: string; 6 | active: boolean; 7 | nodes: any[]; 8 | connections: any; 9 | createdAt: string; 10 | updatedAt: string; 11 | } 12 | 13 | /** 14 | * Represents a full workflow execution response from n8n API 15 | */ 16 | export interface N8NExecutionResponse { 17 | id: number; 18 | data?: ExecutionData; 19 | finished: boolean; 20 | mode: ExecutionMode; 21 | retryOf?: number | null; 22 | retrySuccessId?: number | null; 23 | startedAt: string; 24 | stoppedAt: string; 25 | workflowId: number; 26 | waitTill?: string | null; 27 | customData?: { 28 | [key: string]: any; 29 | }; 30 | } 31 | 32 | /** 33 | * Response structure when listing executions 34 | */ 35 | export interface N8NExecutionListResponse { 36 | data: N8NExecutionResponse[]; 37 | nextCursor?: string; 38 | } 39 | 40 | /** 41 | * Standard error response structure 42 | */ 43 | export interface N8NErrorResponse { 44 | error: string; 45 | } 46 | -------------------------------------------------------------------------------- /src/types/execution.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the execution data structure for a single node in a workflow 3 | */ 4 | export interface NodeExecutionData { 5 | // Node execution metadata 6 | name: string; 7 | type: string; 8 | // Execution status and timing 9 | startTime: number; 10 | endTime?: number; 11 | // Input data received by the node 12 | inputData: { 13 | [key: string]: Array<{ 14 | [key: string]: any; 15 | }>; 16 | }; 17 | // Output data produced by the node 18 | outputData?: { 19 | [key: string]: Array<{ 20 | [key: string]: any; 21 | }>; 22 | }; 23 | // Execution error details if any 24 | error?: { 25 | message: string; 26 | stack?: string; 27 | name?: string; 28 | description?: string; 29 | }; 30 | } 31 | 32 | /** 33 | * Represents the data structure of a workflow execution 34 | */ 35 | export interface ExecutionData { 36 | // Result data 37 | resultData: { 38 | runData: { [nodeName: string]: NodeExecutionData[] }; 39 | lastNodeExecuted?: string; 40 | error?: { 41 | message: string; 42 | stack?: string; 43 | }; 44 | }; 45 | // Workflow data snapshot at execution time 46 | workflowData: { 47 | name: string; 48 | nodes: any[]; 49 | connections: any; 50 | active: boolean; 51 | settings?: object; 52 | }; 53 | // Additional execution metadata 54 | executionData?: { 55 | contextData?: { 56 | [key: string]: any; 57 | }; 58 | nodeExecutionOrder?: string[]; 59 | waitingExecution?: object; 60 | waitingExecutionSource?: object; 61 | }; 62 | } 63 | 64 | /** 65 | * Represents an execution's status 66 | */ 67 | export type ExecutionStatus = 'success' | 'error' | 'waiting'; 68 | 69 | /** 70 | * Represents the execution mode (how it was triggered) 71 | */ 72 | export type ExecutionMode = 'cli' | 'error' | 'integrated' | 'internal' | 'manual' | 'retry' | 'trigger' | 'webhook'; 73 | 74 | /** 75 | * Filtering options for listing executions 76 | */ 77 | export interface ExecutionListOptions { 78 | includeData?: boolean; 79 | status?: ExecutionStatus; 80 | workflowId?: string; 81 | projectId?: string; 82 | limit?: number; 83 | cursor?: string; 84 | } -------------------------------------------------------------------------------- /src/types/node.ts: -------------------------------------------------------------------------------- 1 | export { WorkflowNode } from './workflow'; 2 | -------------------------------------------------------------------------------- /src/types/sdk.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@modelcontextprotocol/sdk' { 2 | export class Server { 3 | constructor(info: { name: string; version: string }, config: { capabilities: { tools: any; resources: any } }); 4 | setRequestHandler(schema: any, handler: (request: any) => Promise): void; 5 | connect(transport: any): Promise; 6 | onerror: (error: any) => void; 7 | } 8 | } 9 | 10 | declare module '@modelcontextprotocol/sdk/stdio' { 11 | export class StdioServerTransport { 12 | constructor(); 13 | } 14 | } 15 | 16 | declare module '@modelcontextprotocol/sdk/types' { 17 | export const CallToolRequestSchema: any; 18 | export const ListToolsRequestSchema: any; 19 | export class McpError extends Error { 20 | constructor(code: string, message: string); 21 | } 22 | export const ErrorCode: { 23 | InvalidParams: string; 24 | MethodNotFound: string; 25 | InternalError: string; 26 | }; 27 | } 28 | 29 | export {}; 30 | -------------------------------------------------------------------------------- /src/types/workflow.ts: -------------------------------------------------------------------------------- 1 | export interface WorkflowNode { 2 | id?: string; 3 | type: string; 4 | name: string; 5 | parameters?: Record; 6 | position?: { x: number; y: number }; 7 | } 8 | 9 | export interface WorkflowConnection { 10 | source: string; 11 | target: string; 12 | sourceOutput?: number; 13 | targetInput?: number; 14 | } 15 | 16 | export interface WorkflowSpec { 17 | name?: string; 18 | nodes: WorkflowNode[]; 19 | connections?: WorkflowConnection[]; 20 | active?: boolean; 21 | settings?: Record; 22 | tags?: string[]; 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/positioning.ts: -------------------------------------------------------------------------------- 1 | export function calculateNextPosition(current: { x: number; y: number }): { x: number; y: number } { 2 | return { x: current.x + 200, y: current.y }; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/validation.ts: -------------------------------------------------------------------------------- 1 | import { WorkflowSpec } from '../types/workflow'; 2 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; 3 | 4 | export function validateWorkflowSpec(input: any): WorkflowSpec { 5 | if (!input || typeof input !== 'object') { 6 | throw new McpError(ErrorCode.InvalidParams, 'Workflow spec must be an object'); 7 | } 8 | 9 | if (!Array.isArray(input.nodes)) { 10 | throw new McpError(ErrorCode.InvalidParams, 'Workflow nodes must be an array'); 11 | } 12 | 13 | for (const node of input.nodes) { 14 | if (typeof node !== 'object' || typeof node.type !== 'string' || typeof node.name !== 'string') { 15 | throw new McpError(ErrorCode.InvalidParams, 'Each node must have a type and name'); 16 | } 17 | } 18 | 19 | // If connections are provided, they must be an array. 20 | if (input.connections && !Array.isArray(input.connections)) { 21 | throw new McpError(ErrorCode.InvalidParams, 'Workflow connections must be an array'); 22 | } 23 | 24 | // Return the validated workflow spec 25 | return { 26 | name: input.name, 27 | nodes: input.nodes, 28 | connections: input.connections || [], 29 | active: input.active, 30 | settings: input.settings, 31 | tags: input.tags 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "rootDir": "./src", 6 | "outDir": "./build", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "baseUrl": ".", 11 | "paths": { 12 | "@modelcontextprotocol/sdk": ["src/types/sdk.d.ts"], 13 | "@modelcontextprotocol/sdk/stdio": ["src/types/sdk.d.ts"], 14 | "@modelcontextprotocol/sdk/types": ["src/types/sdk.d.ts"] 15 | } 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------