UNPKG

n8n

Version:

n8n Workflow Automation Tool

185 lines 9.6 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.createUpdateWorkflowTool = void 0; const db_1 = require("@n8n/db"); const zod_1 = __importDefault(require("zod")); const mcp_constants_1 = require("../../mcp.constants"); const constants_1 = require("./constants"); const credentials_auto_assign_1 = require("./credentials-auto-assign"); const workflow_helpers_1 = require("../../../../workflow-helpers"); const workflow_validation_utils_1 = require("../workflow-validation.utils"); const inputSchema = { workflowId: zod_1.default.string().describe('The ID of the workflow to update'), code: zod_1.default .string() .describe(`Full TypeScript/JavaScript workflow code using the n8n Workflow SDK. Must be validated first with ${constants_1.CODE_BUILDER_VALIDATE_TOOL.toolName}.`), name: zod_1.default .string() .max(128) .optional() .describe('Optional workflow name. If not provided, uses the name from the code.'), description: zod_1.default .string() .max(255) .optional() .describe('Short workflow description summarizing what it does (1-2 sentences, max 255 chars).'), }; const outputSchema = { workflowId: zod_1.default.string().describe('The ID of the updated workflow'), name: zod_1.default.string().describe('The name of the updated workflow'), nodeCount: zod_1.default.number().describe('The number of nodes in the workflow'), url: zod_1.default.string().describe('The URL to open the workflow in n8n'), autoAssignedCredentials: zod_1.default .array(zod_1.default.object({ nodeName: zod_1.default.string().describe('The name of the node that had credentials auto-assigned'), credentialName: zod_1.default.string().describe('The name of the credential that was auto-assigned'), credentialType: zod_1.default.string().describe('The credential type that was auto-assigned'), })) .describe('List of credentials that were automatically assigned to nodes'), note: zod_1.default .string() .optional() .describe('Additional notes about the workflow update, such as any nodes that were skipped during credential auto-assignment.'), hint: zod_1.default .string() .optional() .describe('Actionable hint for recovering from the error. When present, follow the suggested action before retrying.'), }; const createUpdateWorkflowTool = (user, workflowFinderService, workflowService, urlService, telemetry, nodeTypes, credentialsService, sharedWorkflowRepository, collaborationService) => ({ name: constants_1.MCP_UPDATE_WORKFLOW_TOOL.toolName, config: { description: `Update an existing workflow in n8n from validated SDK code. Parses the code into a workflow and saves the changes. Always validate with ${constants_1.CODE_BUILDER_VALIDATE_TOOL.toolName} first.`, inputSchema, outputSchema, annotations: { title: constants_1.MCP_UPDATE_WORKFLOW_TOOL.displayTitle, readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false, }, }, handler: async ({ workflowId, code, name, description, }) => { const telemetryPayload = { user_id: user.id, tool_name: constants_1.MCP_UPDATE_WORKFLOW_TOOL.toolName, parameters: { workflowId, codeLength: code.length, hasName: !!name }, }; try { const existingWorkflow = await (0, workflow_validation_utils_1.getMcpWorkflow)(workflowId, user, ['workflow:update'], workflowFinderService); await collaborationService.ensureWorkflowEditable(existingWorkflow.id); const { ParseValidateHandler, stripImportStatements } = await Promise.resolve().then(() => __importStar(require('@n8n/ai-workflow-builder'))); const handler = new ParseValidateHandler({ generatePinData: false }); const strippedCode = stripImportStatements(code); const result = await handler.parseAndValidate(strippedCode); const workflowJson = result.workflow; const workflowUpdateData = new db_1.WorkflowEntity(); Object.assign(workflowUpdateData, { name: name ?? workflowJson.name, ...(description !== undefined ? { description } : {}), nodes: workflowJson.nodes, connections: workflowJson.connections, pinData: workflowJson.pinData, meta: { ...workflowJson.meta, aiBuilderAssisted: true, builderVariant: 'mcp' }, }); (0, workflow_helpers_1.resolveNodeWebhookIds)(workflowUpdateData, nodeTypes); (0, credentials_auto_assign_1.stripNullCredentialStubs)(workflowUpdateData.nodes); const existingCredsByNode = new Map(existingWorkflow.nodes.map((n) => [n.name, { type: n.type, credentials: n.credentials }])); for (const node of workflowUpdateData.nodes) { if (!node.credentials) { const existing = existingCredsByNode.get(node.name); if (existing?.type === node.type && existing.credentials) { node.credentials = { ...existing.credentials }; } } } const sharedWorkflow = await sharedWorkflowRepository.findOneOrFail({ where: { workflowId, role: 'workflow:owner' }, select: ['projectId'], }); const { assignments: credentialAssignments, skippedHttpNodes } = await (0, credentials_auto_assign_1.autoPopulateNodeCredentials)(workflowUpdateData, user, nodeTypes, credentialsService, sharedWorkflow.projectId); const updatedWorkflow = await workflowService.update(user, workflowUpdateData, workflowId, { aiBuilderAssisted: true, source: 'n8n-mcp', }); void collaborationService.broadcastWorkflowUpdate(workflowId, user.id).catch(() => { }); const baseUrl = urlService.getInstanceBaseUrl(); const workflowUrl = `${baseUrl}/workflow/${updatedWorkflow.id}`; telemetryPayload.results = { success: true, data: { workflowId: updatedWorkflow.id, nodeCount: updatedWorkflow.nodes.length, }, }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); const output = { workflowId: updatedWorkflow.id, name: updatedWorkflow.name, nodeCount: updatedWorkflow.nodes.length, url: workflowUrl, autoAssignedCredentials: credentialAssignments, note: skippedHttpNodes.length ? `HTTP Request nodes (${skippedHttpNodes.join(', ')}) were skipped during credential auto-assignment. Their credentials must be configured manually.` : undefined, }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); telemetryPayload.results = { success: false, error: errorMessage, }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); const hint = (0, workflow_validation_utils_1.getSdkReferenceHint)(error); const output = { error: errorMessage, ...(hint ? { hint } : {}) }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, isError: true, }; } }, }); exports.createUpdateWorkflowTool = createUpdateWorkflowTool; //# sourceMappingURL=update-workflow.tool.js.map