@pagelines/n8n-mcp 0.3.1 → 0.3.2

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.
@@ -1,310 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { validateWorkflow, validatePartialUpdate, validateNodeTypes } from './validators.js';
3
- const createWorkflow = (overrides = {}) => ({
4
- id: '1',
5
- name: 'test_workflow',
6
- active: false,
7
- nodes: [],
8
- connections: {},
9
- createdAt: '2024-01-01',
10
- updatedAt: '2024-01-01',
11
- ...overrides,
12
- });
13
- describe('validateWorkflow', () => {
14
- it('passes for valid snake_case workflow', () => {
15
- const workflow = createWorkflow({
16
- name: 'my_workflow',
17
- nodes: [
18
- {
19
- id: '1',
20
- name: 'webhook_trigger',
21
- type: 'n8n-nodes-base.webhook',
22
- typeVersion: 1,
23
- position: [0, 0],
24
- parameters: { path: 'test' },
25
- },
26
- ],
27
- });
28
- const result = validateWorkflow(workflow);
29
- expect(result.valid).toBe(true);
30
- });
31
- it('warns on non-snake_case workflow name', () => {
32
- const workflow = createWorkflow({ name: 'My-Workflow-Name' });
33
- const result = validateWorkflow(workflow);
34
- expect(result.warnings).toContainEqual(expect.objectContaining({
35
- rule: 'snake_case',
36
- severity: 'warning',
37
- }));
38
- });
39
- it('warns on $json usage', () => {
40
- const workflow = createWorkflow({
41
- nodes: [
42
- {
43
- id: '1',
44
- name: 'set_node',
45
- type: 'n8n-nodes-base.set',
46
- typeVersion: 1,
47
- position: [0, 0],
48
- parameters: { value: '={{ $json.field }}' },
49
- },
50
- ],
51
- });
52
- const result = validateWorkflow(workflow);
53
- expect(result.warnings).toContainEqual(expect.objectContaining({
54
- rule: 'explicit_reference',
55
- severity: 'warning',
56
- }));
57
- });
58
- it('warns on hardcoded secrets', () => {
59
- const workflow = createWorkflow({
60
- nodes: [
61
- {
62
- id: '1',
63
- name: 'http_node',
64
- type: 'n8n-nodes-base.httpRequest',
65
- typeVersion: 1,
66
- position: [0, 0],
67
- parameters: { apiKey: 'sk_live_abc123def456' },
68
- },
69
- ],
70
- });
71
- const result = validateWorkflow(workflow);
72
- expect(result.warnings).toContainEqual(expect.objectContaining({
73
- rule: 'no_hardcoded_secrets',
74
- severity: 'info',
75
- }));
76
- });
77
- it('warns on orphan nodes', () => {
78
- const workflow = createWorkflow({
79
- nodes: [
80
- {
81
- id: '1',
82
- name: 'orphan_node',
83
- type: 'n8n-nodes-base.set',
84
- typeVersion: 1,
85
- position: [0, 0],
86
- parameters: {},
87
- },
88
- ],
89
- connections: {},
90
- });
91
- const result = validateWorkflow(workflow);
92
- expect(result.warnings).toContainEqual(expect.objectContaining({
93
- rule: 'orphan_node',
94
- severity: 'warning',
95
- }));
96
- });
97
- it('info on code node usage', () => {
98
- const workflow = createWorkflow({
99
- nodes: [
100
- {
101
- id: '1',
102
- name: 'my_code',
103
- type: 'n8n-nodes-base.code',
104
- typeVersion: 1,
105
- position: [0, 0],
106
- parameters: { jsCode: 'return items;' },
107
- },
108
- ],
109
- });
110
- const result = validateWorkflow(workflow);
111
- expect(result.warnings).toContainEqual(expect.objectContaining({
112
- rule: 'code_node_usage',
113
- severity: 'info',
114
- }));
115
- });
116
- it('warns on AI node without structured output settings', () => {
117
- const workflow = createWorkflow({
118
- nodes: [
119
- {
120
- id: '1',
121
- name: 'ai_agent',
122
- type: '@n8n/n8n-nodes-langchain.agent',
123
- typeVersion: 1,
124
- position: [0, 0],
125
- parameters: {
126
- outputParser: true,
127
- schemaType: 'manual',
128
- // Missing promptType: 'define' and hasOutputParser: true
129
- },
130
- },
131
- ],
132
- });
133
- const result = validateWorkflow(workflow);
134
- expect(result.warnings).toContainEqual(expect.objectContaining({
135
- rule: 'ai_structured_output',
136
- severity: 'warning',
137
- }));
138
- });
139
- it('passes AI node with correct structured output settings', () => {
140
- const workflow = createWorkflow({
141
- nodes: [
142
- {
143
- id: '1',
144
- name: 'ai_agent',
145
- type: '@n8n/n8n-nodes-langchain.agent',
146
- typeVersion: 1,
147
- position: [0, 0],
148
- parameters: {
149
- outputParser: true,
150
- schemaType: 'manual',
151
- promptType: 'define',
152
- hasOutputParser: true,
153
- },
154
- },
155
- ],
156
- });
157
- const result = validateWorkflow(workflow);
158
- const aiWarnings = result.warnings.filter((w) => w.rule === 'ai_structured_output');
159
- expect(aiWarnings).toHaveLength(0);
160
- });
161
- it('warns on in-memory storage nodes', () => {
162
- const workflow = createWorkflow({
163
- nodes: [
164
- {
165
- id: '1',
166
- name: 'memory_buffer',
167
- type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
168
- typeVersion: 1,
169
- position: [0, 0],
170
- parameters: {},
171
- },
172
- ],
173
- });
174
- const result = validateWorkflow(workflow);
175
- expect(result.warnings).toContainEqual(expect.objectContaining({
176
- rule: 'in_memory_storage',
177
- severity: 'warning',
178
- }));
179
- });
180
- });
181
- describe('validatePartialUpdate', () => {
182
- it('errors when node not found', () => {
183
- const workflow = createWorkflow();
184
- const warnings = validatePartialUpdate(workflow, 'nonexistent', {});
185
- expect(warnings).toContainEqual(expect.objectContaining({
186
- rule: 'node_exists',
187
- severity: 'error',
188
- }));
189
- });
190
- it('errors on parameter loss', () => {
191
- const workflow = createWorkflow({
192
- nodes: [
193
- {
194
- id: '1',
195
- name: 'my_node',
196
- type: 'n8n-nodes-base.set',
197
- typeVersion: 1,
198
- position: [0, 0],
199
- parameters: { existingParam: 'value', anotherParam: 'value2' },
200
- },
201
- ],
202
- });
203
- const warnings = validatePartialUpdate(workflow, 'my_node', {
204
- newParam: 'value', // Missing existingParam and anotherParam
205
- });
206
- expect(warnings).toContainEqual(expect.objectContaining({
207
- rule: 'parameter_preservation',
208
- severity: 'error',
209
- }));
210
- });
211
- it('passes when all parameters preserved', () => {
212
- const workflow = createWorkflow({
213
- nodes: [
214
- {
215
- id: '1',
216
- name: 'my_node',
217
- type: 'n8n-nodes-base.set',
218
- typeVersion: 1,
219
- position: [0, 0],
220
- parameters: { existingParam: 'value' },
221
- },
222
- ],
223
- });
224
- const warnings = validatePartialUpdate(workflow, 'my_node', {
225
- existingParam: 'value',
226
- newParam: 'new',
227
- });
228
- expect(warnings).toHaveLength(0);
229
- });
230
- });
231
- describe('validateNodeTypes', () => {
232
- const availableTypes = new Set([
233
- 'n8n-nodes-base.webhook',
234
- 'n8n-nodes-base.set',
235
- 'n8n-nodes-base.code',
236
- 'n8n-nodes-base.httpRequest',
237
- '@n8n/n8n-nodes-langchain.agent',
238
- '@n8n/n8n-nodes-langchain.chatTrigger',
239
- ]);
240
- it('passes when all node types are valid', () => {
241
- const nodes = [
242
- { name: 'webhook_trigger', type: 'n8n-nodes-base.webhook' },
243
- { name: 'set_data', type: 'n8n-nodes-base.set' },
244
- { name: 'ai_agent', type: '@n8n/n8n-nodes-langchain.agent' },
245
- ];
246
- const errors = validateNodeTypes(nodes, availableTypes);
247
- expect(errors).toHaveLength(0);
248
- });
249
- it('returns error for invalid node type', () => {
250
- const nodes = [
251
- { name: 'my_node', type: 'n8n-nodes-base.nonexistent' },
252
- ];
253
- const errors = validateNodeTypes(nodes, availableTypes);
254
- expect(errors).toHaveLength(1);
255
- expect(errors[0]).toEqual(expect.objectContaining({
256
- nodeType: 'n8n-nodes-base.nonexistent',
257
- nodeName: 'my_node',
258
- }));
259
- });
260
- it('returns errors for multiple invalid node types', () => {
261
- const nodes = [
262
- { name: 'valid_node', type: 'n8n-nodes-base.webhook' },
263
- { name: 'invalid_one', type: 'n8n-nodes-base.fake' },
264
- { name: 'invalid_two', type: 'n8n-nodes-base.bogus' },
265
- ];
266
- const errors = validateNodeTypes(nodes, availableTypes);
267
- expect(errors).toHaveLength(2);
268
- expect(errors.map((e) => e.nodeName)).toEqual(['invalid_one', 'invalid_two']);
269
- });
270
- it('provides suggestions for typos', () => {
271
- const nodes = [
272
- { name: 'trigger', type: 'n8n-nodes-base.webhok' }, // typo: webhok
273
- ];
274
- const errors = validateNodeTypes(nodes, availableTypes);
275
- expect(errors).toHaveLength(1);
276
- expect(errors[0].suggestions).toContain('n8n-nodes-base.webhook');
277
- });
278
- it('provides suggestions for partial matches', () => {
279
- const nodes = [
280
- { name: 'code_node', type: 'n8n-nodes-base.cod' }, // partial: cod
281
- ];
282
- const errors = validateNodeTypes(nodes, availableTypes);
283
- expect(errors).toHaveLength(1);
284
- expect(errors[0].suggestions).toContain('n8n-nodes-base.code');
285
- });
286
- it('returns empty suggestions when no matches found', () => {
287
- const nodes = [
288
- { name: 'xyz_node', type: 'n8n-nodes-base.xyz123completely_random' },
289
- ];
290
- const errors = validateNodeTypes(nodes, availableTypes);
291
- expect(errors).toHaveLength(1);
292
- expect(errors[0].suggestions).toHaveLength(0);
293
- });
294
- it('limits suggestions to 3', () => {
295
- // Create a set with many similar types
296
- const manyTypes = new Set([
297
- 'n8n-nodes-base.httpRequest',
298
- 'n8n-nodes-base.httpRequestTool',
299
- 'n8n-nodes-base.httpRequestV1',
300
- 'n8n-nodes-base.httpRequestV2',
301
- 'n8n-nodes-base.httpRequestV3',
302
- ]);
303
- const nodes = [
304
- { name: 'http', type: 'n8n-nodes-base.http' }, // should match multiple
305
- ];
306
- const errors = validateNodeTypes(nodes, manyTypes);
307
- expect(errors).toHaveLength(1);
308
- expect(errors[0].suggestions.length).toBeLessThanOrEqual(3);
309
- });
310
- });
@@ -1,165 +0,0 @@
1
- # n8n Best Practices
2
-
3
- > **These rules are automatically enforced.** The MCP validates, auto-fixes, and formats on every `workflow_create` and `workflow_update`. You'll only see warnings for issues that can't be auto-fixed.
4
-
5
- ## Quick Reference
6
-
7
- ```javascript
8
- // Explicit reference (always use this)
9
- {{ $('node_name').item.json.field }}
10
-
11
- // Environment variable
12
- {{ $env.API_KEY }}
13
-
14
- // Config node reference
15
- {{ $('config').item.json.setting }}
16
-
17
- // Fallback
18
- {{ $('source').item.json.text || 'default' }}
19
-
20
- // Date
21
- {{ $now.format('yyyy-MM-dd') }}
22
- ```
23
-
24
- ## The Rules
25
-
26
- ### 1. snake_case (auto-fixed)
27
-
28
- ```
29
- Good: fetch_articles, check_approved, generate_content
30
- Bad: FetchArticles, Check Approved, generate-content
31
- ```
32
-
33
- Why: Consistency, readability. **Auto-fixed:** renamed automatically with all references updated.
34
-
35
- ### 2. Explicit References (auto-fixed)
36
-
37
- ```javascript
38
- // Bad - breaks when flow changes
39
- {{ $json.field }}
40
-
41
- // Good - traceable, stable
42
- {{ $('node_name').item.json.field }}
43
- ```
44
-
45
- Why: `$json` references "previous node" implicitly. Reorder nodes, it breaks. **Auto-fixed:** converted to explicit `$('prev_node')` references.
46
-
47
- ### 3. Config Node
48
-
49
- Single source for workflow settings:
50
-
51
- ```
52
- [trigger] → [config] → [rest of workflow]
53
- ```
54
-
55
- Config node (JSON mode):
56
- ```javascript
57
- ={
58
- "channel_id": "{{ $json.body.channelId || '123456' }}",
59
- "max_items": 10,
60
- "ai_model": "gpt-4.1-mini"
61
- }
62
- ```
63
-
64
- Reference everywhere: `{{ $('config').item.json.channel_id }}`
65
-
66
- Why: Change once, not in 5 nodes.
67
-
68
- ### 4. Secrets in Environment (recommended)
69
-
70
- ```javascript
71
- // Hardcoded (works, but less portable)
72
- { "apiKey": "sk_live_abc123" }
73
-
74
- // Environment variable (recommended)
75
- {{ $env.API_KEY }}
76
- ```
77
-
78
- Why: Env vars make workflows portable across environments and avoid committing secrets.
79
-
80
- ## Parameter Preservation
81
-
82
- **Critical:** Partial updates REPLACE the entire `parameters` object.
83
-
84
- ```javascript
85
- // Bad - loses operation and labelIds
86
- {
87
- "type": "updateNode",
88
- "nodeName": "archive_email",
89
- "properties": {
90
- "parameters": {
91
- "messageId": "={{ $json.message_id }}"
92
- }
93
- }
94
- }
95
-
96
- // Good - include ALL parameters
97
- {
98
- "type": "updateNode",
99
- "nodeName": "archive_email",
100
- "properties": {
101
- "parameters": {
102
- "operation": "addLabels",
103
- "messageId": "={{ $json.message_id }}",
104
- "labelIds": ["Label_123"]
105
- }
106
- }
107
- }
108
- ```
109
-
110
- Before updating: read current state with `workflow_get`.
111
-
112
- ## AI Nodes
113
-
114
- ### Structured Output (auto-fixed)
115
-
116
- Always set for predictable JSON. **Auto-fixed:** `promptType: "define"` and `hasOutputParser: true` added automatically.
117
-
118
- | Setting | Value |
119
- |---------|-------|
120
- | `promptType` | `"define"` |
121
- | `hasOutputParser` | `true` |
122
- | `schemaType` | `"manual"` (for nullable fields) |
123
-
124
- ### Memory
125
-
126
- | Don't Use | Use Instead |
127
- |-----------|-------------|
128
- | Windowed Buffer Memory | Postgres Chat Memory |
129
- | In-Memory Vector Store | Postgres pgvector |
130
-
131
- In-memory dies on restart, doesn't scale.
132
-
133
- ## Code Nodes: Last Resort
134
-
135
- | Need | Use Instead |
136
- |------|-------------|
137
- | Transform fields | Set node with expressions |
138
- | Filter items | Filter node or Switch |
139
- | Merge data | Merge node |
140
- | Loop | 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 parsing expressions can't handle
147
-
148
- ## Pre-Edit Checklist
149
-
150
- | Step | Why |
151
- |------|-----|
152
- | 1. Get explicit user approval | Don't surprise |
153
- | 2. List versions | Know rollback point |
154
- | 3. Read full workflow | Understand current state |
155
- | 4. Make targeted change | Minimal surface area |
156
-
157
- > **Note:** Validation and cleanup are now automatic. Every create/update validates, auto-fixes, and formats automatically.
158
-
159
- ## Node-Specific Settings
160
-
161
- See [Node Config](node-config.md) for:
162
- - Resource locator (`__rl`) format with `cachedResultName`
163
- - Google Sheets, Gmail, Discord required fields
164
- - Set node JSON mode vs manual mapping
165
- - Error handling options
@@ -1,205 +0,0 @@
1
- # Node Configuration Guidelines
2
-
3
- > Settings that make AI-created nodes human-editable.
4
- >
5
- > **Node types are validated.** Invalid types are blocked with suggestions before hitting n8n. Use `node_types_list` to discover available nodes.
6
-
7
- ## Resource Locator Pattern
8
-
9
- n8n uses `__rl` (resource locator) format for dropdown fields. Always include `cachedResultName` so humans see friendly names.
10
-
11
- ```javascript
12
- // Human-editable: shows "My Spreadsheet" in UI
13
- "documentId": {
14
- "__rl": true,
15
- "value": "1abc123...",
16
- "mode": "list",
17
- "cachedResultName": "My Spreadsheet",
18
- "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1abc123"
19
- }
20
-
21
- // Hard to edit: shows raw ID only
22
- "documentId": {
23
- "__rl": true,
24
- "value": "1abc123...",
25
- "mode": "id"
26
- }
27
- ```
28
-
29
- | Mode | When to Use |
30
- |------|-------------|
31
- | `list` | Default. Shows dropdown with cached name |
32
- | `id` | Only when ID is dynamic (from expression) |
33
-
34
- ## Set Node
35
-
36
- **Use JSON mode** for MCP-created nodes. Manual mapping is error-prone via API.
37
-
38
- ```javascript
39
- // JSON mode (reliable)
40
- {
41
- "mode": "raw",
42
- "jsonOutput": "={ \"channel_id\": \"{{ $json.channelId || '123' }}\" }"
43
- }
44
-
45
- // Manual mapping (fragile via API)
46
- {
47
- "mode": "manual",
48
- "assignments": { ... } // Complex structure, easy to break
49
- }
50
- ```
51
-
52
- ## Google Sheets
53
-
54
- Required fields (partial updates lose these):
55
-
56
- ```javascript
57
- {
58
- "operation": "append",
59
- "documentId": {
60
- "__rl": true,
61
- "value": "1abc...",
62
- "mode": "list",
63
- "cachedResultName": "Sheet Name"
64
- },
65
- "sheetName": {
66
- "__rl": true,
67
- "value": "gid=0",
68
- "mode": "list",
69
- "cachedResultName": "Sheet1"
70
- },
71
- "columns": { /* mappings */ }
72
- }
73
- ```
74
-
75
- ## Gmail
76
-
77
- Required fields:
78
-
79
- ```javascript
80
- {
81
- "operation": "addLabels", // or send, get, etc.
82
- "messageId": "={{ $('source').item.json.id }}",
83
- "labelIds": ["Label_123", "Label_456"]
84
- }
85
- ```
86
-
87
- Labels need full IDs (`Label_xxx`), not display names.
88
-
89
- ## Discord
90
-
91
- ```javascript
92
- {
93
- "authentication": "webhook",
94
- "webhookUri": {
95
- "__rl": true,
96
- "value": "={{ $env.DISCORD_WEBHOOK }}",
97
- "mode": "id"
98
- },
99
- "content": "Message text"
100
- }
101
- ```
102
-
103
- For bot mode with guild/channel selection:
104
-
105
- ```javascript
106
- {
107
- "guildId": {
108
- "__rl": true,
109
- "value": "123456789",
110
- "mode": "list",
111
- "cachedResultName": "My Server"
112
- },
113
- "channelId": {
114
- "__rl": true,
115
- "value": "987654321",
116
- "mode": "list",
117
- "cachedResultName": "#general"
118
- }
119
- }
120
- ```
121
-
122
- ## AI Nodes (Agent, Chain)
123
-
124
- Always include structured output settings:
125
-
126
- ```javascript
127
- {
128
- "promptType": "define",
129
- "hasOutputParser": true,
130
- "schemaType": "manual", // Required for nullable fields
131
- "schema": { /* JSON Schema */ }
132
- }
133
- ```
134
-
135
- Without these, AI outputs are unpredictable strings.
136
-
137
- ## HTTP Request
138
-
139
- Include authentication method explicitly:
140
-
141
- ```javascript
142
- {
143
- "method": "POST",
144
- "url": "https://api.example.com",
145
- "authentication": "predefinedCredentialType",
146
- "nodeCredentialType": "anthropicApi",
147
- "sendBody": true,
148
- "bodyParameters": { ... }
149
- }
150
- ```
151
-
152
- ## Switch vs If
153
-
154
- Always use Switch (not If):
155
-
156
- ```javascript
157
- // Switch: named outputs, extensible
158
- {
159
- "type": "n8n-nodes-base.switch",
160
- "parameters": {
161
- "rules": {
162
- "rules": [
163
- { "output": 0, "conditions": { ... } },
164
- { "output": 1, "conditions": { ... } }
165
- ]
166
- }
167
- }
168
- }
169
- ```
170
-
171
- If node only has true/false outputs - Switch scales better.
172
-
173
- ## Expression Patterns
174
-
175
- Always explicit references:
176
-
177
- ```javascript
178
- // Good
179
- "={{ $('config').item.json.channel_id }}"
180
-
181
- // Bad - breaks on node reorder
182
- "={{ $json.channel_id }}"
183
- ```
184
-
185
- Environment variables for secrets:
186
-
187
- ```javascript
188
- "={{ $env.API_KEY }}"
189
- ```
190
-
191
- ## Error Handling
192
-
193
- Set `onError` for API nodes:
194
-
195
- ```javascript
196
- {
197
- "parameters": { ... },
198
- "onError": "continueRegularOutput" // or "continueErrorOutput"
199
- }
200
- ```
201
-
202
- Options:
203
- - `stopWorkflow` - default, halts on error
204
- - `continueRegularOutput` - ignore errors, continue
205
- - `continueErrorOutput` - route errors to second output