n8n
Version:
n8n Workflow Automation Tool
236 lines • 11.9 kB
JavaScript
;
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 constants_1 = require("./constants");
const credentials_auto_assign_1 = require("./credentials-auto-assign");
const mcp_constants_1 = require("../../mcp.constants");
const workflow_validation_utils_1 = require("../workflow-validation.utils");
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("Optional project ID to create the workflow in. Use search_projects to find a project by name. 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'),
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) => ({
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.`,
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;
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;
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);
let effectiveProjectId = projectId;
if (!effectiveProjectId) {
const personalProject = await projectRepository.getPersonalProjectForUserOrFail(user.id);
effectiveProjectId = personalProject.id;
}
const { assignments: credentialAssignments, skippedHttpNodes } = await (0, credentials_auto_assign_1.autoPopulateNodeCredentials)(newWorkflow, user, nodeTypes, credentialsService, effectiveProjectId);
const savedWorkflow = await workflowCreationService.createWorkflow(user, newWorkflow, {
projectId,
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,
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) {
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: [],
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