fix: correct version extraction and typeVersion validation for langchain nodes

This commit fixes two critical bugs affecting AI Agent and other langchain nodes:

1. Version Extraction Bug (node-parser.ts)
   - AI Agent was returning version "3" instead of "2.2" (the defaultVersion)
   - Root cause: extractVersion() checked non-existent instance.baseDescription.defaultVersion
   - Fix: Updated priority to check currentVersion first, then description.defaultVersion
   - Impact: All VersionedNodeType nodes now return correct version

2. typeVersion Validation Bypass (workflow-validator.ts)
   - Langchain nodes with invalid typeVersion passed validation (even typeVersion: 99999)
   - Root cause: langchain skip happened before typeVersion validation
   - Fix: Moved typeVersion validation before langchain parameter skip
   - Impact: Invalid typeVersion values now properly caught for all nodes

Also includes:
- Database rebuilt with corrected version data (536 nodes)
- Version bump: 2.17.3 → 2.17.4
- Comprehensive CHANGELOG entry

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-10-07 20:16:45 +02:00
parent 2c536a25fd
commit b986beef2c
5 changed files with 123 additions and 26 deletions

View File

@@ -5,6 +5,95 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.17.4] - 2025-10-07
### 🔧 Validation
**Fixed critical version extraction and typeVersion validation bugs.**
This release fixes two critical bugs that caused incorrect version data and validation bypasses for langchain nodes.
#### Fixed
- **Version Extraction Bug (CRITICAL)**
- **Issue:** AI Agent node returned version "3" instead of "2.2" (the defaultVersion)
- **Impact:**
- MCP tools (`get_node_essentials`, `get_node_info`) returned incorrect version "3"
- Version "3" exists but n8n explicitly marks it as unstable ("Keep 2.2 until blocking bugs are fixed")
- AI agents created workflows with wrong typeVersion, causing runtime issues
- **Root Cause:** `extractVersion()` in node-parser.ts checked `instance.baseDescription.defaultVersion` which doesn't exist on VersionedNodeType instances
- **Fix:** Updated version extraction priority in `node-parser.ts:137-200`
1. Priority 1: Check `currentVersion` property (what VersionedNodeType actually uses)
2. Priority 2: Check `description.defaultVersion` (fixed property name from `baseDescription`)
3. Priority 3: Fallback to max(nodeVersions) as last resort
- **Verification:** AI Agent node now correctly returns version "2.2" across all MCP tools
- **typeVersion Validation Bypass (CRITICAL)**
- **Issue:** Langchain nodes with invalid typeVersion passed validation (even `typeVersion: 99999`)
- **Impact:**
- Invalid typeVersion values were never caught during validation
- Workflows with non-existent typeVersions passed validation but failed at runtime in n8n
- Validation was completely bypassed for all langchain nodes (AI Agent, Chat Trigger, OpenAI Chat Model, etc.)
- **Root Cause:** `workflow-validator.ts:400-405` skipped ALL validation for langchain nodes before typeVersion check
- **Fix:** Moved typeVersion validation BEFORE langchain skip in `workflow-validator.ts:447-493`
- typeVersion now validated for ALL nodes including langchain
- Validation runs before parameter validation skip
- Checks for missing, invalid, outdated, and exceeding-maximum typeVersion values
- **Verification:** Workflows with invalid typeVersion now correctly fail validation
#### Technical Details
**Version Extraction Fix:**
```typescript
// BEFORE (BROKEN):
if (instance?.baseDescription?.defaultVersion) { // Property doesn't exist!
return instance.baseDescription.defaultVersion.toString();
}
// AFTER (FIXED):
if (instance?.currentVersion !== undefined) { // What VersionedNodeType actually uses
return instance.currentVersion.toString();
}
if (instance?.description?.defaultVersion) { // Correct property name
return instance.description.defaultVersion.toString();
}
```
**typeVersion Validation Fix:**
```typescript
// BEFORE (BROKEN):
// Skip ALL node repository validation for langchain nodes
if (normalizedType.startsWith('nodes-langchain.')) {
continue; // typeVersion validation never runs!
}
// AFTER (FIXED):
// Validate typeVersion for ALL versioned nodes (including langchain)
if (nodeInfo.isVersioned) {
// ... typeVersion validation ...
}
// THEN skip parameter validation for langchain nodes
if (normalizedType.startsWith('nodes-langchain.')) {
continue;
}
```
#### Impact
- **Version Accuracy:** AI Agent and all VersionedNodeType nodes now return correct version (2.2, not 3)
- **Validation Reliability:** Invalid typeVersion values are now caught for langchain nodes
- **Workflow Stability:** Prevents creation of workflows with non-existent typeVersions
- **Database Rebuilt:** 536 nodes reloaded with corrected version data
#### Testing
- **Unit Tests:** All existing tests passing
- **Integration Tests:** Verified with n8n-mcp-tester agent
- Version consistency between `get_node_essentials` and `get_node_info`
- typeVersion validation catches invalid values (99, 100000) ✅
- AI Agent correctly reports version "2.2" ✅
## [2.17.3] - 2025-10-07 ## [2.17.3] - 2025-10-07
### 🔧 Validation ### 🔧 Validation

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{ {
"name": "n8n-mcp", "name": "n8n-mcp",
"version": "2.17.3", "version": "2.17.4",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js", "main": "dist/index.js",
"bin": { "bin": {

View File

@@ -135,16 +135,23 @@ export class NodeParser {
} }
private extractVersion(nodeClass: any): string { private extractVersion(nodeClass: any): string {
// Check instance for baseDescription first // Check instance properties first
try { try {
const instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass; const instance = typeof nodeClass === 'function' ? new nodeClass() : nodeClass;
// Handle instance-level baseDescription // PRIORITY 1: Check currentVersion (what VersionedNodeType actually uses)
if (instance?.baseDescription?.defaultVersion) { // For VersionedNodeType, currentVersion = defaultVersion ?? max(nodeVersions)
return instance.baseDescription.defaultVersion.toString(); if (instance?.currentVersion !== undefined) {
return instance.currentVersion.toString();
} }
// Handle instance-level nodeVersions // PRIORITY 2: Handle instance-level description.defaultVersion
// VersionedNodeType stores baseDescription as 'description', not 'baseDescription'
if (instance?.description?.defaultVersion) {
return instance.description.defaultVersion.toString();
}
// PRIORITY 3: Handle instance-level nodeVersions (fallback to max)
if (instance?.nodeVersions) { if (instance?.nodeVersions) {
const versions = Object.keys(instance.nodeVersions); const versions = Object.keys(instance.nodeVersions);
return Math.max(...versions.map(Number)).toString(); return Math.max(...versions.map(Number)).toString();
@@ -154,7 +161,6 @@ export class NodeParser {
if (instance?.description?.version) { if (instance?.description?.version) {
const version = instance.description.version; const version = instance.description.version;
if (Array.isArray(version)) { if (Array.isArray(version)) {
// Find the maximum version from the array
const maxVersion = Math.max(...version.map((v: any) => parseFloat(v.toString()))); const maxVersion = Math.max(...version.map((v: any) => parseFloat(v.toString())));
return maxVersion.toString(); return maxVersion.toString();
} else if (typeof version === 'number' || typeof version === 'string') { } else if (typeof version === 'number' || typeof version === 'string') {
@@ -167,8 +173,9 @@ export class NodeParser {
} }
// Handle class-level VersionedNodeType with defaultVersion // Handle class-level VersionedNodeType with defaultVersion
if (nodeClass.baseDescription?.defaultVersion) { // Note: Most VersionedNodeType classes don't have static properties
return nodeClass.baseDescription.defaultVersion.toString(); if (nodeClass.description?.defaultVersion) {
return nodeClass.description.defaultVersion.toString();
} }
// Handle class-level VersionedNodeType with nodeVersions // Handle class-level VersionedNodeType with nodeVersions

View File

@@ -397,14 +397,7 @@ export class WorkflowValidator {
node.type = normalizedType; node.type = normalizedType;
} }
// Skip ALL node repository validation for langchain nodes // Get node definition using normalized type (needed for typeVersion validation)
// They have dedicated AI-specific validators in validateAISpecificNodes()
// This prevents parameter validation conflicts and ensures proper AI validation
if (normalizedType.startsWith('nodes-langchain.')) {
continue;
}
// Get node definition using normalized type
const nodeInfo = this.nodeRepository.getNode(normalizedType); const nodeInfo = this.nodeRepository.getNode(normalizedType);
if (!nodeInfo) { if (!nodeInfo) {
@@ -451,7 +444,8 @@ export class WorkflowValidator {
continue; continue;
} }
// Validate typeVersion for versioned nodes // Validate typeVersion for ALL versioned nodes (including langchain nodes)
// This validation runs BEFORE the langchain skip to ensure typeVersion is checked
if (nodeInfo.isVersioned) { if (nodeInfo.isVersioned) {
// Check if typeVersion is missing // Check if typeVersion is missing
if (!node.typeVersion) { if (!node.typeVersion) {
@@ -491,6 +485,13 @@ export class WorkflowValidator {
} }
} }
// Skip parameter validation for langchain nodes
// They have dedicated AI-specific validators in validateAISpecificNodes()
// This prevents parameter validation conflicts and ensures proper AI validation
if (normalizedType.startsWith('nodes-langchain.')) {
continue;
}
// Validate node configuration // Validate node configuration
const nodeValidation = this.nodeValidator.validateWithMode( const nodeValidation = this.nodeValidator.validateWithMode(
node.type, node.type,