From 17530c0f72e9b7bfec019ac6dc8c647600b6ddf9 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:22:51 +0200 Subject: [PATCH 1/3] fix: use 'updates' property consistently in updateNode operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Changed UpdateNodeOperation interface to use 'updates' instead of 'changes' - Updated UpdateConnectionOperation for consistency - Fixed implementation in workflow-diff-engine.ts - Updated Zod schema validation - Fixed documentation and examples - Updated tests to match new property name This resolves GitHub issues #159 and #168 where partial workflow updates were failing, forcing AI agents to fall back to expensive full updates. 🤖 Generated with Claude Code Co-Authored-By: Claude --- src/mcp/handlers-workflow-diff.ts | 2 +- .../n8n-update-partial-workflow.ts | 4 ++-- src/services/workflow-diff-engine.ts | 16 ++++++++-------- src/types/workflow-diff.ts | 4 ++-- tests/unit/services/workflow-diff-engine.test.ts | 10 +++++----- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/mcp/handlers-workflow-diff.ts b/src/mcp/handlers-workflow-diff.ts index b6f541e..ece328e 100644 --- a/src/mcp/handlers-workflow-diff.ts +++ b/src/mcp/handlers-workflow-diff.ts @@ -21,7 +21,7 @@ const workflowDiffSchema = z.object({ node: z.any().optional(), nodeId: z.string().optional(), nodeName: z.string().optional(), - changes: z.any().optional(), + updates: z.any().optional(), position: z.tuple([z.number(), z.number()]).optional(), // Connection operations source: z.string().optional(), 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 4d393ed..b5b99dd 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 @@ -48,7 +48,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { }, returns: 'Updated workflow object or validation results if validateOnly=true', examples: [ - '// Update node parameter\nn8n_update_partial_workflow({id: "abc", operations: [{type: "updateNode", nodeName: "HTTP Request", changes: {"parameters.url": "https://api.example.com"}}]})', + '// Update node parameter\nn8n_update_partial_workflow({id: "abc", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.url": "https://api.example.com"}}]})', '// Add connection between nodes\nn8n_update_partial_workflow({id: "xyz", operations: [{type: "addConnection", source: "Webhook", target: "Slack", sourceOutput: "main", targetInput: "main"}]})', '// Multiple operations in one call\nn8n_update_partial_workflow({id: "123", operations: [\n {type: "addNode", node: {name: "Transform", type: "n8n-nodes-base.code", position: [400, 300]}},\n {type: "addConnection", source: "Webhook", target: "Transform"},\n {type: "updateSettings", settings: {timezone: "America/New_York"}}\n]})', '// Validate before applying\nn8n_update_partial_workflow({id: "456", operations: [{type: "removeNode", nodeName: "Old Process"}], validateOnly: true})' @@ -73,7 +73,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { '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', - 'Dot notation for nested updates: use "parameters.url" not nested objects' + 'Use "updates" property for updateNode operations: {type: "updateNode", updates: {...}}' ], relatedTools: ['n8n_update_full_workflow', 'n8n_get_workflow', 'validate_workflow', 'tools_documentation'] } diff --git a/src/services/workflow-diff-engine.ts b/src/services/workflow-diff-engine.ts index b6ef852..6475942 100644 --- a/src/services/workflow-diff-engine.ts +++ b/src/services/workflow-diff-engine.ts @@ -453,8 +453,8 @@ export class WorkflowDiffEngine { const node = this.findNode(workflow, operation.nodeId, operation.nodeName); if (!node) return; - // Apply changes using dot notation - Object.entries(operation.changes).forEach(([path, value]) => { + // Apply updates using dot notation + Object.entries(operation.updates).forEach(([path, value]) => { this.setNestedProperty(node, path, value); }); } @@ -545,18 +545,18 @@ export class WorkflowDiffEngine { type: 'removeConnection', source: operation.source, target: operation.target, - sourceOutput: operation.changes.sourceOutput, - targetInput: operation.changes.targetInput + sourceOutput: operation.updates.sourceOutput, + targetInput: operation.updates.targetInput }); this.applyAddConnection(workflow, { type: 'addConnection', source: operation.source, target: operation.target, - sourceOutput: operation.changes.sourceOutput, - targetInput: operation.changes.targetInput, - sourceIndex: operation.changes.sourceIndex, - targetIndex: operation.changes.targetIndex + sourceOutput: operation.updates.sourceOutput, + targetInput: operation.updates.targetInput, + sourceIndex: operation.updates.sourceIndex, + targetIndex: operation.updates.targetIndex }); } diff --git a/src/types/workflow-diff.ts b/src/types/workflow-diff.ts index 62c32c2..c74ca0b 100644 --- a/src/types/workflow-diff.ts +++ b/src/types/workflow-diff.ts @@ -31,7 +31,7 @@ export interface UpdateNodeOperation extends DiffOperation { type: 'updateNode'; nodeId?: string; // Can use either ID or name nodeName?: string; - changes: { + updates: { [path: string]: any; // Dot notation paths like 'parameters.url' }; } @@ -78,7 +78,7 @@ export interface UpdateConnectionOperation extends DiffOperation { type: 'updateConnection'; source: string; target: string; - changes: { + updates: { sourceOutput?: string; targetInput?: string; sourceIndex?: number; diff --git a/tests/unit/services/workflow-diff-engine.test.ts b/tests/unit/services/workflow-diff-engine.test.ts index f63fa2d..ff2d91f 100644 --- a/tests/unit/services/workflow-diff-engine.test.ts +++ b/tests/unit/services/workflow-diff-engine.test.ts @@ -281,7 +281,7 @@ describe('WorkflowDiffEngine', () => { const operation: UpdateNodeOperation = { type: 'updateNode', nodeId: 'http-1', - changes: { + updates: { 'parameters.method': 'POST', 'parameters.url': 'https://new-api.example.com' } @@ -304,7 +304,7 @@ describe('WorkflowDiffEngine', () => { const operation: UpdateNodeOperation = { type: 'updateNode', nodeName: 'Slack', - changes: { + updates: { 'parameters.resource': 'channel', 'parameters.operation': 'create', 'credentials.slackApi.name': 'New Slack Account' @@ -329,7 +329,7 @@ describe('WorkflowDiffEngine', () => { const operation: UpdateNodeOperation = { type: 'updateNode', nodeId: 'non-existent', - changes: { + updates: { 'parameters.test': 'value' } }; @@ -617,7 +617,7 @@ describe('WorkflowDiffEngine', () => { type: 'updateConnection', source: 'IF', target: 'slack-1', - changes: { + updates: { sourceOutput: 'false', sourceIndex: 0, targetIndex: 0 @@ -1039,7 +1039,7 @@ describe('WorkflowDiffEngine', () => { const operation: UpdateNodeOperation = { type: 'updateNode', nodeId: 'Webhook', // Using name as ID - changes: { + updates: { 'parameters.path': 'new-webhook-path' } }; From 44f92063c32d818b739c95abb96d1d725a875628 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:29:17 +0200 Subject: [PATCH 2/3] test: update handlers-workflow-diff tests to use 'updates' property Fixed remaining test cases that were still using 'changes' instead of 'updates' for updateNode operations. All tests now pass. --- tests/unit/mcp/handlers-workflow-diff.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/unit/mcp/handlers-workflow-diff.test.ts b/tests/unit/mcp/handlers-workflow-diff.test.ts index 013a5f9..8ce2299 100644 --- a/tests/unit/mcp/handlers-workflow-diff.test.ts +++ b/tests/unit/mcp/handlers-workflow-diff.test.ts @@ -159,7 +159,7 @@ describe('handlers-workflow-diff', () => { { type: 'updateNode', nodeId: 'node2', - changes: { name: 'Updated HTTP Request' }, + updates: { name: 'Updated HTTP Request' }, }, ], validateOnly: true, @@ -196,7 +196,7 @@ describe('handlers-workflow-diff', () => { { type: 'updateNode', nodeId: 'node1', - changes: { name: 'Updated Start' }, + updates: { name: 'Updated Start' }, }, { type: 'addNode', @@ -243,7 +243,7 @@ describe('handlers-workflow-diff', () => { { type: 'updateNode', nodeId: 'non-existent-node', - changes: { name: 'Updated' }, + updates: { name: 'Updated' }, }, ], }; @@ -320,7 +320,7 @@ describe('handlers-workflow-diff', () => { const result = await handleUpdatePartialWorkflow({ id: 'test-id', - operations: [{ type: 'updateNode', nodeId: 'node1', changes: {} }], + operations: [{ type: 'updateNode', nodeId: 'node1', updates: {} }], }); expect(result).toEqual({ @@ -341,7 +341,7 @@ describe('handlers-workflow-diff', () => { { // Missing required 'type' field nodeId: 'node1', - changes: {}, + updates: {}, }, ], }; @@ -417,7 +417,7 @@ describe('handlers-workflow-diff', () => { await handleUpdatePartialWorkflow({ id: 'test-id', - operations: [{ type: 'updateNode', nodeId: 'node1', changes: {} }], + operations: [{ type: 'updateNode', nodeId: 'node1', updates: {} }], }); expect(logger.debug).toHaveBeenCalledWith( @@ -502,7 +502,7 @@ describe('handlers-workflow-diff', () => { type: 'updateNode', nodeId: 'node1', nodeName: 'Start', // Both nodeId and nodeName provided - changes: { name: 'New Start' }, + updates: { name: 'New Start' }, description: 'Update start node name', }, { @@ -561,8 +561,8 @@ describe('handlers-workflow-diff', () => { const diffRequest = { id: 'test-workflow-id', operations: [ - { type: 'updateNode', nodeId: 'node1', changes: { name: 'Updated' } }, - { type: 'updateNode', nodeId: 'invalid-node', changes: { name: 'Fail' } }, + { type: 'updateNode', nodeId: 'node1', updates: { name: 'Updated' } }, + { type: 'updateNode', nodeId: 'invalid-node', updates: { name: 'Fail' } }, { type: 'addTag', tag: 'test' }, ], }; From e6f1d6bcf030c38fb2dbd8b223c9b2d083cd2b72 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:46:24 +0200 Subject: [PATCH 3/3] chore: bump version to 2.11.3 and update documentation - Bump version from 2.11.2 to 2.11.3 - Update README.md version badge - Add CHANGELOG.md entry documenting the fix for n8n_update_partial_workflow tool - Fix resolves GitHub issues #159 and #168 --- README.md | 2 +- docs/CHANGELOG.md | 9 +++++++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0062d7e..ebf6c6b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub stars](https://img.shields.io/github/stars/czlonkowski/n8n-mcp?style=social)](https://github.com/czlonkowski/n8n-mcp) -[![Version](https://img.shields.io/badge/version-2.11.2-blue.svg)](https://github.com/czlonkowski/n8n-mcp) +[![Version](https://img.shields.io/badge/version-2.11.3-blue.svg)](https://github.com/czlonkowski/n8n-mcp) [![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp) [![codecov](https://codecov.io/gh/czlonkowski/n8n-mcp/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/czlonkowski/n8n-mcp) [![Tests](https://img.shields.io/badge/tests-1728%20passing-brightgreen.svg)](https://github.com/czlonkowski/n8n-mcp/actions) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 40419cf..8da0ab4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.11.3] - 2025-09-17 + +### Fixed +- **n8n_update_partial_workflow Tool**: Fixed critical bug where updateNode and updateConnection operations were using incorrect property name + - Changed from `changes` property to `updates` property to match documentation and expected behavior + - Resolves issue where AI agents would break workflow connections when updating nodes + - Fixes GitHub issues #159 (update_partial_workflow is invalid) and #168 (partial workflow update returns error) + - All related tests updated to use correct property name + ## [2.11.2] - 2025-09-16 ### Updated diff --git a/package-lock.json b/package-lock.json index ea05125..f154630 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n-mcp", - "version": "2.11.2", + "version": "2.11.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "n8n-mcp", - "version": "2.11.2", + "version": "2.11.3", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.13.2", diff --git a/package.json b/package.json index f1f316e..53b6991 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp", - "version": "2.11.2", + "version": "2.11.3", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "main": "dist/index.js", "bin": {