@pagelines/n8n-mcp 0.1.0 → 0.2.1

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,63 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.2.1] - 2025-01-13
6
+
7
+ ### Added
8
+ - [Node Config Guide](docs/node-config.md) - Human-editable node settings (`__rl` resource locator, Set node JSON mode, AI structured output)
9
+
10
+ ### Changed
11
+ - Sharpened documentation (31% line reduction, higher data-ink ratio)
12
+ - README: Lead with differentiation, compact tool/validation tables
13
+ - Best Practices: Quick reference at top, focused on MCP-validated patterns
14
+ - Architecture: Technical reference, removed redundant philosophy sections
15
+
16
+ ## [0.2.0] - 2025-01-12
17
+
18
+ ### Added
19
+
20
+ #### Version Control System
21
+ - `version_list` - List saved versions of a workflow (stored locally in `~/.n8n-mcp/versions/`)
22
+ - `version_get` - Get a specific saved version
23
+ - `version_save` - Manually save a version snapshot
24
+ - `version_rollback` - Restore a workflow to a previous version
25
+ - `version_diff` - Compare two versions or current state vs a version
26
+ - `version_stats` - Get version control statistics
27
+ - Auto-save versions before workflow updates, rollbacks, and auto-fixes
28
+ - Configurable via `N8N_MCP_VERSIONS` and `N8N_MCP_MAX_VERSIONS` env vars
29
+
30
+ #### Auto-fix System
31
+ - `workflow_autofix` - Auto-fix common validation issues:
32
+ - Convert names to snake_case
33
+ - Replace `$json` with explicit `$('node_name')` references
34
+ - Add AI structured output settings
35
+ - `workflow_format` - Format workflows: sort nodes by position, clean up null values
36
+
37
+ #### Expression Validation
38
+ - Parse and validate n8n expressions in workflow parameters
39
+ - Detect references to non-existent nodes
40
+ - Check for syntax errors (unmatched parentheses, brackets)
41
+ - Warn about deprecated patterns (`$node.` usage)
42
+ - Detect circular references in expressions
43
+ - Suggest optional chaining for deep property access
44
+
45
+ ### Changed
46
+ - `workflow_validate` now includes expression validation and circular reference detection
47
+ - `workflow_update` now auto-saves a version before applying changes
48
+
49
+ ## [0.1.0] - 2025-01-11
50
+
51
+ ### Added
52
+ - Initial release
53
+ - Workflow operations: list, get, create, update, delete, activate, deactivate, execute
54
+ - Execution operations: list, get
55
+ - Workflow validation with best practices enforcement:
56
+ - snake_case naming
57
+ - Explicit node references
58
+ - No hardcoded IDs or secrets
59
+ - Orphan node detection
60
+ - Code node usage detection
61
+ - AI node structured output settings
62
+ - In-memory storage detection
63
+ - Patch-based workflow updates with parameter preservation warnings
package/README.md CHANGED
@@ -1,47 +1,37 @@
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
+ Workflow validation, version control, and patch-based updates for n8n.
4
4
 
5
- ## Features
5
+ ## The Problem
6
6
 
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
7
+ Other n8n MCPs replace entire nodes on update—losing parameters you didn't touch. No rollback. 70MB of SQLite for node docs you can Google.
11
8
 
12
- ## Best Practices Enforced
9
+ ## This MCP
13
10
 
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
11
+ | Feature | This | Others |
12
+ |---------|------|--------|
13
+ | Update approach | Patch (preserves params) | Replace (loses params) |
14
+ | Version control | Auto-snapshot before mutations | Manual/none |
15
+ | Validation | Expression syntax, circular refs, secrets | Basic |
16
+ | Auto-fix | snake_case, $json→$('node'), AI settings | None |
17
+ | Size | ~1,200 LOC, zero deps | 10k+ LOC, 70MB SQLite |
18
18
 
19
- ## Installation
20
-
21
- ```bash
22
- npm install @pagelines/n8n-mcp
23
- ```
24
-
25
- Or run directly:
19
+ ## Install
26
20
 
27
21
  ```bash
28
22
  npx @pagelines/n8n-mcp
29
23
  ```
