@pagelines/n8n-mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.2.0] - 2025-01-12
6
+
7
+ ### Added
8
+
9
+ #### Version Control System
10
+ - `version_list` - List saved versions of a workflow (stored locally in `~/.n8n-mcp/versions/`)
11
+ - `version_get` - Get a specific saved version
12
+ - `version_save` - Manually save a version snapshot
13
+ - `version_rollback` - Restore a workflow to a previous version
14
+ - `version_diff` - Compare two versions or current state vs a version
15
+ - `version_stats` - Get version control statistics
16
+ - Auto-save versions before workflow updates, rollbacks, and auto-fixes
17
+ - Configurable via `N8N_MCP_VERSIONS` and `N8N_MCP_MAX_VERSIONS` env vars
18
+
19
+ #### Auto-fix System
20
+ - `workflow_autofix` - Auto-fix common validation issues:
21
+ - Convert names to snake_case
22
+ - Replace `$json` with explicit `$('node_name')` references
23
+ - Add AI structured output settings
24
+ - `workflow_format` - Format workflows: sort nodes by position, clean up null values
25
+
26
+ #### Expression Validation
27
+ - Parse and validate n8n expressions in workflow parameters
28
+ - Detect references to non-existent nodes
29
+ - Check for syntax errors (unmatched parentheses, brackets)
30
+ - Warn about deprecated patterns (`$node.` usage)
31
+ - Detect circular references in expressions
32
+ - Suggest optional chaining for deep property access
33
+
34
+ ### Changed
35
+ - `workflow_validate` now includes expression validation and circular reference detection
36
+ - `workflow_update` now auto-saves a version before applying changes
37
+
38
+ ## [0.1.0] - 2025-01-11
39
+
40
+ ### Added
41
+ - Initial release
42
+ - Workflow operations: list, get, create, update, delete, activate, deactivate, execute
43
+ - Execution operations: list, get
44
+ - Workflow validation with best practices enforcement:
45
+ - snake_case naming
46
+ - Explicit node references
47
+ - No hardcoded IDs or secrets
48
+ - Orphan node detection
49
+ - Code node usage detection
50
+ - AI node structured output settings
51
+ - In-memory storage detection
52
+ - Patch-based workflow updates with parameter preservation warnings
package/README.md CHANGED
@@ -1,47 +1,26 @@
1
- # pl-n8n-mcp
1
+ # n8n MCP Server
2
2
 
