@pagelines/n8n-mcp 0.2.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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
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
+
5
16
  ## [0.2.0] - 2025-01-12
6
17
 
7
18
  ### Added
package/README.md CHANGED
@@ -1,9 +1,20 @@
1
1
  # n8n MCP Server
2
2
 
3
- > Version control, validation, and patch-based updates for n8n workflows.
3
+ Workflow validation, version control, and patch-based updates for n8n.
4
4
 
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)
5
+ ## The Problem
6
+
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.
8
+
9
+ ## This MCP
10
+
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 |
7
18
 
8
19
  ## Install
9
20
 
@@ -11,7 +22,7 @@
11
22
  npx @pagelines/n8n-mcp
12
23
  ```
13
24
 
14
- Add to MCP config (`~/.claude/mcp.json`):
25
+ Add to `~/.claude/mcp.json`:
15
26
 
16
27
  ```json
17
28
  {
@@ -30,56 +41,39 @@ Add to MCP config (`~/.claude/mcp.json`):
30
41
 
31
42
  ## Tools
32
43
 
33
- ### Workflow Operations
34
-
35
- | Tool | Description |
36
- |------|-------------|
37
- | `workflow_list` | List all workflows |
38
- | `workflow_get` | Get workflow by ID |
39
- | `workflow_create` | Create new workflow |
40
- | `workflow_update` | Patch-based updates (preserves parameters) |
41
- | `workflow_delete` | Delete workflow |
42
- | `workflow_activate` | Enable triggers |
43
- | `workflow_deactivate` | Disable triggers |
44
- | `workflow_execute` | Execute via webhook |
45
-
46
- ### Quality & Validation
47
-
48
- | Tool | Description |
49
- |------|-------------|
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 |
53
-
54
- ### Version Control
55
-
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 |
63
-
64
- ## Validation Rules
65
-
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 |
74
-
75
- ## Environment Variables
76
-
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) |
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` |
50
+
51
+ ## Validation
52
+
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 |
62
+
63
+ ## Config
64
+
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 |
71
+
72
+ ## Docs
73
+
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
83
77
 
84
78
  ## License
85
79
 
@@ -1,107 +1,84 @@
1
1
  # n8n Best Practices
2
2
 
3
- > Enforced by `@pagelines/n8n-mcp`
3
+ ## Quick Reference
4
4
 
5
- ## Guiding Principle
5
+ ```javascript
6
+ // Explicit reference (always use this)
7
+ {{ $('node_name').item.json.field }}
6
8
 
7
- **What is most stable and easiest to maintain?**
9
+ // Environment variable
10
+ {{ $env.API_KEY }}
8
11
 
9
- | Rule | Why |
10
- |------|-----|
11
- | Minimize nodes | Fewer failure points, easier debugging |
12
- | YAGNI | Build only what's needed now |
13
- | Explicit references | `$('node_name')` not `$json` - traceable, stable |
14
- | snake_case | `node_name` not `NodeName` - consistent, readable |
12
+ // Config node reference
13
+ {{ $('config').item.json.setting }}
15
14
 
16
- ---
15
+ // Fallback
16
+ {{ $('source').item.json.text || 'default' }}
17
+
18
+ // Date
19
+ {{ $now.format('yyyy-MM-dd') }}
20
+ ```
17
21
 
18
- ## Naming Convention
22
+ ## The Rules
19
23
 
20
- **snake_case everywhere**
24
+ ### 1. snake_case
21
25
 
22
26
  ```
23
- Workflows: content_factory, publish_linkedin, upload_image
24
- Nodes: trigger_webhook, fetch_articles, check_approved
27
+ Good: fetch_articles, check_approved, generate_content
28
+ Bad: FetchArticles, Check Approved, generate-content
25
29
  ```
26
30
 
27
- Never `NodeName`. Always `node_name`.
28
-
29
- ---
30
-
31
- ## Expression References
31
+ Why: Consistency, readability, auto-fixable.
32
32
 
33
- **NEVER use `$json`. Always explicit node references.**
33
+ ### 2. Explicit References
34
34
 
35
35
  ```javascript
36
36
  // Bad - breaks when flow changes
37
37
  {{ $json.field }}
38
38
 
39
- // Good - traceable and debuggable
39
+ // Good - traceable, stable
40
40
  {{ $('node_name').item.json.field }}
