mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 05:23:08 +00:00
fix: cleanup stale references and update tests after tool removal
- Remove handleListAvailableTools dead code from handlers-n8n-manager.ts - Update error messages to reference n8n_health_check(mode="diagnostic") instead of n8n_diagnostic - Update tool counts in diagnostic messages (14 doc tools, 31 total) - Fix error-handling.test.ts to use valid tools (search_nodes, tools_documentation) - Remove obsolete list-tools.test.ts integration tests - Remove unused ListToolsResponse type from response-types.ts - Update tools.ts QUICK REFERENCE to remove list_nodes references - Update tools-documentation.ts to remove references to removed tools - Update tool-docs files to remove stale relatedTools references - Fix tools.test.ts to not test removed tools (list_nodes, list_ai_tools, etc.) - Fix parameter-validation.test.ts to not test removed tools - Update handlers-n8n-manager.test.ts error message expectations All 399 MCP unit tests now pass. Conceived by Romuald Członkowski - www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: code-reviewer
|
name: code-reviewer
|
||||||
description: Use this agent when you need to review recently written or modified code for quality, security, and maintainability. This agent should be invoked immediately after code changes are made to ensure high standards are maintained. Examples:\n\n<example>\nContext: The user has created a code-review agent that should be called after writing code.\nuser: "Please write a function that validates email addresses"\nassistant: "I'll create an email validation function for you:"\n<function implementation>\nassistant: "Now let me use the code-reviewer agent to review this code for quality and security"\n<commentary>\nSince new code was just written, the code-reviewer agent should be invoked to ensure it meets quality standards.\n</commentary>\n</example>\n\n<example>\nContext: The user has modified existing code and wants it reviewed.\nuser: "I've updated the authentication logic, can you check if it's secure?"\nassistant: "I'll use the code-reviewer agent to analyze your authentication changes for security and best practices"\n<commentary>\nThe user has made changes to security-critical code, so the code-reviewer agent is the appropriate tool to ensure the modifications are secure and well-implemented.\n</commentary>\n</example>
|
description: Use this agent when you need to review recently written or modified code for quality, security, and maintainability. This agent should be invoked immediately after code changes are made to ensure high standards are maintained. Examples:\n\n<example>\nContext: The user has created a code-review agent that should be called after writing code.\nuser: "Please write a function that validates email addresses"\nassistant: "I'll create an email validation function for you:"\n<function implementation>\nassistant: "Now let me use the code-reviewer agent to review this code for quality and security"\n<commentary>\nSince new code was just written, the code-reviewer agent should be invoked to ensure it meets quality standards.\n</commentary>\n</example>\n\n<example>\nContext: The user has modified existing code and wants it reviewed.\nuser: "I've updated the authentication logic, can you check if it's secure?"\nassistant: "I'll use the code-reviewer agent to analyze your authentication changes for security and best practices"\n<commentary>\nThe user has made changes to security-critical code, so the code-reviewer agent is the appropriate tool to ensure the modifications are secure and well-implemented.\n</commentary>\n</example>
|
||||||
|
model: inherit
|
||||||
---
|
---
|
||||||
|
|
||||||
You are a senior code reviewer with extensive experience in software engineering, security, and best practices. Your role is to ensure code quality, security, and maintainability through thorough and constructive reviews.
|
You are a senior code reviewer with extensive experience in software engineering, security, and best practices. Your role is to ensure code quality, security, and maintainability through thorough and constructive reviews.
|
||||||
|
|||||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -1553,7 +1553,7 @@ export async function handleHealthCheck(context?: InstanceContext): Promise<McpT
|
|||||||
'1. Verify n8n instance is running',
|
'1. Verify n8n instance is running',
|
||||||
'2. Check N8N_API_URL is correct',
|
'2. Check N8N_API_URL is correct',
|
||||||
'3. Verify N8N_API_KEY has proper permissions',
|
'3. Verify N8N_API_KEY has proper permissions',
|
||||||
'4. Run n8n_diagnostic for detailed analysis'
|
'4. Run n8n_health_check with mode="diagnostic" for detailed analysis'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1566,63 +1566,6 @@ export async function handleHealthCheck(context?: InstanceContext): Promise<McpT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleListAvailableTools(context?: InstanceContext): Promise<McpToolResponse> {
|
|
||||||
const tools = [
|
|
||||||
{
|
|
||||||
category: 'Workflow Management',
|
|
||||||
tools: [
|
|
||||||
{ name: 'n8n_create_workflow', description: 'Create new workflows' },
|
|
||||||
{ name: 'n8n_get_workflow', description: 'Get workflow by ID' },
|
|
||||||
{ name: 'n8n_get_workflow_details', description: 'Get detailed workflow info with stats' },
|
|
||||||
{ name: 'n8n_get_workflow_structure', description: 'Get simplified workflow structure' },
|
|
||||||
{ name: 'n8n_get_workflow_minimal', description: 'Get minimal workflow info' },
|
|
||||||
{ name: 'n8n_update_workflow', description: 'Update existing workflows' },
|
|
||||||
{ name: 'n8n_delete_workflow', description: 'Delete workflows' },
|
|
||||||
{ name: 'n8n_list_workflows', description: 'List workflows with filters' },
|
|
||||||
{ name: 'n8n_validate_workflow', description: 'Validate workflow from n8n instance' },
|
|
||||||
{ name: 'n8n_autofix_workflow', description: 'Automatically fix common workflow errors' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: 'Execution Management',
|
|
||||||
tools: [
|
|
||||||
{ name: 'n8n_trigger_webhook_workflow', description: 'Trigger workflows via webhook' },
|
|
||||||
{ name: 'n8n_get_execution', description: 'Get execution details' },
|
|
||||||
{ name: 'n8n_list_executions', description: 'List executions with filters' },
|
|
||||||
{ name: 'n8n_delete_execution', description: 'Delete execution records' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
category: 'System',
|
|
||||||
tools: [
|
|
||||||
{ name: 'n8n_health_check', description: 'Check API connectivity' },
|
|
||||||
{ name: 'n8n_list_available_tools', description: 'List all available tools' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const config = getN8nApiConfig();
|
|
||||||
const apiConfigured = config !== null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
tools,
|
|
||||||
apiConfigured,
|
|
||||||
configuration: config ? {
|
|
||||||
apiUrl: config.baseUrl,
|
|
||||||
timeout: config.timeout,
|
|
||||||
maxRetries: config.maxRetries
|
|
||||||
} : null,
|
|
||||||
limitations: [
|
|
||||||
'Cannot execute workflows directly (must use webhooks)',
|
|
||||||
'Cannot stop running executions',
|
|
||||||
'Tags and credentials have limited API support'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment-aware debugging helpers
|
// Environment-aware debugging helpers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1981,7 +1924,7 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
|
|||||||
example: 'validate_workflow({workflow: {...}})'
|
example: 'validate_workflow({workflow: {...}})'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
note: '22 documentation tools available without API configuration'
|
note: '14 documentation tools available without API configuration'
|
||||||
},
|
},
|
||||||
whatYouCannotDo: [
|
whatYouCannotDo: [
|
||||||
'✗ Create/update workflows in n8n instance',
|
'✗ Create/update workflows in n8n instance',
|
||||||
@@ -1996,8 +1939,8 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
|
|||||||
' N8N_API_URL=https://your-n8n-instance.com',
|
' N8N_API_URL=https://your-n8n-instance.com',
|
||||||
' N8N_API_KEY=your_api_key_here',
|
' N8N_API_KEY=your_api_key_here',
|
||||||
'3. Restart the MCP server',
|
'3. Restart the MCP server',
|
||||||
'4. Run n8n_diagnostic again to verify',
|
'4. Run n8n_health_check with mode="diagnostic" to verify',
|
||||||
'5. All 38 tools will be available!'
|
'5. All 31 tools will be available!'
|
||||||
],
|
],
|
||||||
documentation: 'https://github.com/czlonkowski/n8n-mcp?tab=readme-ov-file#n8n-management-tools-optional---requires-api-configuration'
|
documentation: 'https://github.com/czlonkowski/n8n-mcp?tab=readme-ov-file#n8n-management-tools-optional---requires-api-configuration'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,6 @@ export const toolsDocumentationDoc: ToolDocumentation = {
|
|||||||
'Not all internal functions are documented',
|
'Not all internal functions are documented',
|
||||||
'Special topics (code guides) require exact names'
|
'Special topics (code guides) require exact names'
|
||||||
],
|
],
|
||||||
relatedTools: ['n8n_list_available_tools for dynamic tool discovery', 'list_tasks for common configurations', 'get_database_statistics to verify MCP connection']
|
relatedTools: ['n8n_health_check for verifying API connection', 'get_node_for_task for common configurations', 'search_nodes for finding nodes']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -10,7 +10,7 @@ export const getTemplatesForTaskDoc: ToolDocumentation = {
|
|||||||
performance: 'Fast (<100ms) - pre-categorized results',
|
performance: 'Fast (<100ms) - pre-categorized results',
|
||||||
tips: [
|
tips: [
|
||||||
'Returns hand-picked templates for specific automation tasks',
|
'Returns hand-picked templates for specific automation tasks',
|
||||||
'Use list_tasks to see all available task categories',
|
'Available tasks: ai_automation, data_sync, webhook_processing, email_automation, slack_integration, etc.',
|
||||||
'Templates are curated for quality and relevance'
|
'Templates are curated for quality and relevance'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,6 +66,6 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
|||||||
'Profile affects validation time - strict is slower but more thorough',
|
'Profile affects validation time - strict is slower but more thorough',
|
||||||
'Expression validation may flag working but non-standard syntax'
|
'Expression validation may flag working but non-standard syntax'
|
||||||
],
|
],
|
||||||
relatedTools: ['validate_workflow', 'n8n_get_workflow', 'validate_workflow_expressions', 'n8n_health_check', 'n8n_autofix_workflow']
|
relatedTools: ['validate_workflow', 'n8n_get_workflow', 'n8n_health_check', 'n8n_autofix_workflow']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -88,8 +88,8 @@ When working with Code nodes, always start by calling the relevant guide:
|
|||||||
|
|
||||||
1. **Find** the node you need:
|
1. **Find** the node you need:
|
||||||
- search_nodes({query: "slack"}) - Search by keyword
|
- search_nodes({query: "slack"}) - Search by keyword
|
||||||
- list_nodes({category: "communication"}) - List by category
|
- search_nodes({query: "communication"}) - Search by category name
|
||||||
- list_ai_tools() - List AI-capable nodes
|
- search_nodes({query: "AI langchain"}) - Search for AI-capable nodes
|
||||||
|
|
||||||
2. **Configure** the node (ALWAYS START WITH STANDARD DETAIL):
|
2. **Configure** the node (ALWAYS START WITH STANDARD DETAIL):
|
||||||
- ✅ get_node("nodes-base.slack", {detail: 'standard'}) - Get essential properties FIRST (~1-2KB, shows required fields)
|
- ✅ get_node("nodes-base.slack", {detail: 'standard'}) - Get essential properties FIRST (~1-2KB, shows required fields)
|
||||||
@@ -105,9 +105,7 @@ When working with Code nodes, always start by calling the relevant guide:
|
|||||||
## Tool Categories
|
## Tool Categories
|
||||||
|
|
||||||
**Discovery Tools**
|
**Discovery Tools**
|
||||||
- search_nodes - Full-text search across all nodes
|
- search_nodes - Full-text search across all nodes (supports OR, AND, FUZZY modes)
|
||||||
- list_nodes - List nodes with filtering by category, package, or type
|
|
||||||
- list_ai_tools - List all AI-capable nodes with usage guidance
|
|
||||||
|
|
||||||
**Configuration Tools**
|
**Configuration Tools**
|
||||||
- get_node - ✅ Unified node information tool with progressive detail levels:
|
- get_node - ✅ Unified node information tool with progressive detail levels:
|
||||||
@@ -125,10 +123,11 @@ When working with Code nodes, always start by calling the relevant guide:
|
|||||||
- validate_workflow - Complete workflow validation including connections
|
- validate_workflow - Complete workflow validation including connections
|
||||||
|
|
||||||
**Template Tools**
|
**Template Tools**
|
||||||
- list_tasks - List common task templates
|
|
||||||
- get_node_for_task - Get pre-configured node for specific tasks
|
- get_node_for_task - Get pre-configured node for specific tasks
|
||||||
- search_templates - Search workflow templates by keyword
|
- search_templates - Search workflow templates by keyword
|
||||||
- get_template - Get complete workflow JSON by ID
|
- get_template - Get complete workflow JSON by ID
|
||||||
|
- list_node_templates - Find templates using specific nodes
|
||||||
|
- get_templates_for_task - Get curated templates by task type
|
||||||
|
|
||||||
**n8n API Tools** (requires N8N_API_URL configuration)
|
**n8n API Tools** (requires N8N_API_URL configuration)
|
||||||
- n8n_create_workflow - Create new workflows
|
- n8n_create_workflow - Create new workflows
|
||||||
@@ -137,7 +136,7 @@ When working with Code nodes, always start by calling the relevant guide:
|
|||||||
- n8n_trigger_webhook_workflow - Trigger workflow execution
|
- n8n_trigger_webhook_workflow - Trigger workflow execution
|
||||||
|
|
||||||
## Performance Characteristics
|
## Performance Characteristics
|
||||||
- Instant (<10ms): search_nodes, list_nodes, get_node (minimal/standard)
|
- Instant (<10ms): search_nodes, get_node (minimal/standard)
|
||||||
- Fast (<100ms): validate_node_minimal, get_node_for_task
|
- Fast (<100ms): validate_node_minimal, get_node_for_task
|
||||||
- Moderate (100-500ms): validate_workflow, get_node (full detail)
|
- Moderate (100-500ms): validate_workflow, get_node (full detail)
|
||||||
- Network-dependent: All n8n_* tools
|
- Network-dependent: All n8n_* tools
|
||||||
|
|||||||
@@ -521,10 +521,10 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
* QUICK REFERENCE for AI Agents:
|
* QUICK REFERENCE for AI Agents:
|
||||||
*
|
*
|
||||||
* 1. RECOMMENDED WORKFLOW:
|
* 1. RECOMMENDED WORKFLOW:
|
||||||
* - Start: search_nodes → get_node_essentials → get_node_for_task → validate_node_operation
|
* - Start: search_nodes → get_node → get_node_for_task → validate_node_operation
|
||||||
* - Discovery: list_nodes({category:"trigger"}) for browsing categories
|
* - Discovery: search_nodes({query:"trigger"}) for finding nodes
|
||||||
* - Quick Config: get_node_essentials("nodes-base.httpRequest") - only essential properties
|
* - Quick Config: get_node("nodes-base.httpRequest", {detail:"standard"}) - only essential properties
|
||||||
* - Full Details: get_node_info only when essentials aren't enough
|
* - Full Details: get_node with detail="full" only when standard isn't enough
|
||||||
* - Validation: Use validate_node_operation for complex nodes (Slack, Google Sheets, etc.)
|
* - Validation: Use validate_node_operation for complex nodes (Slack, Google Sheets, etc.)
|
||||||
*
|
*
|
||||||
* 2. COMMON NODE TYPES:
|
* 2. COMMON NODE TYPES:
|
||||||
@@ -536,7 +536,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
* 3. SEARCH TIPS:
|
* 3. SEARCH TIPS:
|
||||||
* - search_nodes returns ANY word match (OR logic)
|
* - search_nodes returns ANY word match (OR logic)
|
||||||
* - Single words more precise, multiple words broader
|
* - Single words more precise, multiple words broader
|
||||||
* - If no results: use list_nodes with category filter
|
* - If no results: try different keywords or partial names
|
||||||
*
|
*
|
||||||
* 4. TEMPLATE SEARCHING:
|
* 4. TEMPLATE SEARCHING:
|
||||||
* - search_templates("slack") searches template names/descriptions, NOT node types!
|
* - search_templates("slack") searches template names/descriptions, NOT node types!
|
||||||
@@ -550,7 +550,7 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
|
|||||||
* - Check showWhen/hideWhen to identify the right property variant
|
* - Check showWhen/hideWhen to identify the right property variant
|
||||||
*
|
*
|
||||||
* 6. PERFORMANCE:
|
* 6. PERFORMANCE:
|
||||||
* - get_node_essentials: Fast (<5KB)
|
* - get_node (detail=standard): Fast (<5KB)
|
||||||
* - get_node_info: Slow (100KB+) - use sparingly
|
* - get_node (detail=full): Slow (100KB+) - use sparingly
|
||||||
* - search_nodes/list_nodes: Fast, cached
|
* - search_nodes: Fast, cached
|
||||||
*/
|
*/
|
||||||
@@ -84,16 +84,16 @@ describe('MCP Error Handling', () => {
|
|||||||
|
|
||||||
describe('Tool-Specific Errors', () => {
|
describe('Tool-Specific Errors', () => {
|
||||||
describe('Node Discovery Errors', () => {
|
describe('Node Discovery Errors', () => {
|
||||||
it('should handle invalid category filter', async () => {
|
it('should handle search with no matching results', async () => {
|
||||||
const response = await client.callTool({ name: 'list_nodes', arguments: {
|
const response = await client.callTool({ name: 'search_nodes', arguments: {
|
||||||
category: 'invalid_category'
|
query: 'xyznonexistentnode123'
|
||||||
} });
|
} });
|
||||||
|
|
||||||
// Should return empty array, not error
|
// Should return empty array, not error
|
||||||
const result = JSON.parse((response as any).content[0].text);
|
const result = JSON.parse((response as any).content[0].text);
|
||||||
expect(result).toHaveProperty('nodes');
|
expect(result).toHaveProperty('results');
|
||||||
expect(Array.isArray(result.nodes)).toBe(true);
|
expect(Array.isArray(result.results)).toBe(true);
|
||||||
expect(result.nodes).toHaveLength(0);
|
expect(result.results).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle invalid search mode', async () => {
|
it('should handle invalid search mode', async () => {
|
||||||
@@ -279,9 +279,9 @@ describe('MCP Error Handling', () => {
|
|||||||
|
|
||||||
for (let i = 0; i < requestCount; i++) {
|
for (let i = 0; i < requestCount; i++) {
|
||||||
promises.push(
|
promises.push(
|
||||||
client.callTool({ name: 'list_nodes', arguments: {
|
client.callTool({ name: 'search_nodes', arguments: {
|
||||||
limit: 1,
|
query: i % 2 === 0 ? 'webhook' : 'http',
|
||||||
category: i % 2 === 0 ? 'trigger' : 'transform'
|
limit: 1
|
||||||
} })
|
} })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -322,7 +322,7 @@ describe('MCP Error Handling', () => {
|
|||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
for (let i = 0; i < 20; i++) {
|
for (let i = 0; i < 20; i++) {
|
||||||
await client.callTool({ name: 'get_database_statistics', arguments: {} });
|
await client.callTool({ name: 'tools_documentation', arguments: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = Date.now() - start;
|
const duration = Date.now() - start;
|
||||||
@@ -410,17 +410,17 @@ describe('MCP Error Handling', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should still work
|
// Should still work
|
||||||
const response = await client.callTool({ name: 'list_nodes', arguments: { limit: 1 } });
|
const response = await client.callTool({ name: 'search_nodes', arguments: { query: 'webhook', limit: 1 } });
|
||||||
expect(response).toBeDefined();
|
expect(response).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle mixed success and failure', async () => {
|
it('should handle mixed success and failure', async () => {
|
||||||
const promises = [
|
const promises = [
|
||||||
client.callTool({ name: 'list_nodes', arguments: { limit: 5 } }),
|
client.callTool({ name: 'search_nodes', arguments: { query: 'webhook', limit: 5 } }),
|
||||||
client.callTool({ name: 'get_node', arguments: { nodeType: 'invalid' } }).catch(e => ({ error: e })),
|
client.callTool({ name: 'get_node', arguments: { nodeType: 'invalid' } }).catch(e => ({ error: e })),
|
||||||
client.callTool({ name: 'get_database_statistics', arguments: {} }),
|
client.callTool({ name: 'tools_documentation', arguments: {} }),
|
||||||
client.callTool({ name: 'search_nodes', arguments: { query: '' } }).catch(e => ({ error: e })),
|
client.callTool({ name: 'search_nodes', arguments: { query: '' } }).catch(e => ({ error: e })),
|
||||||
client.callTool({ name: 'list_ai_tools', arguments: {} })
|
client.callTool({ name: 'get_node', arguments: { nodeType: 'nodes-base.httpRequest' } })
|
||||||
];
|
];
|
||||||
|
|
||||||
const results = await Promise.all(promises);
|
const results = await Promise.all(promises);
|
||||||
@@ -436,14 +436,14 @@ describe('MCP Error Handling', () => {
|
|||||||
|
|
||||||
describe('Edge Cases', () => {
|
describe('Edge Cases', () => {
|
||||||
it('should handle empty responses gracefully', async () => {
|
it('should handle empty responses gracefully', async () => {
|
||||||
const response = await client.callTool({ name: 'list_nodes', arguments: {
|
const response = await client.callTool({ name: 'search_nodes', arguments: {
|
||||||
category: 'nonexistent_category'
|
query: 'xyznonexistentnode12345'
|
||||||
} });
|
} });
|
||||||
|
|
||||||
const result = JSON.parse((response as any).content[0].text);
|
const result = JSON.parse((response as any).content[0].text);
|
||||||
expect(result).toHaveProperty('nodes');
|
expect(result).toHaveProperty('results');
|
||||||
expect(Array.isArray(result.nodes)).toBe(true);
|
expect(Array.isArray(result.results)).toBe(true);
|
||||||
expect(result.nodes).toHaveLength(0);
|
expect(result.results).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle special characters in parameters', async () => {
|
it('should handle special characters in parameters', async () => {
|
||||||
@@ -469,14 +469,15 @@ describe('MCP Error Handling', () => {
|
|||||||
|
|
||||||
it('should handle null and undefined gracefully', async () => {
|
it('should handle null and undefined gracefully', async () => {
|
||||||
// Most tools should handle missing optional params
|
// Most tools should handle missing optional params
|
||||||
const response = await client.callTool({ name: 'list_nodes', arguments: {
|
const response = await client.callTool({ name: 'search_nodes', arguments: {
|
||||||
|
query: 'webhook',
|
||||||
limit: undefined as any,
|
limit: undefined as any,
|
||||||
category: null as any
|
mode: null as any
|
||||||
} });
|
} });
|
||||||
|
|
||||||
const result = JSON.parse((response as any).content[0].text);
|
const result = JSON.parse((response as any).content[0].text);
|
||||||
expect(result).toHaveProperty('nodes');
|
expect(result).toHaveProperty('results');
|
||||||
expect(Array.isArray(result.nodes)).toBe(true);
|
expect(Array.isArray(result.results)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,207 +0,0 @@
|
|||||||
/**
|
|
||||||
* Integration Tests: handleListAvailableTools
|
|
||||||
*
|
|
||||||
* Tests tool listing functionality.
|
|
||||||
* Covers tool discovery and configuration status.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import { createMcpContext } from '../utils/mcp-context';
|
|
||||||
import { InstanceContext } from '../../../../src/types/instance-context';
|
|
||||||
import { handleListAvailableTools } from '../../../../src/mcp/handlers-n8n-manager';
|
|
||||||
import { ListToolsResponse } from '../utils/response-types';
|
|
||||||
|
|
||||||
describe('Integration: handleListAvailableTools', () => {
|
|
||||||
let mcpContext: InstanceContext;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mcpContext = createMcpContext();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ======================================================================
|
|
||||||
// List All Tools
|
|
||||||
// ======================================================================
|
|
||||||
|
|
||||||
describe('Tool Listing', () => {
|
|
||||||
it('should list all available tools organized by category', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
|
|
||||||
expect(response.success).toBe(true);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
// Verify tools array exists
|
|
||||||
expect(data).toHaveProperty('tools');
|
|
||||||
expect(Array.isArray(data.tools)).toBe(true);
|
|
||||||
expect(data.tools.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// Verify tool categories
|
|
||||||
const categories = data.tools.map((cat: any) => cat.category);
|
|
||||||
expect(categories).toContain('Workflow Management');
|
|
||||||
expect(categories).toContain('Execution Management');
|
|
||||||
expect(categories).toContain('System');
|
|
||||||
|
|
||||||
// Verify each category has tools
|
|
||||||
data.tools.forEach(category => {
|
|
||||||
expect(category).toHaveProperty('category');
|
|
||||||
expect(category).toHaveProperty('tools');
|
|
||||||
expect(Array.isArray(category.tools)).toBe(true);
|
|
||||||
expect(category.tools.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// Verify each tool has required fields
|
|
||||||
category.tools.forEach(tool => {
|
|
||||||
expect(tool).toHaveProperty('name');
|
|
||||||
expect(tool).toHaveProperty('description');
|
|
||||||
expect(typeof tool.name).toBe('string');
|
|
||||||
expect(typeof tool.description).toBe('string');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include API configuration status', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
|
|
||||||
expect(response.success).toBe(true);
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
// Verify configuration status
|
|
||||||
expect(data).toHaveProperty('apiConfigured');
|
|
||||||
expect(typeof data.apiConfigured).toBe('boolean');
|
|
||||||
|
|
||||||
// Since tests run with API configured, should be true
|
|
||||||
expect(data.apiConfigured).toBe(true);
|
|
||||||
|
|
||||||
// Verify configuration details are present when configured
|
|
||||||
if (data.apiConfigured) {
|
|
||||||
expect(data).toHaveProperty('configuration');
|
|
||||||
expect(data.configuration).toBeDefined();
|
|
||||||
expect(data.configuration).toHaveProperty('apiUrl');
|
|
||||||
expect(data.configuration).toHaveProperty('timeout');
|
|
||||||
expect(data.configuration).toHaveProperty('maxRetries');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include API limitations information', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
|
|
||||||
expect(response.success).toBe(true);
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
// Verify limitations are documented
|
|
||||||
expect(data).toHaveProperty('limitations');
|
|
||||||
expect(Array.isArray(data.limitations)).toBe(true);
|
|
||||||
expect(data.limitations.length).toBeGreaterThan(0);
|
|
||||||
|
|
||||||
// Verify limitations are informative strings
|
|
||||||
data.limitations.forEach(limitation => {
|
|
||||||
expect(typeof limitation).toBe('string');
|
|
||||||
expect(limitation.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Common known limitations
|
|
||||||
const limitationsText = data.limitations.join(' ');
|
|
||||||
expect(limitationsText).toContain('Cannot execute workflows directly');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ======================================================================
|
|
||||||
// Workflow Management Tools
|
|
||||||
// ======================================================================
|
|
||||||
|
|
||||||
describe('Workflow Management Tools', () => {
|
|
||||||
it('should include all workflow management tools', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
const workflowCategory = data.tools.find(cat => cat.category === 'Workflow Management');
|
|
||||||
expect(workflowCategory).toBeDefined();
|
|
||||||
|
|
||||||
const toolNames = workflowCategory!.tools.map(t => t.name);
|
|
||||||
|
|
||||||
// Core workflow tools
|
|
||||||
expect(toolNames).toContain('n8n_create_workflow');
|
|
||||||
expect(toolNames).toContain('n8n_get_workflow');
|
|
||||||
expect(toolNames).toContain('n8n_update_workflow');
|
|
||||||
expect(toolNames).toContain('n8n_delete_workflow');
|
|
||||||
expect(toolNames).toContain('n8n_list_workflows');
|
|
||||||
|
|
||||||
// Enhanced workflow tools
|
|
||||||
expect(toolNames).toContain('n8n_get_workflow_details');
|
|
||||||
expect(toolNames).toContain('n8n_get_workflow_structure');
|
|
||||||
expect(toolNames).toContain('n8n_get_workflow_minimal');
|
|
||||||
expect(toolNames).toContain('n8n_validate_workflow');
|
|
||||||
expect(toolNames).toContain('n8n_autofix_workflow');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ======================================================================
|
|
||||||
// Execution Management Tools
|
|
||||||
// ======================================================================
|
|
||||||
|
|
||||||
describe('Execution Management Tools', () => {
|
|
||||||
it('should include all execution management tools', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
const executionCategory = data.tools.find(cat => cat.category === 'Execution Management');
|
|
||||||
expect(executionCategory).toBeDefined();
|
|
||||||
|
|
||||||
const toolNames = executionCategory!.tools.map(t => t.name);
|
|
||||||
|
|
||||||
expect(toolNames).toContain('n8n_trigger_webhook_workflow');
|
|
||||||
expect(toolNames).toContain('n8n_get_execution');
|
|
||||||
expect(toolNames).toContain('n8n_list_executions');
|
|
||||||
expect(toolNames).toContain('n8n_delete_execution');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ======================================================================
|
|
||||||
// System Tools
|
|
||||||
// ======================================================================
|
|
||||||
|
|
||||||
describe('System Tools', () => {
|
|
||||||
it('should include system tools', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
const systemCategory = data.tools.find(cat => cat.category === 'System');
|
|
||||||
expect(systemCategory).toBeDefined();
|
|
||||||
|
|
||||||
const toolNames = systemCategory!.tools.map(t => t.name);
|
|
||||||
|
|
||||||
expect(toolNames).toContain('n8n_health_check');
|
|
||||||
expect(toolNames).toContain('n8n_list_available_tools');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ======================================================================
|
|
||||||
// Response Format Verification
|
|
||||||
// ======================================================================
|
|
||||||
|
|
||||||
describe('Response Format', () => {
|
|
||||||
it('should return complete tool list response structure', async () => {
|
|
||||||
const response = await handleListAvailableTools(mcpContext);
|
|
||||||
|
|
||||||
expect(response.success).toBe(true);
|
|
||||||
expect(response.data).toBeDefined();
|
|
||||||
|
|
||||||
const data = response.data as ListToolsResponse;
|
|
||||||
|
|
||||||
// Verify all required fields
|
|
||||||
expect(data).toHaveProperty('tools');
|
|
||||||
expect(data).toHaveProperty('apiConfigured');
|
|
||||||
expect(data).toHaveProperty('limitations');
|
|
||||||
|
|
||||||
// Verify optional configuration field
|
|
||||||
if (data.apiConfigured) {
|
|
||||||
expect(data).toHaveProperty('configuration');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify data types
|
|
||||||
expect(Array.isArray(data.tools)).toBe(true);
|
|
||||||
expect(typeof data.apiConfigured).toBe('boolean');
|
|
||||||
expect(Array.isArray(data.limitations)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -19,29 +19,6 @@ export interface HealthCheckResponse {
|
|||||||
[key: string]: any; // Allow dynamic property access for optional field checks
|
[key: string]: any; // Allow dynamic property access for optional field checks
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolDefinition {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolCategory {
|
|
||||||
category: string;
|
|
||||||
tools: ToolDefinition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiConfiguration {
|
|
||||||
apiUrl: string;
|
|
||||||
timeout: number;
|
|
||||||
maxRetries: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ListToolsResponse {
|
|
||||||
tools: ToolCategory[];
|
|
||||||
apiConfigured: boolean;
|
|
||||||
configuration?: ApiConfiguration | null;
|
|
||||||
limitations: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStatus {
|
export interface ApiStatus {
|
||||||
configured: boolean;
|
configured: boolean;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
|
|||||||
@@ -1031,7 +1031,7 @@ describe('handlers-n8n-manager', () => {
|
|||||||
'1. Verify n8n instance is running',
|
'1. Verify n8n instance is running',
|
||||||
'2. Check N8N_API_URL is correct',
|
'2. Check N8N_API_URL is correct',
|
||||||
'3. Verify N8N_API_KEY has proper permissions',
|
'3. Verify N8N_API_KEY has proper permissions',
|
||||||
'4. Run n8n_diagnostic for detailed analysis',
|
'4. Run n8n_health_check with mode="diagnostic" for detailed analysis',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -399,24 +399,11 @@ describe('Parameter Validation', () => {
|
|||||||
expect(result).toEqual({ docs: 'test' });
|
expect(result).toEqual({ docs: 'test' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow list_nodes with no parameters', async () => {
|
it('should allow tools_documentation with no parameters', async () => {
|
||||||
const result = await server.testExecuteTool('list_nodes', {});
|
const result = await server.testExecuteTool('tools_documentation', {});
|
||||||
expect(result).toEqual({ nodes: [] });
|
expect(result).toBeDefined();
|
||||||
});
|
// tools_documentation returns an object with documentation content
|
||||||
|
expect(typeof result).toBe('object');
|
||||||
it('should allow list_ai_tools with no parameters', async () => {
|
|
||||||
const result = await server.testExecuteTool('list_ai_tools', {});
|
|
||||||
expect(result).toEqual({ tools: [] });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow get_database_statistics with no parameters', async () => {
|
|
||||||
const result = await server.testExecuteTool('get_database_statistics', {});
|
|
||||||
expect(result).toEqual({ stats: {} });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow list_tasks with no parameters', async () => {
|
|
||||||
const result = await server.testExecuteTool('list_tasks', {});
|
|
||||||
expect(result).toEqual({ tasks: [] });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -476,8 +463,8 @@ describe('Parameter Validation', () => {
|
|||||||
{ name: 'get_node_documentation', args: {}, expected: 'Missing required parameters for get_node_documentation: nodeType' },
|
{ name: 'get_node_documentation', args: {}, expected: 'Missing required parameters for get_node_documentation: nodeType' },
|
||||||
{ name: 'search_node_properties', args: {}, expected: 'Missing required parameters for search_node_properties: nodeType, query' },
|
{ name: 'search_node_properties', args: {}, expected: 'Missing required parameters for search_node_properties: nodeType, query' },
|
||||||
// Note: get_node_for_task removed in v2.15.0
|
// Note: get_node_for_task removed in v2.15.0
|
||||||
|
// Note: get_node_as_tool_info removed in v2.25.0
|
||||||
{ name: 'get_property_dependencies', args: {}, expected: 'Missing required parameters for get_property_dependencies: nodeType' },
|
{ name: 'get_property_dependencies', args: {}, expected: 'Missing required parameters for get_property_dependencies: nodeType' },
|
||||||
{ name: 'get_node_as_tool_info', args: {}, expected: 'Missing required parameters for get_node_as_tool_info: nodeType' },
|
|
||||||
{ name: 'get_template', args: {}, expected: 'Missing required parameters for get_template: templateId' },
|
{ name: 'get_template', args: {}, expected: 'Missing required parameters for get_template: templateId' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -78,31 +78,6 @@ describe('n8nDocumentationToolsFinal', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('list_nodes', () => {
|
|
||||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'list_nodes');
|
|
||||||
|
|
||||||
it('should exist', () => {
|
|
||||||
expect(tool).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have correct schema properties', () => {
|
|
||||||
const properties = tool?.inputSchema.properties;
|
|
||||||
expect(properties).toHaveProperty('package');
|
|
||||||
expect(properties).toHaveProperty('category');
|
|
||||||
expect(properties).toHaveProperty('developmentStyle');
|
|
||||||
expect(properties).toHaveProperty('isAITool');
|
|
||||||
expect(properties).toHaveProperty('limit');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have correct defaults', () => {
|
|
||||||
expect(tool?.inputSchema.properties.limit.default).toBe(50);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have proper enum values', () => {
|
|
||||||
expect(tool?.inputSchema.properties.developmentStyle.enum).toEqual(['declarative', 'programmatic']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get_node', () => {
|
describe('get_node', () => {
|
||||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'get_node');
|
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'get_node');
|
||||||
|
|
||||||
@@ -205,7 +180,6 @@ describe('n8nDocumentationToolsFinal', () => {
|
|||||||
|
|
||||||
it('should include examples or key information in descriptions', () => {
|
it('should include examples or key information in descriptions', () => {
|
||||||
const toolsWithExamples = [
|
const toolsWithExamples = [
|
||||||
'list_nodes',
|
|
||||||
'get_node',
|
'get_node',
|
||||||
'search_nodes',
|
'search_nodes',
|
||||||
'get_node_documentation'
|
'get_node_documentation'
|
||||||
@@ -250,14 +224,14 @@ describe('n8nDocumentationToolsFinal', () => {
|
|||||||
describe('Tool Categories Coverage', () => {
|
describe('Tool Categories Coverage', () => {
|
||||||
it('should have tools for all major categories', () => {
|
it('should have tools for all major categories', () => {
|
||||||
const categories = {
|
const categories = {
|
||||||
discovery: ['list_nodes', 'search_nodes', 'list_ai_tools'],
|
discovery: ['search_nodes'],
|
||||||
configuration: ['get_node', 'get_node_documentation'],
|
configuration: ['get_node', 'get_node_documentation'],
|
||||||
validation: ['validate_node_operation', 'validate_workflow', 'validate_node_minimal'],
|
validation: ['validate_node_operation', 'validate_workflow', 'validate_node_minimal'],
|
||||||
templates: ['list_tasks', 'search_templates', 'list_templates', 'get_template', 'list_node_templates'], // get_node_for_task removed in v2.15.0
|
templates: ['search_templates', 'get_template', 'list_node_templates', 'get_templates_for_task', 'search_templates_by_metadata'],
|
||||||
documentation: ['tools_documentation']
|
documentation: ['tools_documentation']
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.entries(categories).forEach(([category, expectedTools]) => {
|
Object.entries(categories).forEach(([_category, expectedTools]) => {
|
||||||
expectedTools.forEach(toolName => {
|
expectedTools.forEach(toolName => {
|
||||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
||||||
expect(tool).toBeDefined();
|
expect(tool).toBeDefined();
|
||||||
@@ -294,13 +268,15 @@ describe('n8nDocumentationToolsFinal', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Edge Cases', () => {
|
describe('Edge Cases', () => {
|
||||||
it('should handle tools with no parameters', () => {
|
it('should handle tools with optional parameters only', () => {
|
||||||
const toolsWithNoParams = ['list_ai_tools', 'get_database_statistics'];
|
// Tools where all parameters are optional
|
||||||
|
const toolsWithOptionalParams = ['tools_documentation'];
|
||||||
|
|
||||||
toolsWithNoParams.forEach(toolName => {
|
toolsWithOptionalParams.forEach(toolName => {
|
||||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
||||||
expect(tool).toBeDefined();
|
expect(tool).toBeDefined();
|
||||||
expect(Object.keys(tool?.inputSchema.properties || {}).length).toBe(0);
|
// These tools have properties but no required array or empty required array
|
||||||
|
expect(tool?.inputSchema.required === undefined || tool?.inputSchema.required?.length === 0).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -318,37 +294,6 @@ describe('n8nDocumentationToolsFinal', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('New Template Tools', () => {
|
describe('New Template Tools', () => {
|
||||||
describe('list_templates', () => {
|
|
||||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'list_templates');
|
|
||||||
|
|
||||||
it('should exist and be properly defined', () => {
|
|
||||||
expect(tool).toBeDefined();
|
|
||||||
expect(tool?.description).toContain('minimal data');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have correct parameters', () => {
|
|
||||||
expect(tool?.inputSchema.properties).toHaveProperty('limit');
|
|
||||||
expect(tool?.inputSchema.properties).toHaveProperty('offset');
|
|
||||||
expect(tool?.inputSchema.properties).toHaveProperty('sortBy');
|
|
||||||
|
|
||||||
const limitParam = tool?.inputSchema.properties.limit;
|
|
||||||
expect(limitParam.type).toBe('number');
|
|
||||||
expect(limitParam.minimum).toBe(1);
|
|
||||||
expect(limitParam.maximum).toBe(100);
|
|
||||||
|
|
||||||
const offsetParam = tool?.inputSchema.properties.offset;
|
|
||||||
expect(offsetParam.type).toBe('number');
|
|
||||||
expect(offsetParam.minimum).toBe(0);
|
|
||||||
|
|
||||||
const sortByParam = tool?.inputSchema.properties.sortBy;
|
|
||||||
expect(sortByParam.enum).toEqual(['views', 'created_at', 'name']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have no required parameters', () => {
|
|
||||||
expect(tool?.inputSchema.required).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get_template (enhanced)', () => {
|
describe('get_template (enhanced)', () => {
|
||||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'get_template');
|
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'get_template');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user