mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-05 21:13:07 +00:00
feat: remove unnecessary 5-operation limit from n8n_update_partial_workflow
The 5-operation limit was overly conservative and unnecessary. Analysis showed: - Workflow is cloned before modifications (no original mutation) - All operations validated before any are applied (true atomicity) - First error causes immediate return (no partial state possible) - Two-pass processing handles dependencies correctly Changes: - Remove hard-coded 5-operation limit check from workflow-diff-engine.ts - Update tool descriptions and documentation to reflect unlimited operations - Add tests verifying 50 and 100+ operations work successfully - Add example showing 26 operations in single request The system already ensures complete transactional integrity regardless of operation count. Bottleneck is workflow size, not operation count. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
## Best Practices
|
||||||
|
|
||||||
1. **Use Descriptive Names**: Always provide clear node names and descriptions for operations
|
1. **Use Descriptive Names**: Always provide clear node names and descriptions for operations
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
|||||||
name: 'n8n_update_partial_workflow',
|
name: 'n8n_update_partial_workflow',
|
||||||
category: 'workflow_management',
|
category: 'workflow_management',
|
||||||
essentials: {
|
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'],
|
keyParameters: ['id', 'operations'],
|
||||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "updateNode", ...}]})',
|
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "updateNode", ...}]})',
|
||||||
performance: 'Fast (50-200ms)',
|
performance: 'Fast (50-200ms)',
|
||||||
tips: [
|
tips: [
|
||||||
'Use for targeted changes',
|
'Use for targeted changes',
|
||||||
'Supports up to 5 operations',
|
'Supports multiple operations in one call',
|
||||||
'Validate with validateOnly first'
|
'Validate with validateOnly first'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
full: {
|
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:
|
## Available Operations:
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
|||||||
operations: {
|
operations: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
required: true,
|
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' }
|
validateOnly: { type: 'boolean', description: 'If true, only validate operations without applying them' }
|
||||||
},
|
},
|
||||||
@@ -64,12 +64,10 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
|||||||
bestPractices: [
|
bestPractices: [
|
||||||
'Use validateOnly to test operations',
|
'Use validateOnly to test operations',
|
||||||
'Group related changes in one call',
|
'Group related changes in one call',
|
||||||
'Keep operations under 5 for clarity',
|
|
||||||
'Check operation order for dependencies'
|
'Check operation order for dependencies'
|
||||||
],
|
],
|
||||||
pitfalls: [
|
pitfalls: [
|
||||||
'**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - will not work without n8n API access',
|
'**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',
|
'Operations validated together - all must be valid',
|
||||||
'Order matters for dependent operations (e.g., must add node before connecting to it)',
|
'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',
|
'Node references accept ID or name, but name must be unique',
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_update_partial_workflow',
|
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: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: true, // Allow any extra properties Claude Desktop might add
|
additionalProperties: true, // Allow any extra properties Claude Desktop might add
|
||||||
|
|||||||
@@ -41,17 +41,6 @@ export class WorkflowDiffEngine {
|
|||||||
request: WorkflowDiffRequest
|
request: WorkflowDiffRequest
|
||||||
): Promise<WorkflowDiffResult> {
|
): Promise<WorkflowDiffResult> {
|
||||||
try {
|
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
|
// Clone workflow to avoid modifying original
|
||||||
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { WorkflowDiffEngine } from '@/services/workflow-diff-engine';
|
|||||||
import { createWorkflow, WorkflowBuilder } from '@tests/utils/builders/workflow.builder';
|
import { createWorkflow, WorkflowBuilder } from '@tests/utils/builders/workflow.builder';
|
||||||
import {
|
import {
|
||||||
WorkflowDiffRequest,
|
WorkflowDiffRequest,
|
||||||
|
WorkflowDiffOperation,
|
||||||
AddNodeOperation,
|
AddNodeOperation,
|
||||||
RemoveNodeOperation,
|
RemoveNodeOperation,
|
||||||
UpdateNodeOperation,
|
UpdateNodeOperation,
|
||||||
@@ -60,9 +61,10 @@ describe('WorkflowDiffEngine', () => {
|
|||||||
baseWorkflow.connections = newConnections;
|
baseWorkflow.connections = newConnections;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Operation Limits', () => {
|
describe('Large Operation Batches', () => {
|
||||||
it('should reject more than 5 operations', async () => {
|
it('should handle many operations successfully', async () => {
|
||||||
const operations = Array(6).fill(null).map((_: any, i: number) => ({
|
// Test with 50 operations
|
||||||
|
const operations = Array(50).fill(null).map((_: any, i: number) => ({
|
||||||
type: 'updateName',
|
type: 'updateName',
|
||||||
name: `Name ${i}`
|
name: `Name ${i}`
|
||||||
} as UpdateNameOperation));
|
} as UpdateNameOperation));
|
||||||
@@ -74,9 +76,46 @@ describe('WorkflowDiffEngine', () => {
|
|||||||
|
|
||||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||||
|
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(true);
|
||||||
expect(result.errors).toHaveLength(1);
|
expect(result.operationsApplied).toBe(50);
|
||||||
expect(result.errors![0].message).toContain('Too many operations');
|
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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user