UNPKG

n8n

Version:

n8n Workflow Automation Tool

268 lines 14.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.createCreateWorkflowFromCodeTool = void 0; const db_1 = require("@n8n/db"); const zod_1 = __importDefault(require("zod")); const connection_structure_check_1 = require("./connection-structure-check"); const constants_1 = require("./constants"); const credentials_auto_assign_1 = require("./credentials-auto-assign"); const data_table_validation_1 = require("./data-table-validation"); const mcp_constants_1 = require("../../mcp.constants"); const workflow_validation_utils_1 = require("../workflow-validation.utils"); const not_found_error_1 = require("../../../../errors/response-errors/not-found.error"); const workflow_helpers_1 = require("../../../../workflow-helpers"); const inputSchema = { 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).'), projectId: zod_1.default .string() .optional() .describe("Project ID to create the workflow in. If the user named a project (e.g. 'in my Marketing project'), you MUST call search_projects first to resolve the name to an ID and pass it here — do not guess. If search_projects returns multiple partial matches with no exact match, ask the user to clarify before creating the workflow. Only omit this field when the user did not mention a project at all; in that case it defaults to the user's personal project."), folderId: zod_1.default .string() .optional() .describe('Optional folder ID to create the workflow in. Requires projectId to be set. Use search_folders to find a folder by name within a project.'), }; const outputSchema = { workflowId: zod_1.default.string().describe('The ID of the created workflow'), name: zod_1.default.string().describe('The name of the created 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'), targetProject: zod_1.default .object({ id: zod_1.default.string().describe('The ID of the project the workflow was created in'), name: zod_1.default.string().describe('The display name of the project the workflow was created in'), type: zod_1.default .enum(['personal', 'team']) .describe('Whether the workflow landed in a personal or team project'), }) .describe('The project the workflow was actually created in.'), note: zod_1.default .string() .optional() .describe('Additional notes about the workflow creation, 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 createCreateWorkflowFromCodeTool = (user, workflowCreationService, workflowFinderService, urlService, telemetry, nodeTypes, credentialsService, projectRepository, dataTableOps) => ({ name: constants_1.MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.toolName, config: { description: `Create a workflow in n8n from validated SDK code. This tool expects code that already follows the n8n Workflow SDK patterns and has passed ${constants_1.CODE_BUILDER_VALIDATE_TOOL.toolName}. If code fails to parse, call get_sdk_reference, rewrite the code using the reference, validate again, then retry creation. If the user named a target project, resolve it via search_projects before calling this tool; when projectId is omitted, the workflow is created in the user's personal project. After creation, always tell the user which project the workflow landed in (see the targetProject field in the response).`, inputSchema, outputSchema, annotations: { title: constants_1.MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.displayTitle, readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false, }, }, handler: async ({ code, name, description, projectId, folderId, }) => { const telemetryPayload = { user_id: user.id, tool_name: constants_1.MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.toolName, parameters: { codeLength: code.length, hasName: !!name, hasProjectId: !!projectId, hasFolderId: !!folderId, }, }; if (folderId && !projectId) { const errorMessage = 'projectId is required when folderId is provided'; telemetryPayload.results = { success: false, error: errorMessage }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); return { content: [{ type: 'text', text: JSON.stringify({ error: errorMessage }, null, 2) }], structuredContent: { error: errorMessage }, isError: true, }; } let newWorkflow; let landingProject = null; try { 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 invalidToolSourceResponse = (0, connection_structure_check_1.buildInvalidAiToolSourceErrorResponse)(workflowJson, nodeTypes, (errorMessage) => ({ error: errorMessage }), telemetryPayload, telemetry); if (invalidToolSourceResponse) return invalidToolSourceResponse; newWorkflow = new db_1.WorkflowEntity(); Object.assign(newWorkflow, { name: name ?? workflowJson.name ?? 'Untitled Workflow', ...(description ? { description } : {}), nodes: workflowJson.nodes, connections: workflowJson.connections, settings: { ...workflowJson.settings, executionOrder: 'v1', availableInMCP: true }, pinData: workflowJson.pinData, meta: { ...workflowJson.meta, aiBuilderAssisted: true, builderVariant: 'mcp' }, }); (0, workflow_helpers_1.resolveNodeWebhookIds)(newWorkflow, nodeTypes); (0, credentials_auto_assign_1.stripNullCredentialStubs)(newWorkflow.nodes); landingProject = projectId ? await projectRepository.findOneBy({ id: projectId }) : await projectRepository.getPersonalProjectForUserOrFail(user.id); if (!landingProject) { throw new not_found_error_1.NotFoundError(`Project with id "${projectId}" was not found. Use search_projects to look up a valid project id.`); } const effectiveProjectId = landingProject.id; const dataTableCheck = await (0, data_table_validation_1.validateDataTableReferencesForWorkflow)(newWorkflow.nodes, effectiveProjectId, dataTableOps); if (!dataTableCheck.ok) { throw new Error(dataTableCheck.error); } const { assignments: credentialAssignments, skippedHttpNodes } = await (0, credentials_auto_assign_1.autoPopulateNodeCredentials)(newWorkflow, user, nodeTypes, credentialsService, effectiveProjectId); const savedWorkflow = await workflowCreationService.createWorkflow(user, newWorkflow, { projectId: effectiveProjectId, parentFolderId: folderId, source: 'n8n-mcp', }); const baseUrl = urlService.getInstanceBaseUrl(); const workflowUrl = `${baseUrl}/workflow/${savedWorkflow.id}`; telemetryPayload.results = { success: true, data: { workflowId: savedWorkflow.id, nodeCount: savedWorkflow.nodes.length, }, }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); const output = { workflowId: savedWorkflow.id, name: savedWorkflow.name, nodeCount: savedWorkflow.nodes.length, url: workflowUrl, autoAssignedCredentials: credentialAssignments, targetProject: { id: landingProject.id, name: landingProject.name, type: landingProject.type, }, 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); if (newWorkflow?.id) { let persisted = null; try { persisted = await workflowFinderService.findWorkflowForUser(newWorkflow.id, user, [ 'workflow:read', ]); } catch { } if (persisted && landingProject) { const baseUrl = urlService.getInstanceBaseUrl(); const workflowUrl = `${baseUrl}/workflow/${persisted.id}`; telemetryPayload.results = { success: true, data: { workflowId: persisted.id, nodeCount: persisted.nodes.length, postSaveError: errorMessage, }, }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); const output = { workflowId: persisted.id, name: persisted.name, nodeCount: persisted.nodes.length, url: workflowUrl, autoAssignedCredentials: [], targetProject: { id: landingProject.id, name: landingProject.name, type: landingProject.type, }, note: `Workflow was created successfully, but a post-save operation failed: ${errorMessage}`, }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } } 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, { afterReference: `Rewrite the code, call ${constants_1.CODE_BUILDER_VALIDATE_TOOL.toolName} until it returns valid=true, then call ${constants_1.MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.toolName} again.`, }); const output = { error: errorMessage, ...(hint ? { hint } : {}) }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, isError: true, }; } }, }); exports.createCreateWorkflowFromCodeTool = createCreateWorkflowFromCodeTool; //# sourceMappingURL=create-workflow-from-code.tool.js.map