UNPKG

n8n

Version:

n8n Workflow Automation Tool

349 lines 15.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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 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_validation_utils_1 = require("./workflow-validation.utils"); const inputSchema = zod_1.default.object({ workflowId: zod_1.default.string().describe('The ID of the workflow to execute'), executionMode: zod_1.default .enum(['manual', 'production']) .optional() .default('production') .describe('Use "manual" to test the current version of the workflow. Use "production" to execute the published (active) version.'), 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 = { executionId: zod_1.default.string().nullable(), status: zod_1.default.enum(['started', 'error']).describe('The status of the execution'), error: zod_1.default.string().optional().describe('Error message if the execution failed'), }; const createExecuteWorkflowTool = (user, workflowFinderService, workflowRunner, telemetry, mcpService) => ({ name: 'execute_workflow', config: { description: 'Execute a workflow by ID. Returns the execution ID immediately without waiting for completion. 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: false, openWorldHint: true, }, }, handler: async ({ workflowId, executionMode, inputs }) => { const telemetryPayload = { user_id: user.id, tool_name: 'execute_workflow', parameters: { workflowId, executionMode, inputs: getInputMetaData(inputs) }, }; try { const output = await (0, exports.executeWorkflow)(user, workflowFinderService, workflowRunner, mcpService, workflowId, inputs, executionMode); telemetryPayload.results = { success: true, data: { executionId: output.executionId, status: output.status, }, }; 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 isAccessError = error instanceof mcp_errors_1.WorkflowAccessError; const errorInfo = { message: error.message || 'Unknown error', name: error.constructor.name, }; if ('extra' in error && error.extra) { errorInfo.extra = error.extra; } if (error.cause) { errorInfo.cause = error.cause instanceof Error ? error.cause.message : (0, n8n_workflow_1.jsonStringify)(error.cause); } const output = { executionId: null, status: 'error', error: error.message ?? `${error.constructor.name}: (no message)`, }; telemetryPayload.results = { success: false, error: errorInfo, error_reason: isAccessError ? error.reason : undefined, }; 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, workflowRunner, mcpService, workflowId, inputs, executionMode = 'production') => { const workflow = await (0, workflow_validation_utils_1.getMcpWorkflow)(workflowId, user, ['workflow:execute'], workflowFinderService, { includeActiveVersion: true }); const runData = await buildRunData(workflow, user.id, workflowId, executionMode, inputs, mcpService); const executionId = await workflowRunner.run(runData); return { executionId, status: 'started', }; }; exports.executeWorkflow = executeWorkflow; const getVersionDataForExecution = (workflow, workflowId, executionMode) => { if (executionMode === 'production' && !workflow.activeVersionId) { throw new mcp_errors_1.WorkflowAccessError(`Workflow '${workflowId}' has no published (active) version to execute`, 'workflow_not_active'); } const nodes = executionMode === 'production' ? (workflow.activeVersion?.nodes ?? []) : (workflow.nodes ?? []); const connections = executionMode === 'production' ? (workflow.activeVersion?.connections ?? {}) : (workflow.connections ?? {}); return { nodes, connections }; }; const buildRunData = async (workflow, userId, workflowId, executionMode, inputs, mcpService) => { const { nodes, connections } = getVersionDataForExecution(workflow, workflowId, executionMode); const triggerNode = (0, mcp_utils_1.findMcpSupportedTrigger)(nodes, executionMode); if (!triggerNode) { throw new mcp_errors_1.WorkflowAccessError(`Only workflows with the following trigger nodes can be executed: ${getSupportedTriggerNamesForMode(executionMode).join(', ')}.`, 'unsupported_trigger'); } const mcpMessageId = `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; const isManualExecution = executionMode === 'manual'; const runData = { executionMode: isManualExecution ? 'manual' : getExecutionModeForTrigger(triggerNode), workflowData: { ...workflow, nodes, connections }, userId, isMcpExecution: mcpService.isQueueMode, mcpType: 'service', mcpSessionId: mcpMessageId, mcpMessageId, }; runData.startNodes = [{ name: triggerNode.name, sourceData: null }]; const triggerPinData = await getPinDataForTrigger(triggerNode, inputs); const workflowPinData = isManualExecution ? (workflow.pinData ?? {}) : {}; runData.pinData = { ...workflowPinData, ...triggerPinData }; 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: {}, }, }); return runData; }; 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.MANUAL_TRIGGER_NODE_TYPE: return 'manual'; case n8n_workflow_1.FORM_TRIGGER_NODE_TYPE: return 'trigger'; default: return 'trigger'; } }; const getPinDataForTrigger = async (node, inputs) => { switch (node.type) { case n8n_workflow_1.MANUAL_TRIGGER_NODE_TYPE: return { [node.name]: [{ json: {} }], }; 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 { [node.name]: [{ json: {} }] }; 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 { [node.name]: [{ json: {} }] }; 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 moment = (await Promise.resolve().then(() => __importStar(require('moment-timezone')))).default; const momentTz = moment.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 { [node.name]: [{ json: {} }] }; } }; const getSupportedTriggerNamesForMode = (executionMode) => { return executionMode === 'production' ? Object.values(mcp_constants_1.SUPPORTED_PRODUCTION_MCP_TRIGGERS) : Object.values(mcp_constants_1.SUPPORTED_MCP_TRIGGERS); }; 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