n8n_workflow_versions
Manage workflow version history, rollback to previous versions, and clean up old versions to maintain organized automation workflows.
Instructions
Manage workflow version history, rollback, and cleanup. Six modes:
list: Show version history for a workflow
get: Get details of specific version
rollback: Restore workflow to previous version (creates backup first)
delete: Delete specific version or all versions for a workflow
prune: Manually trigger pruning to keep N most recent versions
truncate: Delete ALL versions for ALL workflows (requires confirmation)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| mode | Yes | Operation mode | |
| workflowId | No | Workflow ID (required for list, rollback, delete, prune) | |
| versionId | No | Version ID (required for get mode and single version delete, optional for rollback) | |
| limit | No | Max versions to return in list mode | |
| validateBefore | No | Validate workflow structure before rollback | |
| deleteAll | No | Delete all versions for workflow (delete mode only) | |
| maxVersions | No | Keep N most recent versions (prune mode only) | |
| confirmTruncate | No | REQUIRED: Must be true to truncate all versions (truncate mode only) |
Implementation Reference
- Main handler function that executes the n8n_workflow_versions tool. Validates input with Zod, handles all 6 modes by delegating to WorkflowVersioningService, and returns structured McpToolResponse.export async function handleWorkflowVersions( args: unknown, repository: NodeRepository, context?: InstanceContext ): Promise<McpToolResponse> { try { const input = workflowVersionsSchema.parse(args); const client = context ? getN8nApiClient(context) : null; const versioningService = new WorkflowVersioningService(repository, client || undefined); switch (input.mode) { case 'list': { if (!input.workflowId) { return { success: false, error: 'workflowId is required for list mode' }; } const versions = await versioningService.getVersionHistory(input.workflowId, input.limit); return { success: true, data: { workflowId: input.workflowId, versions, count: versions.length, message: `Found ${versions.length} version(s) for workflow ${input.workflowId}` } }; } case 'get': { if (!input.versionId) { return { success: false, error: 'versionId is required for get mode' }; } const version = await versioningService.getVersion(input.versionId); if (!version) { return { success: false, error: `Version ${input.versionId} not found` }; } return { success: true, data: version }; } case 'rollback': { if (!input.workflowId) { return { success: false, error: 'workflowId is required for rollback mode' }; } if (!client) { return { success: false, error: 'n8n API not configured. Cannot perform rollback without API access.' }; } const result = await versioningService.restoreVersion( input.workflowId, input.versionId, input.validateBefore ); return { success: result.success, data: result.success ? result : undefined, error: result.success ? undefined : result.message, details: result.success ? undefined : { validationErrors: result.validationErrors } }; } case 'delete': { if (input.deleteAll) { if (!input.workflowId) { return { success: false, error: 'workflowId is required for deleteAll mode' }; } const result = await versioningService.deleteAllVersions(input.workflowId); return { success: true, data: { workflowId: input.workflowId, deleted: result.deleted, message: result.message } }; } else { if (!input.versionId) { return { success: false, error: 'versionId is required for single version delete' }; } const result = await versioningService.deleteVersion(input.versionId); return { success: result.success, data: result.success ? { message: result.message } : undefined, error: result.success ? undefined : result.message }; } } case 'prune': { if (!input.workflowId) { return { success: false, error: 'workflowId is required for prune mode' }; } const result = await versioningService.pruneVersions( input.workflowId, input.maxVersions || 10 ); return { success: true, data: { workflowId: input.workflowId, pruned: result.pruned, remaining: result.remaining, message: `Pruned ${result.pruned} old version(s), ${result.remaining} version(s) remaining` } }; } case 'truncate': { if (!input.confirmTruncate) { return { success: false, error: 'confirmTruncate must be true to truncate all versions. This action cannot be undone.' }; } const result = await versioningService.truncateAllVersions(true); return { success: true, data: { deleted: result.deleted, message: result.message } }; } default: return { success: false, error: `Unknown mode: ${input.mode}` }; } } catch (error) { if (error instanceof z.ZodError) { return { success: false, error: 'Invalid input', details: { errors: error.errors } }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' }; } }
- src/mcp/tools-n8n-manager.ts:436-489 (registration)Tool registration/definition in n8nManagementTools array. Includes name, description, and inputSchema for MCP tool registration.{ name: 'n8n_workflow_versions', description: `Manage workflow version history, rollback, and cleanup. Six modes: - list: Show version history for a workflow - get: Get details of specific version - rollback: Restore workflow to previous version (creates backup first) - delete: Delete specific version or all versions for a workflow - prune: Manually trigger pruning to keep N most recent versions - truncate: Delete ALL versions for ALL workflows (requires confirmation)`, inputSchema: { type: 'object', properties: { mode: { type: 'string', enum: ['list', 'get', 'rollback', 'delete', 'prune', 'truncate'], description: 'Operation mode' }, workflowId: { type: 'string', description: 'Workflow ID (required for list, rollback, delete, prune)' }, versionId: { type: 'number', description: 'Version ID (required for get mode and single version delete, optional for rollback)' }, limit: { type: 'number', default: 10, description: 'Max versions to return in list mode' }, validateBefore: { type: 'boolean', default: true, description: 'Validate workflow structure before rollback' }, deleteAll: { type: 'boolean', default: false, description: 'Delete all versions for workflow (delete mode only)' }, maxVersions: { type: 'number', default: 10, description: 'Keep N most recent versions (prune mode only)' }, confirmTruncate: { type: 'boolean', default: false, description: 'REQUIRED: Must be true to truncate all versions (truncate mode only)' } }, required: ['mode'] } },
- Core helper class implementing all versioning logic: backup creation, history retrieval, rollback with validation, pruning, deletion, storage stats, and version comparison. Used by the handler.export class WorkflowVersioningService { private readonly DEFAULT_MAX_VERSIONS = 10; constructor( private nodeRepository: NodeRepository, private apiClient?: N8nApiClient ) {} /** * Create backup before modification * Automatically prunes to 10 versions after backup creation */ async createBackup( workflowId: string, workflow: any, context: { trigger: 'partial_update' | 'full_update' | 'autofix'; operations?: any[]; fixTypes?: string[]; metadata?: any; } ): Promise<BackupResult> { // Get current max version number const versions = this.nodeRepository.getWorkflowVersions(workflowId, 1); const nextVersion = versions.length > 0 ? versions[0].versionNumber + 1 : 1; // Create new version const versionId = this.nodeRepository.createWorkflowVersion({ workflowId, versionNumber: nextVersion, workflowName: workflow.name || 'Unnamed Workflow', workflowSnapshot: workflow, trigger: context.trigger, operations: context.operations, fixTypes: context.fixTypes, metadata: context.metadata }); // Auto-prune to keep max 10 versions const pruned = this.nodeRepository.pruneWorkflowVersions( workflowId, this.DEFAULT_MAX_VERSIONS ); return { versionId, versionNumber: nextVersion, pruned, message: pruned > 0 ? `Backup created (version ${nextVersion}), pruned ${pruned} old version(s)` : `Backup created (version ${nextVersion})` }; } /** * Get version history for a workflow */ async getVersionHistory(workflowId: string, limit: number = 10): Promise<VersionInfo[]> { const versions = this.nodeRepository.getWorkflowVersions(workflowId, limit); return versions.map(v => ({ id: v.id, workflowId: v.workflowId, versionNumber: v.versionNumber, workflowName: v.workflowName, trigger: v.trigger, operationCount: v.operations ? v.operations.length : undefined, fixTypesApplied: v.fixTypes || undefined, createdAt: v.createdAt, size: JSON.stringify(v.workflowSnapshot).length })); } /** * Get a specific workflow version */ async getVersion(versionId: number): Promise<WorkflowVersion | null> { return this.nodeRepository.getWorkflowVersion(versionId); } /** * Restore workflow to a previous version * Creates backup of current state before restoring */ async restoreVersion( workflowId: string, versionId?: number, validateBefore: boolean = true ): Promise<RestoreResult> { if (!this.apiClient) { return { success: false, message: 'API client not configured - cannot restore workflow', workflowId, toVersionId: versionId || 0, backupCreated: false }; } // Get the version to restore let versionToRestore: WorkflowVersion | null = null; if (versionId) { versionToRestore = this.nodeRepository.getWorkflowVersion(versionId); } else { // Get latest backup versionToRestore = this.nodeRepository.getLatestWorkflowVersion(workflowId); } if (!versionToRestore) { return { success: false, message: versionId ? `Version ${versionId} not found` : `No backup versions found for workflow ${workflowId}`, workflowId, toVersionId: versionId || 0, backupCreated: false }; } // Validate workflow structure if requested if (validateBefore) { const validator = new WorkflowValidator(this.nodeRepository, EnhancedConfigValidator); const validationResult = await validator.validateWorkflow( versionToRestore.workflowSnapshot, { validateNodes: true, validateConnections: true, validateExpressions: false, profile: 'runtime' } ); if (validationResult.errors.length > 0) { return { success: false, message: `Cannot restore - version ${versionToRestore.versionNumber} has validation errors`, workflowId, toVersionId: versionToRestore.id, backupCreated: false, validationErrors: validationResult.errors.map(e => e.message || 'Unknown error') }; } } // Create backup of current workflow before restoring let backupResult: BackupResult | undefined; try { const currentWorkflow = await this.apiClient.getWorkflow(workflowId); backupResult = await this.createBackup(workflowId, currentWorkflow, { trigger: 'partial_update', metadata: { reason: 'Backup before rollback', restoringToVersion: versionToRestore.versionNumber } }); } catch (error: any) { return { success: false, message: `Failed to create backup before restore: ${error.message}`, workflowId, toVersionId: versionToRestore.id, backupCreated: false }; } // Restore the workflow try { await this.apiClient.updateWorkflow(workflowId, versionToRestore.workflowSnapshot); return { success: true, message: `Successfully restored workflow to version ${versionToRestore.versionNumber}`, workflowId, fromVersion: backupResult.versionNumber, toVersionId: versionToRestore.id, backupCreated: true, backupVersionId: backupResult.versionId }; } catch (error: any) { return { success: false, message: `Failed to restore workflow: ${error.message}`, workflowId, toVersionId: versionToRestore.id, backupCreated: true, backupVersionId: backupResult.versionId }; } } /** * Delete a specific version */ async deleteVersion(versionId: number): Promise<{ success: boolean; message: string }> { const version = this.nodeRepository.getWorkflowVersion(versionId); if (!version) { return { success: false, message: `Version ${versionId} not found` }; } this.nodeRepository.deleteWorkflowVersion(versionId); return { success: true, message: `Deleted version ${version.versionNumber} for workflow ${version.workflowId}` }; } /** * Delete all versions for a workflow */ async deleteAllVersions(workflowId: string): Promise<{ deleted: number; message: string }> { const count = this.nodeRepository.getWorkflowVersionCount(workflowId); if (count === 0) { return { deleted: 0, message: `No versions found for workflow ${workflowId}` }; } const deleted = this.nodeRepository.deleteWorkflowVersionsByWorkflowId(workflowId); return { deleted, message: `Deleted ${deleted} version(s) for workflow ${workflowId}` }; } /** * Manually trigger pruning for a workflow */ async pruneVersions( workflowId: string, maxVersions: number = 10 ): Promise<{ pruned: number; remaining: number }> { const pruned = this.nodeRepository.pruneWorkflowVersions(workflowId, maxVersions); const remaining = this.nodeRepository.getWorkflowVersionCount(workflowId); return { pruned, remaining }; } /** * Truncate entire workflow_versions table * Requires explicit confirmation */ async truncateAllVersions(confirm: boolean): Promise<{ deleted: number; message: string }> { if (!confirm) { return { deleted: 0, message: 'Truncate operation not confirmed - no action taken' }; } const deleted = this.nodeRepository.truncateWorkflowVersions(); return { deleted, message: `Truncated workflow_versions table - deleted ${deleted} version(s)` }; } /** * Get storage statistics */ async getStorageStats(): Promise<StorageStats> { const stats = this.nodeRepository.getVersionStorageStats(); return { totalVersions: stats.totalVersions, totalSize: stats.totalSize, totalSizeFormatted: this.formatBytes(stats.totalSize), byWorkflow: stats.byWorkflow.map((w: any) => ({ workflowId: w.workflowId, workflowName: w.workflowName, versionCount: w.versionCount, totalSize: w.totalSize, totalSizeFormatted: this.formatBytes(w.totalSize), lastBackup: w.lastBackup })) }; } /** * Compare two versions */ async compareVersions(versionId1: number, versionId2: number): Promise<VersionDiff> { const v1 = this.nodeRepository.getWorkflowVersion(versionId1); const v2 = this.nodeRepository.getWorkflowVersion(versionId2); if (!v1 || !v2) { throw new Error(`One or both versions not found: ${versionId1}, ${versionId2}`); } // Compare nodes const nodes1 = new Set<string>(v1.workflowSnapshot.nodes?.map((n: any) => n.id as string) || []); const nodes2 = new Set<string>(v2.workflowSnapshot.nodes?.map((n: any) => n.id as string) || []); const addedNodes: string[] = [...nodes2].filter(id => !nodes1.has(id)); const removedNodes: string[] = [...nodes1].filter(id => !nodes2.has(id)); const commonNodes = [...nodes1].filter(id => nodes2.has(id)); // Check for modified nodes const modifiedNodes: string[] = []; for (const nodeId of commonNodes) { const node1 = v1.workflowSnapshot.nodes?.find((n: any) => n.id === nodeId); const node2 = v2.workflowSnapshot.nodes?.find((n: any) => n.id === nodeId); if (JSON.stringify(node1) !== JSON.stringify(node2)) { modifiedNodes.push(nodeId); } } // Compare connections const conn1Str = JSON.stringify(v1.workflowSnapshot.connections || {}); const conn2Str = JSON.stringify(v2.workflowSnapshot.connections || {}); const connectionChanges = conn1Str !== conn2Str ? 1 : 0; // Compare settings const settings1 = v1.workflowSnapshot.settings || {}; const settings2 = v2.workflowSnapshot.settings || {}; const settingChanges = this.diffObjects(settings1, settings2); return { versionId1, versionId2, version1Number: v1.versionNumber, version2Number: v2.versionNumber, addedNodes, removedNodes, modifiedNodes, connectionChanges, settingChanges }; } /** * Format bytes to human-readable string */ private formatBytes(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; } /** * Simple object diff */ private diffObjects(obj1: any, obj2: any): any { const changes: any = {}; const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]); for (const key of allKeys) { if (JSON.stringify(obj1[key]) !== JSON.stringify(obj2[key])) { changes[key] = { before: obj1[key], after: obj2[key] }; } } return changes; } }
- Detailed tool documentation and parameter schema reference used for tool discovery and descriptions.export const n8nWorkflowVersionsDoc: ToolDocumentation = { name: 'n8n_workflow_versions', category: 'workflow_management', essentials: { description: 'Manage workflow version history, rollback to previous versions, and cleanup old versions', keyParameters: ['mode', 'workflowId', 'versionId'], example: 'n8n_workflow_versions({mode: "list", workflowId: "abc123"})', performance: 'Fast for list/get (~100ms), moderate for rollback (~200-500ms)', tips: [ 'Use mode="list" to see all saved versions before rollback', 'Rollback creates a backup version automatically', 'Use prune to clean up old versions and save storage', 'truncate requires explicit confirmTruncate: true' ] }, full: { description: `Comprehensive workflow version management system. Supports six operations: **list** - Show version history for a workflow - Returns all saved versions with timestamps, snapshot sizes, and metadata - Use limit parameter to control how many versions to return **get** - Get details of a specific version - Returns the complete workflow snapshot from that version - Use to compare versions or extract old configurations **rollback** - Restore workflow to a previous version - Creates a backup of the current workflow before rollback - Optionally validates the workflow structure before applying - Returns the restored workflow and backup version ID **delete** - Delete specific version(s) - Delete a single version by versionId - Delete all versions for a workflow with deleteAll: true **prune** - Clean up old versions - Keeps only the N most recent versions (default: 10) - Useful for managing storage and keeping history manageable **truncate** - Delete ALL versions for ALL workflows - Dangerous operation requiring explicit confirmation - Use for complete version history cleanup`, parameters: { mode: { type: 'string', required: true, description: 'Operation mode: "list", "get", "rollback", "delete", "prune", or "truncate"', enum: ['list', 'get', 'rollback', 'delete', 'prune', 'truncate'] }, workflowId: { type: 'string', required: false, description: 'Workflow ID (required for list, rollback, delete, prune modes)' }, versionId: { type: 'number', required: false, description: 'Version ID (required for get mode, optional for rollback to specific version, required for single delete)' }, limit: { type: 'number', required: false, default: 10, description: 'Maximum versions to return in list mode' }, validateBefore: { type: 'boolean', required: false, default: true, description: 'Validate workflow structure before rollback (rollback mode only)' }, deleteAll: { type: 'boolean', required: false, default: false, description: 'Delete all versions for workflow (delete mode only)' }, maxVersions: { type: 'number', required: false, default: 10, description: 'Keep N most recent versions (prune mode only)' }, confirmTruncate: { type: 'boolean', required: false, default: false, description: 'REQUIRED: Must be true to truncate all versions (truncate mode only)' } }, returns: `Response varies by mode: **list mode:** - versions: Array of version objects with id, workflowId, snapshotSize, createdAt - totalCount: Total number of versions **get mode:** - version: Complete version object including workflow snapshot **rollback mode:** - success: Boolean indicating success - restoredVersion: The version that was restored - backupVersionId: ID of the backup created before rollback **delete mode:** - deletedCount: Number of versions deleted **prune mode:** - prunedCount: Number of old versions removed - remainingCount: Number of versions kept **truncate mode:** - deletedCount: Total versions deleted across all workflows`, examples: [ '// List version history\nn8n_workflow_versions({mode: "list", workflowId: "abc123", limit: 5})', '// Get specific version details\nn8n_workflow_versions({mode: "get", versionId: 42})', '// Rollback to latest saved version\nn8n_workflow_versions({mode: "rollback", workflowId: "abc123"})', '// Rollback to specific version\nn8n_workflow_versions({mode: "rollback", workflowId: "abc123", versionId: 42})', '// Delete specific version\nn8n_workflow_versions({mode: "delete", workflowId: "abc123", versionId: 42})', '// Delete all versions for workflow\nn8n_workflow_versions({mode: "delete", workflowId: "abc123", deleteAll: true})', '// Prune to keep only 5 most recent\nn8n_workflow_versions({mode: "prune", workflowId: "abc123", maxVersions: 5})', '// Truncate all versions (dangerous!)\nn8n_workflow_versions({mode: "truncate", confirmTruncate: true})' ], useCases: [ 'Recover from accidental workflow changes', 'Compare workflow versions to understand changes', 'Maintain audit trail of workflow modifications', 'Clean up old versions to save database storage', 'Roll back failed workflow deployments' ], performance: `Performance varies by operation: - list: Fast (~100ms) - simple database query - get: Fast (~100ms) - single row retrieval - rollback: Moderate (~200-500ms) - includes backup creation and workflow update - delete: Fast (~50-100ms) - database delete operation - prune: Moderate (~100-300ms) - depends on number of versions to delete - truncate: Slow (1-5s) - deletes all records across all workflows`, modeComparison: `| Mode | Required Params | Optional Params | Risk Level | |------|-----------------|-----------------|------------| | list | workflowId | limit | Low | | get | versionId | - | Low | | rollback | workflowId | versionId, validateBefore | Medium | | delete | workflowId | versionId, deleteAll | High | | prune | workflowId | maxVersions | Medium | | truncate | confirmTruncate=true | - | Critical |`, bestPractices: [ 'Always list versions before rollback to pick the right one', 'Enable validateBefore for rollback to catch structural issues', 'Use prune regularly to keep version history manageable', 'Never use truncate in production without explicit need', 'Document why you are rolling back for audit purposes' ], pitfalls: [ 'Rollback overwrites current workflow - backup is created automatically', 'Deleted versions cannot be recovered', 'Truncate affects ALL workflows - use with extreme caution', 'Version IDs are sequential but may have gaps after deletes', 'Large workflows may have significant version storage overhead' ], relatedTools: [ 'n8n_get_workflow - View current workflow state', 'n8n_update_partial_workflow - Make incremental changes', 'n8n_validate_workflow - Validate before deployment' ] } };