Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
performance-benchmark.test.ts12 kB
/** * Performance Benchmark Tests * Demonstrates the performance improvements from cache optimization */ import { CachePatcher } from '../src/cache/cache-patcher.js'; import { NCPOrchestrator } from '../src/orchestrator/ncp-orchestrator.js'; import { existsSync, rmSync, mkdirSync, writeFileSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; // Mock profile data for testing const createMockProfile = (mcpCount: number = 10) => { const profile = { name: 'test-profile', description: 'Test profile for benchmarking', mcpServers: {} as any, metadata: { created: new Date().toISOString(), modified: new Date().toISOString() } }; // Add multiple MCPs to simulate real-world usage for (let i = 1; i <= mcpCount; i++) { profile.mcpServers[`test-mcp-${i}`] = { command: 'echo', args: [`MCP ${i} simulation`], env: {} }; } return profile; }; // Mock tools for each MCP const createMockTools = (mcpName: string, toolCount: number = 50) => { const tools = []; for (let i = 1; i <= toolCount; i++) { tools.push({ name: `tool_${i}`, description: `Tool ${i} for ${mcpName} - performs operation ${i}`, inputSchema: { type: 'object', properties: { input: { type: 'string' } } } }); } return tools; }; describe('Performance Benchmarks', () => { let tempDir: string; let tempProfilesDir: string; let tempCacheDir: string; beforeEach(() => { // Create temporary directories tempDir = join(tmpdir(), 'ncp-perf-test-' + Date.now()); tempProfilesDir = join(tempDir, 'profiles'); tempCacheDir = join(tempDir, 'cache'); mkdirSync(tempDir, { recursive: true }); mkdirSync(tempProfilesDir, { recursive: true }); mkdirSync(tempCacheDir, { recursive: true }); }); afterEach(() => { // Clean up if (existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); } }); describe('Cache Operations Performance', () => { test('should demonstrate fast cache patching vs full rebuild', async () => { // Create a custom cache patcher for testing class TestCachePatcher extends CachePatcher { constructor() { super(); this['cacheDir'] = tempCacheDir; this['toolMetadataCachePath'] = join(tempCacheDir, 'all-tools.json'); this['embeddingsCachePath'] = join(tempCacheDir, 'embeddings.json'); } } const cachePatcher = new TestCachePatcher(); const profile = createMockProfile(5); // 5 MCPs with 50 tools each console.log('\\n📊 Performance Benchmark: Cache Operations'); console.log('=' .repeat(60)); // Benchmark: Adding MCPs one by one (incremental patching) const incrementalStart = process.hrtime.bigint(); for (const [mcpName, config] of Object.entries(profile.mcpServers)) { const tools = createMockTools(mcpName); const serverInfo = { name: mcpName, version: '1.0.0' }; await cachePatcher.patchAddMCP(mcpName, config as any, tools, serverInfo); } const incrementalEnd = process.hrtime.bigint(); const incrementalTime = Number(incrementalEnd - incrementalStart) / 1_000_000; // Convert to ms // Update profile hash const profileHash = cachePatcher.generateProfileHash(profile); await cachePatcher.updateProfileHash(profileHash); // Get cache statistics const stats = await cachePatcher.getCacheStats(); console.log(`✅ Incremental cache building: ${incrementalTime.toFixed(2)}ms`); console.log(` • MCPs processed: ${stats.mcpCount}`); console.log(` • Tools cached: ${stats.toolCount}`); console.log(` • Average time per MCP: ${(incrementalTime / stats.mcpCount).toFixed(2)}ms`); // Benchmark: Cache validation (startup simulation) const validationStart = process.hrtime.bigint(); const isValid = await cachePatcher.validateCacheWithProfile(profileHash); const validationEnd = process.hrtime.bigint(); const validationTime = Number(validationEnd - validationStart) / 1_000_000; console.log(`⚡ Cache validation: ${validationTime.toFixed(2)}ms`); console.log(` • Cache valid: ${isValid}`); // Performance assertions expect(incrementalTime).toBeLessThan(1000); // Should complete in under 1 second expect(validationTime).toBeLessThan(50); // Should validate in under 50ms expect(isValid).toBe(true); expect(stats.mcpCount).toBe(5); expect(stats.toolCount).toBe(250); // 5 MCPs × 50 tools each }, 10000); // 10 second timeout test('should demonstrate cache removal performance', async () => { class TestCachePatcher extends CachePatcher { constructor() { super(); this['cacheDir'] = tempCacheDir; this['toolMetadataCachePath'] = join(tempCacheDir, 'all-tools.json'); this['embeddingsCachePath'] = join(tempCacheDir, 'embeddings.json'); } } const cachePatcher = new TestCachePatcher(); // Pre-populate cache with test data for (let i = 1; i <= 3; i++) { const mcpName = `test-mcp-${i}`; const config = { command: 'echo', args: ['test'] }; const tools = createMockTools(mcpName, 20); await cachePatcher.patchAddMCP(mcpName, config, tools, {}); } console.log('\\n🗑️ Performance Benchmark: Cache Removal'); console.log('=' .repeat(60)); const removalStart = process.hrtime.bigint(); // Remove an MCP from cache await cachePatcher.patchRemoveMCP('test-mcp-2'); await cachePatcher.patchRemoveEmbeddings('test-mcp-2'); const removalEnd = process.hrtime.bigint(); const removalTime = Number(removalEnd - removalStart) / 1_000_000; const stats = await cachePatcher.getCacheStats(); console.log(`🔧 MCP removal: ${removalTime.toFixed(2)}ms`); console.log(` • Remaining MCPs: ${stats.mcpCount}`); console.log(` • Remaining tools: ${stats.toolCount}`); expect(removalTime).toBeLessThan(100); // Should complete in under 100ms expect(stats.mcpCount).toBe(2); // Should have 2 MCPs left expect(stats.toolCount).toBe(40); // Should have 40 tools left (2 MCPs × 20 tools) }, 5000); }); describe('Memory Usage Optimization', () => { test('should demonstrate efficient memory usage with cache', async () => { class TestCachePatcher extends CachePatcher { constructor() { super(); this['cacheDir'] = tempCacheDir; this['toolMetadataCachePath'] = join(tempCacheDir, 'all-tools.json'); } } const cachePatcher = new TestCachePatcher(); // Measure initial memory const initialMemory = process.memoryUsage(); // Add a realistic number of MCPs and tools const mcpCount = 10; const toolsPerMCP = 100; for (let i = 1; i <= mcpCount; i++) { const mcpName = `memory-test-mcp-${i}`; const config = { command: 'echo', args: ['test'] }; const tools = createMockTools(mcpName, toolsPerMCP); await cachePatcher.patchAddMCP(mcpName, config, tools, {}); } // Measure memory after caching const finalMemory = process.memoryUsage(); const memoryDiff = finalMemory.heapUsed - initialMemory.heapUsed; const totalTools = mcpCount * toolsPerMCP; console.log('\\n🧠 Memory Usage Analysis'); console.log('=' .repeat(60)); console.log(`📊 Total tools cached: ${totalTools}`); console.log(`📈 Memory increase: ${(memoryDiff / 1024 / 1024).toFixed(2)} MB`); console.log(`⚖️ Memory per tool: ${(memoryDiff / totalTools).toFixed(0)} bytes`); // Memory should be reasonable (less than 50MB for 1000 tools) expect(memoryDiff).toBeLessThan(50 * 1024 * 1024); // Less than 50MB expect(memoryDiff / totalTools).toBeLessThan(12288); // Less than 12KB per tool (CI-friendly threshold) }, 10000); }); describe('Startup Time Simulation', () => { test('should demonstrate optimized vs legacy startup times', async () => { // This test simulates the performance difference between optimized and legacy startup const profile = createMockProfile(8); // 8 MCPs const profileHash = 'test-startup-hash'; console.log('\\n🚀 Startup Performance Simulation'); console.log('=' .repeat(60)); // Simulate optimized startup (cache hit) const optimizedStart = process.hrtime.bigint(); // Fast operations that optimized startup would do: // 1. Profile hash validation const hashValidation = process.hrtime.bigint(); // Hash generation is very fast - use simple string hash for test const testHash = JSON.stringify(profile.mcpServers).split('').reduce( (hash, char) => ((hash << 5) - hash + char.charCodeAt(0)) | 0, 0 ).toString(16); const hashTime = Number(process.hrtime.bigint() - hashValidation) / 1_000_000; // 2. Cache loading simulation (just file I/O) const cacheLoadStart = process.hrtime.bigint(); // Simulate loading cached data const mockCacheData = { version: '1.0.0', profileHash: testHash, mcps: {} as any }; // Simulate processing cached MCPs (no network calls) for (let i = 0; i < 8; i++) { const tools = createMockTools(`mcp-${i}`, 50); mockCacheData.mcps[`mcp-${i}`] = { tools, serverInfo: { name: `mcp-${i}`, version: '1.0.0' } }; } const cacheLoadTime = Number(process.hrtime.bigint() - cacheLoadStart) / 1_000_000; const optimizedTotal = Number(process.hrtime.bigint() - optimizedStart) / 1_000_000; // Simulate legacy startup (cache miss - would need to probe all MCPs) const legacyStart = process.hrtime.bigint(); // Legacy startup would need to: // 1. Probe each MCP server (simulated network delay) let totalProbeTime = 0; for (let i = 0; i < 8; i++) { const probeStart = process.hrtime.bigint(); // Simulate MCP probing (even with 100ms timeout per MCP) await new Promise(resolve => setTimeout(resolve, 50)); // 50ms per MCP totalProbeTime += Number(process.hrtime.bigint() - probeStart) / 1_000_000; } // 2. Index all tools (simulation) const indexingStart = process.hrtime.bigint(); // Simulate tool indexing overhead await new Promise(resolve => setTimeout(resolve, 100)); // 100ms indexing const indexingTime = Number(process.hrtime.bigint() - indexingStart) / 1_000_000; const legacyTotal = Number(process.hrtime.bigint() - legacyStart) / 1_000_000; console.log('⚡ Optimized startup (cache hit):'); console.log(` • Profile hash validation: ${hashTime.toFixed(2)}ms`); console.log(` • Cache loading: ${cacheLoadTime.toFixed(2)}ms`); console.log(` • Total time: ${optimizedTotal.toFixed(2)}ms`); console.log(''); console.log('🐌 Legacy startup (cache miss):'); console.log(` • MCP probing: ${totalProbeTime.toFixed(2)}ms`); console.log(` • Tool indexing: ${indexingTime.toFixed(2)}ms`); console.log(` • Total time: ${legacyTotal.toFixed(2)}ms`); console.log(''); console.log(`🎯 Performance improvement: ${(legacyTotal / optimizedTotal).toFixed(1)}x faster`); console.log(`💾 Time saved: ${(legacyTotal - optimizedTotal).toFixed(2)}ms`); // Performance assertions based on PRD targets expect(optimizedTotal).toBeLessThan(250); // Target: 250ms startup expect(legacyTotal).toBeGreaterThan(400); // Legacy would be much slower expect(legacyTotal / optimizedTotal).toBeGreaterThan(2); // At least 2x improvement }, 15000); // 15 second timeout for this test }); });

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