@pagelines/n8n-mcp 0.2.0 → 0.3.0
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 +37 -0
- package/README.md +70 -60
- package/dist/index.js +122 -18
- package/dist/n8n-client.d.ts +3 -2
- package/dist/n8n-client.js +9 -1
- package/dist/n8n-client.test.js +111 -0
- package/dist/response-format.d.ts +84 -0
- package/dist/response-format.js +183 -0
- package/dist/response-format.test.d.ts +1 -0
- package/dist/response-format.test.js +291 -0
- package/dist/tools.js +67 -3
- package/dist/types.d.ts +27 -0
- package/dist/validators.d.ts +9 -1
- package/dist/validators.js +87 -2
- package/dist/validators.test.js +83 -4
- package/docs/best-practices.md +80 -156
- package/docs/node-config.md +205 -0
- package/logo.png +0 -0
- package/package.json +1 -1
- package/plans/ai-guidelines.md +233 -0
- package/plans/architecture.md +220 -0
- package/src/index.ts +159 -20
- package/src/n8n-client.test.ts +135 -0
- package/src/n8n-client.ts +13 -2
- package/src/response-format.test.ts +355 -0
- package/src/response-format.ts +278 -0
- package/src/tools.ts +68 -3
- package/src/types.ts +33 -0
- package/src/validators.test.ts +101 -4
- package/src/validators.ts +112 -3
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import { N8nClient } from './n8n-client.js';
|
|
15
15
|
import { tools } from './tools.js';
|
|
16
|
-
import { validateWorkflow } from './validators.js';
|
|
16
|
+
import { validateWorkflow, validateNodeTypes } from './validators.js';
|
|
17
17
|
import { validateExpressions, checkCircularReferences } from './expressions.js';
|
|
18
18
|
import { autofixWorkflow, formatWorkflow } from './autofix.js';
|
|
19
19
|
import {
|
|
@@ -24,7 +24,14 @@ import {
|
|
|
24
24
|
diffWorkflows,
|
|
25
25
|
getVersionStats,
|
|
26
26
|
} from './versions.js';
|
|
27
|
-
import
|
|
27
|
+
import {
|
|
28
|
+
formatWorkflowResponse,
|
|
29
|
+
formatExecutionResponse,
|
|
30
|
+
formatExecutionListResponse,
|
|
31
|
+
stringifyResponse,
|
|
32
|
+
type ResponseFormat,
|
|
33
|
+
} from './response-format.js';
|
|
34
|
+
import type { PatchOperation, N8nConnections, N8nNodeTypeSummary } from './types.js';
|
|
28
35
|
|
|
29
36
|
// ─────────────────────────────────────────────────────────────
|
|
30
37
|
// Configuration
|
|
@@ -57,7 +64,7 @@ initVersionControl({
|
|
|
57
64
|
const server = new Server(
|
|
58
65
|
{
|
|
59
66
|
name: '@pagelines/n8n-mcp',
|
|
60
|
-
version: '0.
|
|
67
|
+
version: '0.3.0',
|
|
61
68
|
},
|
|
62
69
|
{
|
|
63
70
|
capabilities: {
|
|
@@ -81,7 +88,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
81
88
|
content: [
|
|
82
89
|
{
|
|
83
90
|
type: 'text',
|
|
84
|
-
|
|
91
|
+
// Use minified JSON to reduce token usage
|
|
92
|
+
text: typeof result === 'string' ? result : stringifyResponse(result),
|
|
85
93
|
},
|
|
86
94
|
],
|
|
87
95
|
};
|
|
@@ -124,18 +132,40 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
124
132
|
|
|
125
133
|
case 'workflow_get': {
|
|
126
134
|
const workflow = await client.getWorkflow(args.id as string);
|
|
127
|
-
|
|
135
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
136
|
+
return formatWorkflowResponse(workflow, format);
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
case 'workflow_create': {
|
|
131
|
-
const
|
|
140
|
+
const inputNodes = args.nodes as Array<{
|
|
132
141
|
name: string;
|
|
133
142
|
type: string;
|
|
134
143
|
typeVersion: number;
|
|
135
144
|
position: [number, number];
|
|
136
145
|
parameters: Record<string, unknown>;
|
|
137
146
|
credentials?: Record<string, { id: string; name: string }>;
|
|
138
|
-
}
|
|
147
|
+
}>;
|
|
148
|
+
|
|
149
|
+
// Validate node types BEFORE creating workflow
|
|
150
|
+
const availableTypes = await client.listNodeTypes();
|
|
151
|
+
const validTypeSet = new Set(availableTypes.map((nt) => nt.name));
|
|
152
|
+
const typeErrors = validateNodeTypes(inputNodes, validTypeSet);
|
|
153
|
+
|
|
154
|
+
if (typeErrors.length > 0) {
|
|
155
|
+
const errorMessages = typeErrors.map((e) => {
|
|
156
|
+
let msg = e.message;
|
|
157
|
+
if (e.suggestions && e.suggestions.length > 0) {
|
|
158
|
+
msg += `. Did you mean: ${e.suggestions.join(', ')}?`;
|
|
159
|
+
}
|
|
160
|
+
return msg;
|
|
161
|
+
});
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Invalid node types detected:\n${errorMessages.join('\n')}\n\n` +
|
|
164
|
+
`Use node_types_list to discover available node types.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const nodes = inputNodes.map((n, i) => ({
|
|
139
169
|
id: crypto.randomUUID(),
|
|
140
170
|
name: n.name,
|
|
141
171
|
type: n.type,
|
|
@@ -145,40 +175,100 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
145
175
|
...(n.credentials && { credentials: n.credentials }),
|
|
146
176
|
}));
|
|
147
177
|
|
|
148
|
-
|
|
178
|
+
let workflow = await client.createWorkflow({
|
|
149
179
|
name: args.name as string,
|
|
150
180
|
nodes,
|
|
151
181
|
connections: (args.connections as N8nConnections) || {},
|
|
152
182
|
settings: args.settings as Record<string, unknown>,
|
|
153
183
|
});
|
|
154
184
|
|
|
155
|
-
// Validate
|
|
185
|
+
// Validate and auto-cleanup
|
|
156
186
|
const validation = validateWorkflow(workflow);
|
|
187
|
+
const autofix = autofixWorkflow(workflow, validation.warnings);
|
|
188
|
+
let formatted = formatWorkflow(autofix.workflow);
|
|
189
|
+
|
|
190
|
+
// Apply cleanup if there were fixes or formatting changes
|
|
191
|
+
if (autofix.fixes.length > 0 || JSON.stringify(workflow) !== JSON.stringify(formatted)) {
|
|
192
|
+
workflow = await client.updateWorkflow(workflow.id, formatted);
|
|
193
|
+
formatted = workflow;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
157
197
|
|
|
158
198
|
return {
|
|
159
|
-
workflow,
|
|
160
|
-
validation
|
|
199
|
+
workflow: formatWorkflowResponse(formatted, format),
|
|
200
|
+
validation: {
|
|
201
|
+
...validation,
|
|
202
|
+
warnings: autofix.unfixable, // Only show unfixable warnings
|
|
203
|
+
},
|
|
204
|
+
autoFixed: autofix.fixes.length > 0 ? autofix.fixes : undefined,
|
|
161
205
|
};
|
|
162
206
|
}
|
|
163
207
|
|
|
164
208
|
case 'workflow_update': {
|
|
209
|
+
const operations = args.operations as PatchOperation[];
|
|
210
|
+
|
|
211
|
+
// Extract addNode operations that need validation
|
|
212
|
+
const addNodeOps = operations.filter(
|
|
213
|
+
(op): op is Extract<PatchOperation, { type: 'addNode' }> =>
|
|
214
|
+
op.type === 'addNode'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (addNodeOps.length > 0) {
|
|
218
|
+
// Fetch available types and validate
|
|
219
|
+
const availableTypes = await client.listNodeTypes();
|
|
220
|
+
const validTypeSet = new Set(availableTypes.map((nt) => nt.name));
|
|
221
|
+
const nodesToValidate = addNodeOps.map((op) => ({
|
|
222
|
+
name: op.node.name,
|
|
223
|
+
type: op.node.type,
|
|
224
|
+
}));
|
|
225
|
+
const typeErrors = validateNodeTypes(nodesToValidate, validTypeSet);
|
|
226
|
+
|
|
227
|
+
if (typeErrors.length > 0) {
|
|
228
|
+
const errorMessages = typeErrors.map((e) => {
|
|
229
|
+
let msg = e.message;
|
|
230
|
+
if (e.suggestions && e.suggestions.length > 0) {
|
|
231
|
+
msg += `. Did you mean: ${e.suggestions.join(', ')}?`;
|
|
232
|
+
}
|
|
233
|
+
return msg;
|
|
234
|
+
});
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Invalid node types in addNode operations:\n${errorMessages.join('\n')}\n\n` +
|
|
237
|
+
`Use node_types_list to discover available node types.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
165
242
|
// Save version before updating
|
|
166
243
|
const currentWorkflow = await client.getWorkflow(args.id as string);
|
|
167
244
|
const versionSaved = await saveVersion(currentWorkflow, 'before_update');
|
|
168
245
|
|
|
169
|
-
|
|
170
|
-
const { workflow, warnings } = await client.patchWorkflow(
|
|
246
|
+
let { workflow, warnings } = await client.patchWorkflow(
|
|
171
247
|
args.id as string,
|
|
172
248
|
operations
|
|
173
249
|
);
|
|
174
250
|
|
|
175
|
-
//
|
|
251
|
+
// Validate and auto-cleanup
|
|
176
252
|
const validation = validateWorkflow(workflow);
|
|
253
|
+
const autofix = autofixWorkflow(workflow, validation.warnings);
|
|
254
|
+
let formatted = formatWorkflow(autofix.workflow);
|
|
255
|
+
|
|
256
|
+
// Apply cleanup if there were fixes or formatting changes
|
|
257
|
+
if (autofix.fixes.length > 0 || JSON.stringify(workflow) !== JSON.stringify(formatted)) {
|
|
258
|
+
workflow = await client.updateWorkflow(args.id as string, formatted);
|
|
259
|
+
formatted = workflow;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
177
263
|
|
|
178
264
|
return {
|
|
179
|
-
workflow,
|
|
265
|
+
workflow: formatWorkflowResponse(formatted, format),
|
|
180
266
|
patchWarnings: warnings,
|
|
181
|
-
validation
|
|
267
|
+
validation: {
|
|
268
|
+
...validation,
|
|
269
|
+
warnings: autofix.unfixable, // Only show unfixable warnings
|
|
270
|
+
},
|
|
271
|
+
autoFixed: autofix.fixes.length > 0 ? autofix.fixes : undefined,
|
|
182
272
|
versionSaved: versionSaved ? versionSaved.id : null,
|
|
183
273
|
};
|
|
184
274
|
}
|
|
@@ -221,15 +311,17 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
221
311
|
status: args.status as 'success' | 'error' | 'waiting' | undefined,
|
|
222
312
|
limit: (args.limit as number) || 20,
|
|
223
313
|
});
|
|
314
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
224
315
|
return {
|
|
225
|
-
executions: response.data,
|
|
316
|
+
executions: formatExecutionListResponse(response.data, format),
|
|
226
317
|
total: response.data.length,
|
|
227
318
|
};
|
|
228
319
|
}
|
|
229
320
|
|
|
230
321
|
case 'execution_get': {
|
|
231
322
|
const execution = await client.getExecution(args.id as string);
|
|
232
|
-
|
|
323
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
324
|
+
return formatExecutionResponse(execution, format);
|
|
233
325
|
}
|
|
234
326
|
|
|
235
327
|
// Validation & Quality
|
|
@@ -296,6 +388,48 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
296
388
|
};
|
|
297
389
|
}
|
|
298
390
|
|
|
391
|
+
// Node Discovery
|
|
392
|
+
case 'node_types_list': {
|
|
393
|
+
const nodeTypes = await client.listNodeTypes();
|
|
394
|
+
const search = (args.search as string)?.toLowerCase();
|
|
395
|
+
const category = args.category as string;
|
|
396
|
+
const limit = (args.limit as number) || 50;
|
|
397
|
+
|
|
398
|
+
let results: N8nNodeTypeSummary[] = nodeTypes.map((nt) => ({
|
|
399
|
+
type: nt.name,
|
|
400
|
+
name: nt.displayName,
|
|
401
|
+
description: nt.description,
|
|
402
|
+
category: nt.codex?.categories?.[0] || nt.group?.[0] || 'Other',
|
|
403
|
+
version: nt.version,
|
|
404
|
+
}));
|
|
405
|
+
|
|
406
|
+
// Apply search filter
|
|
407
|
+
if (search) {
|
|
408
|
+
results = results.filter(
|
|
409
|
+
(nt) =>
|
|
410
|
+
nt.type.toLowerCase().includes(search) ||
|
|
411
|
+
nt.name.toLowerCase().includes(search) ||
|
|
412
|
+
nt.description.toLowerCase().includes(search)
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Apply category filter
|
|
417
|
+
if (category) {
|
|
418
|
+
results = results.filter((nt) =>
|
|
419
|
+
nt.category.toLowerCase().includes(category.toLowerCase())
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Apply limit
|
|
424
|
+
results = results.slice(0, limit);
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
nodeTypes: results,
|
|
428
|
+
total: results.length,
|
|
429
|
+
hint: 'Use the "type" field value when creating nodes (e.g., "n8n-nodes-base.webhook")',
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
299
433
|
// Version Control
|
|
300
434
|
case 'version_list': {
|
|
301
435
|
const versions = await listVersions(args.workflowId as string);
|
|
@@ -314,7 +448,11 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
314
448
|
if (!version) {
|
|
315
449
|
throw new Error(`Version ${args.versionId} not found`);
|
|
316
450
|
}
|
|
317
|
-
|
|
451
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
452
|
+
return {
|
|
453
|
+
meta: version.meta,
|
|
454
|
+
workflow: formatWorkflowResponse(version.workflow, format),
|
|
455
|
+
};
|
|
318
456
|
}
|
|
319
457
|
|
|
320
458
|
case 'version_save': {
|
|
@@ -344,11 +482,12 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
344
482
|
|
|
345
483
|
// Apply the old version
|
|
346
484
|
await client.updateWorkflow(args.workflowId as string, version.workflow);
|
|
485
|
+
const format = (args.format as ResponseFormat) || 'compact';
|
|
347
486
|
|
|
348
487
|
return {
|
|
349
488
|
success: true,
|
|
350
489
|
restoredVersion: version.meta,
|
|
351
|
-
workflow: version.workflow,
|
|
490
|
+
workflow: formatWorkflowResponse(version.workflow, format),
|
|
352
491
|
};
|
|
353
492
|
}
|
|
354
493
|
|
package/src/n8n-client.test.ts
CHANGED
|
@@ -224,4 +224,139 @@ describe('N8nClient', () => {
|
|
|
224
224
|
await expect(client.getWorkflow('999')).rejects.toThrow('n8n API error (404)');
|
|
225
225
|
});
|
|
226
226
|
});
|
|
227
|
+
|
|
228
|
+
describe('listNodeTypes', () => {
|
|
229
|
+
it('calls correct endpoint', async () => {
|
|
230
|
+
const mockNodeTypes = [
|
|
231
|
+
{
|
|
232
|
+
name: 'n8n-nodes-base.webhook',
|
|
233
|
+
displayName: 'Webhook',
|
|
234
|
+
description: 'Starts workflow on webhook call',
|
|
235
|
+
group: ['trigger'],
|
|
236
|
+
version: 2,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'n8n-nodes-base.set',
|
|
240
|
+
displayName: 'Set',
|
|
241
|
+
description: 'Set values',
|
|
242
|
+
group: ['transform'],
|
|
243
|
+
version: 3,
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
mockFetch.mockResolvedValueOnce({
|
|
248
|
+
ok: true,
|
|
249
|
+
text: async () => JSON.stringify(mockNodeTypes),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const result = await client.listNodeTypes();
|
|
253
|
+
|
|
254
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
255
|
+
'https://n8n.example.com/api/v1/nodes',
|
|
256
|
+
expect.objectContaining({
|
|
257
|
+
method: 'GET',
|
|
258
|
+
headers: expect.objectContaining({
|
|
259
|
+
'X-N8N-API-KEY': 'test-api-key',
|
|
260
|
+
}),
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(result).toHaveLength(2);
|
|
265
|
+
expect(result[0].name).toBe('n8n-nodes-base.webhook');
|
|
266
|
+
expect(result[1].name).toBe('n8n-nodes-base.set');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('updateWorkflow', () => {
|
|
271
|
+
it('strips disallowed properties before sending to API', async () => {
|
|
272
|
+
const fullWorkflow = {
|
|
273
|
+
id: '123',
|
|
274
|
+
name: 'test_workflow',
|
|
275
|
+
active: true,
|
|
276
|
+
nodes: [{ id: 'n1', name: 'node1', type: 'test', typeVersion: 1, position: [0, 0] as [number, number], parameters: {} }],
|
|
277
|
+
connections: {},
|
|
278
|
+
settings: { timezone: 'UTC' },
|
|
279
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
280
|
+
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
281
|
+
versionId: 'v1',
|
|
282
|
+
staticData: undefined,
|
|
283
|
+
tags: [{ id: 't1', name: 'tag1' }],
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
mockFetch.mockResolvedValueOnce({
|
|
287
|
+
ok: true,
|
|
288
|
+
text: async () => JSON.stringify(fullWorkflow),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await client.updateWorkflow('123', fullWorkflow);
|
|
292
|
+
|
|
293
|
+
// Verify the request body does NOT contain disallowed properties
|
|
294
|
+
const putCall = mockFetch.mock.calls[0];
|
|
295
|
+
const putBody = JSON.parse(putCall[1].body);
|
|
296
|
+
|
|
297
|
+
// These should be stripped
|
|
298
|
+
expect(putBody.id).toBeUndefined();
|
|
299
|
+
expect(putBody.createdAt).toBeUndefined();
|
|
300
|
+
expect(putBody.updatedAt).toBeUndefined();
|
|
301
|
+
expect(putBody.active).toBeUndefined();
|
|
302
|
+
expect(putBody.versionId).toBeUndefined();
|
|
303
|
+
|
|
304
|
+
// These should be preserved
|
|
305
|
+
expect(putBody.name).toBe('test_workflow');
|
|
306
|
+
expect(putBody.nodes).toHaveLength(1);
|
|
307
|
+
expect(putBody.connections).toEqual({});
|
|
308
|
+
expect(putBody.settings).toEqual({ timezone: 'UTC' });
|
|
309
|
+
expect(putBody.staticData).toBeUndefined();
|
|
310
|
+
expect(putBody.tags).toEqual([{ id: 't1', name: 'tag1' }]);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('works with partial workflow (only some fields)', async () => {
|
|
314
|
+
mockFetch.mockResolvedValueOnce({
|
|
315
|
+
ok: true,
|
|
316
|
+
text: async () => JSON.stringify({ id: '123', name: 'updated' }),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await client.updateWorkflow('123', { name: 'updated', nodes: [] });
|
|
320
|
+
|
|
321
|
+
const putCall = mockFetch.mock.calls[0];
|
|
322
|
+
const putBody = JSON.parse(putCall[1].body);
|
|
323
|
+
|
|
324
|
+
expect(putBody.name).toBe('updated');
|
|
325
|
+
expect(putBody.nodes).toEqual([]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('handles workflow from formatWorkflow (simulating workflow_format apply)', async () => {
|
|
329
|
+
// This simulates the exact scenario that caused the bug:
|
|
330
|
+
// workflow_format returns a full N8nWorkflow object with id, createdAt, etc.
|
|
331
|
+
const formattedWorkflow = {
|
|
332
|
+
id: 'zbB1fCxWgZXgpjB1',
|
|
333
|
+
name: 'my_workflow',
|
|
334
|
+
active: false,
|
|
335
|
+
nodes: [],
|
|
336
|
+
connections: {},
|
|
337
|
+
createdAt: '2024-01-01T00:00:00.000Z',
|
|
338
|
+
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
mockFetch.mockResolvedValueOnce({
|
|
342
|
+
ok: true,
|
|
343
|
+
text: async () => JSON.stringify(formattedWorkflow),
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// This should NOT throw "must NOT have additional properties"
|
|
347
|
+
await client.updateWorkflow('zbB1fCxWgZXgpjB1', formattedWorkflow);
|
|
348
|
+
|
|
349
|
+
const putCall = mockFetch.mock.calls[0];
|
|
350
|
+
const putBody = JSON.parse(putCall[1].body);
|
|
351
|
+
|
|
352
|
+
// Critical: these must NOT be in the request body
|
|
353
|
+
expect(putBody.id).toBeUndefined();
|
|
354
|
+
expect(putBody.createdAt).toBeUndefined();
|
|
355
|
+
expect(putBody.updatedAt).toBeUndefined();
|
|
356
|
+
expect(putBody.active).toBeUndefined();
|
|
357
|
+
|
|
358
|
+
// Only allowed properties should be sent
|
|
359
|
+
expect(Object.keys(putBody).sort()).toEqual(['connections', 'name', 'nodes']);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
227
362
|
});
|
package/src/n8n-client.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
N8nExecutionListItem,
|
|
11
11
|
N8nListResponse,
|
|
12
12
|
N8nNode,
|
|
13
|
+
N8nNodeType,
|
|
13
14
|
PatchOperation,
|
|
14
15
|
} from './types.js';
|
|
15
16
|
|
|
@@ -95,9 +96,11 @@ export class N8nClient {
|
|
|
95
96
|
|
|
96
97
|
async updateWorkflow(
|
|
97
98
|
id: string,
|
|
98
|
-
workflow: Partial<
|
|
99
|
+
workflow: Partial<N8nWorkflow>
|
|
99
100
|
): Promise<N8nWorkflow> {
|
|
100
|
-
|
|
101
|
+
// Strip properties that n8n API doesn't accept on PUT
|
|
102
|
+
const { id: _id, createdAt, updatedAt, active, versionId, ...allowed } = workflow as any;
|
|
103
|
+
return this.request('PUT', `/api/v1/workflows/${id}`, allowed);
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
async deleteWorkflow(id: string): Promise<void> {
|
|
@@ -358,4 +361,12 @@ export class N8nClient {
|
|
|
358
361
|
};
|
|
359
362
|
}
|
|
360
363
|
}
|
|
364
|
+
|
|
365
|
+
// ─────────────────────────────────────────────────────────────
|
|
366
|
+
// Node Types
|
|
367
|
+
// ─────────────────────────────────────────────────────────────
|
|
368
|
+
|
|
369
|
+
async listNodeTypes(): Promise<N8nNodeType[]> {
|
|
370
|
+
return this.request<N8nNodeType[]>('GET', '/api/v1/nodes');
|
|
371
|
+
}
|
|
361
372
|
}
|