create
Generate a new n8n workflow using only real nodes, no mock or placeholder nodes allowed, for streamlined automation development.
Instructions
Create a new n8n workflow with REAL nodes only (no mock/placeholder nodes allowed)
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Workflow name | |
| project | No | Optional project name (only for multi-project repos) | |
| workflow | Yes | The workflow JSON object |
Input Schema (JSON Schema)
{
"properties": {
"name": {
"description": "Workflow name",
"type": "string"
},
"project": {
"description": "Optional project name (only for multi-project repos)",
"type": "string"
},
"workflow": {
"description": "The workflow JSON object",
"type": "object"
}
},
"required": [
"name",
"workflow"
],
"type": "object"
}
Implementation Reference
- src/workflows/manager.ts:86-193 (handler)Core implementation of the 'create' tool: handles workflow creation including naming, validation, file writing, and documentation updates.async createWorkflow(name: string, workflow: any, project?: string): Promise<any> { try { // Enforce dash naming convention if (name.includes('_')) { name = name.replace(/_/g, '-'); console.log(`π Converting underscores to dashes in filename: ${name}`); } // Initialize workflows structure if needed (first time) const wasInitialized = await this.initializer.initialize(); let targetPath: string; let relativePath: string; // Determine where to create the workflow based on structure if (this.structure.type === 'simple' || !project) { // Simple structure: use ./flows/ (workflowsPath already points to workflows dir) targetPath = path.join(this.workflowsPath, 'flows'); relativePath = `flows/${name}.json`; } else if (this.structure.type === 'multi-project' && project) { // Multi-project structure with project specified targetPath = path.join(this.workflowsPath, project, 'workflows'); relativePath = `${project}/workflows/${name}.json`; } else { // Unknown structure: default to simple with flows targetPath = path.join(this.workflowsPath, 'flows'); relativePath = `flows/${name}.json`; } // Create directory if it doesn't exist await fs.mkdir(targetPath, { recursive: true }); // Generate a succinct, unique name const finalName = await this.generateUniqueName(name, targetPath); // Update the workflow's internal name to match if (workflow.name) { workflow.name = finalName; } // Write temporary file for validation in /tmp const tempPath = `/tmp/mcflow_validate_${Date.now()}_${finalName}.json`; await fs.writeFile(tempPath, stringifyWorkflowFile(workflow)); // Validate and auto-fix if needed const validationResult = await this.validator.validateWorkflow(tempPath); if (!validationResult.valid) { // Try to auto-fix const fixResult = await this.validator.autoFixWorkflow(tempPath); if (fixResult.fixed) { // Re-read the fixed workflow const fixedContent = await fs.readFile(tempPath, 'utf-8'); workflow = JSON.parse(fixedContent); } } // Write the final workflow file with proper formatting const filePath = path.join(targetPath, `${finalName}.json`); await fs.writeFile(filePath, stringifyWorkflowFile(workflow)); // Remove temp file try { await fs.unlink(tempPath); } catch {} // Update the relative path with the final name if (this.structure.type === 'simple' || !project) { relativePath = `workflows/flows/${finalName}.json`; } else if (project) { relativePath = `${project}/workflows/${finalName}.json`; } // Update documentation try { const customInstructions = await this.documenter.getCustomInstructions(); await this.documenter.updateWorkflowDocumentation(finalName, workflow, 'create', customInstructions); // Extract and update credentials const credentials = this.initializer.extractCredentialsFromWorkflow(workflow); if (credentials.size > 0) { await this.initializer.updateEnvExample(credentials); } // Update README with workflow list const allWorkflows = await this.getWorkflowList(); await this.initializer.updateReadmeWorkflowList(allWorkflows); } catch (docError) { console.error('Failed to update documentation:', docError); // Don't fail the workflow creation if documentation fails } return { content: [ { type: 'text', text: `β Workflow Created Successfully!\n\n` + `π File: ${relativePath}\n` + `π Name: ${finalName}\n` + `${project ? `π Project: ${project}\n` : ''}` + `\n` + `The workflow has been saved and documented.`, }, ], }; } catch (error) { throw new Error(`Failed to create workflow: ${error}`); } }
- src/tools/handler.ts:35-40 (handler)ToolHandler switch case that dispatches 'create' tool calls to WorkflowManager.createWorkflow.case 'create': return await this.workflowManager.createWorkflow( args?.name as string, args?.workflow as any, args?.project as string );
- src/tools/registry.ts:34-54 (registration)Registers the 'create' tool in getToolDefinitions() with name, description, and inputSchema.name: 'create', description: 'Create a new n8n workflow with REAL nodes only (no mock/placeholder nodes allowed). IMPORTANT: Use dashes in filenames, not underscores (e.g., "my-workflow" not "my_workflow")', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Workflow name (use dashes, not underscores)', }, workflow: { type: 'object', description: 'The workflow JSON object', }, project: { type: 'string', description: 'Optional project name (only for multi-project repos)', }, }, required: ['name', 'workflow'], }, },
- src/tools/registry.ts:34-54 (schema)Defines the input schema for the 'create' tool: requires name and workflow, optional project.name: 'create', description: 'Create a new n8n workflow with REAL nodes only (no mock/placeholder nodes allowed). IMPORTANT: Use dashes in filenames, not underscores (e.g., "my-workflow" not "my_workflow")', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Workflow name (use dashes, not underscores)', }, workflow: { type: 'object', description: 'The workflow JSON object', }, project: { type: 'string', description: 'Optional project name (only for multi-project repos)', }, }, required: ['name', 'workflow'], }, },
- src/workflows/manager.ts:46-79 (helper)Helper method to generate a unique, dash-formatted filename for the new workflow.private async generateUniqueName(baseName: string, targetPath: string): Promise<string> { // Clean up the base name - remove redundant words and make succinct let cleanName = baseName .toLowerCase() .replace(/workflow/gi, '') // Remove 'workflow' as it's redundant .replace(/[\s_]+/g, '-') // Replace spaces/underscores with hyphens .replace(/--+/g, '-') // Remove duplicate hyphens .replace(/^-|-$/g, '') // Remove leading/trailing hyphens .slice(0, 30); // Keep names reasonably short // If name is empty after cleaning, use a default if (!cleanName) { cleanName = 'flow'; } // Check if file exists and generate unique name if needed let finalName = cleanName; let counter = 1; while (true) { try { const filePath = path.join(targetPath, `${finalName}.json`); await fs.access(filePath); // File exists, try with a number suffix finalName = `${cleanName}-${counter}`; counter++; } catch { // File doesn't exist, we can use this name break; } } return finalName; }