fix: enhance node type format normalization for better AI agent compatibility (Issue #74)

- Added support for n8n-nodes-langchain.* → nodes-langchain.* normalization
- Implemented case-insensitive node name matching (e.g., chattrigger → chatTrigger)
- Added smart camelCase detection for common patterns (trigger, request, sheets, etc.)
- Fixed get_node_documentation tool to use same normalization logic as other tools
- Updated all 7 node lookup locations to use normalized types for alternatives
- Enhanced getNodeTypeAlternatives() to normalize all generated alternatives

All MCP tools now consistently handle various format variations:
- nodes-langchain.chatTrigger (correct format)
- n8n-nodes-langchain.chatTrigger (package format)
- n8n-nodes-langchain.chattrigger (package + wrong case)
- nodes-langchain.chattrigger (wrong case only)
- @n8n/n8n-nodes-langchain.chatTrigger (full npm format)

Bump version to 2.7.19

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-07-18 16:25:20 +02:00
parent a7bcd8cd1b
commit f76e2247f9
7 changed files with 124 additions and 11 deletions

View File

@@ -2,7 +2,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![GitHub stars](https://img.shields.io/github/stars/czlonkowski/n8n-mcp?style=social)](https://github.com/czlonkowski/n8n-mcp) [![GitHub stars](https://img.shields.io/github/stars/czlonkowski/n8n-mcp?style=social)](https://github.com/czlonkowski/n8n-mcp)
[![Version](https://img.shields.io/badge/version-2.7.18-blue.svg)](https://github.com/czlonkowski/n8n-mcp) [![Version](https://img.shields.io/badge/version-2.7.19-blue.svg)](https://github.com/czlonkowski/n8n-mcp)
[![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp) [![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp)
[![n8n version](https://img.shields.io/badge/n8n-v1.102.4-orange.svg)](https://github.com/n8n-io/n8n) [![n8n version](https://img.shields.io/badge/n8n-v1.102.4-orange.svg)](https://github.com/n8n-io/n8n)
[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fczlonkowski%2Fn8n--mcp-green.svg)](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp) [![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fczlonkowski%2Fn8n--mcp-green.svg)](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)

Binary file not shown.

View File

@@ -5,6 +5,24 @@ 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.7.19] - 2025-07-18
### Fixed
- **Enhanced node type format normalization** (Issue #74)
- Fixed issue where `n8n-nodes-langchain.chattrigger` (incorrect format) was not being normalized
- Added support for `n8n-nodes-langchain.*``nodes-langchain.*` normalization (without @n8n/ prefix)
- Implemented case-insensitive node name matching (e.g., `chattrigger``chatTrigger`)
- Added smart camelCase detection for common patterns (trigger, request, sheets, etc.)
- Fixed `get_node_documentation` tool to use same normalization logic as other tools
- All MCP tools now consistently handle various format variations:
- `nodes-langchain.chatTrigger` (correct format)
- `n8n-nodes-langchain.chatTrigger` (package format)
- `n8n-nodes-langchain.chattrigger` (package + wrong case)
- `nodes-langchain.chattrigger` (wrong case only)
- `@n8n/n8n-nodes-langchain.chatTrigger` (full npm format)
- Updated all 7 node lookup locations to use normalized types for alternatives generation
- Enhanced `getNodeTypeAlternatives()` to normalize all generated alternatives
## [2.7.18] - 2025-07-18 ## [2.7.18] - 2025-07-18
### Fixed ### Fixed
@@ -712,6 +730,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Basic n8n and MCP integration - Basic n8n and MCP integration
- Core workflow automation features - Core workflow automation features
[2.7.19]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.18...v2.7.19
[2.7.18]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.17...v2.7.18 [2.7.18]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.17...v2.7.18
[2.7.17]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.16...v2.7.17 [2.7.17]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.16...v2.7.17
[2.7.16]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.15...v2.7.16 [2.7.16]: https://github.com/czlonkowski/n8n-mcp/compare/v2.7.15...v2.7.16

View File

@@ -1,6 +1,6 @@
{ {
"name": "n8n-mcp", "name": "n8n-mcp",
"version": "2.7.18", "version": "2.7.19",
"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

@@ -1,6 +1,6 @@
{ {
"name": "n8n-mcp-runtime", "name": "n8n-mcp-runtime",
"version": "2.7.18", "version": "2.7.19",
"description": "n8n MCP Server Runtime Dependencies Only", "description": "n8n MCP Server Runtime Dependencies Only",
"private": true, "private": true,
"dependencies": { "dependencies": {

View File

@@ -354,7 +354,7 @@ export class N8NDocumentationMCPServer {
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);
@@ -975,6 +975,21 @@ export class N8NDocumentationMCPServer {
`).get(nodeType) as NodeRow | undefined; `).get(nodeType) as NodeRow | undefined;
} }
// If still not found, try alternatives
if (!node) {
const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) {
node = this.db!.prepare(`
SELECT node_type, display_name, documentation, description
FROM nodes
WHERE node_type = ?
`).get(alt) as NodeRow | undefined;
if (node) break;
}
}
if (!node) { if (!node) {
throw new Error(`Node ${nodeType} not found`); throw new Error(`Node ${nodeType} not found`);
} }
@@ -1072,7 +1087,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);
@@ -1146,7 +1161,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);
@@ -1304,7 +1319,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);
@@ -1362,7 +1377,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);
@@ -1416,7 +1431,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);
@@ -1593,7 +1608,7 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
if (!node) { if (!node) {
// Fallback to other alternatives for edge cases // Fallback to other alternatives for edge cases
const alternatives = getNodeTypeAlternatives(nodeType); const alternatives = getNodeTypeAlternatives(normalizedType);
for (const alt of alternatives) { for (const alt of alternatives) {
const found = this.repository!.getNode(alt); const found = this.repository!.getNode(alt);

View File

@@ -4,6 +4,7 @@
* Examples: * Examples:
* - 'n8n-nodes-base.httpRequest' → 'nodes-base.httpRequest' * - 'n8n-nodes-base.httpRequest' → 'nodes-base.httpRequest'
* - '@n8n/n8n-nodes-langchain.agent' → 'nodes-langchain.agent' * - '@n8n/n8n-nodes-langchain.agent' → 'nodes-langchain.agent'
* - 'n8n-nodes-langchain.chatTrigger' → 'nodes-langchain.chatTrigger'
* - 'nodes-base.slack' → 'nodes-base.slack' (unchanged) * - 'nodes-base.slack' → 'nodes-base.slack' (unchanged)
* *
* @param nodeType The node type to normalize * @param nodeType The node type to normalize
@@ -20,6 +21,11 @@ export function normalizeNodeType(nodeType: string): string {
return nodeType.replace('@n8n/n8n-nodes-langchain.', 'nodes-langchain.'); return nodeType.replace('@n8n/n8n-nodes-langchain.', 'nodes-langchain.');
} }
// Handle n8n-nodes-langchain -> nodes-langchain (without @n8n/ prefix)
if (nodeType.startsWith('n8n-nodes-langchain.')) {
return nodeType.replace('n8n-nodes-langchain.', 'nodes-langchain.');
}
// Return unchanged if already normalized or unknown format // Return unchanged if already normalized or unknown format
return nodeType; return nodeType;
} }
@@ -36,13 +42,86 @@ export function getNodeTypeAlternatives(nodeType: string): string[] {
// Add lowercase version // Add lowercase version
alternatives.push(nodeType.toLowerCase()); alternatives.push(nodeType.toLowerCase());
// If it has a prefix, try case variations on the node name part
if (nodeType.includes('.')) {
const [prefix, nodeName] = nodeType.split('.');
// Try different case variations for the node name
if (nodeName && nodeName.toLowerCase() !== nodeName) {
alternatives.push(`${prefix}.${nodeName.toLowerCase()}`);
}
// For camelCase names like "chatTrigger", also try with capital first letter variations
// e.g., "chattrigger" -> "chatTrigger"
if (nodeName && nodeName.toLowerCase() === nodeName && nodeName.length > 1) {
// Try to detect common patterns and create camelCase version
const camelCaseVariants = generateCamelCaseVariants(nodeName);
camelCaseVariants.forEach(variant => {
alternatives.push(`${prefix}.${variant}`);
});
}
}
// If it's just a bare node name, try with common prefixes // If it's just a bare node name, try with common prefixes
if (!nodeType.includes('.')) { if (!nodeType.includes('.')) {
alternatives.push(`nodes-base.${nodeType}`); alternatives.push(`nodes-base.${nodeType}`);
alternatives.push(`nodes-langchain.${nodeType}`); alternatives.push(`nodes-langchain.${nodeType}`);
// Also try camelCase variants for bare names
const camelCaseVariants = generateCamelCaseVariants(nodeType);
camelCaseVariants.forEach(variant => {
alternatives.push(`nodes-base.${variant}`);
alternatives.push(`nodes-langchain.${variant}`);
});
} }
return alternatives; // Normalize all alternatives and combine with originals
const normalizedAlternatives = alternatives.map(alt => normalizeNodeType(alt));
// Combine original alternatives with normalized ones and remove duplicates
return [...new Set([...alternatives, ...normalizedAlternatives])];
}
/**
* Generate camelCase variants for a lowercase string
* @param str The lowercase string
* @returns Array of possible camelCase variants
*/
function generateCamelCaseVariants(str: string): string[] {
const variants: string[] = [];
// Common patterns for n8n nodes
const patterns = [
// Pattern: wordTrigger (e.g., chatTrigger, webhookTrigger)
/^(.+)(trigger|node|request|response)$/i,
// Pattern: httpRequest, mysqlDatabase
/^(http|mysql|postgres|mongo|redis|mqtt|smtp|imap|ftp|ssh|api)(.+)$/i,
// Pattern: googleSheets, microsoftTeams
/^(google|microsoft|amazon|slack|discord|telegram)(.+)$/i,
];
for (const pattern of patterns) {
const match = str.toLowerCase().match(pattern);
if (match) {
const [, first, second] = match;
// Capitalize the second part
variants.push(first.toLowerCase() + second.charAt(0).toUpperCase() + second.slice(1).toLowerCase());
}
}
// Generic camelCase: capitalize after common word boundaries
if (variants.length === 0) {
// Try splitting on common boundaries and capitalizing
const words = str.split(/[-_\s]+/);
if (words.length > 1) {
const camelCase = words[0].toLowerCase() + words.slice(1).map(w =>
w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
).join('');
variants.push(camelCase);
}
}
return variants;
} }
/** /**