41
-
42
- // Parallel branch (lookup nodes)
43
- {{ $('lookup_node').all().length > 0 }}
44
-
45
- // Environment variable
46
- {{ $env.API_KEY }}
47
-
48
- // Fallback pattern
49
- {{ $('source').item.json.text || $('source').item.json.media_url }}
50
41
  ```
51
42
 
52
- ---
53
-
54
- ## Secrets and Configuration
55
-
56
- **Secrets in environment variables. Always.**
57
-
58
- ```javascript
59
- // Bad
60
- { "apiKey": "sk_live_abc123" }
43
+ Why: `$json` references "previous node" implicitly. Reorder nodes, it breaks.
61
44
 
62
- // Good
63
- {{ $env.API_KEY }}
64
- ```
45
+ ### 3. Config Node
65
46
 
66
- **For workflow-specific settings, use a config node:**
47
+ Single source for workflow settings:
67
48
 
68
49
  ```
69
50
  [trigger] → [config] → [rest of workflow]
70
51
  ```
71
52
 
72
- Config node uses JSON output mode:
53
+ Config node (JSON mode):
73
54
  ```javascript
74
55
  ={
75
- "channel_id": "{{ $json.body.channelId || '1234567890' }}",
56
+ "channel_id": "{{ $json.body.channelId || '123456' }}",
76
57
  "max_items": 10,
77
58
  "ai_model": "gpt-4.1-mini"
78
59
  }
79
60
  ```
80
61
 
81
- Then reference: `{{ $('config').item.json.channel_id }}`
62
+ Reference everywhere: `{{ $('config').item.json.channel_id }}`
82
63
 
83
- ---
64
+ Why: Change once, not in 5 nodes.
84
65
 
85
- ## Workflow Editing Safety
66
+ ### 4. Secrets in Environment
86
67
 
87
- ### The Golden Rule
88
-
89
- **Never edit a workflow without explicit confirmation and backup.**
90
-
91
- ### Pre-Edit Checklist
68
+ ```javascript
69
+ // Bad
70
+ { "apiKey": "sk_live_abc123" }
92
71
 
93
- 1. **Confirm** - Get explicit user approval
94
- 2. **List versions** - Know your rollback point
95
- 3. **Read full state** - Understand current config
96
- 4. **Make targeted change** - Use patch operations only
97
- 5. **Verify** - Confirm expected state
72
+ // Good
73
+ {{ $env.API_KEY }}
74
+ ```
98
75
 
99
- ### Parameter Preservation
76
+ ## Parameter Preservation
100
77
 
101
- **CRITICAL:** Partial updates REPLACE the entire `parameters` object.
78
+ **Critical:** Partial updates REPLACE the entire `parameters` object.
102
79
 
103
80
  ```javascript
104
- // Bad: Only updates messageId, loses operation and labelIds
81
+ // Bad - loses operation and labelIds
105
82
  {
106
83
  "type": "updateNode",
107
84
  "nodeName": "archive_email",
@@ -112,7 +89,7 @@ Then reference: `{{ $('config').item.json.channel_id }}`
112
89
  }
113
90
  }
114
91
 
115
- // Good: Include ALL required parameters
92
+ // Good - include ALL parameters
116
93
  {
117
94
  "type": "updateNode",
118
95
  "nodeName": "archive_email",
@@ -120,122 +97,64 @@ Then reference: `{{ $('config').item.json.channel_id }}`
120
97
  "parameters": {
121
98
  "operation": "addLabels",
122
99
  "messageId": "={{ $json.message_id }}",
123
- "labelIds": ["Label_123", "Label_456"]
100
+ "labelIds": ["Label_123"]
124
101
  }
125
102
  }
126
103
  }
