Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
ncp-orchestrator.test.ts16.8 kB
/** * NCPOrchestrator tests - Core functionality testing */ import { describe, it, expect, beforeEach, jest } from '@jest/globals'; import { NCPOrchestrator } from '../src/orchestrator/ncp-orchestrator.js'; describe('NCPOrchestrator - Basic Tests', () => { let orchestrator: NCPOrchestrator; beforeEach(() => { jest.clearAllMocks(); orchestrator = new NCPOrchestrator('test'); }); describe('instantiation', () => { it('should create orchestrator with profile name', () => { expect(orchestrator).toBeDefined(); }); it('should create orchestrator with default profile', () => { const defaultOrchestrator = new NCPOrchestrator(); expect(defaultOrchestrator).toBeDefined(); }); }); describe('basic api calls', () => { it('should have find method', () => { expect(typeof orchestrator.find).toBe('function'); }); it('should have run method', () => { expect(typeof orchestrator.run).toBe('function'); }); it('should handle find with empty query', async () => { await orchestrator.initialize(); const result = await orchestrator.find(''); expect(Array.isArray(result)).toBe(true); }); it('should handle find with query', async () => { await orchestrator.initialize(); const result = await orchestrator.find('test'); expect(Array.isArray(result)).toBe(true); }); it('should handle run with non-existent tool', async () => { await orchestrator.initialize(); const result = await orchestrator.run('nonexistent:tool', {}); expect(result).toHaveProperty('success'); expect(result.success).toBe(false); }); }); describe('initialization', () => { it('should initialize without throwing', async () => { await expect(orchestrator.initialize()).resolves.not.toThrow(); }); it('should be able to initialize multiple times', async () => { await orchestrator.initialize(); await expect(orchestrator.initialize()).resolves.not.toThrow(); }); }); describe('cleanup', () => { it('should cleanup without throwing', async () => { await expect(orchestrator.cleanup()).resolves.not.toThrow(); }); it('should cleanup after initialization', async () => { await orchestrator.initialize(); await expect(orchestrator.cleanup()).resolves.not.toThrow(); }); }); describe('error scenarios', () => { it('should handle run method with invalid tool format', async () => { await orchestrator.initialize(); // Tool name without MCP prefix const result = await orchestrator.run('invalidtool', {}); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('should handle find with very long query', async () => { await orchestrator.initialize(); const longQuery = 'a'.repeat(1000); const result = await orchestrator.find(longQuery); expect(Array.isArray(result)).toBe(true); }); it('should handle find with special characters', async () => { await orchestrator.initialize(); const result = await orchestrator.find('!@#$%^&*()'); expect(Array.isArray(result)).toBe(true); }); }); describe('advanced find scenarios', () => { it('should return all tools when query is empty', async () => { await orchestrator.initialize(); const result = await orchestrator.find(''); expect(Array.isArray(result)).toBe(true); }); it('should respect limit parameter', async () => { await orchestrator.initialize(); const result = await orchestrator.find('', 3); expect(Array.isArray(result)).toBe(true); expect(result.length).toBeLessThanOrEqual(3); }); it('should handle detailed flag for schema information', async () => { await orchestrator.initialize(); const result = await orchestrator.find('test', 5, true); expect(Array.isArray(result)).toBe(true); }); it('should handle vector search fallback', async () => { await orchestrator.initialize(); const result = await orchestrator.find('complex search query that uses vector search'); expect(Array.isArray(result)).toBe(true); }); it('should handle tool name extraction', async () => { await orchestrator.initialize(); const result = await orchestrator.find('', 10); expect(Array.isArray(result)).toBe(true); }); }); describe('run method advanced scenarios', () => { it('should handle tool execution with parameters', async () => { await orchestrator.initialize(); const result = await orchestrator.run('test:tool', { param1: 'value1' }); expect(result).toHaveProperty('success'); expect(typeof result.success).toBe('boolean'); }); it('should validate required parameters before execution', async () => { // This test validates that the parameter validation method exists and works const testOrchestrator = new (orchestrator.constructor as any)('test'); // Test the validation method with a mock schema const mockSchema = { type: 'object', properties: { required_param: { type: 'string', description: 'Required parameter' }, optional_param: { type: 'string', description: 'Optional parameter' } }, required: ['required_param'] }; // Mock getToolSchema to return our test schema jest.spyOn(testOrchestrator, 'getToolSchema' as any).mockReturnValue(mockSchema); // Test validation with missing required parameter const validationError = (testOrchestrator as any).validateToolParameters('test-mcp', 'test_tool', {}); expect(validationError).toContain('Missing required parameters: required_param'); // Test validation with valid parameters const validationSuccess = (testOrchestrator as any).validateToolParameters('test-mcp', 'test_tool', { required_param: 'value' }); expect(validationSuccess).toBeNull(); // Test validation with null parameters const validationNull = (testOrchestrator as any).validateToolParameters('test-mcp', 'test_tool', null); expect(validationNull).toContain('Missing required parameters: required_param'); }); it('should handle MCP name resolution from tool name', async () => { await orchestrator.initialize(); // Test tool-to-MCP mapping logic const result = await orchestrator.run('filesystem:read', { path: '/test' }); expect(result).toHaveProperty('success'); }); it('should handle connection establishment', async () => { await orchestrator.initialize(); // This should test connection logic paths const result = await orchestrator.run('memory:store', { key: 'test', value: 'data' }); expect(result).toHaveProperty('success'); }); }); describe('MCP connection and execution scenarios', () => { it('should execute tool with valid MCP connection', async () => { await orchestrator.initialize(); // This should trigger the connection logic const result = await orchestrator.run('memory:store', { key: 'test', value: 'data' }); // Should attempt connection even if it fails in test environment expect(result).toHaveProperty('success'); expect(typeof result.success).toBe('boolean'); }); it('should handle MCP not configured error', async () => { await orchestrator.initialize(); // This should hit the "MCP not configured" path const result = await orchestrator.run('nonexistent:tool', {}); expect(result.success).toBe(false); expect(result.error).toContain('not found'); }); it('should handle connection errors and mark MCP unhealthy', async () => { await orchestrator.initialize(); // This should trigger connection attempt and failure const result = await orchestrator.run('failing:test', {}); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('should handle multiple initialization calls', async () => { await orchestrator.initialize(); await orchestrator.initialize(); await orchestrator.initialize(); // Should not crash with multiple inits expect(orchestrator).toBeDefined(); }); it('should handle cleanup with connections', async () => { await orchestrator.initialize(); // Attempt to create some state that needs cleanup await orchestrator.find('test'); await orchestrator.run('test:tool', {}); await orchestrator.cleanup(); expect(orchestrator).toBeDefined(); }); }); describe('resource management', () => { it('should get all resources from MCPs', async () => { await orchestrator.initialize(); // Mock the getAllResources method behavior to avoid integration complexity const mockGetAllResources = jest.spyOn(orchestrator, 'getAllResources').mockResolvedValue([ { uri: 'file:///tmp/test.txt', name: 'Test File', mimeType: 'text/plain' }, { uri: 'memory://cache/item1', name: 'Cache Item', mimeType: 'application/json' } ]); const resources = await orchestrator.getAllResources(); expect(Array.isArray(resources)).toBe(true); expect(resources).toHaveLength(2); expect(resources[0].uri).toBe('file:///tmp/test.txt'); expect(mockGetAllResources).toHaveBeenCalled(); }); it('should handle resource retrieval errors gracefully', async () => { await orchestrator.initialize(); // Mock getAllResources to simulate error handling const mockGetAllResourcesError = jest.spyOn(orchestrator, 'getAllResources').mockResolvedValue([]); // This should handle resource retrieval errors gracefully const resources = await orchestrator.getAllResources(); expect(Array.isArray(resources)).toBe(true); expect(resources.length).toBe(0); // Should be empty due to errors expect(mockGetAllResourcesError).toHaveBeenCalled(); }); }); describe('schema operations', () => { it('should retrieve tool schema from definitions when no connection exists', async () => { await orchestrator.initialize(); // This should trigger getToolSchema method and find schemas const result = await orchestrator.find('test:tool', 5, true); expect(Array.isArray(result)).toBe(true); // The detailed flag should trigger schema retrieval }); }); describe('advanced MCP operations', () => { beforeEach(async () => { await orchestrator.initialize(); }); it('should get prompts from MCP servers', async () => { // This should trigger getPromptsFromMCP method try { await (orchestrator as any).getPromptsFromMCP('test-prompts'); } catch (error) { // Expected to fail in test environment, but should exercise the code path expect(error).toBeDefined(); } }); it('should handle MCP server connection timeouts for prompts', async () => { // Test prompt connection timeout handling try { await (orchestrator as any).getPromptsFromMCP('memory'); } catch (error) { // Should handle connection timeouts gracefully expect(error).toBeDefined(); } }); it('should get resources from MCP servers with error handling', async () => { // This should trigger getResourcesFromMCP method and error paths try { await (orchestrator as any).getResourcesFromMCP('filesystem'); } catch (error) { // Expected to fail but should test the resource retrieval path expect(error).toBeDefined(); } }); it('should handle resource connection errors gracefully', async () => { // Test resource connection error handling try { await (orchestrator as any).getResourcesFromMCP('nonexistent-mcp'); } catch (error) { expect(error).toBeDefined(); } }); it('should handle cache save failures', async () => { // Test cache save error handling try { await (orchestrator as any).saveToolsCache(); } catch (error) { // Should handle cache save errors gracefully or succeed expect(true).toBe(true); } }); it('should test tool schema retrieval with connections', async () => { // Test getToolSchema with existing connections const result = (orchestrator as any).getToolSchema('memory', 'memory:store'); expect(result === undefined || typeof result === 'object').toBe(true); }); it('should test tool schema retrieval without connections', async () => { // Test getToolSchema without connections const result = (orchestrator as any).getToolSchema('nonexistent', 'test:tool'); expect(result).toBeUndefined(); }); it('should handle MCP server environment variable configuration', async () => { const newOrchestrator = new NCPOrchestrator('env-test'); await newOrchestrator.initialize(); // Should handle custom environment variables expect(newOrchestrator).toBeDefined(); }); it('should test MCP health monitoring integration', async () => { // Test health monitor integration with MCP operations const result = await orchestrator.run('nonexistent:tool', {}); expect(result.success).toBe(false); // Should mark MCP as unhealthy expect(result.error).toBeDefined(); }); it('should handle quick probe timeouts', async () => { // Test QUICK_PROBE_TIMEOUT handling in resource/prompt probes try { await (orchestrator as any).getResourcesFromMCP('slow-mcp'); } catch (error) { // Should timeout quickly for probe operations expect(error).toBeDefined(); } }); it('should test connection pool management', async () => { // Test connection reuse and pool management await orchestrator.run('memory:store', { key: 'test1', value: 'data1' }); await orchestrator.run('memory:store', { key: 'test2', value: 'data2' }); // Should reuse connections efficiently expect(true).toBe(true); // Tests connection management paths }); it('should handle wrapper script creation errors', async () => { const errorOrchestrator = new NCPOrchestrator('wrapper-error'); await errorOrchestrator.initialize(); const result = await errorOrchestrator.run('error-mcp:test', {}); expect(result.success).toBe(false); }); }); describe('connection lifecycle and cleanup', () => { beforeEach(async () => { await orchestrator.initialize(); }); it('should cleanup idle connections', async () => { // Test cleanupIdleConnections method try { await (orchestrator as any).cleanupIdleConnections(); } catch (error) { // Should handle cleanup gracefully } expect(true).toBe(true); }); it('should disconnect specific MCP', async () => { // Test disconnectMCP method try { await (orchestrator as any).disconnectMCP('lifecycle-test'); } catch (error) { // Should handle disconnect gracefully } expect(true).toBe(true); }); it('should handle disconnect errors gracefully', async () => { // Test error handling in disconnectMCP try { await (orchestrator as any).disconnectMCP('nonexistent-connection'); } catch (error) { // Should handle nonexistent connections gracefully } expect(true).toBe(true); }); it('should manage connection idle timeouts', async () => { // Test idle time calculation and connection management const mockConnection = { client: { close: jest.fn() }, transport: {}, tools: [], lastUsed: Date.now() - 100000, // Old timestamp to trigger cleanup connectTime: 1000, executionCount: 1 }; // Simulate idle connection (orchestrator as any).connections.set('idle-test', mockConnection); try { await (orchestrator as any).cleanupIdleConnections(); } catch (error) { // Should handle cleanup process } expect(true).toBe(true); }); }); describe('discovery engine integration', () => { it('should handle discovery engine indexing during initialization', async () => { await orchestrator.initialize(); // Verify discovery engine stats are accessible const discoveryStats = (orchestrator as any).discovery.getStats(); expect(discoveryStats).toBeDefined(); // Test tool discovery functionality const tools = await orchestrator.find('test', 5); expect(Array.isArray(tools)).toBe(true); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/portel-dev/ncp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server