@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.
- package/CHANGELOG.md +10 -6
- package/dist/index.js +2 -71
- package/dist/n8n-client.d.ts +1 -2
- package/dist/n8n-client.js +0 -6
- package/dist/tools.js +0 -29
- package/dist/types.d.ts +1 -28
- package/dist/types.js +1 -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 -382
- 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 -467
- package/src/n8n-client.ts +0 -374
- 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 -183
- 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/src/validators.test.ts
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { validateWorkflow, validatePartialUpdate, validateNodeTypes } from './validators.js';
|
|
3
|
-
import type { N8nWorkflow } from './types.js';
|
|
4
|
-
|
|
5
|
-
const createWorkflow = (overrides: Partial<N8nWorkflow> = {}): N8nWorkflow => ({
|
|
6
|
-
id: '1',
|
|
7
|
-
name: 'test_workflow',
|
|
8
|
-
active: false,
|
|
9
|
-
nodes: [],
|
|
10
|
-
connections: {},
|
|
11
|
-
createdAt: '2024-01-01',
|
|
12
|
-
updatedAt: '2024-01-01',
|
|
13
|
-
...overrides,
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('validateWorkflow', () => {
|
|
17
|
-
it('passes for valid snake_case workflow', () => {
|
|
18
|
-
const workflow = createWorkflow({
|
|
19
|
-
name: 'my_workflow',
|
|
20
|
-
nodes: [
|
|
21
|
-
{
|
|
22
|
-
id: '1',
|
|
23
|
-
name: 'webhook_trigger',
|
|
24
|
-
type: 'n8n-nodes-base.webhook',
|
|
25
|
-
typeVersion: 1,
|
|
26
|
-
position: [0, 0],
|
|
27
|
-
parameters: { path: 'test' },
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const result = validateWorkflow(workflow);
|
|
33
|
-
expect(result.valid).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('warns on non-snake_case workflow name', () => {
|
|
37
|
-
const workflow = createWorkflow({ name: 'My-Workflow-Name' });
|
|
38
|
-
const result = validateWorkflow(workflow);
|
|
39
|
-
|
|
40
|
-
expect(result.warnings).toContainEqual(
|
|
41
|
-
expect.objectContaining({
|
|
42
|
-
rule: 'snake_case',
|
|
43
|
-
severity: 'warning',
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('warns on $json usage', () => {
|
|
49
|
-
const workflow = createWorkflow({
|
|
50
|
-
nodes: [
|
|
51
|
-
{
|
|
52
|
-
id: '1',
|
|
53
|
-
name: 'set_node',
|
|
54
|
-
type: 'n8n-nodes-base.set',
|
|
55
|
-
typeVersion: 1,
|
|
56
|
-
position: [0, 0],
|
|
57
|
-
parameters: { value: '={{ $json.field }}' },
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const result = validateWorkflow(workflow);
|
|
63
|
-
expect(result.warnings).toContainEqual(
|
|
64
|
-
expect.objectContaining({
|
|
65
|
-
rule: 'explicit_reference',
|
|
66
|
-
severity: 'warning',
|
|
67
|
-
})
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('warns on hardcoded secrets', () => {
|
|
72
|
-
const workflow = createWorkflow({
|
|
73
|
-
nodes: [
|
|
74
|
-
{
|
|
75
|
-
id: '1',
|
|
76
|
-
name: 'http_node',
|
|
77
|
-
type: 'n8n-nodes-base.httpRequest',
|
|
78
|
-
typeVersion: 1,
|
|
79
|
-
position: [0, 0],
|
|
80
|
-
parameters: { apiKey: 'sk_live_abc123def456' },
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const result = validateWorkflow(workflow);
|
|
86
|
-
expect(result.warnings).toContainEqual(
|
|
87
|
-
expect.objectContaining({
|
|
88
|
-
rule: 'no_hardcoded_secrets',
|
|
89
|
-
severity: 'info',
|
|
90
|
-
})
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('warns on orphan nodes', () => {
|
|
95
|
-
const workflow = createWorkflow({
|
|
96
|
-
nodes: [
|
|
97
|
-
{
|
|
98
|
-
id: '1',
|
|
99
|
-
name: 'orphan_node',
|
|
100
|
-
type: 'n8n-nodes-base.set',
|
|
101
|
-
typeVersion: 1,
|
|
102
|
-
position: [0, 0],
|
|
103
|
-
parameters: {},
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
connections: {},
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const result = validateWorkflow(workflow);
|
|
110
|
-
expect(result.warnings).toContainEqual(
|
|
111
|
-
expect.objectContaining({
|
|
112
|
-
rule: 'orphan_node',
|
|
113
|
-
severity: 'warning',
|
|
114
|
-
})
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('info on code node usage', () => {
|
|
119
|
-
const workflow = createWorkflow({
|
|
120
|
-
nodes: [
|
|
121
|
-
{
|
|
122
|
-
id: '1',
|
|
123
|
-
name: 'my_code',
|
|
124
|
-
type: 'n8n-nodes-base.code',
|
|
125
|
-
typeVersion: 1,
|
|
126
|
-
position: [0, 0],
|
|
127
|
-
parameters: { jsCode: 'return items;' },
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const result = validateWorkflow(workflow);
|
|
133
|
-
expect(result.warnings).toContainEqual(
|
|
134
|
-
expect.objectContaining({
|
|
135
|
-
rule: 'code_node_usage',
|
|
136
|
-
severity: 'info',
|
|
137
|
-
})
|
|
138
|
-
);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('warns on AI node without structured output settings', () => {
|
|
142
|
-
const workflow = createWorkflow({
|
|
143
|
-
nodes: [
|
|
144
|
-
{
|
|
145
|
-
id: '1',
|
|
146
|
-
name: 'ai_agent',
|
|
147
|
-
type: '@n8n/n8n-nodes-langchain.agent',
|
|
148
|
-
typeVersion: 1,
|
|
149
|
-
position: [0, 0],
|
|
150
|
-
parameters: {
|
|
151
|
-
outputParser: true,
|
|
152
|
-
schemaType: 'manual',
|
|
153
|
-
// Missing promptType: 'define' and hasOutputParser: true
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
const result = validateWorkflow(workflow);
|
|
160
|
-
expect(result.warnings).toContainEqual(
|
|
161
|
-
expect.objectContaining({
|
|
162
|
-
rule: 'ai_structured_output',
|
|
163
|
-
severity: 'warning',
|
|
164
|
-
})
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('passes AI node with correct structured output settings', () => {
|
|
169
|
-
const workflow = createWorkflow({
|
|
170
|
-
nodes: [
|
|
171
|
-
{
|
|
172
|
-
id: '1',
|
|
173
|
-
name: 'ai_agent',
|
|
174
|
-
type: '@n8n/n8n-nodes-langchain.agent',
|
|
175
|
-
typeVersion: 1,
|
|
176
|
-
position: [0, 0],
|
|
177
|
-
parameters: {
|
|
178
|
-
outputParser: true,
|
|
179
|
-
schemaType: 'manual',
|
|
180
|
-
promptType: 'define',
|
|
181
|
-
hasOutputParser: true,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const result = validateWorkflow(workflow);
|
|
188
|
-
const aiWarnings = result.warnings.filter((w) => w.rule === 'ai_structured_output');
|
|
189
|
-
expect(aiWarnings).toHaveLength(0);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('warns on in-memory storage nodes', () => {
|
|
193
|
-
const workflow = createWorkflow({
|
|
194
|
-
nodes: [
|
|
195
|
-
{
|
|
196
|
-
id: '1',
|
|
197
|
-
name: 'memory_buffer',
|
|
198
|
-
type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
|
|
199
|
-
typeVersion: 1,
|
|
200
|
-
position: [0, 0],
|
|
201
|
-
parameters: {},
|
|
202
|
-
},
|
|
203
|
-
],
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const result = validateWorkflow(workflow);
|
|
207
|
-
expect(result.warnings).toContainEqual(
|
|
208
|
-
expect.objectContaining({
|
|
209
|
-
rule: 'in_memory_storage',
|
|
210
|
-
severity: 'warning',
|
|
211
|
-
})
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe('validatePartialUpdate', () => {
|
|
217
|
-
it('errors when node not found', () => {
|
|
218
|
-
const workflow = createWorkflow();
|
|
219
|
-
const warnings = validatePartialUpdate(workflow, 'nonexistent', {});
|
|
220
|
-
|
|
221
|
-
expect(warnings).toContainEqual(
|
|
222
|
-
expect.objectContaining({
|
|
223
|
-
rule: 'node_exists',
|
|
224
|
-
severity: 'error',
|
|
225
|
-
})
|
|
226
|
-
);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('errors on parameter loss', () => {
|
|
230
|
-
const workflow = createWorkflow({
|
|
231
|
-
nodes: [
|
|
232
|
-
{
|
|
233
|
-
id: '1',
|
|
234
|
-
name: 'my_node',
|
|
235
|
-
type: 'n8n-nodes-base.set',
|
|
236
|
-
typeVersion: 1,
|
|
237
|
-
position: [0, 0],
|
|
238
|
-
parameters: { existingParam: 'value', anotherParam: 'value2' },
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const warnings = validatePartialUpdate(workflow, 'my_node', {
|
|
244
|
-
newParam: 'value', // Missing existingParam and anotherParam
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
expect(warnings).toContainEqual(
|
|
248
|
-
expect.objectContaining({
|
|
249
|
-
rule: 'parameter_preservation',
|
|
250
|
-
severity: 'error',
|
|
251
|
-
})
|
|
252
|
-
);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('passes when all parameters preserved', () => {
|
|
256
|
-
const workflow = createWorkflow({
|
|
257
|
-
nodes: [
|
|
258
|
-
{
|
|
259
|
-
id: '1',
|
|
260
|
-
name: 'my_node',
|
|
261
|
-
type: 'n8n-nodes-base.set',
|
|
262
|
-
typeVersion: 1,
|
|
263
|
-
position: [0, 0],
|
|
264
|
-
parameters: { existingParam: 'value' },
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const warnings = validatePartialUpdate(workflow, 'my_node', {
|
|
270
|
-
existingParam: 'value',
|
|
271
|
-
newParam: 'new',
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
expect(warnings).toHaveLength(0);
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
describe('validateNodeTypes', () => {
|
|
279
|
-
const availableTypes = new Set([
|
|
280
|
-
'n8n-nodes-base.webhook',
|
|
281
|
-
'n8n-nodes-base.set',
|
|
282
|
-
'n8n-nodes-base.code',
|
|
283
|
-
'n8n-nodes-base.httpRequest',
|
|
284
|
-
'@n8n/n8n-nodes-langchain.agent',
|
|
285
|
-
'@n8n/n8n-nodes-langchain.chatTrigger',
|
|
286
|
-
]);
|
|
287
|
-
|
|
288
|
-
it('passes when all node types are valid', () => {
|
|
289
|
-
const nodes = [
|
|
290
|
-
{ name: 'webhook_trigger', type: 'n8n-nodes-base.webhook' },
|
|
291
|
-
{ name: 'set_data', type: 'n8n-nodes-base.set' },
|
|
292
|
-
{ name: 'ai_agent', type: '@n8n/n8n-nodes-langchain.agent' },
|
|
293
|
-
];
|
|
294
|
-
|
|
295
|
-
const errors = validateNodeTypes(nodes, availableTypes);
|
|
296
|
-
expect(errors).toHaveLength(0);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
it('returns error for invalid node type', () => {
|
|
300
|
-
const nodes = [
|
|
301
|
-
{ name: 'my_node', type: 'n8n-nodes-base.nonexistent' },
|
|
302
|
-
];
|
|
303
|
-
|
|
304
|
-
const errors = validateNodeTypes(nodes, availableTypes);
|
|
305
|
-
expect(errors).toHaveLength(1);
|
|
306
|
-
expect(errors[0]).toEqual(
|
|
307
|
-
expect.objectContaining({
|
|
308
|
-
nodeType: 'n8n-nodes-base.nonexistent',
|
|
309
|
-
nodeName: 'my_node',
|
|
310
|
-
})
|
|
311
|
-
);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('returns errors for multiple invalid node types', () => {
|
|
315
|
-
const nodes = [
|
|
316
|
-
{ name: 'valid_node', type: 'n8n-nodes-base.webhook' },
|
|
317
|
-
{ name: 'invalid_one', type: 'n8n-nodes-base.fake' },
|
|
318
|
-
{ name: 'invalid_two', type: 'n8n-nodes-base.bogus' },
|
|
319
|
-
];
|
|
320
|
-
|
|
321
|
-
const errors = validateNodeTypes(nodes, availableTypes);
|
|
322
|
-
expect(errors).toHaveLength(2);
|
|
323
|
-
expect(errors.map((e) => e.nodeName)).toEqual(['invalid_one', 'invalid_two']);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
it('provides suggestions for typos', () => {
|
|
327
|
-
const nodes = [
|
|
328
|
-
{ name: 'trigger', type: 'n8n-nodes-base.webhok' }, // typo: webhok
|
|
329
|
-
];
|
|
330
|
-
|
|
331
|
-
const errors = validateNodeTypes(nodes, availableTypes);
|
|
332
|
-
expect(errors).toHaveLength(1);
|
|
333
|
-
expect(errors[0].suggestions).toContain('n8n-nodes-base.webhook');
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it('provides suggestions for partial matches', () => {
|
|
337
|
-
const nodes = [
|
|
338
|
-
{ name: 'code_node', type: 'n8n-nodes-base.cod' }, // partial: cod
|
|
339
|
-
];
|
|
340
|
-
|
|
341
|
-
const errors = validateNodeTypes(nodes, availableTypes);
|
|
342
|
-
expect(errors).toHaveLength(1);
|
|
343
|
-
expect(errors[0].suggestions).toContain('n8n-nodes-base.code');
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it('returns empty suggestions when no matches found', () => {
|
|
347
|
-
const nodes = [
|
|
348
|
-
{ name: 'xyz_node', type: 'n8n-nodes-base.xyz123completely_random' },
|
|
349
|
-
];
|
|
350
|
-
|
|
351
|
-
const errors = validateNodeTypes(nodes, availableTypes);
|
|
352
|
-
expect(errors).toHaveLength(1);
|
|
353
|
-
expect(errors[0].suggestions).toHaveLength(0);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('limits suggestions to 3', () => {
|
|
357
|
-
// Create a set with many similar types
|
|
358
|
-
const manyTypes = new Set([
|
|
359
|
-
'n8n-nodes-base.httpRequest',
|
|
360
|
-
'n8n-nodes-base.httpRequestTool',
|
|
361
|
-
'n8n-nodes-base.httpRequestV1',
|
|
362
|
-
'n8n-nodes-base.httpRequestV2',
|
|
363
|
-
'n8n-nodes-base.httpRequestV3',
|
|
364
|
-
]);
|
|
365
|
-
|
|
366
|
-
const nodes = [
|
|
367
|
-
{ name: 'http', type: 'n8n-nodes-base.http' }, // should match multiple
|
|
368
|
-
];
|
|
369
|
-
|
|
370
|
-
const errors = validateNodeTypes(nodes, manyTypes);
|
|
371
|
-
expect(errors).toHaveLength(1);
|
|
372
|
-
expect(errors[0].suggestions!.length).toBeLessThanOrEqual(3);
|
|
373
|
-
});
|
|
374
|
-
});
|