mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-23 02:43:08 +00:00
Compare commits
3 Commits
fix/notifi
...
v2.40.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f0738e637 | ||
|
|
93816fce30 | ||
|
|
ec19c9dade |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.40.5] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Webhook workflows created via MCP get 404 errors** (Issue #643): Auto-inject `webhookId` (UUID) on webhook-type nodes (`webhook`, `webhookTrigger`, `formTrigger`, `chatTrigger`) during `cleanWorkflowForCreate()` and `cleanWorkflowForUpdate()`. n8n 2.10+ requires this field for proper webhook URL registration; without it, webhooks silently fail with 404. Existing `webhookId` values are preserved.
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.4] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Incorrect data tables availability info**: Removed "enterprise/cloud only" restriction from tool description and documentation — data tables are available on all n8n plans including self-hosted
|
||||
- **Redundant pitfalls removed**: Removed "Requires N8N_API_URL and N8N_API_KEY" and "enterprise or cloud plans" pitfalls — the first is implicit for all n8n management tools, the second was incorrect
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.3] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
2
dist/services/n8n-validation.d.ts.map
vendored
2
dist/services/n8n-validation.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-validation.d.ts","sourceRoot":"","sources":["../../src/services/n8n-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAkBH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUpC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;CAMnC,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,CAEpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAElG;AAGD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBrF;AAiBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoE5E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAkQ/E;AAGD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK7D;AAMD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CA+F5E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0D/E;AAGD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAmB/D;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CA6CpD;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmBpE"}
|
||||
{"version":3,"file":"n8n-validation.d.ts","sourceRoot":"","sources":["../../src/services/n8n-validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAkBH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUpC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;CAMnC,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,CAEpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAElG;AAmBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBrF;AAiBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsE5E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAkQ/E;AAGD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK7D;AAMD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CA+F5E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0D/E;AAGD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAmB/D;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CA6CpD;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmBpE"}
|
||||
21
dist/services/n8n-validation.js
vendored
21
dist/services/n8n-validation.js
vendored
@@ -1,4 +1,7 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.defaultWorkflowSettings = exports.workflowSettingsSchema = exports.workflowConnectionSchema = exports.workflowNodeSchema = void 0;
|
||||
exports.validateWorkflowNode = validateWorkflowNode;
|
||||
@@ -13,6 +16,7 @@ exports.validateOperatorStructure = validateOperatorStructure;
|
||||
exports.getWebhookUrl = getWebhookUrl;
|
||||
exports.getWorkflowStructureExample = getWorkflowStructureExample;
|
||||
exports.getWorkflowFixSuggestions = getWorkflowFixSuggestions;
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const zod_1 = require("zod");
|
||||
const node_type_utils_1 = require("../utils/node-type-utils");
|
||||
const node_classification_1 = require("../utils/node-classification");
|
||||
@@ -76,11 +80,27 @@ function validateWorkflowConnections(connections) {
|
||||
function validateWorkflowSettings(settings) {
|
||||
return exports.workflowSettingsSchema.parse(settings);
|
||||
}
|
||||
const WEBHOOK_NODE_TYPES = new Set([
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.formTrigger',
|
||||
'@n8n/n8n-nodes-langchain.chatTrigger',
|
||||
]);
|
||||
function ensureWebhookIds(nodes) {
|
||||
if (!nodes)
|
||||
return;
|
||||
for (const node of nodes) {
|
||||
if (WEBHOOK_NODE_TYPES.has(node.type) && !node.webhookId) {
|
||||
node.webhookId = crypto_1.default.randomUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
function cleanWorkflowForCreate(workflow) {
|
||||
const { id, createdAt, updatedAt, versionId, meta, active, tags, ...cleanedWorkflow } = workflow;
|
||||
if (!cleanedWorkflow.settings || Object.keys(cleanedWorkflow.settings).length === 0) {
|
||||
cleanedWorkflow.settings = exports.defaultWorkflowSettings;
|
||||
}
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
function cleanWorkflowForUpdate(workflow) {
|
||||
@@ -116,6 +136,7 @@ function cleanWorkflowForUpdate(workflow) {
|
||||
else {
|
||||
cleanedWorkflow.settings = { executionOrder: 'v1' };
|
||||
}
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
function validateWorkflowStructure(workflow) {
|
||||
|
||||
2
dist/services/n8n-validation.js.map
vendored
2
dist/services/n8n-validation.js.map
vendored
File diff suppressed because one or more lines are too long
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.37.3",
|
||||
"version": "2.40.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.37.3",
|
||||
"version": "2.40.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.40.3",
|
||||
"version": "2.40.5",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -14,7 +14,7 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
||||
'Use dryRun: true to preview update/upsert/delete before applying',
|
||||
'Filter supports: eq, neq, like, ilike, gt, gte, lt, lte conditions',
|
||||
'Use returnData: true to get affected rows back from update/upsert/delete',
|
||||
'Requires n8n instance with data tables feature enabled'
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -96,8 +96,6 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
||||
'Use sortBy for deterministic row ordering',
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'Requires n8n instance with data tables feature enabled (available on cloud, enterprise, and self-hosted)',
|
||||
'deleteTable permanently deletes all rows — cannot be undone',
|
||||
'deleteRows requires a filter — cannot delete all rows without one',
|
||||
'Column types cannot be changed after table creation via API',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import crypto from 'crypto';
|
||||
import { z } from 'zod';
|
||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../types/n8n-api';
|
||||
import { isTriggerNode, isActivatableTrigger } from '../utils/node-type-utils';
|
||||
@@ -87,6 +88,22 @@ export function validateWorkflowSettings(settings: unknown): z.infer<typeof work
|
||||
return workflowSettingsSchema.parse(settings);
|
||||
}
|
||||
|
||||
const WEBHOOK_NODE_TYPES = new Set([
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.formTrigger',
|
||||
'@n8n/n8n-nodes-langchain.chatTrigger',
|
||||
]);
|
||||
|
||||
function ensureWebhookIds(nodes?: WorkflowNode[]): void {
|
||||
if (!nodes) return;
|
||||
for (const node of nodes) {
|
||||
if (WEBHOOK_NODE_TYPES.has(node.type) && !node.webhookId) {
|
||||
node.webhookId = crypto.randomUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean workflow data for API operations
|
||||
export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Workflow> {
|
||||
const {
|
||||
@@ -109,6 +126,8 @@ export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Wor
|
||||
cleanedWorkflow.settings = defaultWorkflowSettings;
|
||||
}
|
||||
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
|
||||
@@ -194,6 +213,8 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
||||
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
||||
}
|
||||
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@ import { WorkflowBuilder } from '../../utils/builders/workflow.builder';
|
||||
import { z } from 'zod';
|
||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../../../src/types/n8n-api';
|
||||
|
||||
function webhookNode(id: string, name: string, type: string, typeVersion = 2): WorkflowNode {
|
||||
return { id, name, type, typeVersion, position: [250, 300] as [number, number], parameters: {} };
|
||||
}
|
||||
|
||||
function workflowWithNodes(nodes: WorkflowNode[]): Partial<Workflow> {
|
||||
return { name: 'Test', nodes, connections: {} };
|
||||
}
|
||||
|
||||
describe('n8n-validation', () => {
|
||||
describe('Zod Schemas', () => {
|
||||
describe('workflowNodeSchema', () => {
|
||||
@@ -301,6 +309,44 @@ describe('n8n-validation', () => {
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.settings).toEqual(customSettings);
|
||||
});
|
||||
|
||||
it('should inject webhookId on webhook nodes missing it', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'),
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should preserve existing webhookId on webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
{ ...webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'), webhookId: 'existing-id' },
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBe('existing-id');
|
||||
});
|
||||
|
||||
it('should inject webhookId on formTrigger and chatTrigger nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Form', 'n8n-nodes-base.formTrigger'),
|
||||
webhookNode('2', 'Chat', '@n8n/n8n-nodes-langchain.chatTrigger'),
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
expect(cleaned.nodes![1].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should not inject webhookId on non-webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Set', 'n8n-nodes-base.set', 3.4),
|
||||
]);
|
||||
|
||||
const cleaned = cleanWorkflowForCreate(workflow as Workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanWorkflowForUpdate', () => {
|
||||
@@ -533,6 +579,44 @@ describe('n8n-validation', () => {
|
||||
});
|
||||
expect(cleaned.settings).not.toHaveProperty('someOtherProperty');
|
||||
});
|
||||
|
||||
it('should inject webhookId on webhook nodes missing it', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'),
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should preserve existing webhookId on webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
{ ...webhookNode('1', 'Webhook', 'n8n-nodes-base.webhook'), webhookId: 'existing-id' },
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBe('existing-id');
|
||||
});
|
||||
|
||||
it('should inject webhookId on formTrigger and chatTrigger nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Form', 'n8n-nodes-base.formTrigger'),
|
||||
webhookNode('2', 'Chat', '@n8n/n8n-nodes-langchain.chatTrigger'),
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
expect(cleaned.nodes![1].webhookId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-/);
|
||||
});
|
||||
|
||||
it('should not inject webhookId on non-webhook nodes', () => {
|
||||
const workflow = workflowWithNodes([
|
||||
webhookNode('1', 'Set', 'n8n-nodes-base.set', 3.4),
|
||||
]) as any;
|
||||
|
||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||
expect(cleaned.nodes![0].webhookId).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user