30
24
 
31
- ## Configuration
32
-
33
- ### Claude Code / Cursor
34
-
35
- Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
25
+ Add to `~/.claude/mcp.json`:
36
26
 
37
27
  ```json
38
28
  {
39
29
  "mcpServers": {
40
- "pl-n8n": {
30
+ "n8n": {
41
31
  "command": "npx",
42
32
  "args": ["-y", "@pagelines/n8n-mcp"],
43
33
  "env": {
44
- "N8N_API_URL": "https://your-n8n-instance.com",
34
+ "N8N_API_URL": "https://your-n8n.com",
45
35
  "N8N_API_KEY": "your-api-key"
46
36
  }
47
37
  }
@@ -49,107 +39,42 @@ Add to your MCP settings (`~/.claude/mcp.json` or IDE config):
49
39
  }
50
40
  ```
51
41
 
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
42
  ## Tools
60
43
 
61
- ### Workflow Operations
62
-
63
- | Tool | Description |
64
- |------|-------------|
65
- | `workflow_list` | List all workflows |
66
- | `workflow_get` | Get workflow by ID |
67
- | `workflow_create` | Create new workflow |
68
- | `workflow_update` | Update workflow with patch operations |
69
- | `workflow_delete` | Delete workflow |
70
- | `workflow_activate` | Enable triggers |
71
- | `workflow_deactivate` | Disable triggers |
72
- | `workflow_execute` | Execute via webhook |
73
- | `workflow_validate` | Validate against best practices |
74
-
75
- ### Execution Operations
76
-
77
- | Tool | Description |
78
- |------|-------------|
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" }
44
+ | Category | Tools |
45
+ |----------|-------|
46
+ | Workflow | `list` `get` `create` `update` `delete` `activate` `deactivate` `execute` |
47
+ | Execution | `list` `get` |
48
+ | Validation | `validate` `autofix` `format` |
49
+ | Versions | `list` `get` `save` `rollback` `diff` `stats` |
98
50
 
99
- // Remove connection
100
- { "type": "removeConnection", "from": "node_a", "to": "node_b" }
51
+ ## Validation
101
52
 
102
- // Update settings
103
- { "type": "updateSettings", "settings": { "executionOrder": "v1" } }
53
+ | Rule | Severity | Auto-fix |
54
+ |------|----------|----------|
55
+ | snake_case naming | warning | Yes |
56
+ | Explicit refs (`$('node')` not `$json`) | warning | Yes |
57
+ | AI structured output | warning | Yes |
58
+ | Hardcoded secrets | error | No |
59
+ | Orphan nodes | warning | No |
60
+ | Expression syntax | error | No |
61
+ | Circular references | error | No |
104
62
 
105
- // Rename workflow
106
- { "type": "updateName", "name": "new_name" }
107
- ```
108
-
109
- ## Validation Rules
110
-
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
137
-
138
- ### npm
139
-
140
- Published automatically on push to `main` via GitHub Actions.
141
-
142
- Manual publish:
143
- ```bash
144
- npm publish --access public
145
- ```
63
+ ## Config
146
64
 
147
- ### MCP Registry
65
+ | Variable | Default | Description |
66
+ |----------|---------|-------------|
67
+ | `N8N_API_URL` | required | n8n instance URL |
68
+ | `N8N_API_KEY` | required | API key |
69
+ | `N8N_MCP_VERSIONS` | `true` | Enable version control |
70
+ | `N8N_MCP_MAX_VERSIONS` | `20` | Max snapshots per workflow |
148
71
 
149
- This server is registered at [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io) as `io.github.pagelines/n8n-mcp`.
72
+ ## Docs
150
73
 
151
- The `server.json` file contains the registry metadata.
74
+ - [Best Practices](docs/best-practices.md) - Expression patterns, config nodes, AI settings
75
+ - [Node Config](docs/node-config.md) - Human-editable node settings (`__rl`, Set node, etc.)
76
+ - [Architecture](plans/architecture.md) - Technical reference
152
77
 
153
78
  ## License
154
79
 
155
- MIT
80
+ 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[][];