Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
health-integration.test.ts8.88 kB
/** * Tests for health monitoring integration * Focuses on the integration patterns rather than full CLI flows */ import { MCPHealthMonitor } from '../src/utils/health-monitor.js'; import { jest } from '@jest/globals'; import { tmpdir } from 'os'; import { join } from 'path'; import { mkdirSync, rmSync } from 'fs'; describe('Health Monitoring Integration', () => { let healthMonitor: MCPHealthMonitor; let tempDir: string; beforeEach(() => { // Create temporary directory for test tempDir = join(tmpdir(), `ncp-health-test-${Date.now()}`); mkdirSync(tempDir, { recursive: true }); // Create health monitor without mocking (uses real home directory) healthMonitor = new MCPHealthMonitor(); }); afterEach(() => { // Clean up temp directory try { rmSync(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } jest.restoreAllMocks(); }); describe('Error Message Capture', () => { test('should capture npm 404 errors for non-existent packages', async () => { const health = await healthMonitor.checkMCPHealth( 'test-invalid-package', 'npx', ['-y', '@definitely-does-not-exist/invalid-package'] ); // Health check might timeout before npm error, so check for either unhealthy or error message if (health.status === 'unhealthy') { expect(health.lastError).toBeDefined(); if (health.lastError) { // Should contain npm error details expect(health.lastError.toLowerCase()).toMatch(/404|not found|error|timeout/); } } else { // If healthy, it means npm command didn't fail within timeout - this is also acceptable expect(health.status).toBe('healthy'); } }); test('should capture command not found errors', async () => { const health = await healthMonitor.checkMCPHealth( 'test-invalid-command', 'definitely-not-a-real-command', [] ); expect(health.status).toBe('unhealthy'); expect(health.lastError).toBeDefined(); if (health.lastError) { // Should contain command not found error expect(health.lastError.toLowerCase()).toMatch(/not found|enoent|spawn/); } }); test('should capture permission errors', async () => { const health = await healthMonitor.checkMCPHealth( 'test-permission-error', '/root/some-protected-file', [] ); expect(health.status).toBe('unhealthy'); expect(health.lastError).toBeDefined(); if (health.lastError) { // Should contain permission-related error expect(health.lastError.toLowerCase()).toMatch(/permission|eacces|enoent/); } }); test('should handle timeout scenarios', async () => { // This test verifies timeout handling const health = await healthMonitor.checkMCPHealth( 'test-timeout', 'sleep', ['10'], // Sleep longer than health check timeout {} ); // Should either be unhealthy due to timeout or healthy if sleep exits quickly expect(['healthy', 'unhealthy']).toContain(health.status); if (health.status === 'unhealthy' && health.lastError) { // If unhealthy, should have a meaningful error expect(health.lastError).toBeDefined(); } }); }); describe('Health Status Tracking', () => { test('should track error count and auto-disable after multiple failures', async () => { const mcpName = 'test-multi-failure'; // First failure healthMonitor.markUnhealthy(mcpName, 'Error 1'); let health = healthMonitor.getMCPHealth(mcpName); expect(health?.errorCount).toBe(1); expect(health?.status).toBe('unhealthy'); // Second failure healthMonitor.markUnhealthy(mcpName, 'Error 2'); health = healthMonitor.getMCPHealth(mcpName); expect(health?.errorCount).toBe(2); expect(health?.status).toBe('unhealthy'); // Third failure should disable healthMonitor.markUnhealthy(mcpName, 'Error 3'); health = healthMonitor.getMCPHealth(mcpName); expect(health?.errorCount).toBe(3); expect(health?.status).toBe('disabled'); }); test('should reset error count when MCP becomes healthy', async () => { const mcpName = 'test-recovery'; // Mark as unhealthy healthMonitor.markUnhealthy(mcpName, 'Temporary error'); let health = healthMonitor.getMCPHealth(mcpName); expect(health?.errorCount).toBe(1); // Mark as healthy should reset healthMonitor.markHealthy(mcpName); health = healthMonitor.getMCPHealth(mcpName); expect(health?.errorCount).toBe(0); expect(health?.status).toBe('healthy'); }); test('should provide detailed health reports for AI consumption', async () => { // Set up various MCP states healthMonitor.markHealthy('working-mcp'); healthMonitor.markUnhealthy('broken-mcp', 'npm error 404'); healthMonitor.markUnhealthy('timeout-mcp', 'Connection timeout'); // Disable one MCP await healthMonitor.disableMCP('disabled-mcp', 'User disabled'); const report = healthMonitor.generateHealthReport(); expect(report.totalMCPs).toBeGreaterThan(0); expect(report.details).toBeDefined(); expect(Array.isArray(report.details)).toBe(true); expect(report.recommendations).toBeDefined(); expect(Array.isArray(report.recommendations)).toBe(true); // Should have appropriate counts expect(report.healthy + report.unhealthy + report.disabled).toBe(report.totalMCPs); }); }); describe('Error Message Quality for AI', () => { test('should provide actionable recommendations based on error patterns', async () => { // Test different error patterns const testCases = [ { error: 'npm error code E404', expectedRecommendation: /install|package|npm/i }, { error: 'EACCES: permission denied', expectedRecommendation: /permission/i }, { error: 'ENOENT: no such file or directory', expectedRecommendation: /file|directory|path/i }, { error: 'command not found: nonexistent', expectedRecommendation: /command|install|path/i } ]; for (const testCase of testCases) { healthMonitor.markUnhealthy('test-mcp', testCase.error); const report = healthMonitor.generateHealthReport(); // Should generate relevant recommendations const hasRelevantRecommendation = report.recommendations?.some(rec => testCase.expectedRecommendation.test(rec) ); expect(hasRelevantRecommendation).toBe(true); } }); test('should maintain error history for debugging', async () => { const mcpName = 'history-test'; const errorMessage = 'Detailed error for debugging'; healthMonitor.markUnhealthy(mcpName, errorMessage); const health = healthMonitor.getMCPHealth(mcpName); expect(health?.lastError).toBe(errorMessage); expect(health?.lastCheck).toBeDefined(); expect(new Date(health!.lastCheck).getTime()).toBeCloseTo(Date.now(), -3); // Within ~1 second }); }); describe('Integration with Import Process', () => { test('should handle batch health checks efficiently', async () => { const mcpConfigs = [ { name: 'echo-test', command: 'echo', args: ['test1'] }, { name: 'invalid-test', command: 'nonexistent-command', args: [] }, { name: 'npm-test', command: 'npx', args: ['-y', '@invalid/package'] } ]; const startTime = Date.now(); const report = await healthMonitor.checkMultipleMCPs(mcpConfigs); const endTime = Date.now(); // Should complete in reasonable time expect(endTime - startTime).toBeLessThan(30000); // 30 seconds max expect(report.totalMCPs).toBe(3); expect(report.details).toHaveLength(3); // Should have a mix of results expect(report.healthy + report.unhealthy + report.disabled).toBe(3); }); test('should provide structured data for import feedback', async () => { const mcpName = 'structured-test'; const health = await healthMonitor.checkMCPHealth( mcpName, 'nonexistent-command', [] ); // Should have all required fields for import feedback expect(health.name).toBe(mcpName); expect(health.status).toBeDefined(); expect(health.lastCheck).toBeDefined(); expect(health.errorCount).toBeDefined(); if (health.status === 'unhealthy') { expect(health.lastError).toBeDefined(); expect(typeof health.lastError).toBe('string'); expect(health.lastError!.length).toBeGreaterThan(0); } }); }); });

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