Skip to main content
Glama

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
NameRequiredDescriptionDefault
workflowNoThe workflow JSON object to validate
pathNoPath to workflow file to validate
autofixNoAutomatically fix common issues like multiplex mode

Implementation Reference

  • Input schema and definition for the 'validate' MCP tool
    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', }, }, }, },
  • MCP server registers all tools (including 'validate') by providing getToolDefinitions() in the list tools handler
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: getToolDefinitions(), }));
  • ToolHandler switch case for 'validate': handles input args, reads workflow file if path provided, supports autofix, delegates to validateWorkflow
    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); }
  • Core validateWorkflow function: performs comprehensive validation of workflow structure, nodes, connections, identifies 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), }, ], }; }
  • autofixWorkflow helper: automatically fixes common issues like multiplex mode in merge nodes and returns list of fixes applied
    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 }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mckinleymedia/mcflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server