127
104
  ```
128
105
 
129
- ---
130
-
131
- ## Code Nodes
132
-
133
- **Code nodes are a last resort.** Exhaust built-in options first:
134
-
135
- | Need | Use Instead |
136
- |------|-------------|
137
- | Transform fields | Set node with expressions |
138
- | Filter items | Filter node or If/Switch |
139
- | Merge data | Merge node |
140
- | Loop processing | n8n processes arrays natively |
141
- | Date formatting | `{{ $now.format('yyyy-MM-dd') }}` |
142
-
143
- **When code IS necessary:**
144
- - Re-establishing `pairedItem` after chain breaks
145
- - Complex conditional logic
146
- - API response parsing expressions can't handle
147
-
148
- **Code node rules:**
149
- - Single responsibility (one clear purpose)
150
- - Name it for what it does: `merge_context`, `parse_response`
151
- - No side effects - pure data transformation
152
-
153
- ---
106
+ Before updating: read current state with `workflow_get`.
154
107
 
155
- ## AI Agent Best Practices
108
+ ## AI Nodes
156
109
 
157
110
  ### Structured Output
158
111
 
159
- **Always enable "Require Specific Output Format"** for reliable JSON:
160
-
161
- ```javascript
162
- {
163
- "promptType": "define",
164
- "hasOutputParser": true,
165
- "schemaType": "manual" // Required for nullable fields
166
- }
167
- ```
168
-
169
- Without these, AI outputs are unpredictable.
112
+ Always set for predictable JSON:
170
113
 
171
- ### Memory Storage
114
+ | Setting | Value |
115
+ |---------|-------|
116
+ | `promptType` | `"define"` |
117
+ | `hasOutputParser` | `true` |
118
+ | `schemaType` | `"manual"` (for nullable fields) |
172
119
 
173
- **Never use in-memory storage in production:**
120
+ ### Memory
174
121
 
175
122
  | Don't Use | Use Instead |
176
123
  |-----------|-------------|
177
124
  | Windowed Buffer Memory | Postgres Chat Memory |
178
125
  | In-Memory Vector Store | Postgres pgvector |
179
126
 
180
- In-memory dies with restart and doesn't scale.
181
-
182
- ---
183
-
184
- ## Architecture Patterns
185
-
186
- ### Single Responsibility
187
-
188
- Don't build monolith workflows:
189
-
190
- ```
191
- Bad: One workflow doing signup → email → CRM → calendar → reports
192
-
193
- Good: Five focused workflows that communicate via webhooks
194
- ```
195
-
196
- ### Switch > If Node
197
-
198
- Always use Switch instead of If:
199
- - Named outputs (not just true/false)
200
- - Unlimited conditional branches
201
- - Send to all matching option
202
-
203
- ---
127
+ In-memory dies on restart, doesn't scale.
204
128
 
205
- ## Validation Rules Enforced
129
+ ## Code Nodes: Last Resort
206
130
 
207
- | Rule | Severity | Description |
208
- |------|----------|-------------|
209
- | `snake_case` | warning | Names should be snake_case |
210
- | `explicit_reference` | warning | Use `$('node')` not `$json` |
211
- | `no_hardcoded_ids` | info | Avoid hardcoded IDs |
212
- | `no_hardcoded_secrets` | error | Never hardcode secrets |
213
- | `orphan_node` | warning | Node has no connections |
214
- | `parameter_preservation` | error | Update would remove parameters |
215
- | `code_node_usage` | info | Code node detected |
216
- | `ai_structured_output` | warning | AI node missing structured output |
217
- | `in_memory_storage` | warning | Using non-persistent storage |
218
-
219
- ---
220
-
221
- ## Quick Reference
222
-
223
- ```javascript
224
- // Explicit reference
225
- {{ $('node_name').item.json.field }}
226
-
227
- // Environment variable
228
- {{ $env.VAR_NAME }}
229
-
230
- // Config node reference
231
- {{ $('config').item.json.setting }}
131
+ | Need | Use Instead |
132
+ |------|-------------|
133
+ | Transform fields | Set node with expressions |
134
+ | Filter items | Filter node or Switch |
135
+ | Merge data | Merge node |
136
+ | Loop | n8n processes arrays natively |
137
+ | Date formatting | `{{ $now.format('yyyy-MM-dd') }}` |
232
138
 
233
- // Parallel branch query
234
- {{ $('lookup').all().some(i => i.json.id === $json.id) }}
139
+ When code IS necessary:
140
+ - Re-establishing `pairedItem` after chain breaks
141
+ - Complex conditional logic
142
+ - API parsing expressions can't handle
235
143
 
236
- // Date formatting
237
- {{ $now.format('yyyy-MM-dd') }}
144
+ ## Pre-Edit Checklist
238
145
 
