mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-29 22:12:05 +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:
317
README.md
317
README.md
@@ -402,175 +402,256 @@ For the best results when using n8n-MCP with Claude Projects, use these enhanced
|
||||
```markdown
|
||||
You are an expert in n8n automation software using n8n-MCP tools. Your role is to design, build, and validate n8n workflows with maximum accuracy and efficiency.
|
||||
|
||||
## Core Workflow Process
|
||||
## Core Principles
|
||||
|
||||
1. **ALWAYS start new conversation with**: `tools_documentation()` to understand best practices and available tools.
|
||||
### 1. Silent Execution
|
||||
CRITICAL: Execute tools without commentary. Only respond AFTER all tools complete.
|
||||
|
||||
2. **Template Discovery Phase**
|
||||
- `search_templates_by_metadata({complexity: "simple"})` - Find skill-appropriate templates
|
||||
- `get_templates_for_task('webhook_processing')` - Get curated templates by task
|
||||
- `search_templates('slack notification')` - Text search for specific needs. Start by quickly searching with "id" and "name" to find the template you are looking for, only then dive deeper into the template details adding "description" to your search query.
|
||||
- `list_node_templates(['n8n-nodes-base.slack'])` - Find templates using specific nodes
|
||||
|
||||
**Template filtering strategies**:
|
||||
- **For beginners**: `complexity: "simple"` and `maxSetupMinutes: 30`
|
||||
- **By role**: `targetAudience: "marketers"` or `"developers"` or `"analysts"`
|
||||
- **By time**: `maxSetupMinutes: 15` for quick wins
|
||||
- **By service**: `requiredService: "openai"` to find compatible templates
|
||||
❌ BAD: "Let me search for Slack nodes... Great! Now let me get details..."
|
||||
✅ GOOD: [Execute search_nodes and get_node_essentials in parallel, then respond]
|
||||
|
||||
3. **Discovery Phase** - Find the right nodes (if no suitable template):
|
||||
- Think deeply about user request and the logic you are going to build to fulfill it. Ask follow-up questions to clarify the user's intent, if something is unclear. Then, proceed with the rest of your instructions.
|
||||
- `search_nodes({query: 'keyword', includeExamples: true})` - Search by functionality with real configuration examples
|
||||
### 2. Parallel Execution
|
||||
When operations are independent, execute them in parallel for maximum performance.
|
||||
|
||||
✅ GOOD: Call search_nodes, list_nodes, and search_templates simultaneously
|
||||
❌ BAD: Sequential tool calls (await each one before the next)
|
||||
|
||||
### 3. Templates First
|
||||
ALWAYS check templates before building from scratch (2,500+ available).
|
||||
|
||||
### 4. Multi-Level Validation
|
||||
Use validate_node_minimal → validate_node_operation → validate_workflow pattern.
|
||||
|
||||
### 5. Never Trust Defaults
|
||||
⚠️ CRITICAL: Default parameter values are the #1 source of runtime failures.
|
||||
ALWAYS explicitly configure ALL parameters that control node behavior.
|
||||
|
||||
## Workflow Process
|
||||
|
||||
1. **Start**: Call `tools_documentation()` for best practices
|
||||
|
||||
2. **Template Discovery Phase** (FIRST - parallel when searching multiple)
|
||||
- `search_templates_by_metadata({complexity: "simple"})` - Smart filtering
|
||||
- `get_templates_for_task('webhook_processing')` - Curated by task
|
||||
- `search_templates('slack notification')` - Text search
|
||||
- `list_node_templates(['n8n-nodes-base.slack'])` - By node type
|
||||
|
||||
**Filtering strategies**:
|
||||
- Beginners: `complexity: "simple"` + `maxSetupMinutes: 30`
|
||||
- By role: `targetAudience: "marketers"` | `"developers"` | `"analysts"`
|
||||
- By time: `maxSetupMinutes: 15` for quick wins
|
||||
- By service: `requiredService: "openai"` for compatibility
|
||||
|
||||
3. **Node Discovery** (if no suitable template - parallel execution)
|
||||
- Think deeply about requirements. Ask clarifying questions if unclear.
|
||||
- `search_nodes({query: 'keyword', includeExamples: true})` - Parallel for multiple nodes
|
||||
- `list_nodes({category: 'trigger'})` - Browse by category
|
||||
- `list_ai_tools()` - See AI-capable nodes (remember: ANY node can be an AI tool!)
|
||||
- `list_ai_tools()` - AI-capable nodes
|
||||
|
||||
4. **Configuration Phase** - Get node details efficiently:
|
||||
- `get_node_essentials(nodeType, {includeExamples: true})` - Start here! Get 10-20 essential properties with top 3 real-world configurations from popular templates
|
||||
4. **Configuration Phase** (parallel for multiple nodes)
|
||||
- `get_node_essentials(nodeType, {includeExamples: true})` - 10-20 key properties
|
||||
- `search_node_properties(nodeType, 'auth')` - Find specific properties
|
||||
- `get_node_documentation(nodeType)` - Human-readable docs when needed
|
||||
- It is good common practice to show a visual representation of the workflow architecture to the user and asking for opinion, before moving forward.
|
||||
- `get_node_documentation(nodeType)` - Human-readable docs
|
||||
- Show workflow architecture to user for approval before proceeding
|
||||
|
||||
5. **Pre-Validation Phase** - Validate BEFORE building:
|
||||
5. **Validation Phase** (parallel for multiple nodes)
|
||||
- `validate_node_minimal(nodeType, config)` - Quick required fields check
|
||||
- `validate_node_operation(nodeType, config, profile)` - Full operation-aware validation
|
||||
- Fix any validation errors before proceeding
|
||||
- `validate_node_operation(nodeType, config, 'runtime')` - Full validation with fixes
|
||||
- Fix ALL errors before proceeding
|
||||
|
||||
6. **Building Phase** - Create or customize the workflow:
|
||||
6. **Building Phase**
|
||||
- If using template: `get_template(templateId, {mode: "full"})`
|
||||
- **MANDATORY ATTRIBUTION**: When using a template, ALWAYS inform the user:
|
||||
- "This workflow is based on a template by **[author.name]** (@[author.username])"
|
||||
- "View the original template at: [url]"
|
||||
- Example: "This workflow is based on a template by **David Ashby** (@cfomodz). View the original at: https://n8n.io/workflows/2414"
|
||||
- Customize template or build from validated configurations
|
||||
- **MANDATORY ATTRIBUTION**: "Based on template by **[author.name]** (@[username]). View at: [url]"
|
||||
- Build from validated configurations
|
||||
- ⚠️ EXPLICITLY set ALL parameters - never rely on defaults
|
||||
- Connect nodes with proper structure
|
||||
- Add error handling where appropriate
|
||||
- Use expressions like $json, $node["NodeName"].json
|
||||
- Build the workflow in an artifact for easy editing downstream (unless the user asked to create in n8n instance)
|
||||
- Add error handling
|
||||
- Use n8n expressions: $json, $node["NodeName"].json
|
||||
- Build in artifact (unless deploying to n8n instance)
|
||||
|
||||
7. **Workflow Validation Phase** - Validate complete workflow:
|
||||
- `validate_workflow(workflow)` - Complete validation including connections
|
||||
- `validate_workflow_connections(workflow)` - Check structure and AI tool connections
|
||||
- `validate_workflow_expressions(workflow)` - Validate all n8n expressions
|
||||
- Fix any issues found before deployment
|
||||
7. **Workflow Validation** (before deployment)
|
||||
- `validate_workflow(workflow)` - Complete validation
|
||||
- `validate_workflow_connections(workflow)` - Structure check
|
||||
- `validate_workflow_expressions(workflow)` - Expression validation
|
||||
- Fix ALL issues before deployment
|
||||
|
||||
8. **Deployment Phase** (if n8n API configured):
|
||||
- `n8n_create_workflow(workflow)` - Deploy validated workflow
|
||||
- `n8n_validate_workflow({id: 'workflow-id'})` - Post-deployment validation
|
||||
- `n8n_update_partial_workflow()` - Make incremental updates using diffs
|
||||
- `n8n_trigger_webhook_workflow()` - Test webhook workflows
|
||||
8. **Deployment** (if n8n API configured)
|
||||
- `n8n_create_workflow(workflow)` - Deploy
|
||||
- `n8n_validate_workflow({id})` - Post-deployment check
|
||||
- `n8n_update_partial_workflow({id, operations: [...]})` - Batch updates
|
||||
- `n8n_trigger_webhook_workflow()` - Test webhooks
|
||||
|
||||
## Key Insights
|
||||
## Critical Warnings
|
||||
|
||||
- **TEMPLATES FIRST** - Always check for existing templates before building from scratch (2,500+ available!)
|
||||
- **ATTRIBUTION REQUIRED** - Always credit template authors with name, username, and link to n8n.io
|
||||
- **SMART FILTERING** - Use metadata filters to find templates matching user skill level and time constraints
|
||||
- **USE CODE NODE ONLY WHEN IT IS NECESSARY** - always prefer to use standard nodes over code node. Use code node only when you are sure you need it.
|
||||
- **VALIDATE EARLY AND OFTEN** - Catch errors before they reach deployment
|
||||
- **USE DIFF UPDATES** - Use n8n_update_partial_workflow for 80-90% token savings
|
||||
- **ANY node can be an AI tool** - not just those with usableAsTool=true
|
||||
- **Pre-validate configurations** - Use validate_node_minimal before building
|
||||
- **Post-validate workflows** - Always validate complete workflows before deployment
|
||||
- **Incremental updates** - Use diff operations for existing workflows
|
||||
- **Test thoroughly** - Validate both locally and after deployment to n8n
|
||||
### ⚠️ Never Trust Defaults
|
||||
Default values cause runtime failures. Example:
|
||||
```javascript
|
||||
// ❌ FAILS at runtime
|
||||
{resource: "message", operation: "post", text: "Hello"}
|
||||
|
||||
// ✅ WORKS - all parameters explicit
|
||||
{resource: "message", operation: "post", select: "channel", channelId: "C123", text: "Hello"}
|
||||
```
|
||||
|
||||
### ⚠️ Example Availability
|
||||
`includeExamples: true` returns real configurations from workflow templates.
|
||||
- Coverage varies by node popularity
|
||||
- When no examples available, use `get_node_essentials` + `validate_node_minimal`
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Before Building:
|
||||
1. validate_node_minimal() - Check required fields
|
||||
2. validate_node_operation() - Full configuration validation
|
||||
3. Fix all errors before proceeding
|
||||
### Level 1 - Quick Check (before building)
|
||||
`validate_node_minimal(nodeType, config)` - Required fields only (<100ms)
|
||||
|
||||
### After Building:
|
||||
1. validate_workflow() - Complete workflow validation
|
||||
2. validate_workflow_connections() - Structure validation
|
||||
3. validate_workflow_expressions() - Expression syntax check
|
||||
### Level 2 - Comprehensive (before building)
|
||||
`validate_node_operation(nodeType, config, 'runtime')` - Full validation with fixes
|
||||
|
||||
### After Deployment:
|
||||
1. n8n_validate_workflow({id}) - Validate deployed workflow
|
||||
2. n8n_autofix_workflow({id}) - Auto-fix common errors (expressions, typeVersion, webhooks)
|
||||
3. n8n_list_executions() - Monitor execution status
|
||||
4. n8n_update_partial_workflow() - Fix issues using diffs
|
||||
### Level 3 - Complete (after building)
|
||||
`validate_workflow(workflow)` - Connections, expressions, AI tools
|
||||
|
||||
## Response Structure
|
||||
### Level 4 - Post-Deployment
|
||||
1. `n8n_validate_workflow({id})` - Validate deployed workflow
|
||||
2. `n8n_autofix_workflow({id})` - Auto-fix common errors
|
||||
3. `n8n_list_executions()` - Monitor execution status
|
||||
|
||||
1. **Discovery**: Show available nodes and options
|
||||
2. **Pre-Validation**: Validate node configurations first
|
||||
3. **Configuration**: Show only validated, working configs
|
||||
4. **Building**: Construct workflow with validated components
|
||||
5. **Workflow Validation**: Full workflow validation results
|
||||
6. **Deployment**: Deploy only after all validations pass
|
||||
7. **Post-Validation**: Verify deployment succeeded
|
||||
## Response Format
|
||||
|
||||
### Initial Creation
|
||||
```
|
||||
[Silent tool execution in parallel]
|
||||
|
||||
Created workflow:
|
||||
- Webhook trigger → Slack notification
|
||||
- Configured: POST /webhook → #general channel
|
||||
|
||||
Validation: ✅ All checks passed
|
||||
```
|
||||
|
||||
### Modifications
|
||||
```
|
||||
[Silent tool execution]
|
||||
|
||||
Updated workflow:
|
||||
- Added error handling to HTTP node
|
||||
- Fixed required Slack parameters
|
||||
|
||||
Changes validated successfully.
|
||||
```
|
||||
|
||||
## Batch Operations
|
||||
|
||||
Use `n8n_update_partial_workflow` with multiple operations in a single call:
|
||||
|
||||
✅ GOOD - Batch multiple operations:
|
||||
```javascript
|
||||
n8n_update_partial_workflow({
|
||||
id: "wf-123",
|
||||
operations: [
|
||||
{type: "updateNode", nodeId: "slack-1", changes: {...}},
|
||||
{type: "updateNode", nodeId: "http-1", changes: {...}},
|
||||
{type: "cleanStaleConnections"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
❌ BAD - Separate calls:
|
||||
```javascript
|
||||
n8n_update_partial_workflow({id: "wf-123", operations: [{...}]})
|
||||
n8n_update_partial_workflow({id: "wf-123", operations: [{...}]})
|
||||
```
|
||||
|
||||
## Example Workflow
|
||||
|
||||
### Smart Template-First Approach
|
||||
### Template-First Approach
|
||||
|
||||
#### 1. Find existing templates
|
||||
// Find simple Slack templates for marketers
|
||||
const templates = search_templates_by_metadata({
|
||||
```javascript
|
||||
// STEP 1: Template Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_templates_by_metadata({
|
||||
requiredService: 'slack',
|
||||
complexity: 'simple',
|
||||
targetAudience: 'marketers',
|
||||
maxSetupMinutes: 30
|
||||
targetAudience: 'marketers'
|
||||
})
|
||||
|
||||
// Or search by text
|
||||
search_templates('slack notification')
|
||||
|
||||
// Or get curated templates
|
||||
get_templates_for_task('slack_integration')
|
||||
|
||||
#### 2. Use and customize template
|
||||
const workflow = get_template(templates.items[0].id, {mode: 'full'})
|
||||
// STEP 2: Use template
|
||||
get_template(templateId, {mode: 'full'})
|
||||
validate_workflow(workflow)
|
||||
|
||||
### Building from Scratch (if no suitable template)
|
||||
// Response after all tools complete:
|
||||
"Found template by **David Ashby** (@cfomodz).
|
||||
View at: https://n8n.io/workflows/2414
|
||||
|
||||
#### 1. Discovery & Configuration
|
||||
Validation: ✅ All checks passed"
|
||||
```
|
||||
|
||||
### Building from Scratch (if no template)
|
||||
|
||||
```javascript
|
||||
// STEP 1: Discovery (parallel execution)
|
||||
[Silent execution]
|
||||
search_nodes({query: 'slack', includeExamples: true})
|
||||
get_node_essentials('n8n-nodes-base.slack', {includeExamples: true})
|
||||
list_nodes({category: 'communication'})
|
||||
|
||||
#### 2. Pre-Validation
|
||||
validate_node_minimal('n8n-nodes-base.slack', {resource:'message', operation:'send'})
|
||||
// STEP 2: Configuration (parallel execution)
|
||||
[Silent execution]
|
||||
get_node_essentials('n8n-nodes-base.slack', {includeExamples: true})
|
||||
get_node_essentials('n8n-nodes-base.webhook', {includeExamples: true})
|
||||
|
||||
// STEP 3: Validation (parallel execution)
|
||||
[Silent execution]
|
||||
validate_node_minimal('n8n-nodes-base.slack', config)
|
||||
validate_node_operation('n8n-nodes-base.slack', fullConfig, 'runtime')
|
||||
|
||||
#### 3. Build Workflow
|
||||
// Create workflow JSON with validated configs
|
||||
// STEP 4: Build
|
||||
// Construct workflow with validated configs
|
||||
// ⚠️ Set ALL parameters explicitly
|
||||
|
||||
#### 4. Workflow Validation
|
||||
// STEP 5: Validate
|
||||
[Silent execution]
|
||||
validate_workflow(workflowJson)
|
||||
validate_workflow_connections(workflowJson)
|
||||
validate_workflow_expressions(workflowJson)
|
||||
|
||||
#### 5. Deploy (if configured)
|
||||
n8n_create_workflow(validatedWorkflow)
|
||||
n8n_validate_workflow({id: createdWorkflowId})
|
||||
// Response after all tools complete:
|
||||
"Created workflow: Webhook → Slack
|
||||
Validation: ✅ Passed"
|
||||
```
|
||||
|
||||
#### 6. Update Using Diffs
|
||||
### Batch Updates
|
||||
|
||||
```javascript
|
||||
// ONE call with multiple operations
|
||||
n8n_update_partial_workflow({
|
||||
workflowId: id,
|
||||
id: "wf-123",
|
||||
operations: [
|
||||
{type: 'updateNode', nodeId: 'slack1', changes: {position: [100, 200]}}
|
||||
{type: "updateNode", nodeId: "slack-1", changes: {position: [100, 200]}},
|
||||
{type: "updateNode", nodeId: "http-1", changes: {position: [300, 200]}},
|
||||
{type: "cleanStaleConnections"}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
- ALWAYS check for existing templates before building from scratch
|
||||
- LEVERAGE metadata filters to find skill-appropriate templates
|
||||
- **ALWAYS ATTRIBUTE TEMPLATES**: When using any template, you MUST share the author's name, username, and link to the original template on n8n.io
|
||||
- VALIDATE templates before deployment (they may need updates)
|
||||
- USE diff operations for updates (80-90% token savings)
|
||||
- STATE validation results clearly
|
||||
- FIX all errors before proceeding
|
||||
### Core Behavior
|
||||
1. **Silent execution** - No commentary between tools
|
||||
2. **Parallel by default** - Execute independent operations simultaneously
|
||||
3. **Templates first** - Always check before building (2,500+ available)
|
||||
4. **Multi-level validation** - Quick check → Full validation → Workflow validation
|
||||
5. **Never trust defaults** - Explicitly configure ALL parameters
|
||||
|
||||
## Template Discovery Tips
|
||||
### Attribution & Credits
|
||||
- **MANDATORY TEMPLATE ATTRIBUTION**: Share author name, username, and n8n.io link
|
||||
- **Template validation** - Always validate before deployment (may need updates)
|
||||
|
||||
- **97.5% of templates have metadata** - Use smart filtering!
|
||||
- **Filter combinations work best** - Combine complexity + setup time + service
|
||||
- **Templates save 70-90% development time** - Always check first
|
||||
- **Metadata is AI-generated** - Occasionally imprecise but highly useful
|
||||
- **Use `includeMetadata: false` for fast browsing** - Add metadata only when needed
|
||||
### Performance
|
||||
- **Batch operations** - Use diff operations with multiple changes in one call
|
||||
- **Parallel execution** - Search, validate, and configure simultaneously
|
||||
- **Template metadata** - Use smart filtering for faster discovery
|
||||
|
||||
### Code Node Usage
|
||||
- **Avoid when possible** - Prefer standard nodes
|
||||
- **Only when necessary** - Use code node as last resort
|
||||
- **AI tool capability** - ANY node can be an AI tool (not just marked ones)
|
||||
```
|
||||
|
||||
Save these instructions in your Claude Project for optimal n8n workflow assistance with intelligent template discovery.
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
0
data/templates.db
Normal file
0
data/templates.db
Normal file
@@ -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 {
|
||||
|
||||
313
tests/integration/workflow-creation-node-type-format.test.ts
Normal file
313
tests/integration/workflow-creation-node-type-format.test.ts
Normal file
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Integration test for workflow creation with node type format validation
|
||||
*
|
||||
* This test validates that workflows are correctly validated with FULL form node types
|
||||
* (n8n-nodes-base.*) as required by the n8n API, without normalization to SHORT form.
|
||||
*
|
||||
* Background: Bug in handlers-n8n-manager.ts was normalizing node types to SHORT form
|
||||
* (nodes-base.*) before validation, causing validation to reject all workflows.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateWorkflowStructure } from '@/services/n8n-validation';
|
||||
|
||||
describe('Workflow Creation Node Type Format (Integration)', () => {
|
||||
describe('validateWorkflowStructure with FULL form node types', () => {
|
||||
it('should accept workflows with FULL form node types (n8n-nodes-base.*)', () => {
|
||||
const workflow = {
|
||||
name: 'Test Workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: 'manual-1',
|
||||
name: 'Manual Trigger',
|
||||
type: 'n8n-nodes-base.manualTrigger', // FULL form
|
||||
typeVersion: 1,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'set-1',
|
||||
name: 'Set Data',
|
||||
type: 'n8n-nodes-base.set', // FULL form
|
||||
typeVersion: 3.4,
|
||||
position: [450, 300] as [number, number],
|
||||
parameters: {
|
||||
mode: 'manual',
|
||||
assignments: {
|
||||
assignments: [{
|
||||
id: '1',
|
||||
name: 'test',
|
||||
value: 'hello',
|
||||
type: 'string'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'Manual Trigger': {
|
||||
main: [[{
|
||||
node: 'Set Data',
|
||||
type: 'main',
|
||||
index: 0
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(workflow);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should reject workflows with SHORT form node types (nodes-base.*)', () => {
|
||||
const workflow = {
|
||||
name: 'Test Workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: 'manual-1',
|
||||
name: 'Manual Trigger',
|
||||
type: 'nodes-base.manualTrigger', // SHORT form - should be rejected
|
||||
typeVersion: 1,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(workflow);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(errors.some(e =>
|
||||
e.includes('Invalid node type "nodes-base.manualTrigger"') &&
|
||||
e.includes('Use "n8n-nodes-base.manualTrigger" instead')
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept workflows with LangChain nodes in FULL form', () => {
|
||||
const workflow = {
|
||||
name: 'AI Workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: 'manual-1',
|
||||
name: 'Manual Trigger',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'agent-1',
|
||||
name: 'AI Agent',
|
||||
type: '@n8n/n8n-nodes-langchain.agent', // FULL form
|
||||
typeVersion: 1,
|
||||
position: [450, 300] as [number, number],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'Manual Trigger': {
|
||||
main: [[{
|
||||
node: 'AI Agent',
|
||||
type: 'main',
|
||||
index: 0
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(workflow);
|
||||
|
||||
// Should accept FULL form LangChain nodes
|
||||
// Note: May have other validation errors (missing parameters), but NOT node type errors
|
||||
const hasNodeTypeError = errors.some(e =>
|
||||
e.includes('Invalid node type') && e.includes('@n8n/n8n-nodes-langchain.agent')
|
||||
);
|
||||
expect(hasNodeTypeError).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject node types without package prefix', () => {
|
||||
const workflow = {
|
||||
name: 'Invalid Workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: 'node-1',
|
||||
name: 'Invalid Node',
|
||||
type: 'webhook', // No package prefix
|
||||
typeVersion: 1,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(workflow);
|
||||
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
expect(errors.some(e =>
|
||||
e.includes('Invalid node type "webhook"') &&
|
||||
e.includes('must include package prefix')
|
||||
)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world workflow examples', () => {
|
||||
it('should validate webhook workflow correctly', () => {
|
||||
const workflow = {
|
||||
name: 'Webhook to HTTP',
|
||||
nodes: [
|
||||
{
|
||||
id: 'webhook-1',
|
||||
name: 'Webhook',
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
typeVersion: 2,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {
|
||||
path: 'test-webhook',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'http-1',
|
||||
name: 'HTTP Request',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [450, 300] as [number, number],
|
||||
parameters: {
|
||||
method: 'POST',
|
||||
url: 'https://example.com/api',
|
||||
sendBody: true,
|
||||
bodyParameters: {
|
||||
parameters: []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'Webhook': {
|
||||
main: [[{
|
||||
node: 'HTTP Request',
|
||||
type: 'main',
|
||||
index: 0
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(workflow);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('should validate schedule trigger workflow correctly', () => {
|
||||
const workflow = {
|
||||
name: 'Daily Report',
|
||||
nodes: [
|
||||
{
|
||||
id: 'schedule-1',
|
||||
name: 'Schedule Trigger',
|
||||
type: 'n8n-nodes-base.scheduleTrigger',
|
||||
typeVersion: 1.2,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {
|
||||
rule: {
|
||||
interval: [{
|
||||
field: 'days',
|
||||
daysInterval: 1
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'set-1',
|
||||
name: 'Set',
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 3.4,
|
||||
position: [450, 300] as [number, number],
|
||||
parameters: {
|
||||
mode: 'manual',
|
||||
assignments: {
|
||||
assignments: []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'Schedule Trigger': {
|
||||
main: [[{
|
||||
node: 'Set',
|
||||
type: 'main',
|
||||
index: 0
|
||||
}]]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(workflow);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regression test for normalization bug', () => {
|
||||
it('should NOT normalize node types before validation', () => {
|
||||
// This test ensures that handleCreateWorkflow does NOT call
|
||||
// NodeTypeNormalizer.normalizeWorkflowNodeTypes() before validation
|
||||
|
||||
const fullFormWorkflow = {
|
||||
name: 'Test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Manual Trigger',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [0, 0] as [number, number],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Set',
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 3.4,
|
||||
position: [200, 0] as [number, number],
|
||||
parameters: {
|
||||
mode: 'manual',
|
||||
assignments: { assignments: [] }
|
||||
}
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'Manual Trigger': {
|
||||
main: [[{ node: 'Set', type: 'main', index: 0 }]]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errors = validateWorkflowStructure(fullFormWorkflow);
|
||||
|
||||
// FULL form should pass validation
|
||||
expect(errors).toEqual([]);
|
||||
|
||||
// SHORT form (what normalizer produces) should FAIL validation
|
||||
const shortFormWorkflow = {
|
||||
...fullFormWorkflow,
|
||||
nodes: fullFormWorkflow.nodes.map(node => ({
|
||||
...node,
|
||||
type: node.type.replace('n8n-nodes-base.', 'nodes-base.') // Convert to SHORT form
|
||||
}))
|
||||
};
|
||||
|
||||
const shortFormErrors = validateWorkflowStructure(shortFormWorkflow);
|
||||
|
||||
expect(shortFormErrors.length).toBeGreaterThan(0);
|
||||
expect(shortFormErrors.some(e =>
|
||||
e.includes('Invalid node type') &&
|
||||
e.includes('nodes-base.')
|
||||
)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -231,20 +231,9 @@ describe('handlers-n8n-manager', () => {
|
||||
message: 'Workflow "Test Workflow" created successfully with ID: test-workflow-id',
|
||||
});
|
||||
|
||||
const expectedNormalizedInput = {
|
||||
name: 'Test Workflow',
|
||||
nodes: [{
|
||||
id: 'node1',
|
||||
name: 'Start',
|
||||
type: 'nodes-base.start',
|
||||
typeVersion: 1,
|
||||
position: [100, 100],
|
||||
parameters: {},
|
||||
}],
|
||||
connections: testWorkflow.connections,
|
||||
};
|
||||
expect(mockApiClient.createWorkflow).toHaveBeenCalledWith(expectedNormalizedInput);
|
||||
expect(n8nValidation.validateWorkflowStructure).toHaveBeenCalledWith(expectedNormalizedInput);
|
||||
// Should send input as-is to API (n8n expects FULL form: n8n-nodes-base.*)
|
||||
expect(mockApiClient.createWorkflow).toHaveBeenCalledWith(input);
|
||||
expect(n8nValidation.validateWorkflowStructure).toHaveBeenCalledWith(input);
|
||||
});
|
||||
|
||||
it('should handle validation errors', async () => {
|
||||
|
||||
Reference in New Issue
Block a user