feat: Remove 9 low-value tools and consolidate n8n_health_check (v2.25.0)

Telemetry-driven tool cleanup to improve API clarity:

**Removed Tools (9):**
- list_nodes - Use search_nodes instead
- list_ai_tools - Use search_nodes with isAITool filter
- list_tasks - Low usage (0.02%)
- get_database_statistics - Use n8n_health_check
- list_templates - Use search_templates or get_templates_for_task
- get_node_as_tool_info - Documented in get_node
- validate_workflow_connections - Use validate_workflow
- validate_workflow_expressions - Use validate_workflow
- n8n_list_available_tools - Use n8n_health_check
- n8n_diagnostic - Merged into n8n_health_check

**Consolidated Tool:**
- n8n_health_check now supports mode='diagnostic' for detailed troubleshooting

**Tool Count:**
- Before: 38 tools
- After: 31 tools (18% reduction)

Concieved by Romuald Członkowski - www.aiadvisors.pl/en

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-11-25 00:33:29 +01:00
parent 9ee4b9492f
commit 2e40374da3
6 changed files with 37 additions and 287 deletions

View File

@@ -1844,8 +1844,8 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
} }
// Check which tools are available // Check which tools are available
const documentationTools = 22; // Base documentation tools const documentationTools = 14; // Base documentation tools (after v2.25.0 cleanup)
const managementTools = apiConfigured ? 16 : 0; const managementTools = apiConfigured ? 17 : 0; // Management tools requiring API (includes n8n_health_check)
const totalTools = documentationTools + managementTools; const totalTools = documentationTools + managementTools;
// Check npm version // Check npm version

View File

