mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-20 17:33:08 +00:00
fix(p0): remove incorrect node type normalization before n8n API calls
## Bug Description
handleCreateWorkflow and handleUpdateFullWorkflow were incorrectly
normalizing node types from FULL form (n8n-nodes-base.webhook) to
SHORT form (nodes-base.webhook) before validation and API calls.
This caused 100% failure rate for workflow creation because:
- n8n API requires FULL form (n8n-nodes-base.*)
- Database stores SHORT form (nodes-base.*)
- NodeTypeNormalizer converts TO SHORT form (for database)
- But was being used BEFORE API calls (incorrect)
## Root Cause
NodeTypeNormalizer was designed for database lookups but was
incorrectly applied to API operations. The method name
`normalizeToFullForm()` is misleading - it actually normalizes
TO SHORT form.
## Changes
1. handlers-n8n-manager.ts:
- Removed NodeTypeNormalizer.normalizeWorkflowNodeTypes() from
handleCreateWorkflow (line 288)
- Removed normalization from handleUpdateFullWorkflow (line 544-557)
- Added proactive SHORT form detection with helpful errors
- Added comments explaining n8n API expects FULL form
2. node-type-normalizer.ts:
- Added prominent WARNING about not using before API calls
- Added examples showing CORRECT vs INCORRECT usage
- Clarified this is FOR DATABASE OPERATIONS ONLY
3. handlers-n8n-manager.test.ts:
- Fixed test to expect FULL form (not SHORT) sent to API
- Removed incorrect expectedNormalizedInput assertion
4. NEW: workflow-creation-node-type-format.test.ts:
- 7 integration tests with real validation (unmocked)
- Tests FULL form acceptance, SHORT form rejection
- Tests real-world workflows (webhook, schedule trigger)
- Regression test to prevent bug reintroduction
## Verification
Before fix:
❌ Manual Trigger → Set: FAILED
❌ Webhook → HTTP Request: FAILED
Failure rate: 100%
After fix:
✅ Manual Trigger → Set: SUCCESS (ID: kTAaDZwdpzj8gqzM)
✅ Webhook → HTTP Request: SUCCESS (ID: aPtQUb54uuHIqX52)
✅ All 39 tests passing (32 unit + 7 integration)
Success rate: 100%
## Impact
- Fixes: Complete blocking bug preventing all workflow creation
- Risk: Zero (removing buggy behavior)
- Breaking: None (external API unchanged)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -284,14 +284,37 @@ export async function handleCreateWorkflow(args: unknown, context?: InstanceCont
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = createWorkflowSchema.parse(args);
|
||||
|
||||
// Normalize all node types before validation
|
||||
const normalizedInput = NodeTypeNormalizer.normalizeWorkflowNodeTypes(input);
|
||||
// Proactively detect SHORT form node types (common mistake)
|
||||
const shortFormErrors: string[] = [];
|
||||
input.nodes?.forEach((node: any, index: number) => {
|
||||
if (node.type?.startsWith('nodes-base.') || node.type?.startsWith('nodes-langchain.')) {
|
||||
const fullForm = node.type.startsWith('nodes-base.')
|
||||
? node.type.replace('nodes-base.', 'n8n-nodes-base.')
|
||||
: node.type.replace('nodes-langchain.', '@n8n/n8n-nodes-langchain.');
|
||||
shortFormErrors.push(
|
||||
`Node ${index} ("${node.name}") uses SHORT form "${node.type}". ` +
|
||||
`The n8n API requires FULL form. Change to "${fullForm}"`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Validate workflow structure
|
||||
const errors = validateWorkflowStructure(normalizedInput);
|
||||
if (shortFormErrors.length > 0) {
|
||||
telemetry.trackWorkflowCreation(input, false);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Node type format error: n8n API requires FULL form node types',
|
||||
details: {
|
||||
errors: shortFormErrors,
|
||||
hint: 'Use n8n-nodes-base.* instead of nodes-base.* for standard nodes'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Validate workflow structure (n8n API expects FULL form: n8n-nodes-base.*)
|
||||
const errors = validateWorkflowStructure(input);
|
||||
if (errors.length > 0) {
|
||||
// Track validation failure
|
||||
telemetry.trackWorkflowCreation(normalizedInput, false);
|
||||
telemetry.trackWorkflowCreation(input, false);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
@@ -300,8 +323,8 @@ export async function handleCreateWorkflow(args: unknown, context?: InstanceCont
|
||||
};
|
||||
}
|
||||
|
||||
// Create workflow with normalized node types
|
||||
const workflow = await client.createWorkflow(normalizedInput);
|
||||
// Create workflow (n8n API expects node types in FULL form)
|
||||
const workflow = await client.createWorkflow(input);
|
||||
|
||||
// Track successful workflow creation
|
||||
telemetry.trackWorkflowCreation(workflow, true);
|
||||
@@ -540,10 +563,8 @@ export async function handleUpdateWorkflow(args: unknown, context?: InstanceCont
|
||||
};
|
||||
}
|
||||
|
||||
// Normalize all node types before validation
|
||||
const normalizedWorkflow = NodeTypeNormalizer.normalizeWorkflowNodeTypes(fullWorkflow);
|
||||
|
||||
const errors = validateWorkflowStructure(normalizedWorkflow);
|
||||
// Validate workflow structure (n8n API expects FULL form: n8n-nodes-base.*)
|
||||
const errors = validateWorkflowStructure(fullWorkflow);
|
||||
if (errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -551,11 +572,6 @@ export async function handleUpdateWorkflow(args: unknown, context?: InstanceCont
|
||||
details: { errors }
|
||||
};
|
||||
}
|
||||
|
||||
// Update updateData with normalized nodes if they were modified
|
||||
if (updateData.nodes) {
|
||||
updateData.nodes = normalizedWorkflow.nodes;
|
||||
}
|
||||
}
|
||||
|
||||
// Update workflow
|
||||
|
||||
@@ -1,27 +1,44 @@
|
||||
/**
|
||||
* Universal Node Type Normalizer
|
||||
* Universal Node Type Normalizer - FOR DATABASE OPERATIONS ONLY
|
||||
*
|
||||
* Converts ANY node type variation to the canonical SHORT form used by the database.
|
||||
* This fixes the critical issue where AI agents or external sources may produce
|
||||
* full-form node types (e.g., "n8n-nodes-base.webhook") which need to be normalized
|
||||
* to match the database storage format (e.g., "nodes-base.webhook").
|
||||
* ⚠️ WARNING: Do NOT use before n8n API calls!
|
||||
*
|
||||
* This class converts node types to SHORT form (database format).
|
||||
* The n8n API requires FULL form (n8n-nodes-base.*).
|
||||
*
|
||||
* **Use this ONLY when:**
|
||||
* - Querying the node database
|
||||
* - Searching for node information
|
||||
* - Looking up node metadata
|
||||
*
|
||||
* **Do NOT use before:**
|
||||
* - Creating workflows (n8n_create_workflow)
|
||||
* - Updating workflows (n8n_update_workflow)
|
||||
* - Any n8n API calls
|
||||
*
|
||||
* **IMPORTANT:** The n8n-mcp database stores nodes in SHORT form:
|
||||
* - n8n-nodes-base → nodes-base
|
||||
* - @n8n/n8n-nodes-langchain → nodes-langchain
|
||||
*
|
||||
* Handles:
|
||||
* - Full form → Short form (n8n-nodes-base.X → nodes-base.X)
|
||||
* - Already short form → Unchanged
|
||||
* - LangChain nodes → Proper short prefix
|
||||
* But the n8n API requires FULL form:
|
||||
* - nodes-base → n8n-nodes-base
|
||||
* - nodes-langchain → @n8n/n8n-nodes-langchain
|
||||
*
|
||||
* @example
|
||||
* NodeTypeNormalizer.normalizeToFullForm('n8n-nodes-base.webhook')
|
||||
* @example Database Lookup (CORRECT usage)
|
||||
* const dbType = NodeTypeNormalizer.normalizeToFullForm('n8n-nodes-base.webhook')
|
||||
* // → 'nodes-base.webhook'
|
||||
* const node = await repository.getNode(dbType)
|
||||
*
|
||||
* @example
|
||||
* NodeTypeNormalizer.normalizeToFullForm('nodes-base.webhook')
|
||||
* // → 'nodes-base.webhook' (unchanged)
|
||||
* @example API Call (INCORRECT - Do NOT do this!)
|
||||
* const workflow = { nodes: [{ type: 'n8n-nodes-base.webhook' }] }
|
||||
* const normalized = NodeTypeNormalizer.normalizeWorkflowNodeTypes(workflow)
|
||||
* // ❌ WRONG! normalized has SHORT form, API needs FULL form
|
||||
* await client.createWorkflow(normalized) // FAILS!
|
||||
*
|
||||
* @example API Call (CORRECT)
|
||||
* const workflow = { nodes: [{ type: 'n8n-nodes-base.webhook' }] }
|
||||
* // ✅ Send as-is to API (FULL form required)
|
||||
* await client.createWorkflow(workflow) // WORKS!
|
||||
*/
|
||||
|
||||
export interface NodeTypeNormalizationResult {
|
||||
|
||||
Reference in New Issue
Block a user