mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 06:22:04 +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:
315
README.md
315
README.md
@@ -402,175 +402,256 @@ For the best results when using n8n-MCP with Claude Projects, use these enhanced
|
|||||||
```markdown
|
```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.
|
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**
|
❌ BAD: "Let me search for Slack nodes... Great! Now let me get details..."
|
||||||
- `search_templates_by_metadata({complexity: "simple"})` - Find skill-appropriate templates
|
✅ GOOD: [Execute search_nodes and get_node_essentials in parallel, then respond]
|
||||||
- `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**:
|
### 2. Parallel Execution
|
||||||
- **For beginners**: `complexity: "simple"` and `maxSetupMinutes: 30`
|
When operations are independent, execute them in parallel for maximum performance.
|
||||||
- **By role**: `targetAudience: "marketers"` or `"developers"` or `"analysts"`
|
|
||||||
- **By time**: `maxSetupMinutes: 15` for quick wins
|
|
||||||
- **By service**: `requiredService: "openai"` to find compatible templates
|
|
||||||
|
|
||||||
3. **Discovery Phase** - Find the right nodes (if no suitable template):
|
✅ GOOD: Call search_nodes, list_nodes, and search_templates simultaneously
|
||||||
- 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.
|
❌ BAD: Sequential tool calls (await each one before the next)
|
||||||
- `search_nodes({query: 'keyword', includeExamples: true})` - Search by functionality with real configuration examples
|
|
||||||
|
### 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_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:
|
4. **Configuration Phase** (parallel for multiple nodes)
|
||||||
- `get_node_essentials(nodeType, {includeExamples: true})` - Start here! Get 10-20 essential properties with top 3 real-world configurations from popular templates
|
- `get_node_essentials(nodeType, {includeExamples: true})` - 10-20 key properties
|
||||||
- `search_node_properties(nodeType, 'auth')` - Find specific properties
|
- `search_node_properties(nodeType, 'auth')` - Find specific properties
|
||||||
- `get_node_documentation(nodeType)` - Human-readable docs when needed
|
- `get_node_documentation(nodeType)` - Human-readable docs
|
||||||
- It is good common practice to show a visual representation of the workflow architecture to the user and asking for opinion, before moving forward.
|
- 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_minimal(nodeType, config)` - Quick required fields check
|
||||||
- `validate_node_operation(nodeType, config, profile)` - Full operation-aware validation
|
- `validate_node_operation(nodeType, config, 'runtime')` - Full validation with fixes
|
||||||
- Fix any validation errors before proceeding
|
- Fix ALL errors before proceeding
|
||||||
|
|
||||||
6. **Building Phase** - Create or customize the workflow:
|
6. **Building Phase**
|
||||||
- If using template: `get_template(templateId, {mode: "full"})`
|
- If using template: `get_template(templateId, {mode: "full"})`
|
||||||
- **MANDATORY ATTRIBUTION**: When using a template, ALWAYS inform the user:
|
- **MANDATORY ATTRIBUTION**: "Based on template by **[author.name]** (@[username]). View at: [url]"
|
||||||
- "This workflow is based on a template by **[author.name]** (@[author.username])"
|
- Build from validated configurations
|
||||||
- "View the original template at: [url]"
|
- ⚠️ EXPLICITLY set ALL parameters - never rely on defaults
|
||||||
- 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
|
|
||||||
- Connect nodes with proper structure
|
- Connect nodes with proper structure
|
||||||
- Add error handling where appropriate
|
- Add error handling
|
||||||
- Use expressions like $json, $node["NodeName"].json
|
- Use n8n expressions: $json, $node["NodeName"].json
|
||||||
- Build the workflow in an artifact for easy editing downstream (unless the user asked to create in n8n instance)
|
- Build in artifact (unless deploying to n8n instance)
|
||||||
|
|
||||||
7. **Workflow Validation Phase** - Validate complete workflow:
|
7. **Workflow Validation** (before deployment)
|
||||||
- `validate_workflow(workflow)` - Complete validation including connections
|
- `validate_workflow(workflow)` - Complete validation
|
||||||
- `validate_workflow_connections(workflow)` - Check structure and AI tool connections
|
- `validate_workflow_connections(workflow)` - Structure check
|
||||||
- `validate_workflow_expressions(workflow)` - Validate all n8n expressions
|
- `validate_workflow_expressions(workflow)` - Expression validation
|
||||||
- Fix any issues found before deployment
|
- Fix ALL issues before deployment
|
||||||
|
|
||||||
8. **Deployment Phase** (if n8n API configured):
|
8. **Deployment** (if n8n API configured)
|
||||||
- `n8n_create_workflow(workflow)` - Deploy validated workflow
|
- `n8n_create_workflow(workflow)` - Deploy
|
||||||
- `n8n_validate_workflow({id: 'workflow-id'})` - Post-deployment validation
|
- `n8n_validate_workflow({id})` - Post-deployment check
|
||||||
- `n8n_update_partial_workflow()` - Make incremental updates using diffs
|
- `n8n_update_partial_workflow({id, operations: [...]})` - Batch updates
|
||||||
- `n8n_trigger_webhook_workflow()` - Test webhook workflows
|
- `n8n_trigger_webhook_workflow()` - Test webhooks
|
||||||
|
|
||||||
## Key Insights
|
## Critical Warnings
|
||||||
|
|
||||||
- **TEMPLATES FIRST** - Always check for existing templates before building from scratch (2,500+ available!)
|
### ⚠️ Never Trust Defaults
|
||||||
- **ATTRIBUTION REQUIRED** - Always credit template authors with name, username, and link to n8n.io
|
Default values cause runtime failures. Example:
|
||||||
- **SMART FILTERING** - Use metadata filters to find templates matching user skill level and time constraints
|
```javascript
|
||||||
- **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.
|
// ❌ FAILS at runtime
|
||||||
- **VALIDATE EARLY AND OFTEN** - Catch errors before they reach deployment
|
{resource: "message", operation: "post", text: "Hello"}
|
||||||
- **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
|
// ✅ WORKS - all parameters explicit
|
||||||
- **Pre-validate configurations** - Use validate_node_minimal before building
|
{resource: "message", operation: "post", select: "channel", channelId: "C123", text: "Hello"}
|
||||||
- **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
|
### ⚠️ 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
|
## Validation Strategy
|
||||||
|
|
||||||
### Before Building:
|
### Level 1 - Quick Check (before building)
|
||||||
1. validate_node_minimal() - Check required fields
|
`validate_node_minimal(nodeType, config)` - Required fields only (<100ms)
|
||||||
2. validate_node_operation() - Full configuration validation
|
|
||||||
3. Fix all errors before proceeding
|
|
||||||
|
|
||||||
### After Building:
|
### Level 2 - Comprehensive (before building)
|
||||||
1. validate_workflow() - Complete workflow validation
|
`validate_node_operation(nodeType, config, 'runtime')` - Full validation with fixes
|
||||||
2. validate_workflow_connections() - Structure validation
|
|
||||||
3. validate_workflow_expressions() - Expression syntax check
|
|
||||||
|
|
||||||
### After Deployment:
|
### Level 3 - Complete (after building)
|
||||||
1. n8n_validate_workflow({id}) - Validate deployed workflow
|
`validate_workflow(workflow)` - Connections, expressions, AI tools
|
||||||
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
|
|
||||||
|
|
||||||
## 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
|
## Response Format
|
||||||
2. **Pre-Validation**: Validate node configurations first
|
|
||||||
3. **Configuration**: Show only validated, working configs
|
### Initial Creation
|
||||||
4. **Building**: Construct workflow with validated components
|
```
|
||||||
5. **Workflow Validation**: Full workflow validation results
|
[Silent tool execution in parallel]
|
||||||
6. **Deployment**: Deploy only after all validations pass
|
|
||||||
7. **Post-Validation**: Verify deployment succeeded
|
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
|
## Example Workflow
|
||||||
|
|
||||||
### Smart Template-First Approach
|
### Template-First Approach
|
||||||
|
|
||||||
#### 1. Find existing templates
|
```javascript
|
||||||
// Find simple Slack templates for marketers
|
// STEP 1: Template Discovery (parallel execution)
|
||||||
const templates = search_templates_by_metadata({
|
[Silent execution]
|
||||||
|
search_templates_by_metadata({
|
||||||
requiredService: 'slack',
|
requiredService: 'slack',
|
||||||
complexity: 'simple',
|
complexity: 'simple',
|
||||||
targetAudience: 'marketers',
|
targetAudience: 'marketers'
|
||||||
maxSetupMinutes: 30
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Or search by text
|
|
||||||
search_templates('slack notification')
|
|
||||||
|
|
||||||
// Or get curated templates
|
|
||||||
get_templates_for_task('slack_integration')
|
get_templates_for_task('slack_integration')
|
||||||
|
|
||||||
#### 2. Use and customize template
|
// STEP 2: Use template
|
||||||
const workflow = get_template(templates.items[0].id, {mode: 'full'})
|
get_template(templateId, {mode: 'full'})
|
||||||
validate_workflow(workflow)
|
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})
|
search_nodes({query: 'slack', includeExamples: true})
|
||||||
get_node_essentials('n8n-nodes-base.slack', {includeExamples: true})
|
list_nodes({category: 'communication'})
|
||||||
|
|
||||||
#### 2. Pre-Validation
|
// STEP 2: Configuration (parallel execution)
|
||||||
validate_node_minimal('n8n-nodes-base.slack', {resource:'message', operation:'send'})
|
[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')
|
validate_node_operation('n8n-nodes-base.slack', fullConfig, 'runtime')
|
||||||
|
|
||||||
#### 3. Build Workflow
|
// STEP 4: Build
|
||||||
// Create workflow JSON with validated configs
|
// Construct workflow with validated configs
|
||||||
|
// ⚠️ Set ALL parameters explicitly
|
||||||
|
|
||||||
#### 4. Workflow Validation
|
// STEP 5: Validate
|
||||||
|
[Silent execution]
|
||||||
validate_workflow(workflowJson)
|
validate_workflow(workflowJson)
|
||||||
validate_workflow_connections(workflowJson)
|
|
||||||
validate_workflow_expressions(workflowJson)
|
|
||||||
|
|
||||||
#### 5. Deploy (if configured)
|
// Response after all tools complete:
|
||||||
n8n_create_workflow(validatedWorkflow)
|
"Created workflow: Webhook → Slack
|
||||||
n8n_validate_workflow({id: createdWorkflowId})
|
Validation: ✅ Passed"
|
||||||
|
```
|
||||||
|
|
||||||
#### 6. Update Using Diffs
|
### Batch Updates
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ONE call with multiple operations
|
||||||
n8n_update_partial_workflow({
|
n8n_update_partial_workflow({
|
||||||
workflowId: id,
|
id: "wf-123",
|
||||||
operations: [
|
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
|
## Important Rules
|
||||||
|
|
||||||
- ALWAYS check for existing templates before building from scratch
|
### Core Behavior
|
||||||
- LEVERAGE metadata filters to find skill-appropriate templates
|
1. **Silent execution** - No commentary between tools
|
||||||
- **ALWAYS ATTRIBUTE TEMPLATES**: When using any template, you MUST share the author's name, username, and link to the original template on n8n.io
|
2. **Parallel by default** - Execute independent operations simultaneously
|
||||||
- VALIDATE templates before deployment (they may need updates)
|
3. **Templates first** - Always check before building (2,500+ available)
|
||||||
- USE diff operations for updates (80-90% token savings)
|
4. **Multi-level validation** - Quick check → Full validation → Workflow validation
|
||||||
- STATE validation results clearly
|
5. **Never trust defaults** - Explicitly configure ALL parameters
|
||||||
- FIX all errors before proceeding
|
|
||||||
|
|
||||||
## 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!
|
### Performance
|
||||||
- **Filter combinations work best** - Combine complexity + setup time + service
|
- **Batch operations** - Use diff operations with multiple changes in one call
|
||||||
- **Templates save 70-90% development time** - Always check first
|
- **Parallel execution** - Search, validate, and configure simultaneously
|
||||||
- **Metadata is AI-generated** - Occasionally imprecise but highly useful
|
- **Template metadata** - Use smart filtering for faster discovery
|
||||||
- **Use `includeMetadata: false` for fast browsing** - Add metadata only when needed
|
|
||||||
|
### 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.
|
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 client = ensureApiConfigured(context);
|
||||||
const input = createWorkflowSchema.parse(args);
|
const input = createWorkflowSchema.parse(args);
|
||||||
|
|
||||||
// Normalize all node types before validation
|
// Proactively detect SHORT form node types (common mistake)
|
||||||
const normalizedInput = NodeTypeNormalizer.normalizeWorkflowNodeTypes(input);
|
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
|
if (shortFormErrors.length > 0) {
|
||||||
const errors = validateWorkflowStructure(normalizedInput);
|
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) {
|
if (errors.length > 0) {
|
||||||
// Track validation failure
|
// Track validation failure
|
||||||
telemetry.trackWorkflowCreation(normalizedInput, false);
|
telemetry.trackWorkflowCreation(input, false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -300,8 +323,8 @@ export async function handleCreateWorkflow(args: unknown, context?: InstanceCont
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create workflow with normalized node types
|
// Create workflow (n8n API expects node types in FULL form)
|
||||||
const workflow = await client.createWorkflow(normalizedInput);
|
const workflow = await client.createWorkflow(input);
|
||||||
|
|
||||||
// Track successful workflow creation
|
// Track successful workflow creation
|
||||||
telemetry.trackWorkflowCreation(workflow, true);
|
telemetry.trackWorkflowCreation(workflow, true);
|
||||||
@@ -540,10 +563,8 @@ export async function handleUpdateWorkflow(args: unknown, context?: InstanceCont
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize all node types before validation
|
// Validate workflow structure (n8n API expects FULL form: n8n-nodes-base.*)
|
||||||
const normalizedWorkflow = NodeTypeNormalizer.normalizeWorkflowNodeTypes(fullWorkflow);
|
const errors = validateWorkflowStructure(fullWorkflow);
|
||||||
|
|
||||||
const errors = validateWorkflowStructure(normalizedWorkflow);
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -551,11 +572,6 @@ export async function handleUpdateWorkflow(args: unknown, context?: InstanceCont
|
|||||||
details: { errors }
|
details: { errors }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updateData with normalized nodes if they were modified
|
|
||||||
if (updateData.nodes) {
|
|
||||||
updateData.nodes = normalizedWorkflow.nodes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update workflow
|
// 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.
|
* ⚠️ WARNING: Do NOT use before n8n API calls!
|
||||||
* 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
|
* This class converts node types to SHORT form (database format).
|
||||||
* to match the database storage format (e.g., "nodes-base.webhook").
|
* 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:
|
* **IMPORTANT:** The n8n-mcp database stores nodes in SHORT form:
|
||||||
* - n8n-nodes-base → nodes-base
|
* - n8n-nodes-base → nodes-base
|
||||||
* - @n8n/n8n-nodes-langchain → nodes-langchain
|
* - @n8n/n8n-nodes-langchain → nodes-langchain
|
||||||
*
|
*
|
||||||
* Handles:
|
* But the n8n API requires FULL form:
|
||||||
* - Full form → Short form (n8n-nodes-base.X → nodes-base.X)
|
* - nodes-base → n8n-nodes-base
|
||||||
* - Already short form → Unchanged
|
* - nodes-langchain → @n8n/n8n-nodes-langchain
|
||||||
* - LangChain nodes → Proper short prefix
|
|
||||||
*
|
*
|
||||||
* @example
|
* @example Database Lookup (CORRECT usage)
|
||||||
* NodeTypeNormalizer.normalizeToFullForm('n8n-nodes-base.webhook')
|
* const dbType = NodeTypeNormalizer.normalizeToFullForm('n8n-nodes-base.webhook')
|
||||||
* // → 'nodes-base.webhook'
|
* // → 'nodes-base.webhook'
|
||||||
|
* const node = await repository.getNode(dbType)
|
||||||
*
|
*
|
||||||
* @example
|
* @example API Call (INCORRECT - Do NOT do this!)
|
||||||
* NodeTypeNormalizer.normalizeToFullForm('nodes-base.webhook')
|
* const workflow = { nodes: [{ type: 'n8n-nodes-base.webhook' }] }
|
||||||
* // → 'nodes-base.webhook' (unchanged)
|
* 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 {
|
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',
|
message: 'Workflow "Test Workflow" created successfully with ID: test-workflow-id',
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedNormalizedInput = {
|
// Should send input as-is to API (n8n expects FULL form: n8n-nodes-base.*)
|
||||||
name: 'Test Workflow',
|
expect(mockApiClient.createWorkflow).toHaveBeenCalledWith(input);
|
||||||
nodes: [{
|
expect(n8nValidation.validateWorkflowStructure).toHaveBeenCalledWith(input);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle validation errors', async () => {
|
it('should handle validation errors', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user