diff --git a/docs/workflow-diff-examples.md b/docs/workflow-diff-examples.md index c999e2e..4a349bf 100644 --- a/docs/workflow-diff-examples.md +++ b/docs/workflow-diff-examples.md @@ -296,6 +296,193 @@ The `n8n_update_partial_workflow` tool allows you to make targeted changes to wo } ``` +### Example 5: Large Batch Workflow Refactoring +Demonstrates handling many operations in a single request - no longer limited to 5 operations! + +```json +{ + "id": "workflow-batch", + "operations": [ + // Add 10 processing nodes + { + "type": "addNode", + "node": { + "name": "Filter Active Users", + "type": "n8n-nodes-base.filter", + "position": [400, 200], + "parameters": { "conditions": { "boolean": [{ "value1": "={{$json.active}}", "value2": true }] } } + } + }, + { + "type": "addNode", + "node": { + "name": "Transform User Data", + "type": "n8n-nodes-base.set", + "position": [600, 200], + "parameters": { "values": { "string": [{ "name": "formatted_name", "value": "={{$json.firstName}} {{$json.lastName}}" }] } } + } + }, + { + "type": "addNode", + "node": { + "name": "Validate Email", + "type": "n8n-nodes-base.if", + "position": [800, 200], + "parameters": { "conditions": { "string": [{ "value1": "={{$json.email}}", "operation": "contains", "value2": "@" }] } } + } + }, + { + "type": "addNode", + "node": { + "name": "Enrich with API", + "type": "n8n-nodes-base.httpRequest", + "position": [1000, 150], + "parameters": { "url": "https://api.example.com/enrich", "method": "POST" } + } + }, + { + "type": "addNode", + "node": { + "name": "Log Invalid Emails", + "type": "n8n-nodes-base.code", + "position": [1000, 350], + "parameters": { "jsCode": "console.log('Invalid email:', $json.email);\nreturn $json;" } + } + }, + { + "type": "addNode", + "node": { + "name": "Merge Results", + "type": "n8n-nodes-base.merge", + "position": [1200, 250] + } + }, + { + "type": "addNode", + "node": { + "name": "Deduplicate", + "type": "n8n-nodes-base.removeDuplicates", + "position": [1400, 250], + "parameters": { "propertyName": "id" } + } + }, + { + "type": "addNode", + "node": { + "name": "Sort by Date", + "type": "n8n-nodes-base.sort", + "position": [1600, 250], + "parameters": { "sortFieldsUi": { "sortField": [{ "fieldName": "created_at", "order": "descending" }] } } + } + }, + { + "type": "addNode", + "node": { + "name": "Batch for DB", + "type": "n8n-nodes-base.splitInBatches", + "position": [1800, 250], + "parameters": { "batchSize": 100 } + } + }, + { + "type": "addNode", + "node": { + "name": "Save to Database", + "type": "n8n-nodes-base.postgres", + "position": [2000, 250], + "parameters": { "operation": "insert", "table": "processed_users" } + } + }, + // Connect all the nodes + { + "type": "addConnection", + "source": "Get Users", + "target": "Filter Active Users" + }, + { + "type": "addConnection", + "source": "Filter Active Users", + "target": "Transform User Data" + }, + { + "type": "addConnection", + "source": "Transform User Data", + "target": "Validate Email" + }, + { + "type": "addConnection", + "source": "Validate Email", + "sourceOutput": "true", + "target": "Enrich with API" + }, + { + "type": "addConnection", + "source": "Validate Email", + "sourceOutput": "false", + "target": "Log Invalid Emails" + }, + { + "type": "addConnection", + "source": "Enrich with API", + "target": "Merge Results" + }, + { + "type": "addConnection", + "source": "Log Invalid Emails", + "target": "Merge Results", + "targetInput": "input2" + }, + { + "type": "addConnection", + "source": "Merge Results", + "target": "Deduplicate" + }, + { + "type": "addConnection", + "source": "Deduplicate", + "target": "Sort by Date" + }, + { + "type": "addConnection", + "source": "Sort by Date", + "target": "Batch for DB" + }, + { + "type": "addConnection", + "source": "Batch for DB", + "target": "Save to Database" + }, + // Update workflow metadata + { + "type": "updateName", + "name": "User Processing Pipeline v2" + }, + { + "type": "updateSettings", + "settings": { + "executionOrder": "v1", + "timezone": "UTC", + "saveDataSuccessExecution": "all" + } + }, + { + "type": "addTag", + "tag": "production" + }, + { + "type": "addTag", + "tag": "user-processing" + }, + { + "type": "addTag", + "tag": "v2" + } + ] +} +``` + +This example shows 26 operations in a single request, creating a complete data processing pipeline with proper error handling, validation, and batch processing. + ## Best Practices 1. **Use Descriptive Names**: Always provide clear node names and descriptions for operations diff --git a/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts b/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts index b5b99dd..190ec37 100644 --- a/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts +++ b/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts @@ -4,18 +4,18 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { name: 'n8n_update_partial_workflow', category: 'workflow_management', essentials: { - description: 'Update workflow incrementally with diff operations. Max 5 ops. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag.', + description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag.', keyParameters: ['id', 'operations'], example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "updateNode", ...}]})', performance: 'Fast (50-200ms)', tips: [ 'Use for targeted changes', - 'Supports up to 5 operations', + 'Supports multiple operations in one call', 'Validate with validateOnly first' ] }, full: { - description: `Updates workflows using surgical diff operations instead of full replacement. Supports 13 operation types for precise modifications. Operations are validated and applied atomically - all succeed or none are applied. Maximum 5 operations per call for safety. + description: `Updates workflows using surgical diff operations instead of full replacement. Supports 13 operation types for precise modifications. Operations are validated and applied atomically - all succeed or none are applied. ## Available Operations: @@ -42,7 +42,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { operations: { type: 'array', required: true, - description: 'Array of diff operations. Each must have "type" field and operation-specific properties. Max 5 operations. Nodes can be referenced by ID or name.' + description: 'Array of diff operations. Each must have "type" field and operation-specific properties. Nodes can be referenced by ID or name.' }, validateOnly: { type: 'boolean', description: 'If true, only validate operations without applying them' } }, @@ -64,12 +64,10 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { bestPractices: [ 'Use validateOnly to test operations', 'Group related changes in one call', - 'Keep operations under 5 for clarity', 'Check operation order for dependencies' ], pitfalls: [ '**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - will not work without n8n API access', - 'Maximum 5 operations per call - split larger updates', 'Operations validated together - all must be valid', 'Order matters for dependent operations (e.g., must add node before connecting to it)', 'Node references accept ID or name, but name must be unique', diff --git a/src/mcp/tools-n8n-manager.ts b/src/mcp/tools-n8n-manager.ts index 058fb7f..ae0093d 100644 --- a/src/mcp/tools-n8n-manager.ts +++ b/src/mcp/tools-n8n-manager.ts @@ -160,7 +160,7 @@ export const n8nManagementTools: ToolDefinition[] = [ }, { name: 'n8n_update_partial_workflow', - description: `Update workflow incrementally with diff operations. Max 5 ops. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`, + description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`, inputSchema: { type: 'object', additionalProperties: true, // Allow any extra properties Claude Desktop might add diff --git a/src/services/workflow-diff-engine.ts b/src/services/workflow-diff-engine.ts index 6475942..5b2c01c 100644 --- a/src/services/workflow-diff-engine.ts +++ b/src/services/workflow-diff-engine.ts @@ -41,17 +41,6 @@ export class WorkflowDiffEngine { request: WorkflowDiffRequest ): Promise { try { - // Limit operations to keep complexity manageable - if (request.operations.length > 5) { - return { - success: false, - errors: [{ - operation: -1, - message: 'Too many operations. Maximum 5 operations allowed per request to ensure transactional integrity.' - }] - }; - } - // Clone workflow to avoid modifying original const workflowCopy = JSON.parse(JSON.stringify(workflow)); diff --git a/tests/unit/services/workflow-diff-engine.test.ts b/tests/unit/services/workflow-diff-engine.test.ts index ff2d91f..0bbe5cb 100644 --- a/tests/unit/services/workflow-diff-engine.test.ts +++ b/tests/unit/services/workflow-diff-engine.test.ts @@ -1,8 +1,9 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { WorkflowDiffEngine } from '@/services/workflow-diff-engine'; import { createWorkflow, WorkflowBuilder } from '@tests/utils/builders/workflow.builder'; -import { +import { WorkflowDiffRequest, + WorkflowDiffOperation, AddNodeOperation, RemoveNodeOperation, UpdateNodeOperation, @@ -60,9 +61,10 @@ describe('WorkflowDiffEngine', () => { baseWorkflow.connections = newConnections; }); - describe('Operation Limits', () => { - it('should reject more than 5 operations', async () => { - const operations = Array(6).fill(null).map((_: any, i: number) => ({ + describe('Large Operation Batches', () => { + it('should handle many operations successfully', async () => { + // Test with 50 operations + const operations = Array(50).fill(null).map((_: any, i: number) => ({ type: 'updateName', name: `Name ${i}` } as UpdateNameOperation)); @@ -73,10 +75,47 @@ describe('WorkflowDiffEngine', () => { }; const result = await diffEngine.applyDiff(baseWorkflow, request); - - expect(result.success).toBe(false); - expect(result.errors).toHaveLength(1); - expect(result.errors![0].message).toContain('Too many operations'); + + expect(result.success).toBe(true); + expect(result.operationsApplied).toBe(50); + expect(result.workflow!.name).toBe('Name 49'); // Last operation wins + }); + + it('should handle 100+ mixed operations', async () => { + const operations: WorkflowDiffOperation[] = [ + // Add 30 nodes + ...Array(30).fill(null).map((_: any, i: number) => ({ + type: 'addNode', + node: { + name: `Node${i}`, + type: 'n8n-nodes-base.code', + position: [i * 100, 300], + parameters: {} + } + } as AddNodeOperation)), + // Update names 30 times + ...Array(30).fill(null).map((_: any, i: number) => ({ + type: 'updateName', + name: `Workflow Version ${i}` + } as UpdateNameOperation)), + // Add 40 tags + ...Array(40).fill(null).map((_: any, i: number) => ({ + type: 'addTag', + tag: `tag${i}` + } as AddTagOperation)) + ]; + + const request: WorkflowDiffRequest = { + id: 'test-workflow', + operations + }; + + const result = await diffEngine.applyDiff(baseWorkflow, request); + + expect(result.success).toBe(true); + expect(result.operationsApplied).toBe(100); + expect(result.workflow!.nodes.length).toBeGreaterThan(30); + expect(result.workflow!.name).toBe('Workflow Version 29'); }); });