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://opensource.org/licenses/MIT)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp)
|
[](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://www.npmjs.com/package/n8n-mcp)
|
||||||
[](https://github.com/n8n-io/n8n)
|
[](https://github.com/n8n-io/n8n)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
[](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/),
|
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
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user