validate
Validate workflow structure and check for common issues to ensure proper functionality and reliability.
Instructions
Validate a workflow structure and check for common issues
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| workflow | No | The workflow JSON object to validate | |
| path | No | Path to workflow file to validate | |
| autofix | No | Automatically fix common issues like multiplex mode |
Implementation Reference
- src/tools/registry.ts:102-121 (schema)Input schema and definition for the 'validate' MCP toolname: 'validate', description: 'Validate a workflow structure and check for common issues', inputSchema: { type: 'object', properties: { workflow: { type: 'object', description: 'The workflow JSON object to validate', }, path: { type: 'string', description: 'Path to workflow file to validate', }, autofix: { type: 'boolean', description: 'Automatically fix common issues like multiplex mode', }, }, }, },
- src/server/mcflow.ts:76-78 (registration)MCP server registers all tools (including 'validate') by providing getToolDefinitions() in the list tools handlerthis.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: getToolDefinitions(), }));
- src/tools/handler.ts:54-81 (handler)ToolHandler switch case for 'validate': handles input args, reads workflow file if path provided, supports autofix, delegates to validateWorkflowcase 'validate': const validatePath = args?.path as string; const validateWorkflow = args?.workflow as any; const autofix = args?.autofix as boolean; if (validatePath) { const fullPath = path.join(this.workflowsPath, validatePath); const content = await fs.readFile(fullPath, 'utf-8'); const workflow = JSON.parse(content); if (autofix) { const fixed = await autofixWorkflow(workflow); if (fixed.changed) { await fs.writeFile(fullPath, JSON.stringify(fixed.workflow, null, 2)); return { content: [{ type: 'text', text: `✅ Fixed ${fixed.fixes.length} issues:\n${fixed.fixes.join('\n')}\n\nWorkflow saved!` }] }; } } return await validateWorkflow(workflow); } else { return await validateWorkflow(validateWorkflow); }
- src/workflows/validator.ts:36-144 (helper)Core validateWorkflow function: performs comprehensive validation of workflow structure, nodes, connections, identifies issues/warnings/recommendationsexport async function validateWorkflow(workflow: any): Promise<any> { const issues: string[] = []; const warnings: string[] = []; const recommendations: string[] = []; if (!workflow.name) { issues.push('Workflow must have a name'); } if (!workflow.nodes || !Array.isArray(workflow.nodes)) { issues.push('Workflow must have a nodes array'); } else { const nodeIds = new Set(); let hasTrigger = false; for (const node of workflow.nodes) { if (!node.id) { issues.push(`Node missing ID: ${JSON.stringify(node)}`); } else if (nodeIds.has(node.id)) { issues.push(`Duplicate node ID: ${node.id}`); } else { nodeIds.add(node.id); } if (!node.type) { issues.push(`Node ${node.id} missing type`); } else { if (node.type.includes('trigger') || node.type.includes('Trigger')) { hasTrigger = true; } if (node.type === 'n8n-nodes-base.merge') { const mode = node.parameters?.mode; const combinationMode = node.parameters?.combinationMode; if (mode === 'multiplex') { issues.push(`⚠️ Node "${node.name}" uses 'multiplex' mode which often outputs empty data.`); recommendations.push(`Change "${node.name}" from multiplex to: mode='combine', combinationMode='mergeByPosition'`); } else if (mode === 'combine' && !combinationMode) { warnings.push(`Node "${node.name}" is missing combinationMode parameter`); } if (workflow.connections) { let inputCount = 0; for (const [, targets] of Object.entries(workflow.connections)) { const targetList = targets as any; if (targetList.main) { for (const outputs of targetList.main) { if (Array.isArray(outputs)) { for (const connection of outputs) { if (connection.node === node.name || connection.node === node.id) { inputCount++; } } } } } } if (inputCount < 2) { warnings.push(`Merge node "${node.name}" has only ${inputCount} input(s). Merge nodes need at least 2 inputs.`); } } } if (node.type === 'n8n-nodes-base.rssFeedRead') { if (!node.parameters?.url) { issues.push(`RSS node "${node.name}" is missing URL parameter`); } } } if (!node.position || typeof node.position[0] !== 'number' || typeof node.position[1] !== 'number') { warnings.push(`Node ${node.id} has invalid position`); } } if (!hasTrigger) { warnings.push('Workflow has no trigger node'); } } if (workflow.connections) { for (const [, outputs] of Object.entries(workflow.connections as any)) { for (const [, connections] of Object.entries(outputs as any)) { for (const connection of connections as any[]) { for (const target of connection) { if (!workflow.nodes.find((n: any) => n.id === target.node)) { issues.push(`Connection references non-existent node: ${target.node}`); } } } } } } return { content: [ { type: 'text', text: JSON.stringify({ valid: issues.length === 0, issues, warnings, recommendations, }, null, 2), }, ], }; }
- src/workflows/validator.ts:1-34 (helper)autofixWorkflow helper: automatically fixes common issues like multiplex mode in merge nodes and returns list of fixes appliedexport async function autofixWorkflow(workflow: any): Promise<{changed: boolean, fixes: string[], workflow: any}> { const fixes: string[] = []; let changed = false; if (workflow.nodes && Array.isArray(workflow.nodes)) { for (const node of workflow.nodes) { if (node.type === 'n8n-nodes-base.merge') { const mode = node.parameters?.mode; const combinationMode = node.parameters?.combinationMode; if (mode === 'multiplex') { node.parameters.mode = 'combine'; node.parameters.combinationMode = 'mergeByPosition'; fixes.push(`Fixed "${node.name}": Changed from multiplex to combine mode with mergeByPosition`); changed = true; } if (mode === 'combine' && combinationMode === 'multiplex') { node.parameters.combinationMode = 'mergeByPosition'; fixes.push(`Fixed "${node.name}": Changed combinationMode from multiplex to mergeByPosition`); changed = true; } if (mode === 'combine' && !combinationMode) { node.parameters.combinationMode = 'mergeByPosition'; fixes.push(`Fixed "${node.name}": Added missing combinationMode: mergeByPosition`); changed = true; } } } } return { changed, fixes, workflow }; }