@@ -837,8 +837,6 @@ export class N8NDocumentationMCPServer {
validationResult = ToolValidation.validateNodeMinimal(args); validationResult = ToolValidation.validateNodeMinimal(args);
break; break;
case 'validate_workflow': case 'validate_workflow':
case 'validate_workflow_connections':
case 'validate_workflow_expressions':
validationResult = ToolValidation.validateWorkflow(args); validationResult = ToolValidation.validateWorkflow(args);
break; break;
case 'search_nodes': case 'search_nodes':
@@ -1015,23 +1013,14 @@ export class N8NDocumentationMCPServer {
case 'tools_documentation': case 'tools_documentation':
// No required parameters // No required parameters
return this.getToolsDocumentation(args.topic, args.depth); return this.getToolsDocumentation(args.topic, args.depth);
case 'list_nodes':
// No required parameters
return this.listNodes(args);
case 'search_nodes': case 'search_nodes':
this.validateToolParams(name, args, ['query']); this.validateToolParams(name, args, ['query']);
// Convert limit to number if provided, otherwise use default // Convert limit to number if provided, otherwise use default
const limit = args.limit !== undefined ? Number(args.limit) || 20 : 20; const limit = args.limit !== undefined ? Number(args.limit) || 20 : 20;
return this.searchNodes(args.query, limit, { mode: args.mode, includeExamples: args.includeExamples }); return this.searchNodes(args.query, limit, { mode: args.mode, includeExamples: args.includeExamples });
case 'list_ai_tools':
// No required parameters
return this.listAITools();
case 'get_node_documentation': case 'get_node_documentation':
this.validateToolParams(name, args, ['nodeType']); this.validateToolParams(name, args, ['nodeType']);
return this.getNodeDocumentation(args.nodeType); return this.getNodeDocumentation(args.nodeType);
case 'get_database_statistics':
// No required parameters
return this.getDatabaseStatistics();
case 'get_node': case 'get_node':
this.validateToolParams(name, args, ['nodeType']); this.validateToolParams(name, args, ['nodeType']);
return this.getNode( return this.getNode(
@@ -1047,9 +1036,6 @@ export class N8NDocumentationMCPServer {
this.validateToolParams(name, args, ['nodeType', 'query']); this.validateToolParams(name, args, ['nodeType', 'query']);
const maxResults = args.maxResults !== undefined ? Number(args.maxResults) || 20 : 20; const maxResults = args.maxResults !== undefined ? Number(args.maxResults) || 20 : 20;
return this.searchNodeProperties(args.nodeType, args.query, maxResults); return this.searchNodeProperties(args.nodeType, args.query, maxResults);
case 'list_tasks':
// No required parameters
return this.listTasks(args.category);
case 'validate_node_operation': case 'validate_node_operation':
this.validateToolParams(name, args, ['nodeType', 'config']); this.validateToolParams(name, args, ['nodeType', 'config']);
// Ensure config is an object // Ensure config is an object
@@ -1101,16 +1087,6 @@ export class N8NDocumentationMCPServer {
case 'get_property_dependencies': case 'get_property_dependencies':
this.validateToolParams(name, args, ['nodeType']); this.validateToolParams(name, args, ['nodeType']);
return this.getPropertyDependencies(args.nodeType, args.config); return this.getPropertyDependencies(args.nodeType, args.config);
case 'get_node_as_tool_info':
this.validateToolParams(name, args, ['nodeType']);
return this.getNodeAsToolInfo(args.nodeType);
case 'list_templates':
// No required params
const listLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 100);
const listOffset = Math.max(Number(args.offset) || 0, 0);
const sortBy = args.sortBy || 'views';
const includeMetadata = Boolean(args.includeMetadata);
return this.listTemplates(listLimit, listOffset, sortBy, includeMetadata);
case 'list_node_templates': case 'list_node_templates':
this.validateToolParams(name, args, ['nodeTypes']); this.validateToolParams(name, args, ['nodeTypes']);
const templateLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 100); const templateLimit = Math.min(Math.max(Number(args.limit) || 10, 1), 100);
@@ -1147,12 +1123,6 @@ export class N8NDocumentationMCPServer {
case 'validate_workflow': case 'validate_workflow':
this.validateToolParams(name, args, ['workflow']); this.validateToolParams(name, args, ['workflow']);
return this.validateWorkflow(args.workflow, args.options); return this.validateWorkflow(args.workflow, args.options);
case 'validate_workflow_connections':
this.validateToolParams(name, args, ['workflow']);
return this.validateWorkflowConnections(args.workflow);
case 'validate_workflow_expressions':
this.validateToolParams(name, args, ['workflow']);
return this.validateWorkflowExpressions(args.workflow);
// n8n Management Tools (if API is configured) // n8n Management Tools (if API is configured)
case 'n8n_create_workflow': case 'n8n_create_workflow':
@@ -1205,14 +1175,11 @@ export class N8NDocumentationMCPServer {
this.validateToolParams(name, args, ['id']); this.validateToolParams(name, args, ['id']);
return n8nHandlers.handleDeleteExecution(args, this.instanceContext); return n8nHandlers.handleDeleteExecution(args, this.instanceContext);
case 'n8n_health_check': case 'n8n_health_check':
// No required parameters // No required parameters - supports mode='status' (default) or mode='diagnostic'
if (args.mode === 'diagnostic') {
return n8nHandlers.handleDiagnostic({ params: { arguments: args } }, this.instanceContext);
}
return n8nHandlers.handleHealthCheck(this.instanceContext); return n8nHandlers.handleHealthCheck(this.instanceContext);
case 'n8n_list_available_tools':
// No required parameters
return n8nHandlers.handleListAvailableTools(this.instanceContext);
case 'n8n_diagnostic':
// No required parameters
return n8nHandlers.handleDiagnostic({ params: { arguments: args } }, this.instanceContext);
case 'n8n_workflow_versions': case 'n8n_workflow_versions':
this.validateToolParams(name, args, ['mode']); this.validateToolParams(name, args, ['mode']);
return n8nHandlers.handleWorkflowVersions(args, this.repository!, this.instanceContext); return n8nHandlers.handleWorkflowVersions(args, this.repository!, this.instanceContext);

View File

@@ -436,29 +436,19 @@ Examples:
// System Tools // System Tools
{ {
name: 'n8n_health_check', name: 'n8n_health_check',
description: `Check n8n instance health and API connectivity. Returns status and available features.`, description: `Check n8n instance health and API connectivity. Use mode='diagnostic' for detailed troubleshooting with env vars and tool status.`,
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'n8n_list_available_tools',
description: `List available n8n tools and capabilities.`,
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'n8n_diagnostic',
description: `Diagnose n8n API config. Shows tool status, API connectivity, env vars. Helps troubleshoot missing tools.`,
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
mode: {
type: 'string',
enum: ['status', 'diagnostic'],
description: 'Mode: "status" (default) for quick health check, "diagnostic" for detailed debug info including env vars and tool status',
default: 'status'
},
verbose: { verbose: {
type: 'boolean', type: 'boolean',
description: 'Include detailed debug information (default: false)' description: 'Include extra details in diagnostic mode (default: false)'
} }
} }
} }

View File

@@ -26,37 +26,6 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
}, },
}, },
}, },
{
name: 'list_nodes',
description: `List n8n nodes. Common: list_nodes({limit:200}) for all, list_nodes({category:'trigger'}) for triggers. Package: "n8n-nodes-base" or "@n8n/n8n-nodes-langchain". Categories: trigger/transform/output/input.`,
inputSchema: {
type: 'object',
properties: {
package: {
type: 'string',
description: '"n8n-nodes-base" (core) or "@n8n/n8n-nodes-langchain" (AI)',
},
category: {
type: 'string',
description: 'trigger|transform|output|input|AI',
},
developmentStyle: {
type: 'string',
enum: ['declarative', 'programmatic'],
description: 'Usually "programmatic"',
},
isAITool: {
type: 'boolean',
description: 'Filter AI-capable nodes',
},
limit: {
type: 'number',
description: 'Max results (default 50, use 200+ for all)',
default: 50,
},
},
},
},
{ {
name: 'search_nodes', name: 'search_nodes',
description: `Search n8n nodes by keyword with optional real-world examples. Pass query as string. Example: query="webhook" or query="database". Returns max 20 results. Use includeExamples=true to get top 2 template configs per node.`, description: `Search n8n nodes by keyword with optional real-world examples. Pass query as string. Example: query="webhook" or query="database". Returns max 20 results. Use includeExamples=true to get top 2 template configs per node.`,
@@ -87,14 +56,6 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
required: ['query'], required: ['query'],
}, },
}, },
{
name: 'list_ai_tools',
description: `List 263 AI-optimized nodes. Note: ANY node can be AI tool! Connect any node to AI Agent's tool port. Community nodes need N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true.`,
inputSchema: {
type: 'object',
properties: {},
},
},
{ {
name: 'get_node_documentation', name: 'get_node_documentation',
description: `Get readable docs with examples/auth/patterns. Better than raw schema! 87% coverage. Format: "nodes-base.slack"`, description: `Get readable docs with examples/auth/patterns. Better than raw schema! 87% coverage. Format: "nodes-base.slack"`,
@@ -109,14 +70,6 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
required: ['nodeType'], required: ['nodeType'],
}, },
}, },
{
name: 'get_database_statistics',
description: `Node stats: 525 total, 263 AI tools, 104 triggers, 87% docs coverage. Verifies MCP working.`,
inputSchema: {
type: 'object',
properties: {},
},
},
{ {
name: 'get_node', name: 'get_node',
description: `Get node info with progressive detail levels. Detail: minimal (~200 tokens), standard (~1-2K, default), full (~3-8K). Version modes: versions (history), compare (diff), breaking (changes), migrations (auto-migrate). Supports includeTypeInfo and includeExamples. Use standard for most tasks.`, description: `Get node info with progressive detail levels. Detail: minimal (~200 tokens), standard (~1-2K, default), full (~3-8K). Version modes: versions (history), compare (diff), breaking (changes), migrations (auto-migrate). Supports includeTypeInfo and includeExamples. Use standard for most tasks.`,
@@ -184,19 +137,6 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
required: ['nodeType', 'query'], required: ['nodeType', 'query'],
}, },
}, },
{
name: 'list_tasks',
description: `List task templates by category: HTTP/API, Webhooks, Database, AI, Data Processing, Communication.`,
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Filter by category (optional)',
},
},
},
},
{ {
name: 'validate_node_operation', name: 'validate_node_operation',
description: `Validate n8n node configuration. Pass nodeType as string and config as object. Example: nodeType="nodes-base.slack", config={resource:"channel",operation:"create"}`, description: `Validate n8n node configuration. Pass nodeType as string and config as object. Example: nodeType="nodes-base.slack", config={resource:"channel",operation:"create"}`,
@@ -316,53 +256,6 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
required: ['nodeType'], required: ['nodeType'],
}, },
}, },
{
name: 'get_node_as_tool_info',
description: `How to use ANY node as AI tool. Shows requirements, use cases, examples. Works for all nodes, not just AI-marked ones.`,
inputSchema: {
type: 'object',
properties: {
nodeType: {
type: 'string',
description: 'Full node type WITH prefix: "nodes-base.slack", "nodes-base.googleSheets", etc.',
},
},
required: ['nodeType'],
},
},
{
name: 'list_templates',
description: `List all templates with minimal data (id, name, description, views, node count). Optionally include AI-generated metadata for smart filtering.`,
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Number of results (1-100). Default 10.',
default: 10,
minimum: 1,
maximum: 100,
},
offset: {
type: 'number',
description: 'Pagination offset. Default 0.',
default: 0,
minimum: 0,
},
sortBy: {
type: 'string',
enum: ['views', 'created_at', 'name'],
description: 'Sort field. Default: views (popularity).',
default: 'views',
},
includeMetadata: {
type: 'boolean',
description: 'Include AI-generated metadata (categories, complexity, setup time, etc.). Default false.',
default: false,
},
},
},
},
{ {
name: 'list_node_templates', name: 'list_node_templates',
description: `Find templates using specific nodes. Returns paginated results. Use FULL types: "n8n-nodes-base.httpRequest".`, description: `Find templates using specific nodes. Returns paginated results. Use FULL types: "n8n-nodes-base.httpRequest".`,
@@ -622,107 +515,6 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
required: ['valid', 'summary'] required: ['valid', 'summary']
}, },
}, },
{
name: 'validate_workflow_connections',
description: `Check workflow connections only: valid nodes, no cycles, proper triggers, AI tool links. Fast structure validation.`,
inputSchema: {
type: 'object',
properties: {
workflow: {
type: 'object',
description: 'The workflow JSON with nodes array and connections object.',
},
},
required: ['workflow'],
additionalProperties: false,
},
outputSchema: {
type: 'object',
properties: {
valid: { type: 'boolean' },
statistics: {
type: 'object',
properties: {
totalNodes: { type: 'number' },
triggerNodes: { type: 'number' },
validConnections: { type: 'number' },
invalidConnections: { type: 'number' }
}
},
errors: {
type: 'array',
items: {
type: 'object',
properties: {
node: { type: 'string' },
message: { type: 'string' }
}
}
},
warnings: {
type: 'array',
items: {
type: 'object',
properties: {
node: { type: 'string' },
message: { type: 'string' }
}
}
}
},
required: ['valid', 'statistics']
},
},
{
name: 'validate_workflow_expressions',
description: `Validate n8n expressions: syntax {{}}, variables ($json/$node), references. Returns errors with locations.`,
inputSchema: {
type: 'object',
properties: {
workflow: {
type: 'object',
description: 'The workflow JSON to check for expression errors.',
},
},
required: ['workflow'],
additionalProperties: false,
},
outputSchema: {
type: 'object',
properties: {
valid: { type: 'boolean' },
statistics: {
type: 'object',
properties: {
totalNodes: { type: 'number' },
expressionsValidated: { type: 'number' }
}
},
errors: {
type: 'array',
items: {
type: 'object',
properties: {
node: { type: 'string' },
message: { type: 'string' }
}
}
},
warnings: {
type: 'array',
items: {
type: 'object',
properties: {
node: { type: 'string' },
message: { type: 'string' }
}
}
},
tips: { type: 'array', items: { type: 'string' } }
},
required: ['valid', 'statistics']
},
},
]; ];
/** /**

View File

@@ -74,7 +74,7 @@ describe('MCP Protocol Compliance', () => {
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
expectedOrder.push(i); expectedOrder.push(i);
requests.push( requests.push(
client.callTool({ name: 'get_database_statistics', arguments: {} }) client.callTool({ name: 'tools_documentation', arguments: {} })
.then(() => i) .then(() => i)
); );
} }
@@ -125,7 +125,7 @@ describe('MCP Protocol Compliance', () => {
it('should handle missing params gracefully', async () => { it('should handle missing params gracefully', async () => {
// Most tools should work without params // Most tools should work without params
const response = await client.callTool({ name: 'list_nodes', arguments: {} }); const response = await client.callTool({ name: 'search_nodes', arguments: { query: 'webhook' } });
expect(response).toBeDefined(); expect(response).toBeDefined();
}); });
@@ -147,7 +147,7 @@ describe('MCP Protocol Compliance', () => {
describe('Content Types', () => { describe('Content Types', () => {
it('should handle text content in tool responses', async () => { it('should handle text content in tool responses', async () => {
const response = await client.callTool({ name: 'get_database_statistics', arguments: {} }); const response = await client.callTool({ name: 'tools_documentation', arguments: {} });
expect((response as any).content).toHaveLength(1); expect((response as any).content).toHaveLength(1);
expect((response as any).content[0]).toHaveProperty('type', 'text'); expect((response as any).content[0]).toHaveProperty('type', 'text');
@@ -167,14 +167,15 @@ describe('MCP Protocol Compliance', () => {
}); });
it('should handle JSON content properly', async () => { it('should handle JSON content properly', async () => {
const response = await client.callTool({ name: 'list_nodes', arguments: { const response = await client.callTool({ name: 'search_nodes', arguments: {
query: 'webhook',
limit: 5 limit: 5
} }); } });
expect((response as any).content).toHaveLength(1); expect((response as any).content).toHaveLength(1);
const content = JSON.parse((response as any).content[0].text); const content = JSON.parse((response as any).content[0].text);
expect(content).toHaveProperty('nodes'); expect(content).toHaveProperty('results');
expect(Array.isArray(content.nodes)).toBe(true); expect(Array.isArray(content.results)).toBe(true);
}); });
}); });
@@ -197,10 +198,10 @@ describe('MCP Protocol Compliance', () => {
const results: string[] = []; const results: string[] = [];
// Start multiple requests with different delays // Start multiple requests with different delays
const p1 = client.callTool({ name: 'get_database_statistics', arguments: {} }) const p1 = client.callTool({ name: 'tools_documentation', arguments: {} })
.then(() => { results.push('stats'); return 'stats'; }); .then(() => { results.push('docs'); return 'docs'; });
const p2 = client.callTool({ name: 'list_nodes', arguments: { limit: 1 } }) const p2 = client.callTool({ name: 'search_nodes', arguments: { query: 'webhook', limit: 1 } })
.then(() => { results.push('nodes'); return 'nodes'; }); .then(() => { results.push('nodes'); return 'nodes'; });
const p3 = client.callTool({ name: 'search_nodes', arguments: { query: 'http' } }) const p3 = client.callTool({ name: 'search_nodes', arguments: { query: 'http' } })
@@ -232,13 +233,13 @@ describe('MCP Protocol Compliance', () => {
it('should support optional parameters', async () => { it('should support optional parameters', async () => {
// Call with minimal params // Call with minimal params
const response1 = await client.callTool({ name: 'list_nodes', arguments: {} }); const response1 = await client.callTool({ name: 'search_nodes', arguments: { query: 'webhook' } });
// Call with all params // Call with all params
const response2 = await client.callTool({ name: 'list_nodes', arguments: { const response2 = await client.callTool({ name: 'search_nodes', arguments: {
query: 'webhook',
limit: 10, limit: 10,
category: 'trigger', mode: 'OR'
package: 'n8n-nodes-base'
} }); } });
expect(response1).toBeDefined(); expect(response1).toBeDefined();
@@ -255,7 +256,7 @@ describe('MCP Protocol Compliance', () => {
await testClient.connect(clientTransport); await testClient.connect(clientTransport);
// Make a request // Make a request
const response = await testClient.callTool({ name: 'get_database_statistics', arguments: {} }); const response = await testClient.callTool({ name: 'tools_documentation', arguments: {} });
expect(response).toBeDefined(); expect(response).toBeDefined();
// Close client // Close client
@@ -263,7 +264,7 @@ describe('MCP Protocol Compliance', () => {
// Further requests should fail // Further requests should fail
try { try {
await testClient.callTool({ name: 'get_database_statistics', arguments: {} }); await testClient.callTool({ name: 'tools_documentation', arguments: {} });
expect.fail('Should have thrown an error'); expect.fail('Should have thrown an error');
} catch (error) { } catch (error) {
expect(error).toBeDefined(); expect(error).toBeDefined();
@@ -286,7 +287,7 @@ describe('MCP Protocol Compliance', () => {
const testClient = new Client({ name: 'test', version: '1.0.0' }, {}); const testClient = new Client({ name: 'test', version: '1.0.0' }, {});
await testClient.connect(clientTransport); await testClient.connect(clientTransport);
const response = await testClient.callTool({ name: 'get_database_statistics', arguments: {} }); const response = await testClient.callTool({ name: 'tools_documentation', arguments: {} });
expect(response).toBeDefined(); expect(response).toBeDefined();
await testClient.close(); await testClient.close();

View File

@@ -1068,14 +1068,14 @@ describe('handlers-n8n-manager', () => {
}, },
toolsAvailability: { toolsAvailability: {
documentationTools: { documentationTools: {
count: 22, count: 14,
enabled: true, enabled: true,
}, },
managementTools: { managementTools: {
count: 16, count: 17,
enabled: true, enabled: true,
}, },
totalAvailable: 38, totalAvailable: 31,
}, },
}); });