@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.
@@ -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: serverName,
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
- const inputSchemaShape = isChatTrigger
121
- ? { input: zod_1.z.string().describe('Input text for the chat workflow') }
122
- : { input: zod_1.z.string().optional().describe('Input text or data for the workflow') };
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: `Execute the n8n workflow "${workflowData.name}". ${isChatTrigger
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 (args.input !== undefined) {
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 = fileData.updatedAt
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
- if (fileUpdatedAt && serverUpdatedAt && fileUpdatedAt > serverUpdatedAt) {
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 = fileData.updatedAt
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
- if (fileUpdatedAt && serverUpdatedAt && fileUpdatedAt > serverUpdatedAt) {
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
  }