239
- // Fallback
240
- {{ $json.text || $json.description || 'default' }}
241
- ```
146
+ | Step | Why |
147
+ |------|-----|
148
+ | 1. Get explicit user approval | Don't surprise |
149
+ | 2. List versions | Know rollback point |
150
+ | 3. Read full workflow | Understand current state |
151
+ | 4. Make targeted change | Minimal surface area |
152
+ | 5. Validate after | Catch issues immediately |
153
+
154
+ ## Node-Specific Settings
155
+
156
+ See [Node Config](node-config.md) for:
157
+ - Resource locator (`__rl`) format with `cachedResultName`
158
+ - Google Sheets, Gmail, Discord required fields
159
+ - Set node JSON mode vs manual mapping
160
+ - Error handling options
@@ -0,0 +1,203 @@
1
+ # Node Configuration Guidelines
2
+
3
+ > Settings that make AI-created nodes human-editable
4
+
5
+ ## Resource Locator Pattern
6
+
7
+ n8n uses `__rl` (resource locator) format for dropdown fields. Always include `cachedResultName` so humans see friendly names.
8
+
9
+ ```javascript
10
+ // Human-editable: shows "My Spreadsheet" in UI
11
+ "documentId": {
12
+ "__rl": true,
13
+ "value": "1abc123...",
14
+ "mode": "list",
15
+ "cachedResultName": "My Spreadsheet",
16
+ "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1abc123"
17
+ }
18
+
19
+ // Hard to edit: shows raw ID only
20
+ "documentId": {
21
+ "__rl": true,
22
+ "value": "1abc123...",
23
+ "mode": "id"
24
+ }
25
+ ```
26
+
27
+ | Mode | When to Use |
28
+ |------|-------------|
29
+ | `list` | Default. Shows dropdown with cached name |
30
+ | `id` | Only when ID is dynamic (from expression) |
31
+
32
+ ## Set Node
33
+
34
+ **Use JSON mode** for MCP-created nodes. Manual mapping is error-prone via API.
35
+
36
+ ```javascript
37
+ // JSON mode (reliable)
38
+ {
39
+ "mode": "raw",
40
+ "jsonOutput": "={ \"channel_id\": \"{{ $json.channelId || '123' }}\" }"
41
+ }
42
+
43
+ // Manual mapping (fragile via API)
44
+ {
45
+ "mode": "manual",
46
+ "assignments": { ... } // Complex structure, easy to break
47
+ }
48
+ ```
49
+
50
+ ## Google Sheets
51
+
52
+ Required fields (partial updates lose these):
53
+
54
+ ```javascript
55
+ {
56
+ "operation": "append",
57
+ "documentId": {
58
+ "__rl": true,
59
+ "value": "1abc...",
60
+ "mode": "list",
61
+ "cachedResultName": "Sheet Name"
62
+ },
63
+ "sheetName": {
64
+ "__rl": true,
65
+ "value": "gid=0",
66
+ "mode": "list",
67
+ "cachedResultName": "Sheet1"
68
+ },
69
+ "columns": { /* mappings */ }
70
+ }
71
+ ```
72
+
73
+ ## Gmail
74
+
75
+ Required fields:
76
+
77
+ ```javascript
78
+ {
79
+ "operation": "addLabels", // or send, get, etc.
80
+ "messageId": "={{ $('source').item.json.id }}",
81
+ "labelIds": ["Label_123", "Label_456"]
82
+ }
83
+ ```
84
+
85
+ Labels need full IDs (`Label_xxx`), not display names.
86
+
87
+ ## Discord
88
+
89
+ ```javascript
90
+ {
91
+ "authentication": "webhook",
92
+ "webhookUri": {
93
+ "__rl": true,
94
+ "value": "={{ $env.DISCORD_WEBHOOK }}",
95
+ "mode": "id"
96
+ },
97
+ "content": "Message text"
98
+ }
99
+ ```
100
+
101
+ For bot mode with guild/channel selection:
102
+
103
+ ```javascript
104
+ {
105
+ "guildId": {
106
+ "__rl": true,
107
+ "value": "123456789",
108
+ "mode": "list",
109
+ "cachedResultName": "My Server"
110
+ },
111
+ "channelId": {
112
+ "__rl": true,
113
+ "value": "987654321",
114
+ "mode": "list",
115
+ "cachedResultName": "#general"
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## AI Nodes (Agent, Chain)
121
+
122
+ Always include structured output settings:
123
+
124
+ ```javascript
125
+ {
126
+ "promptType": "define",
127
+ "hasOutputParser": true,
128
+ "schemaType": "manual", // Required for nullable fields
129
+ "schema": { /* JSON Schema */ }
130
+ }
131
+ ```
132
+
133
+ Without these, AI outputs are unpredictable strings.
134
+
135
+ ## HTTP Request
136
+
137
+ Include authentication method explicitly:
138
+
139
+ ```javascript
140
+ {
141
+ "method": "POST",
142
+ "url": "https://api.example.com",
143
+ "authentication": "predefinedCredentialType",
144
+ "nodeCredentialType": "anthropicApi",
145
+ "sendBody": true,
146
+ "bodyParameters": { ... }
147
+ }
148
+ ```
149
+
150
+ ## Switch vs If
151
+
152
+ Always use Switch (not If):
153
+
154
+ ```javascript
155
+ // Switch: named outputs, extensible
156
+ {
157
+ "type": "n8n-nodes-base.switch",
158
+ "parameters": {
159
+ "rules": {
160
+ "rules": [
161
+ { "output": 0, "conditions": { ... } },
162
+ { "output": 1, "conditions": { ... } }
163
+ ]
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ If node only has true/false outputs - Switch scales better.
170
+
171
+ ## Expression Patterns
172
+
173
+ Always explicit references:
174
+
175
+ ```javascript
176
+ // Good
177
+ "={{ $('config').item.json.channel_id }}"
178
+
179
+ // Bad - breaks on node reorder
180
+ "={{ $json.channel_id }}"
181
+ ```
182
+
183
+ Environment variables for secrets:
184
+
185
+ ```javascript
186
+ "={{ $env.API_KEY }}"
187
+ ```
188
+
189
+ ## Error Handling
190
+
191
+ Set `onError` for API nodes:
192
+
193
+ ```javascript
194
+ {
195
+ "parameters": { ... },
196
+ "onError": "continueRegularOutput" // or "continueErrorOutput"
197
+ }
198
+ ```
199
+
200
+ Options:
201
+ - `stopWorkflow` - default, halts on error
202
+ - `continueRegularOutput` - ignore errors, continue
203
+ - `continueErrorOutput` - route errors to second output
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagelines/n8n-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Opinionated MCP server for n8n workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,233 @@
1
+ # AI Guidelines for @pagelines/n8n-mcp
2
+
3
+ Entry point for AI assistants working on this codebase.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npm run dev # Watch mode
9
+ npm run test # Run tests
10
+ npm run build # Compile TypeScript
11
+ ```
12
+
13
+ ## Codebase Map
14
+
15
+ | File | Purpose | When to modify |
16
+ |------|---------|----------------|
17
+ | `src/index.ts` | MCP server, tool dispatch | Adding new tools |
18
+ | `src/types.ts` | Type definitions | New data structures |
19
+ | `src/tools.ts` | Tool JSON schemas | New tool definitions |
20
+ | `src/n8n-client.ts` | n8n REST API calls | New API operations |
21
+ | `src/validators.ts` | Validation rules | New lint rules |
22
+ | `src/expressions.ts` | Expression parsing | Expression handling |
23
+ | `src/autofix.ts` | Auto-fix transforms | New auto-fixes |
24
+ | `src/versions.ts` | Version control | Version features |
25
+
26
+ ## Coding Standards
27
+
28
+ ### Do
29
+ - Write pure functions that return values
30
+ - Use async/await, never callbacks
31
+ - Add types for all parameters and returns
32
+ - Keep functions under 50 lines
33
+ - Name variables descriptively
34
+ - Write tests for new validators
35
+
36
+ ### Don't
37
+ - Use `any` type
38
+ - Mutate input parameters
39
+ - Add external dependencies without strong justification
40
+ - Create abstraction before duplication (rule of 3)
41
+ - Add features not explicitly requested
42
+
43
+ ## Adding a New Tool
44
+
45
+ 1. **Define type** in `types.ts` (if needed)
46
+ 2. **Add schema** in `tools.ts`:
47
+ ```typescript
48
+ {
49
+ name: 'my_tool',
50
+ description: 'What it does',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: { /* ... */ },
54
+ required: ['param1']
55
+ }
56
+ }
57
+ ```
58
+ 3. **Add handler** in `index.ts`:
59
+ ```typescript
60
+ case 'my_tool':
61
+ return await handleMyTool(args);
62
+ ```
63
+ 4. **Implement logic** in appropriate module
64
+ 5. **Add test** in `*.test.ts`
65
+
66
+ ## Adding a Validation Rule
67
+
68
+ 1. **Add rule function** in `validators.ts`:
69
+ ```typescript
70
+ function checkMyRule(workflow: N8nWorkflow): ValidationWarning[] {
71
+ const warnings: ValidationWarning[] = [];
72
+ // validation logic
73
+ if (problem) {
74
+ warnings.push({
75
+ rule: 'my_rule',
76
+ severity: 'warning', // or 'error' | 'info'
77
+ message: 'Description of issue',
78
+ nodeId: node.id,
79
+ nodeName: node.name,
80
+ suggestion: 'How to fix'
81
+ });
82
+ }
83
+ return warnings;
84
+ }
85
+ ```
86
+
87
+ 2. **Add to composition** in `validateWorkflow()`:
88
+ ```typescript
89
+ ...checkMyRule(workflow),
90
+ ```
91
+
92
+ 3. **Add test** in `validators.test.ts`:
93
+ ```typescript
94
+ it('detects my_rule violations', () => {
95
+ const workflow = createWorkflow({
96
+ // problematic config
97
+ });
98
+ const warnings = validateWorkflow(workflow);
99
+ expect(warnings).toContainEqual(
100
+ expect.objectContaining({ rule: 'my_rule' })
101
+ );
102
+ });
103
+ ```
104
+
105
+ ## Adding an Auto-Fix
106
+
107
+ 1. **Check if fixable** - some issues require human judgment
108
+ 2. **Add fix logic** in `autofix.ts`:
109
+ ```typescript
110
+ if (warning.rule === 'my_rule') {
111
+ // transform workflow
112
+ fixed.push(warning);
113
+ } else {
114
+ unfixed.push(warning);
115
+ }
116
+ ```
117
+ 3. **Update references** if renaming anything
118
+ 4. **Test both dry-run and apply modes**
119
+
120
+ ## Patterns to Follow
121
+
122
+ ### Safe Mutations
123
+ ```typescript
124
+ // Always clone before mutating
125
+ const copy = JSON.parse(JSON.stringify(original));
126
+ // Modify copy, return copy
127
+ ```
128
+
129
+ ### Async Operations
130
+ ```typescript
131
+ // All file/network operations are async
132
+ async function doThing(): Promise<Result> {
133
+ const data = await fetchData();
134
+ return transform(data);
135
+ }
136
+ ```
137
+
138
+ ### Error Responses
139
+ ```typescript
140
+ // MCP error format
141
+ return {
142
+ content: [{ type: 'text', text: 'Error message' }],
143
+ isError: true
144
+ };
145
+ ```
146
+
147
+ ### Validation Returns
148
+ ```typescript
149
+ // Always return array (empty if valid)
150
+ function validate(): ValidationWarning[] {
151
+ const warnings: ValidationWarning[] = [];
152
+ // ... checks ...
153
+ return warnings;
154
+ }
155
+ ```
156
+
157
+ ## Testing Patterns
158
+
159
+ ### Fixture Factory
160
+ ```typescript
161
+ function createWorkflow(overrides: Partial<N8nWorkflow> = {}): N8nWorkflow {
162
+ return {
163
+ id: 'test-id',
164
+ name: 'test_workflow',
165
+ nodes: [],
166
+ connections: {},
167
+ ...overrides
168
+ };
169
+ }
170
+ ```
171
+
172
+ ### Assertion Patterns
173
+ ```typescript
174
+ // Check warning exists
175
+ expect(warnings).toContainEqual(
176
+ expect.objectContaining({ rule: 'rule_name' })
177
+ );
178
+
179
+ // Check no warnings
180
+ expect(warnings).toHaveLength(0);
181
+
182
+ // Check severity
183
+ expect(warnings[0].severity).toBe('error');
184
+ ```
185
+
186
+ ## Out of Scope
187
+
188
+ Do not implement:
189
+ - Node documentation/search (use web search)
190
+ - Template marketplace features
191
+ - Credential management
192
+ - Multi-instance sync
193
+ - Real-time execution monitoring
194
+
195
+ These are either security risks or solved problems (Google, n8n UI).
196
+
197
+ ## Questions to Ask
198
+
199
+ Before adding a feature:
200
+ 1. Does this serve workflow editing/validation/versioning?
201
+ 2. Can this be solved with existing web search?
202
+ 3. Does this add runtime dependencies?
203
+ 4. Is there duplication that justifies abstraction?
204
+
205
+ If any answer suggests bloat, reconsider.
206
+
207
+ ## File Locations
208
+
209
+ ```
210
+ ~/.n8n-mcp/versions/ # Version control storage
211
+ {workflowId}/
212
+ {timestamp}_{hash}.json
213
+ ```
214
+
215
+ ## Environment Variables
216
+
217
+ ```bash
218
+ N8N_API_URL=http://localhost:5678 # n8n instance URL
219
+ N8N_API_KEY=your-api-key # n8n API key
220
+ ```
221
+
222
+ ## Debugging
223
+
224
+ ```bash
225
+ # Run with verbose output
226
+ DEBUG=* npm run dev
227
+
228
+ # Test single file
229
+ npx vitest run src/validators.test.ts
230
+
231
+ # Watch single test
232
+ npx vitest watch src/validators.test.ts
233
+ ```
@@ -0,0 +1,177 @@
1
+ # Architecture
2
+
3
+ ## vs czlonkowski/n8n-mcp
4
+
5
+ | Concern | czlonkowski | @pagelines |
6
+ |---------|-------------|------------|
7
+ | Node docs | 70MB SQLite, 1084 nodes | None (use Google) |
8
+ | Templates | 2,709 indexed | None (use n8n.io) |
9
+ | Update mode | Full replace | Patch (preserves params) |
10
+ | Version control | Limited | Auto-snapshot, diff, rollback |
11
+ | Validation | Basic | Rules + expressions + circular refs |
12
+ | Auto-fix | No | Yes |
13
+ | Dependencies | SQLite, heavy | Zero runtime |
14
+ | Lines of code | ~10k+ | ~1,200 |
15
+
16
+ ## Modules
17
+
18
+ ```
19
+ src/
20
+ ├── index.ts # MCP server, tool dispatch
21
+ ├── types.ts # Type definitions
22
+ ├── tools.ts # Tool schemas (JSON Schema)
23
+ ├── n8n-client.ts # n8n REST API client
24
+ ├── validators.ts # Validation rules
25
+ ├── expressions.ts # Expression parsing ({{ }})
26
+ ├── autofix.ts # Auto-fix transforms
27
+ └── versions.ts # Version control (local fs)
28
+ ```
29
+
30
+ ## Data Flow
31
+
32
+ ```
33
+ Claude Request
34
+
35
+ MCP Server (stdio)
36
+
37
+ Tool Handler
38
+
39
+ ┌─────────────────────────────────┐
40
+ │ n8n-client validators │
41
+ │ expressions autofix │
42
+ │ versions │
43
+ └─────────────────────────────────┘
44
+
45
+ JSON Response → Claude
46
+ ```
47
+
48
+ ## Tools (19 total)
49
+
50
+ ### Workflow (8)
51
+ | Tool | Description |
52
+ |------|-------------|
53
+ | `workflow_list` | List workflows, filter by active |
54
+ | `workflow_get` | Get full workflow |
55
+ | `workflow_create` | Create with nodes/connections |
56
+ | `workflow_update` | Patch operations |
57
+ | `workflow_delete` | Delete workflow |
58
+ | `workflow_activate` | Enable triggers |
59
+ | `workflow_deactivate` | Disable triggers |
60
+ | `workflow_execute` | Trigger via webhook |
61
+
62
+ ### Execution (2)
63
+ | Tool | Description |
64
+ |------|-------------|
65
+ | `execution_list` | List executions, filter by status |
66
+ | `execution_get` | Get execution with run data |
67
+
68
+ ### Validation (3)
69
+ | Tool | Description |
70
+ |------|-------------|
71
+ | `workflow_validate` | Check rules + expressions + circular refs |
72
+ | `workflow_autofix` | Fix auto-fixable issues (dry-run default) |
73
+ | `workflow_format` | Sort nodes, clean nulls |
74
+
75
+ ### Version Control (6)
76
+ | Tool | Description |
77
+ |------|-------------|
78
+ | `version_list` | List snapshots for workflow |
79
+ | `version_get` | Get specific version |
80
+ | `version_save` | Manual snapshot with reason |
81
+ | `version_rollback` | Restore previous (auto-saves current first) |
82
+ | `version_diff` | Compare versions |
83
+ | `version_stats` | Version control statistics |
84
+
85
+ ## Patch Operations
86
+
87
+ `workflow_update` accepts these operation types:
88
+
89
+ ```
90
+ addNode, removeNode, updateNode
91
+ addConnection, removeConnection
92
+ updateSettings, updateName
93
+ ```
94
+
95
+ Key: Preserves unmodified parameters.
96
+
97
+ ## Validation Rules
98
+
99
+ | Rule | Severity | Description |
100
+ |------|----------|-------------|
101
+ | `snake_case` | warning | Names should be snake_case |
102
+ | `explicit_reference` | warning | Use `$('node')` not `$json` |
103
+ | `no_hardcoded_ids` | info | Avoid hardcoded IDs |
104
+ | `no_hardcoded_secrets` | error | Never hardcode secrets |
105
+ | `code_node_usage` | info | Code node detected |
106
+ | `ai_structured_output` | warning | AI node missing structured output |
107
+ | `in_memory_storage` | warning | Non-persistent storage |
108
+ | `orphan_node` | warning | Node has no connections |
109
+ | `node_exists` | error | Node doesn't exist (for updates) |
110
+ | `parameter_preservation` | error | Update would lose parameters |
111
+
112
+ ## Expression Validation
113
+
114
+ | Issue | Severity | Pattern |
115
+ |-------|----------|---------|
116
+ | Uses `$json` | warning | `$json.` without `$('` prefix |
117
+ | Uses `$input` | info | `$input.` usage |
118
+ | Missing node reference | error | `$('node')` where node doesn't exist |
119
+ | Unclosed `}}` | error | Unmatched delimiters |
120
+ | Unbalanced parens/brackets | error | `(` vs `)`, `[` vs `]` |
121
+ | Deprecated `$node` | warning | `$node.` pattern |
122
+ | Deep property without `?.` | info | Missing optional chaining |
123
+
124
+ Plus: **Circular reference detection** across all expressions.
125
+
126
+ ## Auto-Fix
127
+
128
+ | Rule | Transform |
129
+ |------|-----------|
130
+ | `snake_case` | Rename + update all references |
131
+ | `explicit_reference` | `$json.x` → `$('prev_node').item.json.x` |
132
+ | `ai_structured_output` | Add `promptType: "define"`, `hasOutputParser: true` |
133
+
134
+ **Not fixable** (require judgment): secrets, hardcoded IDs, orphan nodes, code nodes, memory storage.
135
+
136
+ ## Version Control
137
+
138
+ Storage: `~/.n8n-mcp/versions/{workflowId}/{timestamp}_{hash}.json`
139
+
140
+ ```json
141
+ {
142
+ "id": "1704067200000_abc123",
143
+ "workflowId": "123",
144
+ "timestamp": "2024-01-01T00:00:00.000Z",
145
+ "reason": "before update",
146
+ "metadata": {
147
+ "name": "my_workflow",
148
+ "nodeCount": 5,
149
+ "contentHash": "abc123"
150
+ },
151
+ "workflow": { /* full snapshot */ }
152
+ }
153
+ ```
154
+
155
+ Features:
156
+ - Hash-based deduplication (no duplicate saves)
157
+ - Max versions pruning (default: 20)
158
+ - Auto-save before `workflow_update` and `version_rollback`
159
+
160
+ ## Extension
161
+
162
+ Add validation rule:
163
+ ```typescript
164
+ // validators.ts
165
+ function checkMyRule(workflow: N8nWorkflow): ValidationWarning[] {
166
+ // return warnings
167
+ }
168
+ // Add to validateWorkflow()
169
+ ```
170
+
171
+ Add auto-fix:
172
+ ```typescript
173
+ // autofix.ts
174
+ if (warning.rule === 'my_rule' && fixable) {
175
+ // transform workflow
176
+ }
177
+ ```