mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-20 01:13:07 +00:00
Merge pull request #201 from czlonkowski/fix/update-partial-workflow-operation
Summary Fixed critical bug in n8n_update_partial_workflow where operations were using wrong property name Changed from changes to updates for consistency with operation naming Resolves issues where AI agents had to fall back to expensive full workflow updates Fixes Resolves update_partial_workflow is invalid #159 - update_partial_workflow is invalid Resolves Partial Workflow Update returns error #168 - Partial Workflow Update returns error Changes Made Updated UpdateNodeOperation interface to use updates instead of changes Updated UpdateConnectionOperation for consistency Fixed implementation in workflow-diff-engine.ts Updated Zod schema validation in handlers-workflow-diff.ts Fixed documentation and examples Updated all tests to use new property name Test Plan Build passes (npm run build) Tests pass for workflow-diff-engine Manually tested with real workflow - updates work correctly Verified connections are preserved after updates Before & After Before: {type: "updateNode", nodeId: "123", changes: {...}} ❌ Failed After: {type: "updateNode", nodeId: "123", updates: {...}} ✅ Works
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
|
||||
@@ -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
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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']
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user