Skip to main content
Glama

deploy

Deploy n8n workflows to an instance, handling import commands with options for specific files, changed workflows, activation, and separate imports.

Instructions

Deploy workflows to n8n instance - handles all n8n import commands internally

Input Schema

NameRequiredDescriptionDefault
activateNoActivate workflows after importing
allNoDeploy ALL workflows, not just changed ones
pathNoOptional: Deploy specific workflow file. If not provided, deploys all changed workflows
separateNoImport as separate workflows (not merged)

Input Schema (JSON Schema)

{ "properties": { "activate": { "description": "Activate workflows after importing", "type": "boolean" }, "all": { "description": "Deploy ALL workflows, not just changed ones", "type": "boolean" }, "path": { "description": "Optional: Deploy specific workflow file. If not provided, deploys all changed workflows", "type": "string" }, "separate": { "description": "Import as separate workflows (not merged)", "type": "boolean" } }, "type": "object" }

Implementation Reference

  • Tool schema definition and registration in the array returned by getToolDefinitions()
    name: 'deploy', description: 'Deploy workflows to n8n instance - handles all n8n import commands internally', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Optional: Deploy specific workflow file. If not provided, deploys all changed workflows', }, all: { type: 'boolean', description: 'Deploy ALL workflows, not just changed ones', }, activate: { type: 'boolean', description: 'Activate workflows after importing', }, separate: { type: 'boolean', description: 'Import as separate workflows (not merged)', }, }, }, },
  • Dispatch handler in ToolHandler.handleTool() that routes 'deploy' calls to N8nManager methods based on input parameters
    case 'deploy': const deployPath = args?.path as string; const deployAll = args?.all as boolean; const deployOptions = { activate: args?.activate as boolean, separate: args?.separate as boolean, }; if (deployPath) { return await this.n8nManager.importWorkflow(deployPath, deployOptions); } else if (deployAll) { return await this.n8nManager.deployAllWorkflows(deployOptions); } else { return await this.n8nManager.deployChangedWorkflows(deployOptions); }
  • Core handler N8nManager.importWorkflow() - compiles single workflow, injects code, uses n8n CLI to import/deploy to n8n instance
    async importWorkflow(workflowPath: string, options: { separate?: boolean; activate?: boolean; } = {}): Promise<any> { try { // Check if n8n is available const n8nAvailable = await this.checkN8nAvailability(); if (!n8nAvailable) { return { content: [ { type: 'text', text: '❌ n8n CLI is not installed!\n\n' + 'To deploy workflows, you need to install n8n:\n' + ' npm install -g n8n\n' + ' or\n' + ' yarn global add n8n\n\n' + 'After installation, run "n8n start" to start the server.', }, ], }; } // Handle different path formats let fullPath: string; // If the path starts with 'workflows/', remove it to avoid doubling if (workflowPath.startsWith('workflows/')) { workflowPath = workflowPath.substring('workflows/'.length); } // If it's an absolute path, use it directly if (path.isAbsolute(workflowPath)) { fullPath = workflowPath; } else { fullPath = path.join(this.workflowsPath, workflowPath); } // Verify file exists await fs.access(fullPath); // Create temporary file with injected content in /tmp const tempPath = `/tmp/mcflow_deploy_${Date.now()}_${path.basename(workflowPath)}`; try { // Compile the workflow (inject external code/prompts) const workflow = await this.compiler.compileWorkflow(fullPath); // Save compiled workflow to dist directory const fileName = path.basename(workflowPath); await this.compiler.saveCompiledWorkflow(fileName, workflow); // Ensure workflow has required fields for n8n if (!workflow.active && workflow.active !== false) { workflow.active = false; // Default to inactive } // Track which nodes had content injected (for reporting) const injected: string[] = []; if (!workflow.settings) { workflow.settings = { executionOrder: 'v1' }; } if (!workflow.connections) { workflow.connections = {}; } // IMPORTANT: Log whether this is an update or create const isUpdate = !!workflow.id; if (workflow.id) { console.error(`Updating existing workflow with ID: ${workflow.id}`); } else { console.error('No workflow ID found - n8n will create a new workflow'); } // Log injection details if (injected.length > 0) { console.error(`Injecting content for nodes: ${injected.join(', ')}`); } // Validate that code nodes have content let emptyCodeNodes = []; if (workflow.nodes) { for (const node of workflow.nodes) { if (node.type === 'n8n-nodes-base.code' && node.parameters) { if ((!node.parameters.jsCode || node.parameters.jsCode === '') && (!node.parameters.pythonCode || node.parameters.pythonCode === '')) { emptyCodeNodes.push(node.name || 'unnamed'); } } } } if (emptyCodeNodes.length > 0) { console.error(`⚠️ WARNING: The following code nodes have empty content: ${emptyCodeNodes.join(', ')}`); console.error('This may cause nodes to appear disconnected in n8n.'); console.error('Make sure to use "mcflow deploy" instead of deploying the workflow file directly.'); } // Write temporary workflow with injected content // n8n expects a single workflow object (not in an array) for file import await fs.writeFile(tempPath, JSON.stringify(workflow, null, 2)); // Build command using temp file // Use --force flag to update existing workflows let command = `n8n import:workflow --input="${tempPath}" --force`; if (options.activate) { command += ' --activate'; } console.error(`Executing: ${command}`); const { stdout, stderr } = await execAsync(command); // Clean up temp file await fs.unlink(tempPath).catch(() => {}); // Check for errors if (this.hasRealError(stderr, stdout)) { throw new Error(stderr); } return { content: [ { type: 'text', text: `✅ Workflow ${isUpdate ? 'updated' : 'deployed'} successfully!\n\n` + `📁 File: ${workflowPath}\n` + `${options.activate ? '▶️ Status: Activated\n' : '⏸️ Status: Inactive\n'}` + `${injected.length > 0 ? `💉 Injected nodes: ${injected.join(', ')}\n` : ''}` + `${options.separate ? '📦 Mode: Separate execution\n' : ''}` + `\n${stdout || 'Deployment completed.'}`, }, ], }; } catch (error: any) { // Always clean up temp file on error await fs.unlink(tempPath).catch(() => {}); throw new Error(`Failed to deploy workflow: ${error.message}`); } } catch (error: any) { throw new Error(`Failed to deploy workflow: ${error.message}`); } }
  • Handler N8nManager.deployChangedWorkflows() - deploys only workflows that have changed (default behavior)
    async deployChangedWorkflows(options: { activate?: boolean; separate?: boolean; } = {}): Promise<any> { try { // Get list of changed workflow files from change tracker const changedFiles = await this.changeTracker.getChangedWorkflows(); if (changedFiles.length === 0) { // Show current deployment status const statusDetails = await this.changeTracker.getChangeDetails(); return { content: [ { type: 'text', text: statusDetails, }, ], }; } // Deploy all changed workflows in parallel const deployPromises = changedFiles.map(async (file) => { const fullPath = path.join(this.workflowsPath, file); // Create a temporary file with injected code in /tmp const tempPath = `/tmp/mcflow_deploy_${Date.now()}_${path.basename(file)}`; try { // Compile the workflow (inject external code/prompts) const workflow = await this.compiler.compileWorkflow(fullPath); // Validate that code nodes have content let emptyCodeNodes = []; if (workflow.nodes) { for (const node of workflow.nodes) { if (node.type === 'n8n-nodes-base.code' && node.parameters) { if ((!node.parameters.jsCode || node.parameters.jsCode === '') && (!node.parameters.pythonCode || node.parameters.pythonCode === '')) { emptyCodeNodes.push(node.name || 'unnamed'); } } } } if (emptyCodeNodes.length > 0) { console.error(`⚠️ WARNING in ${path.basename(file)}: Empty code nodes: ${emptyCodeNodes.join(', ')}`); } // Track which nodes had content injected (for reporting) const injected: string[] = []; // Write temporary workflow with injected code // n8n expects a single workflow object (not in an array) for file import await fs.writeFile(tempPath, JSON.stringify(workflow, null, 2)); // Build command for this workflow using temp file // Use --force flag to update existing workflows let command = `n8n import:workflow --input="${tempPath}" --force`; if (options.activate) { command += ' --activate'; } const { stdout, stderr } = await execAsync(command); // Clean up temp file await fs.unlink(tempPath).catch(() => {}); if (this.hasRealError(stderr, stdout)) { return { file: path.basename(file), relativePath: file, status: 'failed', error: stderr }; } // Log success with any warnings if (stderr) { console.error(`Deployed ${path.basename(file)} with warnings: ${stderr}`); } // Log if code was injected if (injected.length > 0) { console.log(`Injected code for nodes: ${injected.join(', ')}`); } // Mark as deployed in change tracker await this.changeTracker.markDeployed(file); return { file: path.basename(file), relativePath: file, status: 'success', output: stdout || stderr }; } catch (error: any) { // Clean up temp file on error await fs.unlink(tempPath).catch(() => {}); console.error(`Failed to deploy ${path.basename(file)}: ${error.message}`); return { file: path.basename(file), relativePath: file, status: 'failed', error: error.message }; } }); // Wait for all deployments to complete const results = await Promise.all(deployPromises); // Format results const successful = results.filter(r => r.status === 'success'); const failed = results.filter(r => r.status === 'failed'); let output = `🚀 Deployed ${successful.length}/${changedFiles.length} workflows\n\n`; if (successful.length > 0) { output += '✅ Successfully deployed:\n'; for (const result of successful) { output += ` • ${result.file}\n`; } } if (failed.length > 0) { output += '\n❌ Failed to deploy:\n'; for (const result of failed) { // Extract meaningful error message const errorMsg = result.error || 'Unknown error'; const shortError = errorMsg.split('\n')[0].substring(0, 100); output += ` • ${result.file}: ${shortError}\n`; // Log full error to console for debugging console.error(`Full error for ${result.file}:`, result.error); } } output += `\n${options.activate ? '▶️ Status: All activated' : '⏸️ Status: Not activated'}`; output += `\n${options.separate ? '📦 Mode: Separate execution' : '📦 Mode: Standard'}`; return { content: [ { type: 'text', text: output, }, ], }; } catch (error: any) { throw new Error(`Failed to deploy changed workflows: ${error.message}`); } }
  • Handler N8nManager.deployAllWorkflows() - deploys ALL workflows when 'all: true' parameter is provided
    async deployAllWorkflows(options: { activate?: boolean; separate?: boolean; } = {}): Promise<any> { try { // Find the flows directory intelligently let flowsPath: string = ''; const possiblePaths = [ path.join(this.workflowsPath, 'flows'), this.workflowsPath ]; for (const testPath of possiblePaths) { try { await fs.access(testPath); const files = await fs.readdir(testPath); if (files.some(f => f.endsWith('.json'))) { flowsPath = testPath; break; } } catch {} } if (!flowsPath) { flowsPath = possiblePaths[0]; // Default to first option } // Get all workflow files (exclude package/config files) const files = await fs.readdir(flowsPath); const workflowFiles = files.filter(f => f.endsWith('.json') && !f.includes('package.json') && !f.includes('workflow_package.json') ); if (workflowFiles.length === 0) { return { content: [ { type: 'text', text: '📭 No workflows found to deploy.', }, ], }; } // Deploy all workflows in parallel const deployPromises = workflowFiles.map(async (file) => { const fullPath = path.join(flowsPath, file); // Create a temporary file for the compiled workflow // Use a simpler name to avoid path issues const tempFileName = `deploy_${Date.now()}_${file.replace(/[^a-z0-9.-]/gi, '_')}`; const tempPath = path.join('/tmp', tempFileName); try { // Compile the workflow (inject external code/prompts) console.error(`Compiling: ${fullPath}`); const workflow = await this.compiler.compileWorkflow(fullPath); // Write compiled workflow to temp file await fs.writeFile(tempPath, JSON.stringify(workflow, null, 2)); console.error(`Written to temp: ${tempPath}`); // Build command for this workflow using the compiled temp file // Use --force flag to update existing workflows let command = `n8n import:workflow --input="${tempPath}" --force`; if (options.activate) { command += ' --activate'; } console.error(`Executing command for ${file}: ${command}`); const { stdout, stderr } = await execAsync(command); // Clean up temp file await fs.unlink(tempPath).catch(() => {}); // Log raw output for debugging console.error(`[${file}] stdout: ${stdout}`); console.error(`[${file}] stderr: ${stderr}`); if (this.hasRealError(stderr, stdout)) { console.error(`[${file}] Detected real error in deployment`); return { file, status: 'failed', error: stderr }; } // Check for success indicators const isSuccess = stdout?.includes('Successfully imported') || stderr?.includes('Successfully imported') || stderr?.includes('Importing'); if (!isSuccess && !stdout && !stderr) { console.error(`[${file}] No output from import command - possible silent failure`); return { file, status: 'failed', error: 'No output from import command' }; } // Log success with any warnings if (stderr && !this.hasRealError(stderr, stdout)) { console.error(`[${file}] Deployed with warnings: ${stderr}`); } else { console.error(`[${file}] Deployed successfully`); } return { file, status: 'success', output: stdout || stderr || 'Import completed' }; } catch (error: any) { // Clean up temp file on error await fs.unlink(tempPath).catch(() => {}); console.error(`Failed to deploy ${file}: ${error.message}`); return { file, status: 'failed', error: error.message }; } }); // Wait for all deployments to complete const results = await Promise.all(deployPromises); // Format results const successful = results.filter(r => r.status === 'success'); const failed = results.filter(r => r.status === 'failed'); console.error(`\n=== Deployment Summary ===`); console.error(`Total workflows: ${workflowFiles.length}`); console.error(`Successful: ${successful.length}`); console.error(`Failed: ${failed.length}`); console.error(`Results:`, JSON.stringify(results, null, 2)); let output = `🚀 Deployed ${successful.length}/${workflowFiles.length} workflows\n\n`; if (successful.length > 0) { output += '✅ Successfully deployed:\n'; for (const result of successful) { output += ` • ${result.file}\n`; } } if (failed.length > 0) { output += '\n❌ Failed to deploy:\n'; for (const result of failed) { // Extract meaningful error message const errorMsg = result.error || 'Unknown error'; const shortError = errorMsg.split('\n')[0].substring(0, 100); output += ` • ${result.file}: ${shortError}\n`; // Log full error to console for debugging console.error(`Full error for ${result.file}:`, result.error); } } output += `\n${options.activate ? '▶️ Status: All activated' : '⏸️ Status: Not activated'}`; output += `\n${options.separate ? '📦 Mode: Separate execution' : '📦 Mode: Standard'}`; return { content: [ { type: 'text', text: output, }, ], }; } catch (error: any) { throw new Error(`Failed to deploy all workflows: ${error.message}`); } }

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