n8n
Version:
n8n Workflow Automation Tool
308 lines • 12.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeWorkflow = exports.createExecuteWorkflowTool = void 0;
const constants_1 = require("@n8n/constants");
const moment_timezone_1 = __importDefault(require("moment-timezone"));
const n8n_workflow_1 = require("n8n-workflow");
const zod_1 = __importDefault(require("zod"));
const mcp_constants_1 = require("../mcp.constants");
const mcp_errors_1 = require("../mcp.errors");
const mcp_utils_1 = require("../mcp.utils");
const WORKFLOW_EXECUTION_TIMEOUT_MS = 5 * constants_1.Time.minutes.toMilliseconds;
const inputSchema = zod_1.default.object({
workflowId: zod_1.default.string().describe('The ID of the workflow to execute'),
inputs: zod_1.default
.discriminatedUnion('type', [
zod_1.default.object({
type: zod_1.default.literal('chat'),
chatInput: zod_1.default.string().describe('Input for chat-based workflows'),
}),
zod_1.default.object({
type: zod_1.default.literal('form'),
formData: zod_1.default.record(zod_1.default.unknown()).describe('Input data for form-based workflows'),
}),
zod_1.default.object({
type: zod_1.default.literal('webhook'),
webhookData: zod_1.default
.object({
method: zod_1.default
.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'])
.optional()
.default('GET')
.describe('HTTP method (defaults to GET)'),
query: zod_1.default.record(zod_1.default.string()).optional().describe('Query string parameters'),
body: zod_1.default
.record(zod_1.default.unknown())
.optional()
.describe('Request body data (main webhook payload)'),
headers: zod_1.default
.record(zod_1.default.string())
.optional()
.describe('HTTP headers (e.g., authorization, content-type)'),
})
.describe('Input data for webhook-based workflows'),
}),
])
.optional()
.describe('Inputs to provide to the workflow.'),
});
const outputSchema = {
success: zod_1.default.boolean(),
executionId: zod_1.default.string().nullable().optional(),
result: zod_1.default.unknown().optional().describe('Workflow execution result data'),
error: zod_1.default.unknown().optional(),
};
const createExecuteWorkflowTool = (user, workflowFinderService, activeExecutions, workflowRunner, telemetry) => ({
name: 'execute_workflow',
config: {
description: 'Execute a workflow by ID. Before executing always ensure you know the input schema by first using the get_workflow_details tool and consulting workflow description',
inputSchema: inputSchema.shape,
outputSchema,
annotations: {
title: 'Execute Workflow',
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
handler: async ({ workflowId, inputs }) => {
const telemetryPayload = {
user_id: user.id,
tool_name: 'execute_workflow',
parameters: { workflowId, inputs: getInputMetaData(inputs) },
};
try {
const output = await (0, exports.executeWorkflow)(user, workflowFinderService, activeExecutions, workflowRunner, workflowId, inputs);
telemetryPayload.results = {
success: output.success,
data: {
executionId: output.executionId,
},
};
telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload);
return {
content: [{ type: 'text', text: (0, n8n_workflow_1.jsonStringify)(output) }],
structuredContent: output,
};
}
catch (er) {
const error = (0, n8n_workflow_1.ensureError)(er);
const isTimeout = error instanceof mcp_errors_1.McpExecutionTimeoutError;
const output = {
success: false,
executionId: isTimeout ? error.executionId : null,
error: isTimeout
? `Workflow execution timed out after ${WORKFLOW_EXECUTION_TIMEOUT_MS / constants_1.Time.milliseconds.toSeconds} seconds`
: error.message,
};
telemetryPayload.results = {
success: false,
error: isTimeout ? 'Workflow execution timed out' : error.message,
};
telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload);
return {
content: [{ type: 'text', text: (0, n8n_workflow_1.jsonStringify)(output) }],
structuredContent: output,
};
}
},
});
exports.createExecuteWorkflowTool = createExecuteWorkflowTool;
const executeWorkflow = async (user, workflowFinderService, activeExecutions, workflowRunner, workflowId, inputs) => {
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, ['workflow:execute'], { includeActiveVersion: true });
if (!workflow || workflow.isArchived) {
throw new n8n_workflow_1.UserError('Workflow not found');
}
if (!workflow.settings?.availableInMCP) {
throw new n8n_workflow_1.UserError('Workflow is not available for execution via MCP. Enable access in the workflow settings to make it available.');
}
const nodes = workflow.activeVersion?.nodes ?? [];
const connections = workflow.activeVersion?.connections ?? {};
const triggerNode = (0, mcp_utils_1.findMcpSupportedTrigger)(nodes);
if (!triggerNode) {
throw new n8n_workflow_1.UserError(`Only workflows with the following trigger nodes can be executed: ${Object.values(mcp_constants_1.SUPPORTED_MCP_TRIGGERS).join(', ')}.`);
}
const runData = {
executionMode: getExecutionModeForTrigger(triggerNode),
workflowData: { ...workflow, nodes, connections },
userId: user.id,
};
runData.startNodes = [{ name: triggerNode.name, sourceData: null }];
runData.pinData = getPinDataForTrigger(triggerNode, inputs);
runData.executionData = (0, n8n_workflow_1.createRunExecutionData)({
startData: {},
resultData: {
pinData: runData.pinData,
runData: {},
},
executionData: {
contextData: {},
metadata: {},
nodeExecutionStack: [
{
node: triggerNode,
data: {
main: [runData.pinData[triggerNode.name]],
},
source: null,
},
],
waitingExecution: {},
waitingExecutionSource: {},
},
});
const executionId = await workflowRunner.run(runData);
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new mcp_errors_1.McpExecutionTimeoutError(executionId, WORKFLOW_EXECUTION_TIMEOUT_MS));
}, WORKFLOW_EXECUTION_TIMEOUT_MS);
});
try {
const data = await Promise.race([
activeExecutions.getPostExecutePromise(executionId),
timeoutPromise,
]);
clearTimeout(timeoutId);
if (data === undefined) {
throw new n8n_workflow_1.UnexpectedError('Workflow did not return any data');
}
return {
success: data.status !== 'error' && !data.data.resultData?.error,
executionId,
result: data.data.resultData,
error: data.data.resultData?.error,
};
}
catch (error) {
if (timeoutId)
clearTimeout(timeoutId);
if (error instanceof mcp_errors_1.McpExecutionTimeoutError) {
try {
const cancellationError = new n8n_workflow_1.TimeoutExecutionCancelledError(error.executionId);
activeExecutions.stopExecution(error.executionId, cancellationError);
}
catch (stopError) {
throw new n8n_workflow_1.UnexpectedError(`Failed to stop timed-out execution [id: ${error.executionId}]: ${(0, n8n_workflow_1.ensureError)(stopError).message}`);
}
}
throw error;
}
};
exports.executeWorkflow = executeWorkflow;
const getExecutionModeForTrigger = (node) => {
switch (node.type) {
case n8n_workflow_1.WEBHOOK_NODE_TYPE:
return 'webhook';
case n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE:
return 'chat';
case n8n_workflow_1.FORM_TRIGGER_NODE_TYPE:
return 'trigger';
default:
return 'trigger';
}
};
const getPinDataForTrigger = (node, inputs) => {
switch (node.type) {
case n8n_workflow_1.WEBHOOK_NODE_TYPE: {
const webhookData = inputs?.type === 'webhook' ? inputs.webhookData : undefined;
return {
[node.name]: [
{
json: {
headers: webhookData?.headers ?? {},
query: webhookData?.query ?? {},
body: webhookData?.body ?? {},
},
},
],
};
}
case n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE:
if (!inputs || inputs.type !== 'chat')
return {};
return {
[node.name]: [
{
json: {
sessionId: `mcp-session-${Date.now()}`,
action: 'sendMessage',
chatInput: inputs.chatInput,
},
},
],
};
case n8n_workflow_1.FORM_TRIGGER_NODE_TYPE:
if (!inputs || inputs.type !== 'form')
return {};
return {
[node.name]: [
{
json: {
submittedAt: new Date().toISOString(),
formMode: 'mcp',
...(inputs.formData ?? {}),
},
},
],
};
case n8n_workflow_1.SCHEDULE_TRIGGER_NODE_TYPE: {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const momentTz = moment_timezone_1.default.tz(timezone);
return {
[node.name]: [
{
json: {
timestamp: momentTz.toISOString(true),
'Readable date': momentTz.format('MMMM Do YYYY, h:mm:ss a'),
'Readable time': momentTz.format('h:mm:ss a'),
'Day of week': momentTz.format('dddd'),
Year: momentTz.format('YYYY'),
Month: momentTz.format('MMMM'),
'Day of month': momentTz.format('DD'),
Hour: momentTz.format('HH'),
Minute: momentTz.format('mm'),
Second: momentTz.format('ss'),
Timezone: `${timezone} (UTC${momentTz.format('Z')})`,
},
},
],
};
}
default:
return {};
}
};
const getInputMetaData = (inputs) => {
if (!inputs) {
return undefined;
}
switch (inputs.type) {
case 'chat':
return {
type: 'chat',
parameter_count: 1,
};
case 'form':
return {
type: 'form',
parameter_count: Object.keys(inputs.formData ?? {}).length,
};
case 'webhook':
return {
type: 'webhook',
parameter_count: [
inputs.webhookData?.body ? Object.keys(inputs.webhookData.body).length : 0,
inputs.webhookData?.query ? Object.keys(inputs.webhookData.query).length : 0,
inputs.webhookData?.headers ? Object.keys(inputs.webhookData.headers).length : 0,
].reduce((a, b) => a + b, 0),
};
default:
return undefined;
}
};
//# sourceMappingURL=execute-workflow.tool.js.map