@pagelines/n8n-mcp 0.3.0 → 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.
- package/CHANGELOG.md +22 -6
- package/dist/index.js +2 -71
- package/dist/n8n-client.d.ts +1 -2
- package/dist/n8n-client.js +3 -8
- package/dist/tools.js +0 -29
- package/dist/types.d.ts +17 -27
- package/dist/types.js +34 -1
- package/dist/validators.d.ts +1 -9
- package/dist/validators.js +0 -85
- package/package.json +2 -2
- package/.github/workflows/ci.yml +0 -38
- package/dist/n8n-client.test.d.ts +0 -1
- package/dist/n8n-client.test.js +0 -295
- package/dist/response-format.test.d.ts +0 -1
- package/dist/response-format.test.js +0 -291
- package/dist/validators.test.d.ts +0 -1
- package/dist/validators.test.js +0 -310
- package/docs/best-practices.md +0 -165
- package/docs/node-config.md +0 -205
- package/plans/ai-guidelines.md +0 -233
- package/plans/architecture.md +0 -220
- package/server.json +0 -66
- package/src/autofix.ts +0 -275
- package/src/expressions.ts +0 -254
- package/src/index.ts +0 -550
- package/src/n8n-client.test.ts +0 -362
- package/src/n8n-client.ts +0 -372
- package/src/response-format.test.ts +0 -355
- package/src/response-format.ts +0 -278
- package/src/tools.ts +0 -489
- package/src/types.ts +0 -140
- package/src/validators.test.ts +0 -374
- package/src/validators.ts +0 -394
- package/src/versions.ts +0 -320
- package/tsconfig.json +0 -15
- package/vitest.config.ts +0 -8
package/dist/validators.test.js
DELETED
|
@@ -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
|
-
});
|
package/docs/best-practices.md
DELETED
|
@@ -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
|
package/docs/node-config.md
DELETED
|
@@ -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
|