3
- > **@pagelines/n8n-mcp** - Opinionated MCP server for n8n workflow automation by [PageLines](https://github.com/pagelines)
3
+ > Version control, validation, and patch-based updates for n8n workflows.
4
4
 
5
- ## Features
5
+ [![npm version](https://img.shields.io/npm/v/@pagelines/n8n-mcp.svg)](https://www.npmjs.com/package/@pagelines/n8n-mcp)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
7
 
7
- - **Minimal footprint** - ~1,200 lines total, no database, no bloat
8
- - **Patch-based updates** - Never lose parameters, always preserves existing data
9
- - **Built-in validation** - Enforces best practices automatically
10
- - **Safety warnings** - Alerts when updates might cause issues
11
-
12
- ## Best Practices Enforced
13
-
14
- - `snake_case` naming for workflows and nodes
15
- - Explicit node references (`$('node_name').item.json.field` not `$json`)
16
- - No hardcoded IDs or secrets
17
- - No orphan nodes
18
-
19
- ## Installation
20
-
21
- ```bash
22
- npm install @pagelines/n8n-mcp
23
- ```
24
-
25
- Or run directly:
8
+ ## Install
26
9
 
27
10
  ```bash
28
11
  npx @pagelines/n8n-mcp
29
12
  ```
30
13
 
31
- ## Configuration
32
-
33
- ### Claude Code / Cursor
34
-
35
- Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
14
+ Add to MCP config (`~/.claude/mcp.json`):
36
15
 
37
16
  ```json
38
17
  {
39
18
  "mcpServers": {
40
- "pl-n8n": {
19
+ "n8n": {
41
20
  "command": "npx",
42
21
  "args": ["-y", "@pagelines/n8n-mcp"],
43
22
  "env": {
44
- "N8N_API_URL": "https://your-n8n-instance.com",
23
+ "N8N_API_URL": "https://your-n8n.com",
45
24
  "N8N_API_KEY": "your-api-key"
46
25
  }
47
26
  }
@@ -49,13 +28,6 @@ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
49
28
  }
50
29
  ```
51
30
 
52
- ### Environment Variables
53
-
54
- | Variable | Description |
55
- |----------|-------------|
56
- | `N8N_API_URL` | Your n8n instance URL |
57
- | `N8N_API_KEY` | API key from n8n settings |
58
-
59
31
  ## Tools
60
32
 
61
33
  ### Workflow Operations
@@ -65,91 +37,50 @@ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
65
37
  | `workflow_list` | List all workflows |
66
38
  | `workflow_get` | Get workflow by ID |
67
39
  | `workflow_create` | Create new workflow |
68
- | `workflow_update` | Update workflow with patch operations |
40
+ | `workflow_update` | Patch-based updates (preserves parameters) |
69
41
  | `workflow_delete` | Delete workflow |
70
42
  | `workflow_activate` | Enable triggers |
71
43
  | `workflow_deactivate` | Disable triggers |
72
44
  | `workflow_execute` | Execute via webhook |
73
- | `workflow_validate` | Validate against best practices |
74
45
 
75
- ### Execution Operations
46
+ ### Quality & Validation
76
47
 
77
48
  | Tool | Description |
78
49
  |------|-------------|
79
- | `execution_list` | List executions |
80
- | `execution_get` | Get execution details |
81
-
82
- ## Patch Operations
83
-
84
- The `workflow_update` tool uses patch operations to safely modify workflows:
85
-
86
- ```javascript
87
- // Add a node
88
- { "type": "addNode", "node": { "name": "my_node", "type": "n8n-nodes-base.set", ... } }
89
-
90
- // Update a node (INCLUDE ALL existing parameters)
91
- { "type": "updateNode", "nodeName": "my_node", "properties": { "parameters": { ...existing, "newParam": "value" } } }
92
-
93
- // Remove a node
94
- { "type": "removeNode", "nodeName": "my_node" }
95
-
96
- // Add connection
97
- { "type": "addConnection", "from": "node_a", "to": "node_b" }
98
-
99
- // Remove connection
100
- { "type": "removeConnection", "from": "node_a", "to": "node_b" }
50
+ | `workflow_validate` | Check best practices, expressions, circular refs |
51
+ | `workflow_autofix` | Auto-fix snake_case, explicit refs, AI settings |
52
+ | `workflow_format` | Sort nodes, clean nulls |
101
53
 
102
- // Update settings
103
- { "type": "updateSettings", "settings": { "executionOrder": "v1" } }
54
+ ### Version Control
104
55
 
105
- // Rename workflow
106
- { "type": "updateName", "name": "new_name" }
107
- ```
56
+ | Tool | Description |
57
+ |------|-------------|
58
+ | `version_list` | List saved versions |
59
+ | `version_get` | Get specific version |
60
+ | `version_save` | Manual snapshot |
61
+ | `version_rollback` | Restore previous version |
62
+ | `version_diff` | Compare versions |
108
63
 
109
64
  ## Validation Rules
110
65
 
111
- | Rule | Severity | Description |
112
- |------|----------|-------------|
113
- | `snake_case` | warning | Names should be snake_case |
114
- | `explicit_reference` | warning | Use `$('node')` not `$json` |
115
- | `no_hardcoded_ids` | info | Avoid hardcoded IDs |
116
- | `no_hardcoded_secrets` | error | Never hardcode secrets |
117
- | `orphan_node` | warning | Node has no connections |
118
- | `parameter_preservation` | error | Update would remove parameters |
119
-
120
- ## Development
121
-
122
- ```bash
123
- # Install dependencies
124
- npm install
125
-
126
- # Build
127
- npm run build
128
-
129
- # Watch mode
130
- npm run dev
131
-
132
- # Run tests
133
- npm test
134
- ```
135
-
136
- ## Deployment
66
+ | Rule | Severity |
67
+ |------|----------|
68
+ | `snake_case` naming | warning |
69
+ | Explicit refs (`$('node')` not `$json`) | warning |
70
+ | No hardcoded secrets | error |
71
+ | No orphan nodes | warning |
72
+ | AI structured output | warning |
73
+ | Expression syntax | error |
137
74
 
138
- ### npm
75
+ ## Environment Variables
139
76
 
140
- Published automatically on push to `main` via GitHub Actions.
141
-
142
- Manual publish:
143
- ```bash
144
- npm publish --access public
145
- ```
146
-
147
- ### MCP Registry
148
-
149
- This server is registered at [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io) as `io.github.pagelines/n8n-mcp`.
150
-
151
- The `server.json` file contains the registry metadata.
77
+ | Variable | Description |
78
+ |----------|-------------|
79
+ | `N8N_API_URL` | Your n8n instance URL |
80
+ | `N8N_API_KEY` | API key from n8n settings |
81
+ | `N8N_MCP_VERSIONS` | Enable version control (default: true) |
82
+ | `N8N_MCP_MAX_VERSIONS` | Max versions per workflow (default: 20) |
152
83
 
153
84
  ## License
154
85
 
155
- MIT
86
+ MIT - [PageLines](https://github.com/pagelines)
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Auto-fix common n8n workflow issues
3
+ * Transforms workflows to follow best practices
4
+ */
5
+ import type { N8nWorkflow, ValidationWarning } from './types.js';
6
+ export interface AutofixResult {
7
+ workflow: N8nWorkflow;
8
+ fixes: AutofixAction[];
9
+ unfixable: ValidationWarning[];
10
+ }
11
+ export interface AutofixAction {
12
+ type: string;
13
+ target: string;
14
+ description: string;
15
+ before?: string;
16
+ after?: string;
17
+ }
18
+ /**
19
+ * Auto-fix a workflow based on validation warnings
20
+ */
21
+ export declare function autofixWorkflow(workflow: N8nWorkflow, warnings: ValidationWarning[]): AutofixResult;
22
+ /**
23
+ * Format a workflow for consistency
24
+ * - Sorts nodes by position
25
+ * - Normalizes connection format
26
+ * - Removes empty/null values
27
+ */
28
+ export declare function formatWorkflow(workflow: N8nWorkflow): N8nWorkflow;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Auto-fix common n8n workflow issues
3
+ * Transforms workflows to follow best practices
4
+ */
5
+ /**
6
+ * Auto-fix a workflow based on validation warnings
7
+ */
8
+ export function autofixWorkflow(workflow, warnings) {
9
+ // Deep clone to avoid mutation
10
+ const fixed = JSON.parse(JSON.stringify(workflow));
11
+ const fixes = [];
12
+ const unfixable = [];
13
+ for (const warning of warnings) {
14
+ const result = attemptFix(fixed, warning);
15
+ if (result) {
16
+ fixes.push(result);
17
+ }
18
+ else {
19
+ unfixable.push(warning);
20
+ }
21
+ }
22
+ return { workflow: fixed, fixes, unfixable };
23
+ }
24
+ function attemptFix(workflow, warning) {
25
+ switch (warning.rule) {
26
+ case 'snake_case':
27
+ return fixSnakeCase(workflow, warning);
28
+ case 'explicit_reference':
29
+ return fixExplicitReference(workflow, warning);
30
+ case 'ai_structured_output':
31
+ return fixAIStructuredOutput(workflow, warning);
32
+ default:
33
+ // Rules that can't be auto-fixed:
34
+ // - no_hardcoded_secrets (need manual review)
35
+ // - no_hardcoded_ids (need manual review)
36
+ // - orphan_node (need context to know what to connect)
37
+ // - code_node_usage (info only)
38
+ // - in_memory_storage (architectural decision)
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Fix snake_case naming
44
+ */
45
+ function fixSnakeCase(workflow, warning) {
46
+ const target = warning.node || 'workflow';
47
+ if (!warning.node) {
48
+ // Fix workflow name
49
+ const oldName = workflow.name;
50
+ const newName = toSnakeCase(oldName);
51
+ if (oldName === newName)
52
+ return null;
53
+ workflow.name = newName;
54
+ return {
55
+ type: 'rename',
56
+ target: 'workflow',
57
+ description: `Renamed workflow to snake_case`,
58
+ before: oldName,
59
+ after: newName,
60
+ };
61
+ }
62
+ // Fix node name
63
+ const node = workflow.nodes.find((n) => n.name === warning.node);
64
+ if (!node)
65
+ return null;
66
+ const oldName = node.name;
67
+ const newName = toSnakeCase(oldName);
68
+ if (oldName === newName)
69
+ return null;
70
+ // Update node name
71
+ node.name = newName;
72
+ // Update connections that reference this node
73
+ if (workflow.connections[oldName]) {
74
+ workflow.connections[newName] = workflow.connections[oldName];
75
+ delete workflow.connections[oldName];
76
+ }
77
+ // Update connections that target this node
78
+ for (const outputs of Object.values(workflow.connections)) {
79
+ for (const outputType of Object.values(outputs)) {
80
+ for (const connections of outputType) {
81
+ for (const conn of connections) {
82
+ if (conn.node === oldName) {
83
+ conn.node = newName;
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ return {
90
+ type: 'rename',
91
+ target: `node:${oldName}`,
92
+ description: `Renamed node to snake_case`,
93
+ before: oldName,
94
+ after: newName,
95
+ };
96
+ }
97
+ /**
98
+ * Convert to snake_case
99
+ */
100
+ function toSnakeCase(name) {
101
+ return name
102
+ .replace(/([A-Z])/g, '_$1')
103
+ .replace(/[-\s]+/g, '_')
104
+ .replace(/^_/, '')
105
+ .replace(/_+/g, '_')
106
+ .toLowerCase();
107
+ }
108
+ /**
109
+ * Fix $json to explicit references
110
+ * This is a best-effort fix - may need manual review
111
+ */
112
+ function fixExplicitReference(workflow, warning) {
113
+ if (!warning.node)
114
+ return null;
115
+ const node = workflow.nodes.find((n) => n.name === warning.node);
116
+ if (!node)
117
+ return null;
118
+ // Find the previous node in the connection chain
119
+ const previousNode = findPreviousNode(workflow, node.name);
120
+ if (!previousNode) {
121
+ // Can't auto-fix without knowing the source
122
+ return null;
123
+ }
124
+ // Replace $json with explicit reference
125
+ const params = JSON.stringify(node.parameters);
126
+ const fixedParams = params
127
+ .replace(/\$json\./g, `$('${previousNode}').item.json.`)
128
+ .replace(/\{\{\s*\$json\./g, `{{ $('${previousNode}').item.json.`);
129
+ if (params === fixedParams)
130
+ return null;
131
+ node.parameters = JSON.parse(fixedParams);
132
+ return {
133
+ type: 'expression_fix',
134
+ target: `node:${node.name}`,
135
+ description: `Changed $json to explicit $('${previousNode}') reference`,
136
+ before: '$json.',
137
+ after: `$('${previousNode}').item.json.`,
138
+ };
139
+ }
140
+ /**
141
+ * Find the previous node in the connection chain
142
+ */
143
+ function findPreviousNode(workflow, nodeName) {
144
+ for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
145
+ for (const outputType of Object.values(outputs)) {
146
+ for (const connections of outputType) {
147
+ for (const conn of connections) {
148
+ if (conn.node === nodeName) {
149
+ return sourceName;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+ /**
158
+ * Fix AI structured output settings
159
+ */
160
+ function fixAIStructuredOutput(workflow, warning) {
161
+ if (!warning.node)
162
+ return null;
163
+ const node = workflow.nodes.find((n) => n.name === warning.node);
164
+ if (!node)
165
+ return null;
166
+ const params = node.parameters;
167
+ const changes = [];
168
+ if (params.promptType !== 'define') {
169
+ params.promptType = 'define';
170
+ changes.push('promptType: "define"');
171
+ }
172
+ if (params.hasOutputParser !== true) {
173
+ params.hasOutputParser = true;
174
+ changes.push('hasOutputParser: true');
175
+ }
176
+ if (changes.length === 0)
177
+ return null;
178
+ return {
179
+ type: 'parameter_fix',
180
+ target: `node:${node.name}`,
181
+ description: `Added AI structured output settings: ${changes.join(', ')}`,
182
+ };
183
+ }
184
+ /**
185
+ * Format a workflow for consistency
186
+ * - Sorts nodes by position
187
+ * - Normalizes connection format
188
+ * - Removes empty/null values
189
+ */
190
+ export function formatWorkflow(workflow) {
191
+ const formatted = JSON.parse(JSON.stringify(workflow));
192
+ // Sort nodes by Y position, then X
193
+ formatted.nodes.sort((a, b) => {
194
+ const [ax, ay] = a.position;
195
+ const [bx, by] = b.position;
196
+ if (ay !== by)
197
+ return ay - by;
198
+ return ax - bx;
199
+ });
200
+ // Clean up parameters - remove undefined/null
201
+ for (const node of formatted.nodes) {
202
+ node.parameters = cleanObject(node.parameters);
203
+ }
204
+ return formatted;
205
+ }
206
+ /**
207
+ * Remove null/undefined values from object
208
+ */
209
+ function cleanObject(obj) {
210
+ const cleaned = {};
211
+ for (const [key, value] of Object.entries(obj)) {
212
+ if (value === null || value === undefined)
213
+ continue;
214
+ if (typeof value === 'object' && !Array.isArray(value)) {
215
+ cleaned[key] = cleanObject(value);
216
+ }
217
+ else {
218
+ cleaned[key] = value;
219
+ }
220
+ }
221
+ return cleaned;
222
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * n8n expression validation
3
+ * Parses and validates expressions in workflow parameters
4
+ */
5
+ import type { N8nWorkflow } from './types.js';
6
+ export interface ExpressionIssue {
7
+ node: string;
8
+ parameter: string;
9
+ expression: string;
10
+ issue: string;
11
+ severity: 'error' | 'warning' | 'info';
12
+ suggestion?: string;
13
+ }
14
+ /**
15
+ * Validate all expressions in a workflow
16
+ */
17
+ export declare function validateExpressions(workflow: N8nWorkflow): ExpressionIssue[];
18
+ /**
19
+ * Get all referenced nodes from expressions
20
+ */
21
+ export declare function getReferencedNodes(workflow: N8nWorkflow): Map<string, string[]>;
22
+ /**
23
+ * Check for circular references in expressions
24
+ */
25
+ export declare function checkCircularReferences(workflow: N8nWorkflow): string[][];