@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/index.ts
DELETED
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* @pagelines/n8n-mcp
|
|
4
|
-
* Opinionated MCP server for n8n workflow automation
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
8
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
-
import {
|
|
10
|
-
CallToolRequestSchema,
|
|
11
|
-
ListToolsRequestSchema,
|
|
12
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
-
|
|
14
|
-
import { N8nClient } from './n8n-client.js';
|
|
15
|
-
import { tools } from './tools.js';
|
|
16
|
-
import { validateWorkflow, validateNodeTypes } from './validators.js';
|
|
17
|
-
import { validateExpressions, checkCircularReferences } from './expressions.js';
|
|
18
|
-
import { autofixWorkflow, formatWorkflow } from './autofix.js';
|
|
19
|
-
import {
|
|
20
|
-
initVersionControl,
|
|
21
|
-
saveVersion,
|
|
22
|
-
listVersions,
|
|
23
|
-
getVersion,
|
|
24
|
-
diffWorkflows,
|
|
25
|
-
getVersionStats,
|
|
26
|
-
} from './versions.js';
|
|
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';
|
|
35
|
-
|
|
36
|
-
// ─────────────────────────────────────────────────────────────
|
|
37
|
-
// Configuration
|
|
38
|
-
// ─────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
const N8N_API_URL = process.env.N8N_API_URL || process.env.N8N_HOST || '';
|
|
41
|
-
const N8N_API_KEY = process.env.N8N_API_KEY || '';
|
|
42
|
-
|
|
43
|
-
if (!N8N_API_URL || !N8N_API_KEY) {
|
|
44
|
-
console.error('Error: N8N_API_URL and N8N_API_KEY environment variables are required');
|
|
45
|
-
console.error('Set them in your MCP server configuration or environment');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const client = new N8nClient({
|
|
50
|
-
apiUrl: N8N_API_URL,
|
|
51
|
-
apiKey: N8N_API_KEY,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Initialize version control
|
|
55
|
-
initVersionControl({
|
|
56
|
-
enabled: process.env.N8N_MCP_VERSIONS !== 'false',
|
|
57
|
-
maxVersions: parseInt(process.env.N8N_MCP_MAX_VERSIONS || '20', 10),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// ─────────────────────────────────────────────────────────────
|
|
61
|
-
// MCP Server
|
|
62
|
-
// ─────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
const server = new Server(
|
|
65
|
-
{
|
|
66
|
-
name: '@pagelines/n8n-mcp',
|
|
67
|
-
version: '0.3.1',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
capabilities: {
|
|
71
|
-
tools: {},
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// List available tools
|
|
77
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
78
|
-
tools,
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
// Handle tool calls
|
|
82
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
83
|
-
const { name, arguments: args } = request.params;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const result = await handleTool(name, args || {});
|
|
87
|
-
return {
|
|
88
|
-
content: [
|
|
89
|
-
{
|
|
90
|
-
type: 'text',
|
|
91
|
-
// Use minified JSON to reduce token usage
|
|
92
|
-
text: typeof result === 'string' ? result : stringifyResponse(result),
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
96
|
-
} catch (error) {
|
|
97
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
-
return {
|
|
99
|
-
content: [
|
|
100
|
-
{
|
|
101
|
-
type: 'text',
|
|
102
|
-
text: `Error: ${message}`,
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
isError: true,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// ─────────────────────────────────────────────────────────────
|
|
111
|
-
// Tool Handlers
|
|
112
|
-
// ─────────────────────────────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
async function handleTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
115
|
-
switch (name) {
|
|
116
|
-
// Workflow operations
|
|
117
|
-
case 'workflow_list': {
|
|
118
|
-
const response = await client.listWorkflows({
|
|
119
|
-
active: args.active as boolean | undefined,
|
|
120
|
-
limit: (args.limit as number) || 100,
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
workflows: response.data.map((w) => ({
|
|
124
|
-
id: w.id,
|
|
125
|
-
name: w.name,
|
|
126
|
-
active: w.active,
|
|
127
|
-
updatedAt: w.updatedAt,
|
|
128
|
-
})),
|
|
129
|
-
total: response.data.length,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
case 'workflow_get': {
|
|
134
|
-
const workflow = await client.getWorkflow(args.id as string);
|
|
135
|
-
const format = (args.format as ResponseFormat) || 'compact';
|
|
136
|
-
return formatWorkflowResponse(workflow, format);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
case 'workflow_create': {
|
|
140
|
-
const inputNodes = args.nodes as Array<{
|
|
141
|
-
name: string;
|
|
142
|
-
type: string;
|
|
143
|
-
typeVersion: number;
|
|
144
|
-
position: [number, number];
|
|
145
|
-
parameters: Record<string, unknown>;
|
|
146
|
-
credentials?: Record<string, { id: string; name: string }>;
|
|
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) => ({
|
|
169
|
-
id: crypto.randomUUID(),
|
|
170
|
-
name: n.name,
|
|
171
|
-
type: n.type,
|
|
172
|
-
typeVersion: n.typeVersion,
|
|
173
|
-
position: n.position || [250, 250 + i * 100],
|
|
174
|
-
parameters: n.parameters || {},
|
|
175
|
-
...(n.credentials && { credentials: n.credentials }),
|
|
176
|
-
}));
|
|
177
|
-
|
|
178
|
-
let workflow = await client.createWorkflow({
|
|
179
|
-
name: args.name as string,
|
|
180
|
-
nodes,
|
|
181
|
-
connections: (args.connections as N8nConnections) || {},
|
|
182
|
-
settings: args.settings as Record<string, unknown>,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Validate and auto-cleanup
|
|
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';
|
|
197
|
-
|
|
198
|
-
return {
|
|
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,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
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
|
-
|
|
242
|
-
// Save version before updating
|
|
243
|
-
const currentWorkflow = await client.getWorkflow(args.id as string);
|
|
244
|
-
const versionSaved = await saveVersion(currentWorkflow, 'before_update');
|
|
245
|
-
|
|
246
|
-
let { workflow, warnings } = await client.patchWorkflow(
|
|
247
|
-
args.id as string,
|
|
248
|
-
operations
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
// Validate and auto-cleanup
|
|
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';
|
|
263
|
-
|
|
264
|
-
return {
|
|
265
|
-
workflow: formatWorkflowResponse(formatted, format),
|
|
266
|
-
patchWarnings: warnings,
|
|
267
|
-
validation: {
|
|
268
|
-
...validation,
|
|
269
|
-
warnings: autofix.unfixable, // Only show unfixable warnings
|
|
270
|
-
},
|
|
271
|
-
autoFixed: autofix.fixes.length > 0 ? autofix.fixes : undefined,
|
|
272
|
-
versionSaved: versionSaved ? versionSaved.id : null,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
case 'workflow_delete': {
|
|
277
|
-
await client.deleteWorkflow(args.id as string);
|
|
278
|
-
return { success: true, message: `Workflow ${args.id} deleted` };
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
case 'workflow_activate': {
|
|
282
|
-
const workflow = await client.activateWorkflow(args.id as string);
|
|
283
|
-
return {
|
|
284
|
-
id: workflow.id,
|
|
285
|
-
name: workflow.name,
|
|
286
|
-
active: workflow.active,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
case 'workflow_deactivate': {
|
|
291
|
-
const workflow = await client.deactivateWorkflow(args.id as string);
|
|
292
|
-
return {
|
|
293
|
-
id: workflow.id,
|
|
294
|
-
name: workflow.name,
|
|
295
|
-
active: workflow.active,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
case 'workflow_execute': {
|
|
300
|
-
const result = await client.executeWorkflow(
|
|
301
|
-
args.id as string,
|
|
302
|
-
args.data as Record<string, unknown>
|
|
303
|
-
);
|
|
304
|
-
return result;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Execution operations
|
|
308
|
-
case 'execution_list': {
|
|
309
|
-
const response = await client.listExecutions({
|
|
310
|
-
workflowId: args.workflowId as string | undefined,
|
|
311
|
-
status: args.status as 'success' | 'error' | 'waiting' | undefined,
|
|
312
|
-
limit: (args.limit as number) || 20,
|
|
313
|
-
});
|
|
314
|
-
const format = (args.format as ResponseFormat) || 'compact';
|
|
315
|
-
return {
|
|
316
|
-
executions: formatExecutionListResponse(response.data, format),
|
|
317
|
-
total: response.data.length,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
case 'execution_get': {
|
|
322
|
-
const execution = await client.getExecution(args.id as string);
|
|
323
|
-
const format = (args.format as ResponseFormat) || 'compact';
|
|
324
|
-
return formatExecutionResponse(execution, format);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Validation & Quality
|
|
328
|
-
case 'workflow_validate': {
|
|
329
|
-
const workflow = await client.getWorkflow(args.id as string);
|
|
330
|
-
const validation = validateWorkflow(workflow);
|
|
331
|
-
const expressionIssues = validateExpressions(workflow);
|
|
332
|
-
const circularRefs = checkCircularReferences(workflow);
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
workflowId: workflow.id,
|
|
336
|
-
workflowName: workflow.name,
|
|
337
|
-
...validation,
|
|
338
|
-
expressionIssues,
|
|
339
|
-
circularReferences: circularRefs.length > 0 ? circularRefs : null,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
case 'workflow_autofix': {
|
|
344
|
-
const workflow = await client.getWorkflow(args.id as string);
|
|
345
|
-
const validation = validateWorkflow(workflow);
|
|
346
|
-
const result = autofixWorkflow(workflow, validation.warnings);
|
|
347
|
-
|
|
348
|
-
if (args.apply && result.fixes.length > 0) {
|
|
349
|
-
// Save version before applying fixes
|
|
350
|
-
await saveVersion(workflow, 'before_autofix');
|
|
351
|
-
|
|
352
|
-
// Apply the fixed workflow
|
|
353
|
-
await client.updateWorkflow(args.id as string, result.workflow);
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
applied: true,
|
|
357
|
-
fixes: result.fixes,
|
|
358
|
-
unfixable: result.unfixable,
|
|
359
|
-
workflow: result.workflow,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
applied: false,
|
|
365
|
-
fixes: result.fixes,
|
|
366
|
-
unfixable: result.unfixable,
|
|
367
|
-
previewWorkflow: result.workflow,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
case 'workflow_format': {
|
|
372
|
-
const workflow = await client.getWorkflow(args.id as string);
|
|
373
|
-
const formatted = formatWorkflow(workflow);
|
|
374
|
-
|
|
375
|
-
if (args.apply) {
|
|
376
|
-
await saveVersion(workflow, 'before_format');
|
|
377
|
-
await client.updateWorkflow(args.id as string, formatted);
|
|
378
|
-
|
|
379
|
-
return {
|
|
380
|
-
applied: true,
|
|
381
|
-
workflow: formatted,
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
applied: false,
|
|
387
|
-
previewWorkflow: formatted,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
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
|
-
|
|
433
|
-
// Version Control
|
|
434
|
-
case 'version_list': {
|
|
435
|
-
const versions = await listVersions(args.workflowId as string);
|
|
436
|
-
return {
|
|
437
|
-
workflowId: args.workflowId,
|
|
438
|
-
versions,
|
|
439
|
-
total: versions.length,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
case 'version_get': {
|
|
444
|
-
const version = await getVersion(
|
|
445
|
-
args.workflowId as string,
|
|
446
|
-
args.versionId as string
|
|
447
|
-
);
|
|
448
|
-
if (!version) {
|
|
449
|
-
throw new Error(`Version ${args.versionId} not found`);
|
|
450
|
-
}
|
|
451
|
-
const format = (args.format as ResponseFormat) || 'compact';
|
|
452
|
-
return {
|
|
453
|
-
meta: version.meta,
|
|
454
|
-
workflow: formatWorkflowResponse(version.workflow, format),
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
case 'version_save': {
|
|
459
|
-
const workflow = await client.getWorkflow(args.workflowId as string);
|
|
460
|
-
const version = await saveVersion(
|
|
461
|
-
workflow,
|
|
462
|
-
(args.reason as string) || 'manual'
|
|
463
|
-
);
|
|
464
|
-
if (!version) {
|
|
465
|
-
return { saved: false, message: 'No changes detected since last version' };
|
|
466
|
-
}
|
|
467
|
-
return { saved: true, version };
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
case 'version_rollback': {
|
|
471
|
-
const version = await getVersion(
|
|
472
|
-
args.workflowId as string,
|
|
473
|
-
args.versionId as string
|
|
474
|
-
);
|
|
475
|
-
if (!version) {
|
|
476
|
-
throw new Error(`Version ${args.versionId} not found`);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Save current state before rollback
|
|
480
|
-
const currentWorkflow = await client.getWorkflow(args.workflowId as string);
|
|
481
|
-
await saveVersion(currentWorkflow, 'before_rollback');
|
|
482
|
-
|
|
483
|
-
// Apply the old version
|
|
484
|
-
await client.updateWorkflow(args.workflowId as string, version.workflow);
|
|
485
|
-
const format = (args.format as ResponseFormat) || 'compact';
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
success: true,
|
|
489
|
-
restoredVersion: version.meta,
|
|
490
|
-
workflow: formatWorkflowResponse(version.workflow, format),
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
case 'version_diff': {
|
|
495
|
-
const toVersion = await getVersion(
|
|
496
|
-
args.workflowId as string,
|
|
497
|
-
args.toVersionId as string
|
|
498
|
-
);
|
|
499
|
-
if (!toVersion) {
|
|
500
|
-
throw new Error(`Version ${args.toVersionId} not found`);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
let fromWorkflow;
|
|
504
|
-
if (args.fromVersionId) {
|
|
505
|
-
const fromVersion = await getVersion(
|
|
506
|
-
args.workflowId as string,
|
|
507
|
-
args.fromVersionId as string
|
|
508
|
-
);
|
|
509
|
-
if (!fromVersion) {
|
|
510
|
-
throw new Error(`Version ${args.fromVersionId} not found`);
|
|
511
|
-
}
|
|
512
|
-
fromWorkflow = fromVersion.workflow;
|
|
513
|
-
} else {
|
|
514
|
-
// Compare against current workflow state
|
|
515
|
-
fromWorkflow = await client.getWorkflow(args.workflowId as string);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const diff = diffWorkflows(fromWorkflow, toVersion.workflow);
|
|
519
|
-
|
|
520
|
-
return {
|
|
521
|
-
from: args.fromVersionId || 'current',
|
|
522
|
-
to: args.toVersionId,
|
|
523
|
-
diff,
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
case 'version_stats': {
|
|
528
|
-
const stats = await getVersionStats();
|
|
529
|
-
return stats;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
default:
|
|
533
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// ─────────────────────────────────────────────────────────────
|
|
538
|
-
// Start Server
|
|
539
|
-
// ─────────────────────────────────────────────────────────────
|
|
540
|
-
|
|
541
|
-
async function main() {
|
|
542
|
-
const transport = new StdioServerTransport();
|
|
543
|
-
await server.connect(transport);
|
|
544
|
-
console.error('@pagelines/n8n-mcp server started');
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
main().catch((error) => {
|
|
548
|
-
console.error('Fatal error:', error);
|
|
549
|
-
process.exit(1);
|
|
550
|
-
});
|