fix: update integration tests to use valid tools after v2.25.0 removal

Replaced all references to removed tools in integration tests:
- list_nodes -> search_nodes
- get_database_statistics -> tools_documentation
- list_ai_tools -> search_nodes/tools_documentation
- list_tasks -> tools_documentation
- get_node_as_tool_info -> removed test section

Updated test files:
- tests/integration/mcp-protocol/basic-connection.test.ts
- tests/integration/mcp-protocol/performance.test.ts
- tests/integration/mcp-protocol/session-management.test.ts
- tests/integration/mcp-protocol/test-helpers.ts
- tests/integration/mcp-protocol/tool-invocation.test.ts
- tests/integration/telemetry/mcp-telemetry.test.ts
- tests/unit/mcp/disabled-tools.test.ts
- tests/unit/mcp/tools-documentation.test.ts

Conceived by Romuald Członkowski - https://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:
czlonkowski
2025-11-25 13:13:26 +01:00
parent 13b8abaafb
commit 7f03f51e87
8 changed files with 175 additions and 312 deletions

View File

@@ -5,69 +5,68 @@ describe('Basic MCP Connection', () => {
it('should initialize MCP server', async () => {
const server = new N8NDocumentationMCPServer();
// Test executeTool directly - it returns raw data
const result = await server.executeTool('get_database_statistics', {});
// Test executeTool directly - tools_documentation returns a string
const result = await server.executeTool('tools_documentation', {});
expect(result).toBeDefined();
expect(typeof result).toBe('object');
expect(result.totalNodes).toBeDefined();
expect(result.statistics).toBeDefined();
expect(typeof result).toBe('string');
expect(result).toContain('n8n MCP');
await server.shutdown();
});
it('should execute list_nodes tool', async () => {
it('should execute search_nodes tool', async () => {
const server = new N8NDocumentationMCPServer();
// First check if we have any nodes in the database
const stats = await server.executeTool('get_database_statistics', {});
const hasNodes = stats.totalNodes > 0;
try {
// Search for a common node to verify database has content
const result = await server.executeTool('search_nodes', { query: 'http', limit: 5 });
expect(result).toBeDefined();
expect(typeof result).toBe('object');
expect(result.results).toBeDefined();
expect(Array.isArray(result.results)).toBe(true);
const result = await server.executeTool('list_nodes', { limit: 5 });
expect(result).toBeDefined();
expect(typeof result).toBe('object');
expect(result.nodes).toBeDefined();
expect(Array.isArray(result.nodes)).toBe(true);
if (hasNodes) {
// If database has nodes, we should get up to 5
expect(result.nodes.length).toBeLessThanOrEqual(5);
expect(result.nodes.length).toBeGreaterThan(0);
expect(result.nodes[0]).toHaveProperty('nodeType');
expect(result.nodes[0]).toHaveProperty('displayName');
} else {
// In test environment with empty database, we expect empty results
expect(result.nodes).toHaveLength(0);
if (result.totalCount > 0) {
// If database has nodes, we should get results
expect(result.results.length).toBeLessThanOrEqual(5);
expect(result.results.length).toBeGreaterThan(0);
expect(result.results[0]).toHaveProperty('nodeType');
expect(result.results[0]).toHaveProperty('displayName');
}
} catch (error: any) {
// In test environment with empty database, expect appropriate error
expect(error.message).toContain('Database is empty');
}
await server.shutdown();
});
it('should search nodes', async () => {
it('should search nodes by keyword', async () => {
const server = new N8NDocumentationMCPServer();
// First check if we have any nodes in the database
const stats = await server.executeTool('get_database_statistics', {});
const hasNodes = stats.totalNodes > 0;
try {
// Search to check if database has nodes
const searchResult = await server.executeTool('search_nodes', { query: 'set', limit: 1 });
const hasNodes = searchResult.totalCount > 0;
const result = await server.executeTool('search_nodes', { query: 'webhook' });
expect(result).toBeDefined();
expect(typeof result).toBe('object');
expect(result.results).toBeDefined();
expect(Array.isArray(result.results)).toBe(true);
const result = await server.executeTool('search_nodes', { query: 'webhook' });
expect(result).toBeDefined();
expect(typeof result).toBe('object');
expect(result.results).toBeDefined();
expect(Array.isArray(result.results)).toBe(true);
// Only expect results if the database has nodes
if (hasNodes) {
expect(result.results.length).toBeGreaterThan(0);
expect(result.totalCount).toBeGreaterThan(0);
// Only expect results if the database has nodes
if (hasNodes) {
expect(result.results.length).toBeGreaterThan(0);
expect(result.totalCount).toBeGreaterThan(0);
// Should find webhook node
const webhookNode = result.results.find((n: any) => n.nodeType === 'nodes-base.webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode.displayName).toContain('Webhook');
} else {
// In test environment with empty database, we expect empty results
expect(result.results).toHaveLength(0);
expect(result.totalCount).toBe(0);
// Should find webhook node
const webhookNode = result.results.find((n: any) => n.nodeType === 'nodes-base.webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode.displayName).toContain('Webhook');
}
} catch (error: any) {
// In test environment with empty database, expect appropriate error
expect(error.message).toContain('Database is empty');
}
await server.shutdown();

View File

@@ -23,13 +23,13 @@ describe('MCP Performance Tests', () => {
await client.connect(clientTransport);
// Verify database is populated by checking statistics
const statsResponse = await client.callTool({ name: 'get_database_statistics', arguments: {} });
if ((statsResponse as any).content && (statsResponse as any).content[0]) {
const stats = JSON.parse((statsResponse as any).content[0].text);
// Verify database is populated by searching for a common node
const searchResponse = await client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 1 } });
if ((searchResponse as any).content && (searchResponse as any).content[0]) {
const searchResult = JSON.parse((searchResponse as any).content[0].text);
// Ensure database has nodes for testing
if (!stats.totalNodes || stats.totalNodes === 0) {
console.error('Database stats:', stats);
if (!searchResult.totalCount || searchResult.totalCount === 0) {
console.error('Search result:', searchResult);
throw new Error('Test database not properly populated');
}
}
@@ -46,13 +46,13 @@ describe('MCP Performance Tests', () => {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
}
const duration = performance.now() - start;
const avgTime = duration / iterations;
console.log(`Average response time for get_database_statistics: ${avgTime.toFixed(2)}ms`);
console.log(`Average response time for tools_documentation: ${avgTime.toFixed(2)}ms`);
console.log(`Environment: ${process.env.CI ? 'CI' : 'Local'}`);
// Environment-aware threshold (relaxed +20% for type safety overhead)
@@ -60,18 +60,18 @@ describe('MCP Performance Tests', () => {
expect(avgTime).toBeLessThan(threshold);
});
it('should handle list operations efficiently', async () => {
it('should handle search operations efficiently', async () => {
const iterations = 50;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
await client.callTool({ name: 'list_nodes', arguments: { limit: 10 } });
await client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 10 } });
}
const duration = performance.now() - start;
const avgTime = duration / iterations;
console.log(`Average response time for list_nodes: ${avgTime.toFixed(2)}ms`);
console.log(`Average response time for search_nodes: ${avgTime.toFixed(2)}ms`);
console.log(`Environment: ${process.env.CI ? 'CI' : 'Local'}`);
// Environment-aware threshold
@@ -137,7 +137,7 @@ describe('MCP Performance Tests', () => {
const promises = [];
for (let i = 0; i < concurrentRequests; i++) {
promises.push(
client.callTool({ name: 'list_nodes', arguments: { limit: 5 } })
client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 5 } })
);
}
@@ -156,11 +156,11 @@ describe('MCP Performance Tests', () => {
it('should handle mixed concurrent operations', async () => {
const operations = [
{ tool: 'list_nodes', params: { limit: 10 } },
{ tool: 'search_nodes', params: { query: 'http' } },
{ tool: 'get_database_statistics', params: {} },
{ tool: 'list_ai_tools', params: {} },
{ tool: 'list_tasks', params: {} }
{ tool: 'search_nodes', params: { query: 'http', limit: 10 } },
{ tool: 'search_nodes', params: { query: 'webhook' } },
{ tool: 'tools_documentation', params: {} },
{ tool: 'get_node', params: { nodeType: 'nodes-base.httpRequest' } },
{ tool: 'get_node', params: { nodeType: 'nodes-base.webhook' } }
];
const rounds = 10;
@@ -186,16 +186,17 @@ describe('MCP Performance Tests', () => {
});
describe('Large Data Performance', () => {
it('should handle large node lists efficiently', async () => {
it('should handle large search results efficiently', async () => {
const start = performance.now();
const response = await client.callTool({ name: 'list_nodes', arguments: {
limit: 200 // Get many nodes
const response = await client.callTool({ name: 'search_nodes', arguments: {
query: 'n8n', // Broad query to get many results
limit: 200
} });
const duration = performance.now() - start;
console.log(`Time to list 200 nodes: ${duration.toFixed(2)}ms`);
console.log(`Time to search 200 nodes: ${duration.toFixed(2)}ms`);
// Environment-aware threshold
const threshold = process.env.CI ? 200 : 100;
@@ -204,7 +205,7 @@ describe('MCP Performance Tests', () => {
// Check the response content
expect(response).toBeDefined();
let nodes;
let results;
if (response.content && Array.isArray(response.content) && response.content[0]) {
// MCP standard response format
expect(response.content[0].type).toBe('text');
@@ -212,8 +213,8 @@ describe('MCP Performance Tests', () => {
try {
const parsed = JSON.parse(response.content[0].text);
// list_nodes returns an object with nodes property
nodes = parsed.nodes || parsed;
// search_nodes returns an object with results property
results = parsed.results || parsed;
} catch (e) {
console.error('Failed to parse JSON:', e);
console.error('Response text was:', response.content[0].text);
@@ -221,18 +222,18 @@ describe('MCP Performance Tests', () => {
}
} else if (Array.isArray(response)) {
// Direct array response
nodes = response;
} else if (response.nodes) {
// Object with nodes property
nodes = response.nodes;
results = response;
} else if (response.results) {
// Object with results property
results = response.results;
} else {
console.error('Unexpected response format:', response);
throw new Error('Unexpected response format');
}
expect(nodes).toBeDefined();
expect(Array.isArray(nodes)).toBe(true);
expect(nodes.length).toBeGreaterThan(100);
expect(results).toBeDefined();
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBeGreaterThan(50);
});
it('should handle large workflow validation efficiently', async () => {
@@ -304,7 +305,7 @@ describe('MCP Performance Tests', () => {
for (let j = 0; j < batchSize; j++) {
promises.push(
client.callTool({ name: 'get_database_statistics', arguments: {} })
client.callTool({ name: 'tools_documentation', arguments: {} })
);
}
@@ -330,7 +331,7 @@ describe('MCP Performance Tests', () => {
// Perform large operations
for (let i = 0; i < 10; i++) {
await client.callTool({ name: 'list_nodes', arguments: { limit: 200 } });
await client.callTool({ name: 'search_nodes', arguments: { query: 'n8n', limit: 200 } });
await client.callTool({ name: 'get_node', arguments: {
nodeType: 'nodes-base.httpRequest'
} });
@@ -363,7 +364,7 @@ describe('MCP Performance Tests', () => {
const promises = [];
for (let i = 0; i < load; i++) {
promises.push(
client.callTool({ name: 'list_nodes', arguments: { limit: 1 } })
client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 1 } })
);
}
@@ -403,16 +404,16 @@ describe('MCP Performance Tests', () => {
const operation = i % 4;
switch (operation) {
case 0:
promises.push(client.callTool({ name: 'list_nodes', arguments: { limit: 5 } }));
promises.push(client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 5 } }));
break;
case 1:
promises.push(client.callTool({ name: 'search_nodes', arguments: { query: 'test' } }));
break;
case 2:
promises.push(client.callTool({ name: 'get_database_statistics', arguments: {} }));
promises.push(client.callTool({ name: 'tools_documentation', arguments: {} }));
break;
case 3:
promises.push(client.callTool({ name: 'list_ai_tools', arguments: {} }));
promises.push(client.callTool({ name: 'get_node', arguments: { nodeType: 'nodes-base.set' } }));
break;
}
}
@@ -431,10 +432,10 @@ describe('MCP Performance Tests', () => {
});
describe('Critical Path Optimization', () => {
it('should optimize tool listing performance', async () => {
it('should optimize search performance', async () => {
// Warm up with multiple calls to ensure everything is initialized
for (let i = 0; i < 5; i++) {
await client.callTool({ name: 'list_nodes', arguments: { limit: 1 } });
await client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 1 } });
}
const iterations = 100;
@@ -442,7 +443,7 @@ describe('MCP Performance Tests', () => {
for (let i = 0; i < iterations; i++) {
const start = performance.now();
await client.callTool({ name: 'list_nodes', arguments: { limit: 20 } });
await client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 20 } });
times.push(performance.now() - start);
}
@@ -454,7 +455,7 @@ describe('MCP Performance Tests', () => {
const minTime = Math.min(...trimmedTimes);
const maxTime = Math.max(...trimmedTimes);
console.log(`list_nodes performance - Avg: ${avgTime.toFixed(2)}ms, Min: ${minTime.toFixed(2)}ms, Max: ${maxTime.toFixed(2)}ms`);
console.log(`search_nodes performance - Avg: ${avgTime.toFixed(2)}ms, Min: ${minTime.toFixed(2)}ms, Max: ${maxTime.toFixed(2)}ms`);
console.log(`Environment: ${process.env.CI ? 'CI' : 'Local'}`);
// Environment-aware thresholds
@@ -467,7 +468,7 @@ describe('MCP Performance Tests', () => {
expect(maxTime).toBeLessThan(avgTime * maxMultiplier);
});
it('should optimize search performance', async () => {
it('should handle varied search queries efficiently', async () => {
// Warm up with multiple calls
for (let i = 0; i < 3; i++) {
await client.callTool({ name: 'search_nodes', arguments: { query: 'test' } });
@@ -542,7 +543,7 @@ describe('MCP Performance Tests', () => {
while (performance.now() - start < duration) {
try {
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
requestCount++;
} catch (error) {
errorCount++;
@@ -591,7 +592,7 @@ describe('MCP Performance Tests', () => {
const recoveryTimes: number[] = [];
for (let i = 0; i < 10; i++) {
const start = performance.now();
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
recoveryTimes.push(performance.now() - start);
}

View File

@@ -100,8 +100,8 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client.connect(clientTransport);
// Make some requests
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'list_nodes', arguments: { limit: 5 } });
await client.callTool({ name: 'tools_documentation', arguments: {} });
await client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 5 } });
// Clean termination
await client.close();
@@ -109,7 +109,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
// Client should be closed
try {
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
expect.fail('Should not be able to make requests after close');
} catch (error) {
expect(error).toBeDefined();
@@ -133,7 +133,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client.connect(clientTransport);
// Make a request to ensure connection is active
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
// Simulate abrupt disconnection by closing transport
await clientTransport.close();
@@ -141,7 +141,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
// Further operations should fail
try {
await client.callTool({ name: 'list_nodes', arguments: {} });
await client.callTool({ name: 'search_nodes', arguments: { query: 'http' } });
expect.fail('Should not be able to make requests after transport close');
} catch (error) {
expect(error).toBeDefined();
@@ -179,14 +179,14 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client1.connect(ct1);
// First session operations
const response1 = await client1.callTool({ name: 'list_nodes', arguments: { limit: 3 } });
const response1 = await client1.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 3 } });
expect(response1).toBeDefined();
expect((response1 as any).content).toBeDefined();
expect((response1 as any).content[0]).toHaveProperty('type', 'text');
const data1 = JSON.parse(((response1 as any).content[0] as any).text);
// Handle both array response and object with nodes property
const nodes1 = Array.isArray(data1) ? data1 : data1.nodes;
expect(nodes1).toHaveLength(3);
// Handle both array response and object with results property
const results1 = Array.isArray(data1) ? data1 : data1.results;
expect(results1.length).toBeLessThanOrEqual(3);
// Close first session completely
await client1.close();
@@ -204,14 +204,14 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client2.connect(ct2);
// Second session operations
const response2 = await client2.callTool({ name: 'list_nodes', arguments: { limit: 5 } });
const response2 = await client2.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 5 } });
expect(response2).toBeDefined();
expect((response2 as any).content).toBeDefined();
expect((response2 as any).content[0]).toHaveProperty('type', 'text');
const data2 = JSON.parse(((response2 as any).content[0] as any).text);
// Handle both array response and object with nodes property
const nodes2 = Array.isArray(data2) ? data2 : data2.nodes;
expect(nodes2).toHaveLength(5);
// Handle both array response and object with results property
const results2 = Array.isArray(data2) ? data2 : data2.results;
expect(results2.length).toBeLessThanOrEqual(5);
// Clean up
await client2.close();
@@ -228,7 +228,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
const client1 = new Client({ name: 'multi-seq-1', version: '1.0.0' }, {});
await client1.connect(ct1);
const resp1 = await client1.callTool({ name: 'get_database_statistics', arguments: {} });
const resp1 = await client1.callTool({ name: 'tools_documentation', arguments: {} });
expect(resp1).toBeDefined();
await client1.close();
@@ -240,7 +240,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
const client2 = new Client({ name: 'multi-seq-2', version: '1.0.0' }, {});
await client2.connect(ct2);
const resp2 = await client2.callTool({ name: 'get_database_statistics', arguments: {} });
const resp2 = await client2.callTool({ name: 'tools_documentation', arguments: {} });
expect(resp2).toBeDefined();
await client2.close();
@@ -261,7 +261,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client1.connect(ct1);
// Make some requests
await client1.callTool({ name: 'list_nodes', arguments: { limit: 10 } });
await client1.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 10 } });
await client1.close();
await mcpServer1.close();
@@ -276,7 +276,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client2.connect(ct2);
// Should work normally
const response = await client2.callTool({ name: 'get_database_statistics', arguments: {} });
const response = await client2.callTool({ name: 'tools_documentation', arguments: {} });
expect(response).toBeDefined();
await client2.close();
@@ -299,7 +299,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client.connect(clientTransport);
// Quick operation
const response = await client.callTool({ name: 'get_database_statistics', arguments: {} });
const response = await client.callTool({ name: 'tools_documentation', arguments: {} });
expect(response).toBeDefined();
// Explicit cleanup for each iteration
@@ -392,7 +392,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
// Light operation
if (i % 10 === 0) {
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
}
// Explicit cleanup
@@ -420,8 +420,8 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
const promises = [];
for (let i = 0; i < requestCount; i++) {
const toolName = i % 2 === 0 ? 'list_nodes' : 'get_database_statistics';
const params = toolName === 'list_nodes' ? { limit: 1 } : {};
const toolName = i % 2 === 0 ? 'search_nodes' : 'tools_documentation';
const params = toolName === 'search_nodes' ? { query: 'http', limit: 1 } : {};
promises.push(client.callTool({ name: toolName as any, arguments: params }));
}
@@ -460,7 +460,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
}
// Session should still be active
const response = await client.callTool({ name: 'get_database_statistics', arguments: {} });
const response = await client.callTool({ name: 'tools_documentation', arguments: {} });
expect(response).toBeDefined();
await client.close();
@@ -496,7 +496,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
});
// Session should still work
const response = await client.callTool({ name: 'list_nodes', arguments: { limit: 1 } });
const response = await client.callTool({ name: 'search_nodes', arguments: { query: 'http', limit: 1 } });
expect(response).toBeDefined();
await client.close();
@@ -539,7 +539,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
resources.clients.push(client);
// Make a request to ensure connection is active
await client.callTool({ name: 'get_database_statistics', arguments: {} });
await client.callTool({ name: 'tools_documentation', arguments: {} });
}
// Verify all resources are active
@@ -586,7 +586,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
// 3. Verify cleanup by attempting operations (should fail)
for (let i = 0; i < resources.clients.length; i++) {
try {
await resources.clients[i].callTool({ name: 'get_database_statistics', arguments: {} });
await resources.clients[i].callTool({ name: 'tools_documentation', arguments: {} });
expect.fail('Client should be closed');
} catch (error) {
// Expected - client is closed
@@ -645,7 +645,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
await client.connect(ct1);
// Initial request
const response1 = await client.callTool({ name: 'get_database_statistics', arguments: {} });
const response1 = await client.callTool({ name: 'tools_documentation', arguments: {} });
expect(response1).toBeDefined();
// Close first client
@@ -680,7 +680,7 @@ describe('MCP Session Management', { timeout: 15000 }, () => {
}, 3000);
try {
const response2 = await newClient.callTool({ name: 'get_database_statistics', arguments: {} });
const response2 = await newClient.callTool({ name: 'tools_documentation', arguments: {} });
clearTimeout(callTimeout);
expect(response2).toBeDefined();
} catch (error) {

View File

@@ -114,7 +114,7 @@ export class TestableN8NMCPServer {
// The MCP server initializes its database lazily
// We can trigger initialization by calling executeTool
try {
await this.mcpServer.executeTool('get_database_statistics', {});
await this.mcpServer.executeTool('tools_documentation', {});
} catch (error) {
// Ignore errors, we just want to trigger initialization
}

View File

@@ -30,66 +30,6 @@ describe('MCP Tool Invocation', () => {
});
describe('Node Discovery Tools', () => {
describe('list_nodes', () => {
it('should list nodes with default parameters', async () => {
const response = await client.callTool({ name: 'list_nodes', arguments: {} });
expect((response as any).content).toHaveLength(1);
expect((response as any).content[0].type).toBe('text');
const result = JSON.parse(((response as any).content[0]).text);
// The result is an object with nodes array and totalCount
expect(result).toHaveProperty('nodes');
expect(result).toHaveProperty('totalCount');
const nodes = result.nodes;
expect(Array.isArray(nodes)).toBe(true);
expect(nodes.length).toBeGreaterThan(0);
// Check node structure
const firstNode = nodes[0];
expect(firstNode).toHaveProperty('nodeType');
expect(firstNode).toHaveProperty('displayName');
expect(firstNode).toHaveProperty('category');
});
it('should filter nodes by category', async () => {
const response = await client.callTool({ name: 'list_nodes', arguments: {
category: 'trigger'
}});
const result = JSON.parse(((response as any).content[0]).text);
const nodes = result.nodes;
expect(nodes.length).toBeGreaterThan(0);
nodes.forEach((node: any) => {
expect(node.category).toBe('trigger');
});
});
it('should limit results', async () => {
const response = await client.callTool({ name: 'list_nodes', arguments: {
limit: 5
}});
const result = JSON.parse(((response as any).content[0]).text);
const nodes = result.nodes;
expect(nodes).toHaveLength(5);
});
it('should filter by package', async () => {
const response = await client.callTool({ name: 'list_nodes', arguments: {
package: 'n8n-nodes-base'
}});
const result = JSON.parse(((response as any).content[0]).text);
const nodes = result.nodes;
expect(nodes.length).toBeGreaterThan(0);
nodes.forEach((node: any) => {
expect(node.package).toBe('n8n-nodes-base');
});
});
});
describe('search_nodes', () => {
it('should search nodes by keyword', async () => {
const response = await client.callTool({ name: 'search_nodes', arguments: {
@@ -427,85 +367,8 @@ describe('MCP Tool Invocation', () => {
});
});
describe('AI Tools', () => {
describe('list_ai_tools', () => {
it('should list AI-capable nodes', async () => {
const response = await client.callTool({ name: 'list_ai_tools', arguments: {} });
const result = JSON.parse(((response as any).content[0]).text);
expect(result).toHaveProperty('tools');
const aiTools = result.tools;
expect(Array.isArray(aiTools)).toBe(true);
expect(aiTools.length).toBeGreaterThan(0);
// All should have nodeType and displayName
aiTools.forEach((tool: any) => {
expect(tool).toHaveProperty('nodeType');
expect(tool).toHaveProperty('displayName');
});
});
});
describe('get_node_as_tool_info', () => {
it('should provide AI tool usage information', async () => {
const response = await client.callTool({ name: 'get_node_as_tool_info', arguments: {
nodeType: 'nodes-base.slack'
}});
const info = JSON.parse(((response as any).content[0]).text);
expect(info).toHaveProperty('nodeType');
expect(info).toHaveProperty('isMarkedAsAITool');
expect(info).toHaveProperty('aiToolCapabilities');
expect(info.aiToolCapabilities).toHaveProperty('commonUseCases');
});
});
});
describe('Task Templates', () => {
// get_node_for_task was removed in v2.15.0
// Use search_nodes({ includeExamples: true }) instead for real-world examples
describe('list_tasks', () => {
it('should list all available tasks', async () => {
const response = await client.callTool({ name: 'list_tasks', arguments: {} });
const result = JSON.parse(((response as any).content[0]).text);
expect(result).toHaveProperty('totalTasks');
expect(result).toHaveProperty('categories');
expect(result.totalTasks).toBeGreaterThan(0);
// Check categories structure
const categories = result.categories;
expect(typeof categories).toBe('object');
// Check at least one category has tasks
const hasTasksInCategories = Object.values(categories).some((tasks: any) =>
Array.isArray(tasks) && tasks.length > 0
);
expect(hasTasksInCategories).toBe(true);
});
it('should filter by category', async () => {
const response = await client.callTool({ name: 'list_tasks', arguments: {
category: 'HTTP/API'
}});
const result = JSON.parse(((response as any).content[0]).text);
expect(result).toHaveProperty('category', 'HTTP/API');
expect(result).toHaveProperty('tasks');
const httpTasks = result.tasks;
expect(Array.isArray(httpTasks)).toBe(true);
expect(httpTasks.length).toBeGreaterThan(0);
httpTasks.forEach((task: any) => {
expect(task).toHaveProperty('task');
expect(task).toHaveProperty('description');
expect(task).toHaveProperty('nodeType');
});
});
});
});
// AI Tools section removed - list_ai_tools and get_node_as_tool_info were removed in v2.25.0
// Use search_nodes with query for finding AI-capable nodes
describe('Complex Tool Interactions', () => {
it('should handle tool chaining', async () => {
@@ -526,20 +389,20 @@ describe('MCP Tool Invocation', () => {
});
it('should handle parallel tool calls', async () => {
const tools = [
'list_nodes',
'get_database_statistics',
'list_ai_tools',
'list_tasks'
const toolCalls = [
{ name: 'search_nodes', arguments: { query: 'http' } },
{ name: 'tools_documentation', arguments: {} },
{ name: 'get_node', arguments: { nodeType: 'nodes-base.httpRequest' } },
{ name: 'search_nodes', arguments: { query: 'webhook' } }
];
const promises = tools.map(tool =>
client.callTool({ name: tool as any, arguments: {} })
const promises = toolCalls.map(call =>
client.callTool(call)
);
const responses = await Promise.all(promises);
expect(responses).toHaveLength(tools.length);
expect(responses).toHaveLength(toolCalls.length);
responses.forEach(response => {
expect(response.content).toHaveLength(1);
expect(((response as any).content[0]).type).toBe('text');

View File

@@ -500,15 +500,15 @@ describe.skip('MCP Telemetry Integration', () => {
const slowToolRequest: CallToolRequest = {
method: 'tools/call',
params: {
name: 'list_nodes',
arguments: { limit: 1000 }
name: 'search_nodes',
arguments: { query: 'http', limit: 1000 }
}
};
// Mock a slow operation
vi.spyOn(mcpServer as any, 'executeTool').mockImplementation(async () => {
await new Promise(resolve => setTimeout(resolve, 2000)); // 2 second delay
return { nodes: [], totalCount: 0 };
return { results: [], totalCount: 0 };
});
const server = (mcpServer as any).server;
@@ -519,7 +519,7 @@ describe.skip('MCP Telemetry Integration', () => {
}
expect(telemetry.trackToolUsage).toHaveBeenCalledWith(
'list_nodes',
'search_nodes',
true,
expect.any(Number)
);

View File

@@ -73,14 +73,14 @@ describe('Disabled Tools Feature (Issue #410)', () => {
});
it('should parse multiple disabled tools correctly', () => {
process.env.DISABLED_TOOLS = 'n8n_diagnostic,n8n_health_check,list_nodes';
process.env.DISABLED_TOOLS = 'n8n_diagnostic,n8n_health_check,search_nodes';
server = new TestableN8NMCPServer();
const disabledTools = server.testGetDisabledTools();
expect(disabledTools.size).toBe(3);
expect(disabledTools.has('n8n_diagnostic')).toBe(true);
expect(disabledTools.has('n8n_health_check')).toBe(true);
expect(disabledTools.has('list_nodes')).toBe(true);
expect(disabledTools.has('search_nodes')).toBe(true);
});
it('should trim whitespace from tool names', () => {
@@ -94,14 +94,14 @@ describe('Disabled Tools Feature (Issue #410)', () => {
});
it('should filter out empty entries from comma-separated list', () => {
process.env.DISABLED_TOOLS = 'n8n_diagnostic,,n8n_health_check,,,list_nodes';
process.env.DISABLED_TOOLS = 'n8n_diagnostic,,n8n_health_check,,,search_nodes';
server = new TestableN8NMCPServer();
const disabledTools = server.testGetDisabledTools();
expect(disabledTools.size).toBe(3);
expect(disabledTools.has('n8n_diagnostic')).toBe(true);
expect(disabledTools.has('n8n_health_check')).toBe(true);
expect(disabledTools.has('list_nodes')).toBe(true);
expect(disabledTools.has('search_nodes')).toBe(true);
});
it('should handle single comma correctly', () => {

View File

@@ -49,7 +49,7 @@ vi.mock('@/mcp/tool-docs', () => ({
performance: 'Instant - uses in-memory index',
bestPractices: ['Start with single words', 'Use FUZZY for uncertain names'],
pitfalls: ['Overly specific queries may return no results'],
relatedTools: ['list_nodes', 'get_node_info']
relatedTools: ['get_node', 'get_node_documentation']
}
},
validate_workflow: {
@@ -172,7 +172,7 @@ describe('tools-documentation', () => {
expect(doc).toContain('## Common Pitfalls');
expect(doc).toContain('- Overly specific queries');
expect(doc).toContain('## Related Tools');
expect(doc).toContain('- list_nodes');
expect(doc).toContain('- get_node');
});
});