validate
Check n8n workflows for compatibility problems, node errors, and connection issues, with optional automatic fixes for common problems.
Instructions
Validate workflows for n8n compatibility, node issues, and connection problems
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| fix | No | Automatically fix common issues (default: false) | |
| workflow | No | Specific workflow to validate (optional, validates all if not specified) |
Input Schema (JSON Schema)
{
"properties": {
"fix": {
"description": "Automatically fix common issues (default: false)",
"type": "boolean"
},
"workflow": {
"description": "Specific workflow to validate (optional, validates all if not specified)",
"type": "string"
}
},
"type": "object"
}
Implementation Reference
- src/workflows/validator.ts:36-144 (handler)Core implementation of the validate tool logic: performs comprehensive checks on workflow name, nodes (IDs, types, triggers, merge modes, RSS params, positions), connections, and categorizes issues/warnings/recommendations.export 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/tools/handler.ts:54-80 (handler)Dispatch handler for 'validate' tool: parses args, reads and parses workflow from path if provided, optionally applies autofix and saves, then delegates to core validateWorkflow function.case '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/tools/registry.ts:101-121 (registration)Registration of the 'validate' tool in the tools definitions array, including name, description, and input schema.{ name: '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/workflows/validator.ts:1-34 (helper)Helper function autofixWorkflow that automatically fixes common issues like incorrect merge node modes and returns details of changes made.export 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 }; }