Skip to main content
Glama
index.ts21.2 kB
/** * @file CLI Module for AI MCP Gateway * @description Command-line interface for system management and monitoring. * * **Available Commands:** * - `help` - Display help message and usage instructions * - `status` - Check gateway health (API, Redis, Database) * - `models list` - List all models by layer with priority and status * - `models info <id>` - Show detailed model configuration * - `providers` - List configured AI providers * - `db status` - Show database connection status * - `config show` - Display current configuration * - `config set <key> <value>` - Update configuration * * **Design Notes:** * - Runs without server initialization (fast startup) * - Uses fetch API to communicate with running gateway * - Beautiful console output with box-drawing characters * - Supports environment variable configuration * * @example * ```bash * # Show gateway status * npm run start -- status * * # List all models * npm run start -- models list * * # Using Docker * docker exec gateway node dist/index.js status * ``` * * @see {@link ../index.ts} for CLI entry point detection */ import { logger } from '../logging/logger.js'; /** Current CLI version - should match package.json */ const VERSION = '0.1.0'; /** * CLI Command definition interface. * Used to define the command tree with handlers and subcommands. */ interface CLICommand { /** Command name as typed by user */ name: string; /** Human-readable description for help text */ description: string; /** Alternative names for the command */ aliases?: string[]; /** Nested subcommands (e.g., 'models list', 'config set') */ subcommands?: CLICommand[]; /** Async handler function to execute the command */ handler?: (args: string[]) => Promise<void>; } /** * API Base URL for gateway communication. * Override with API_URL environment variable for remote gateways. */ const API_BASE = process.env.API_URL || 'http://localhost:3000'; /** * Make a GET request to the gateway API. * Handles connection errors with user-friendly messages. * * @param endpoint - API endpoint path (e.g., '/health', '/v1/models') * @returns Promise resolving to parsed JSON response * @throws Error if API is unreachable or returns error status */ async function fetchAPI(endpoint: string): Promise<unknown> { try { const response = await fetch(`${API_BASE}${endpoint}`); if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return await response.json(); } catch (error) { if (error instanceof Error && error.message.includes('ECONNREFUSED')) { console.error('❌ Cannot connect to MCP Gateway API'); console.error(' Make sure the gateway is running with: npm run start:api'); process.exit(1); } throw error; } } /** * Display CLI help message with usage instructions and examples. * Called when user runs `help`, `--help`, or `-h`. */ async function showHelp(): Promise<void> { console.log(` ╔═══════════════════════════════════════════════════════════════╗ ║ MCP Gateway CLI v${VERSION} ║ ╚═══════════════════════════════════════════════════════════════╝ Usage: mcp-gateway <command> [subcommand] [options] Commands: help Show this help message status Show gateway status and health models list List all available models by layer models info <id> Show detailed info about a model providers List configured providers and their status db status Show database connection status config show Show current configuration config set <key> <value> Set a configuration value Examples: mcp-gateway status mcp-gateway models list mcp-gateway providers mcp-gateway config show Environment Variables: API_URL Gateway API URL (default: http://localhost:3000) MODE Run mode: 'api' or 'mcp' (default: mcp) For more information, visit: https://github.com/babasida246/ai-mcp-gateway `); } /** * Display gateway status including API health, Redis, and Database connections. * Shows server stats with formatted output. */ async function showStatus(): Promise<void> { console.log('\n🔍 Checking MCP Gateway status...\n'); try { const health = await fetchAPI('/health') as { status: string; redis: boolean; database: boolean; timestamp: string; providers: Record<string, boolean>; healthyProviders: string[]; layers: Record<string, { enabled: boolean; models: Array<{ id: string }> }>; }; const statusIcon = health.status === 'ok' ? '✅' : '❌'; const redisIcon = health.redis ? '✅' : '❌'; const dbIcon = health.database ? '✅' : '❌'; console.log('┌─────────────────────────────────────────────────────┐'); console.log('│ Gateway Status │'); console.log('├─────────────────────────────────────────────────────┤'); console.log(`│ Status: ${statusIcon} ${health.status.toUpperCase().padEnd(35)}│`); console.log(`│ Database: ${dbIcon} ${(health.database ? 'Connected' : 'Disconnected').padEnd(35)}│`); console.log(`│ Redis: ${redisIcon} ${(health.redis ? 'Connected' : 'Disconnected').padEnd(35)}│`); console.log(`│ Timestamp: ${health.timestamp.padEnd(37)}│`); console.log('├─────────────────────────────────────────────────────┤'); console.log('│ Active Providers │'); console.log('├─────────────────────────────────────────────────────┤'); if (health.healthyProviders.length > 0) { health.healthyProviders.forEach(provider => { console.log(`│ ✅ ${provider.padEnd(46)}│`); }); } else { console.log('│ ⚠️ No active providers │'); } console.log('├─────────────────────────────────────────────────────┤'); console.log('│ Layer Summary │'); console.log('├─────────────────────────────────────────────────────┤'); for (const [layer, data] of Object.entries(health.layers)) { const enabledIcon = data.enabled ? '✅' : '⬛'; const modelCount = data.models?.length || 0; console.log(`│ ${enabledIcon} ${layer}: ${modelCount} model(s)`.padEnd(52) + '│'); } console.log('└─────────────────────────────────────────────────────┘\n'); } catch (error) { console.error('❌ Failed to get status:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // Models list command async function listModels(): Promise<void> { console.log('\n📋 Listing all available models...\n'); try { const data = await fetchAPI('/v1/models/layers') as { layers: Record<string, { enabled: boolean; models: Array<{ id: string; provider: string; apiModelName: string; enabled: boolean; priority: number; }>; }>; }; for (const [layerName, layer] of Object.entries(data.layers)) { const enabledIcon = layer.enabled ? '🟢' : '🔴'; console.log(`\n${enabledIcon} ${layerName} (${layer.enabled ? 'Enabled' : 'Disabled'})`); console.log('─'.repeat(60)); if (layer.models.length === 0) { console.log(' No models configured'); continue; } console.log(' Priority │ ID │ Provider'); console.log(' ─────────┼─────────────────────────────────┼──────────'); layer.models.forEach(model => { const statusIcon = model.enabled ? '✓' : '✗'; const priority = model.priority.toString().padStart(2); const id = model.id.substring(0, 30).padEnd(30); const provider = model.provider; console.log(` ${statusIcon} ${priority} │ ${id} │ ${provider}`); }); } console.log('\n'); } catch (error) { console.error('❌ Failed to list models:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // Model info command async function showModelInfo(modelId: string): Promise<void> { console.log(`\n📋 Model Info: ${modelId}\n`); try { const data = await fetchAPI('/v1/models/layers') as { layers: Record<string, { models: Array<{ id: string; provider: string; apiModelName: string; enabled: boolean; priority: number; }>; }>; }; let found = false; for (const [layerName, layer] of Object.entries(data.layers)) { const model = layer.models.find(m => m.id === modelId || m.id.includes(modelId)); if (model) { found = true; console.log('┌─────────────────────────────────────────────────────┐'); console.log(`│ ID: ${model.id.padEnd(39)}│`); console.log(`│ Provider: ${model.provider.padEnd(39)}│`); console.log(`│ API Name: ${model.apiModelName.substring(0, 39).padEnd(39)}│`); console.log(`│ Layer: ${layerName.padEnd(39)}│`); console.log(`│ Priority: ${model.priority.toString().padEnd(39)}│`); console.log(`│ Enabled: ${(model.enabled ? 'Yes' : 'No').padEnd(39)}│`); console.log('└─────────────────────────────────────────────────────┘\n'); break; } } if (!found) { console.log(`❌ Model not found: ${modelId}`); console.log(' Use "ai-mcp-gateway models list" to see available models'); } } catch (error) { console.error('❌ Failed to get model info:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // Providers command async function listProviders(): Promise<void> { console.log('\n🔌 Configured Providers\n'); try { const health = await fetchAPI('/health') as { providers: Record<string, boolean>; healthyProviders: string[]; }; console.log('┌─────────────────────────────────────────────────────┐'); console.log('│ Provider │ Status │ API Key │'); console.log('├────────────────────┼─────────────┼─────────────────┤'); for (const [provider, hasKey] of Object.entries(health.providers)) { const isHealthy = health.healthyProviders.includes(provider); const statusIcon = isHealthy ? '✅ Active' : (hasKey ? '⚠️ Error' : '⬛ Inactive'); const keyStatus = hasKey ? '✓ Configured' : '✗ Missing'; console.log(`│ ${provider.padEnd(18)}│ ${statusIcon.padEnd(12)}│ ${keyStatus.padEnd(16)}│`); } console.log('└─────────────────────────────────────────────────────┘\n'); } catch (error) { console.error('❌ Failed to list providers:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // DB Status command async function showDbStatus(): Promise<void> { console.log('\n🗄️ Database Status\n'); try { const health = await fetchAPI('/health') as { database: boolean; }; const dbIcon = health.database ? '✅' : '❌'; console.log('┌─────────────────────────────────────────────────────┐'); console.log(`│ PostgreSQL: ${dbIcon} ${(health.database ? 'Connected' : 'Disconnected').padEnd(35)}│`); console.log('└─────────────────────────────────────────────────────┘\n'); if (health.database) { // Try to get more DB stats try { const stats = await fetchAPI('/v1/stats') as { totalRequests?: number; totalCost?: number; }; if (stats) { console.log('📊 Usage Statistics:'); console.log(` Total Requests: ${stats.totalRequests || 'N/A'}`); console.log(` Total Cost: $${stats.totalCost?.toFixed(4) || 'N/A'}\n`); } } catch { // Stats endpoint might not exist } } } catch (error) { console.error('❌ Failed to get DB status:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // Config show command async function showConfig(): Promise<void> { console.log('\n⚙️ Current Configuration\n'); try { const health = await fetchAPI('/health') as { configuration: { logLevel: string; defaultLayer: string; enableCrossCheck: boolean; enableAutoEscalate: boolean; maxEscalationLayer: string; enableCostTracking: boolean; costAlertThreshold: number; }; }; const config = health.configuration; console.log('┌─────────────────────────────────────────────────────┐'); console.log('│ Runtime Configuration │'); console.log('├─────────────────────────────────────────────────────┤'); console.log(`│ Log Level: ${(config.logLevel || 'info').padEnd(29)}│`); console.log(`│ Default Layer: ${(config.defaultLayer || 'L0').padEnd(29)}│`); console.log(`│ Cross Check: ${(config.enableCrossCheck ? 'Enabled' : 'Disabled').padEnd(29)}│`); console.log(`│ Auto Escalate: ${(config.enableAutoEscalate ? 'Enabled' : 'Disabled').padEnd(29)}│`); console.log(`│ Max Escalation: ${(config.maxEscalationLayer || 'L0').padEnd(29)}│`); console.log(`│ Cost Tracking: ${(config.enableCostTracking ? 'Enabled' : 'Disabled').padEnd(29)}│`); console.log(`│ Cost Alert Threshold: $${(config.costAlertThreshold || 1).toString().padEnd(27)}│`); console.log('└─────────────────────────────────────────────────────┘\n'); console.log('Environment Variables:'); console.log(` MODE=${process.env.MODE || 'mcp'}`); console.log(` API_URL=${API_BASE}`); console.log(` LOG_LEVEL=${process.env.LOG_LEVEL || 'info'}\n`); } catch (error) { console.error('❌ Failed to get config:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // Config set command async function setConfig(key: string, value: string): Promise<void> { console.log(`\n⚙️ Setting ${key} = ${value}...\n`); try { const response = await fetch(`${API_BASE}/v1/config`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ [key]: value }), }); if (!response.ok) { const error = await response.json() as { error?: string }; throw new Error(error.error || 'Failed to update config'); } console.log(`✅ Configuration updated: ${key} = ${value}\n`); } catch (error) { console.error('❌ Failed to set config:', error instanceof Error ? error.message : 'Unknown error'); process.exit(1); } } // Main CLI entry point export async function runCLI(args: string[]): Promise<void> { const command = args[0]?.toLowerCase(); const subcommand = args[1]?.toLowerCase(); // Handle version if (command === '--version' || command === '-v') { console.log(`ai-mcp-gateway v${VERSION}`); return; } // Handle help if (!command || command === 'help' || command === '--help' || command === '-h') { await showHelp(); return; } // Handle status if (command === 'status') { await showStatus(); return; } // Handle models if (command === 'models') { if (!subcommand || subcommand === 'list') { await listModels(); return; } if (subcommand === 'info' && args[2]) { await showModelInfo(args[2]); return; } console.error('Usage: ai-mcp-gateway models [list|info <id>]'); process.exit(1); } // Handle providers if (command === 'providers') { await listProviders(); return; } // Handle db if (command === 'db') { if (!subcommand || subcommand === 'status') { await showDbStatus(); return; } console.error('Usage: ai-mcp-gateway db [status]'); process.exit(1); } // Handle config if (command === 'config') { if (!subcommand || subcommand === 'show') { await showConfig(); return; } if (subcommand === 'set' && args[2] && args[3]) { await setConfig(args[2], args[3]); return; } console.error('Usage: ai-mcp-gateway config [show|set <key> <value>]'); process.exit(1); } // Unknown command // Support starting the MCP server via CLI: `mcp-serve` (allows --transport and --port) if (command === 'mcp-serve') { // Simple option parsing for transport and port let transport = 'stdio'; let port = 3001; for (let i = 1; i < args.length; i++) { const a = args[i]; const v = args[i + 1]; if (!v) continue; if (a === '--transport' || a === '-t') transport = v; if (a === '--port' || a === '-p') port = parseInt(v, 10) || port; } try { const { startMcpServer } = await import('../mcp/adapter/index.js'); console.log(`Starting MCP server via CLI (transport=${transport}, port=${port})`); await startMcpServer({ transport: transport as any, port }); return; } catch (err) { console.error('Failed to start MCP server:', err instanceof Error ? err.message : String(err)); process.exit(1); } } console.error(`Unknown command: ${command}`); console.error('Run "ai-mcp-gateway help" for usage information'); process.exit(1); }

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/babasida246/ai-mcp-gateway'

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