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:
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp)
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -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/),
|
||||
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
|
||||
|
||||
### Fixed
|
||||
@@ -712,6 +730,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Basic n8n and MCP integration
|
||||
- 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.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.7.18",
|
||||
"version": "2.7.19",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp-runtime",
|
||||
"version": "2.7.18",
|
||||
"version": "2.7.19",
|
||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -354,7 +354,7 @@ export class N8NDocumentationMCPServer {
|
||||
|
||||
if (!node) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
const found = this.repository!.getNode(alt);
|
||||
@@ -975,6 +975,21 @@ export class N8NDocumentationMCPServer {
|
||||
`).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) {
|
||||
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) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
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) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
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) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
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) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
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) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
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) {
|
||||
// Fallback to other alternatives for edge cases
|
||||
const alternatives = getNodeTypeAlternatives(nodeType);
|
||||
const alternatives = getNodeTypeAlternatives(normalizedType);
|
||||
|
||||
for (const alt of alternatives) {
|
||||
const found = this.repository!.getNode(alt);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* Examples:
|
||||
* - 'n8n-nodes-base.httpRequest' → 'nodes-base.httpRequest'
|
||||
* - '@n8n/n8n-nodes-langchain.agent' → 'nodes-langchain.agent'
|
||||
* - 'n8n-nodes-langchain.chatTrigger' → 'nodes-langchain.chatTrigger'
|
||||
* - 'nodes-base.slack' → 'nodes-base.slack' (unchanged)
|
||||
*
|
||||
* @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.');
|
||||
}
|
||||
|
||||
// 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 nodeType;
|
||||
}
|
||||
@@ -36,13 +42,86 @@ export function getNodeTypeAlternatives(nodeType: string): string[] {
|
||||
// Add lowercase version
|
||||
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 (!nodeType.includes('.')) {
|
||||
alternatives.push(`nodes-base.${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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user