@atom8n/n8n 2.5.1 → 2.5.3
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/dist/build.tsbuildinfo +1 -1
- package/dist/commands/mcp.js +58 -10
- package/dist/controllers/cli.controller.js +19 -12
- package/dist/typecheck.tsbuildinfo +1 -1
- package/package.json +21 -21
package/dist/commands/mcp.js
CHANGED
|
@@ -98,7 +98,7 @@ let Mcp = class Mcp extends base_command_1.BaseCommand {
|
|
|
98
98
|
this.sanitizeToolName(path_1.default.basename(workflows[0].filePath, '.n8n'))
|
|
99
99
|
: 'n8n MCP Server';
|
|
100
100
|
const server = new McpServer({
|
|
101
|
-
name:
|
|
101
|
+
name: 'n8n MCP Server',
|
|
102
102
|
version: '1.0.0',
|
|
103
103
|
});
|
|
104
104
|
const usedToolNames = new Set();
|
|
@@ -116,20 +116,65 @@ let Mcp = class Mcp extends base_command_1.BaseCommand {
|
|
|
116
116
|
node.type === 'n8n-nodes-base.start');
|
|
117
117
|
const triggerType = triggerNode?.type ?? 'unknown';
|
|
118
118
|
const isChatTrigger = triggerType === '@n8n/n8n-nodes-langchain.chatTrigger';
|
|
119
|
+
const isSubFlowTrigger = triggerType === 'n8n-nodes-base.executeWorkflowTrigger';
|
|
119
120
|
this.logStderr(`[mcp] Registering tool "${toolName}" (trigger: ${triggerType})`);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
let inputSchemaShape;
|
|
122
|
+
let description;
|
|
123
|
+
if (isSubFlowTrigger) {
|
|
124
|
+
const params = triggerNode?.parameters;
|
|
125
|
+
const workflowInputs = (params?.workflowInputs?.values ??
|
|
126
|
+
[]);
|
|
127
|
+
inputSchemaShape = {};
|
|
128
|
+
for (const field of workflowInputs) {
|
|
129
|
+
const name = field.name;
|
|
130
|
+
if (!name)
|
|
131
|
+
continue;
|
|
132
|
+
let zodType;
|
|
133
|
+
switch (field.type) {
|
|
134
|
+
case 'number':
|
|
135
|
+
zodType = zod_1.z.number();
|
|
136
|
+
break;
|
|
137
|
+
case 'boolean':
|
|
138
|
+
zodType = zod_1.z.boolean();
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
zodType = zod_1.z.string();
|
|
142
|
+
}
|
|
143
|
+
if (field.defaultValue !== undefined && field.defaultValue !== '') {
|
|
144
|
+
zodType = zodType.optional().describe(`${name} (default: ${field.defaultValue})`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
zodType = zodType.describe(name);
|
|
148
|
+
}
|
|
149
|
+
inputSchemaShape[name] = zodType;
|
|
150
|
+
}
|
|
151
|
+
if (Object.keys(inputSchemaShape).length === 0) {
|
|
152
|
+
inputSchemaShape = {
|
|
153
|
+
input: zod_1.z.string().optional().describe('Input data for the workflow'),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
description = `Execute the n8n workflow "${workflowData.name}". Provide the required input parameters.`;
|
|
157
|
+
this.logStderr(`[mcp] Sub-flow inputs: ${workflowInputs.map((f) => f.name).join(', ') || '(none)'}`);
|
|
158
|
+
}
|
|
159
|
+
else if (isChatTrigger) {
|
|
160
|
+
inputSchemaShape = { input: zod_1.z.string().describe('Input text for the chat workflow') };
|
|
161
|
+
description = `Execute the n8n workflow "${workflowData.name}". This is a chat-based workflow — provide input text.`;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
inputSchemaShape = {
|
|
165
|
+
input: zod_1.z.string().optional().describe('Input text or data for the workflow trigger'),
|
|
166
|
+
};
|
|
167
|
+
description = `Execute the n8n workflow "${workflowData.name}". Provide optional input text for the workflow trigger.`;
|
|
168
|
+
}
|
|
123
169
|
const wfData = workflowData;
|
|
124
170
|
const wfIsChatTrigger = isChatTrigger;
|
|
171
|
+
const wfIsSubFlowTrigger = isSubFlowTrigger;
|
|
125
172
|
const wfToolName = toolName;
|
|
126
173
|
server.registerTool(toolName, {
|
|
127
|
-
description
|
|
128
|
-
? 'This is a chat-based workflow — provide input text.'
|
|
129
|
-
: 'Provide optional input text for the workflow trigger.'}`,
|
|
174
|
+
description,
|
|
130
175
|
inputSchema: inputSchemaShape,
|
|
131
176
|
}, async (args) => {
|
|
132
|
-
return await this.executeWorkflowTool(wfToolName, wfData, wfIsChatTrigger, serverUrl, args);
|
|
177
|
+
return await this.executeWorkflowTool(wfToolName, wfData, wfIsChatTrigger, wfIsSubFlowTrigger, serverUrl, args);
|
|
133
178
|
});
|
|
134
179
|
}
|
|
135
180
|
this.logStderr(`[mcp] Registered ${usedToolNames.size} tool(s): ${[...usedToolNames].join(', ')}`);
|
|
@@ -149,7 +194,7 @@ let Mcp = class Mcp extends base_command_1.BaseCommand {
|
|
|
149
194
|
});
|
|
150
195
|
this.logStderr(`[mcp] Server stopped.`);
|
|
151
196
|
}
|
|
152
|
-
async executeWorkflowTool(toolName, workflowData, isChatTrigger, serverUrl, args) {
|
|
197
|
+
async executeWorkflowTool(toolName, workflowData, isChatTrigger, isSubFlowTrigger, serverUrl, args) {
|
|
153
198
|
this.logStderr(`[mcp] ── TOOL CALL: "${toolName}" ──`);
|
|
154
199
|
this.logStderr(`[mcp] Input: ${JSON.stringify(args)}`);
|
|
155
200
|
try {
|
|
@@ -162,7 +207,10 @@ let Mcp = class Mcp extends base_command_1.BaseCommand {
|
|
|
162
207
|
const executeUrl = `${serverUrl}/rest/cli/run`;
|
|
163
208
|
this.logStderr(`[mcp] POST ${executeUrl}`);
|
|
164
209
|
const requestBody = { workflowData };
|
|
165
|
-
if (
|
|
210
|
+
if (isSubFlowTrigger) {
|
|
211
|
+
requestBody.inputData = args;
|
|
212
|
+
}
|
|
213
|
+
else if (args.input !== undefined) {
|
|
166
214
|
if (isChatTrigger) {
|
|
167
215
|
requestBody.chatInput = String(args.input);
|
|
168
216
|
}
|
|
@@ -58,6 +58,7 @@ let CliController = CliController_1 = class CliController {
|
|
|
58
58
|
const fileData = body.workflowData;
|
|
59
59
|
const chatInput = body.chatInput;
|
|
60
60
|
const inputData = body.inputData;
|
|
61
|
+
const fileModifiedAt = body.fileModifiedAt;
|
|
61
62
|
this.logger.info(`[cli] inputData: ${inputData ? JSON.stringify(inputData) : '(none)'}`);
|
|
62
63
|
if (!fileData.nodes || !Array.isArray(fileData.nodes)) {
|
|
63
64
|
res.status(400).json({ error: 'Workflow does not contain valid nodes' });
|
|
@@ -70,7 +71,7 @@ let CliController = CliController_1 = class CliController {
|
|
|
70
71
|
this.logger.info(`[cli] Workflow: "${fileData.name}", Nodes: ${fileData.nodes.length}`);
|
|
71
72
|
try {
|
|
72
73
|
const user = await this.ownershipService.getInstanceOwner();
|
|
73
|
-
const workflowId = await this.syncWorkflow(fileData, user.id);
|
|
74
|
+
const workflowId = await this.syncWorkflow(fileData, user.id, fileModifiedAt);
|
|
74
75
|
const workflow = await this.workflowRepository.findOneBy({ id: workflowId });
|
|
75
76
|
if (!workflow) {
|
|
76
77
|
res.status(500).json({ error: 'Failed to sync workflow to database' });
|
|
@@ -228,17 +229,19 @@ let CliController = CliController_1 = class CliController {
|
|
|
228
229
|
});
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
|
-
async syncWorkflow(fileData, userId) {
|
|
232
|
+
async syncWorkflow(fileData, userId, fileModifiedAt) {
|
|
232
233
|
if (fileData.id && (0, utils_1.isWorkflowIdValid)(fileData.id)) {
|
|
233
234
|
const existing = await this.workflowRepository.findOneBy({ id: fileData.id });
|
|
234
235
|
if (existing) {
|
|
235
|
-
const fileUpdatedAt =
|
|
236
|
-
? new Date(fileData.updatedAt)
|
|
237
|
-
: null;
|
|
236
|
+
const fileUpdatedAt = fileModifiedAt ? new Date(fileModifiedAt) : null;
|
|
238
237
|
const serverUpdatedAt = existing.updatedAt
|
|
239
238
|
? new Date(existing.updatedAt)
|
|
240
239
|
: null;
|
|
241
|
-
|
|
240
|
+
this.logger.info(`[cli] Found existing workflow (ID match): ${existing.id}, ` +
|
|
241
|
+
`fileUpdatedAt=${fileUpdatedAt?.toISOString() ?? 'null'}, ` +
|
|
242
|
+
`serverUpdatedAt=${serverUpdatedAt?.toISOString() ?? 'null'}`);
|
|
243
|
+
const shouldUpdate = !fileUpdatedAt || !serverUpdatedAt || fileUpdatedAt >= serverUpdatedAt;
|
|
244
|
+
if (shouldUpdate) {
|
|
242
245
|
this.logger.info(`[cli] Updating existing workflow (ID match): ${existing.id}`);
|
|
243
246
|
await this.workflowRepository.update(existing.id, {
|
|
244
247
|
nodes: fileData.nodes,
|
|
@@ -249,7 +252,8 @@ let CliController = CliController_1 = class CliController {
|
|
|
249
252
|
});
|
|
250
253
|
}
|
|
251
254
|
else {
|
|
252
|
-
this.logger.info(`[cli] Using existing workflow (ID match): ${existing.id}`
|
|
255
|
+
this.logger.info(`[cli] Using existing workflow (ID match): ${existing.id} ` +
|
|
256
|
+
`(server is newer: ${serverUpdatedAt.toISOString()} > ${fileUpdatedAt.toISOString()})`);
|
|
253
257
|
}
|
|
254
258
|
return existing.id;
|
|
255
259
|
}
|
|
@@ -257,13 +261,15 @@ let CliController = CliController_1 = class CliController {
|
|
|
257
261
|
if (fileData.name) {
|
|
258
262
|
const existing = await this.workflowRepository.findOneBy({ name: fileData.name });
|
|
259
263
|
if (existing) {
|
|
260
|
-
const fileUpdatedAt =
|
|
261
|
-
? new Date(fileData.updatedAt)
|
|
262
|
-
: null;
|
|
264
|
+
const fileUpdatedAt = fileModifiedAt ? new Date(fileModifiedAt) : null;
|
|
263
265
|
const serverUpdatedAt = existing.updatedAt
|
|
264
266
|
? new Date(existing.updatedAt)
|
|
265
267
|
: null;
|
|
266
|
-
|
|
268
|
+
this.logger.info(`[cli] Found existing workflow (name match): ${existing.id}, ` +
|
|
269
|
+
`fileUpdatedAt=${fileUpdatedAt?.toISOString() ?? 'null'}, ` +
|
|
270
|
+
`serverUpdatedAt=${serverUpdatedAt?.toISOString() ?? 'null'}`);
|
|
271
|
+
const shouldUpdate = !fileUpdatedAt || !serverUpdatedAt || fileUpdatedAt >= serverUpdatedAt;
|
|
272
|
+
if (shouldUpdate) {
|
|
267
273
|
this.logger.info(`[cli] Updating existing workflow (name match): ${existing.id}`);
|
|
268
274
|
await this.workflowRepository.update(existing.id, {
|
|
269
275
|
nodes: fileData.nodes,
|
|
@@ -274,7 +280,8 @@ let CliController = CliController_1 = class CliController {
|
|
|
274
280
|
});
|
|
275
281
|
}
|
|
276
282
|
else {
|
|
277
|
-
this.logger.info(`[cli] Using existing workflow (name match): ${existing.id}`
|
|
283
|
+
this.logger.info(`[cli] Using existing workflow (name match): ${existing.id} ` +
|
|
284
|
+
`(server is newer: ${serverUpdatedAt.toISOString()} > ${fileUpdatedAt.toISOString()})`);
|
|
278
285
|
}
|
|
279
286
|
return existing.id;
|
|
280
287
|
}
|