UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

298 lines (297 loc) 10.8 kB
/** * MCP Port Manager for Hive AI HTTP+SSE Transport * * Manages port allocation, conflict detection, and configuration * for the HTTP+SSE MCP server across different environments. */ import { getConfig, setConfig } from '../storage/unified-database.js'; import { spawn } from 'child_process'; export class MCPPortManager { constructor(options = {}) { this.defaultPort = options.defaultPort || 3000; // Standard MCP port this.portRange = options.portRange || { min: 3000, max: 3010 }; // Standard MCP port range } /** * Check if a specific port is available */ async isPortAvailable(port) { return new Promise((resolve) => { // Use lsof to check if port is in use (Unix/macOS/Linux) const checkProcess = spawn('lsof', ['-ti', `:${port}`], { stdio: ['pipe', 'pipe', 'pipe'] }); let hasOutput = false; checkProcess.stdout.on('data', () => { hasOutput = true; }); checkProcess.on('close', (code) => { // If lsof finds processes using the port, it returns them // If no processes are found, it returns exit code 1 resolve(!hasOutput); }); checkProcess.on('error', () => { // If lsof command fails, assume port is available // This handles Windows or systems without lsof resolve(true); }); // Timeout after 2 seconds setTimeout(() => { checkProcess.kill(); resolve(true); }, 2000); }); } /** * Get detailed information about a port */ async getPortStatus(port) { const available = await this.isPortAvailable(port); let processInfo = ''; if (!available) { processInfo = await this.getProcessInfoForPort(port); } return { port, available, inUse: !available, processInfo: processInfo || undefined }; } /** * Get process information for a port */ async getProcessInfoForPort(port) { return new Promise((resolve) => { const process = spawn('lsof', ['-t', `-i:${port}`], { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; process.stdout.on('data', (data) => { output += data.toString(); }); process.on('close', () => { const pids = output.trim().split('\n').filter(pid => pid); if (pids.length > 0) { // Get process name for first PID this.getProcessName(pids[0]).then(name => { resolve(`PID ${pids[0]} (${name})`); }).catch(() => { resolve(`PID ${pids[0]}`); }); } else { resolve('Unknown process'); } }); process.on('error', () => { resolve('Unknown process'); }); // Timeout after 2 seconds setTimeout(() => { process.kill(); resolve('Process lookup timeout'); }, 2000); }); } /** * Get process name by PID */ async getProcessName(pid) { return new Promise((resolve, reject) => { const process = spawn('ps', ['-p', pid, '-o', 'comm='], { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; process.stdout.on('data', (data) => { output += data.toString(); }); process.on('close', (code) => { if (code === 0) { resolve(output.trim()); } else { reject(new Error('Process not found')); } }); process.on('error', reject); }); } /** * Find an available port within the configured range */ async findAvailablePort() { // First try the default port if (await this.isPortAvailable(this.defaultPort)) { return this.defaultPort; } // Then try the configured port for this environment const configuredPort = await this.getConfiguredPort(); if (configuredPort && configuredPort !== this.defaultPort) { if (await this.isPortAvailable(configuredPort)) { return configuredPort; } } // Scan the standard MCP port range first for (let port = this.portRange.min; port <= this.portRange.max; port++) { if (await this.isPortAvailable(port)) { return port; } } // Fallback: let OS assign a port if standard range is unavailable console.error(`[MCPPortManager] Standard MCP ports ${this.portRange.min}-${this.portRange.max} unavailable, using OS-assigned port`); return this.getOSAssignedPort(); } /** * Get OS-assigned available port (bulletproof for all users globally) */ async getOSAssignedPort() { return new Promise(async (resolve, reject) => { const { createServer } = await import('net'); const server = createServer(); // Let OS assign any available port server.listen(0, '127.0.0.1', () => { const address = server.address(); if (address && typeof address === 'object') { const port = address.port; server.close(() => { console.error(`[MCPPortManager] OS assigned port ${port}`); resolve(port); }); } else { reject(new Error('Failed to get server address')); } }); server.on('error', reject); }); } /** * Get the configured port from database */ async getConfiguredPort() { try { const port = await getConfig('mcp_server_port'); return port ? parseInt(port) : null; } catch (error) { return null; } } /** * Save port configuration to database */ async savePortConfiguration(port, autoDetected = false) { // Save the port number await setConfig('mcp_server_port', port.toString()); // Save metadata about the configuration const portConfig = { port, autoDetected, lastUpdated: new Date().toISOString() }; await setConfig('mcp_server_port_config', JSON.stringify(portConfig)); } /** * Get current port configuration */ async getPortConfiguration() { try { const configStr = await getConfig('mcp_server_port_config'); return configStr ? JSON.parse(configStr) : null; } catch (error) { return null; } } /** * Simple port availability check with fallback */ async resolvePortConflict() { const changes = []; // Try configured port first const configuredPort = await this.getConfiguredPort(); if (configuredPort && await this.isPortAvailable(configuredPort)) { return { resolved: true, port: configuredPort, changes: [] }; } // Find available port in range try { const availablePort = await this.findAvailablePort(); await this.savePortConfiguration(availablePort, true); changes.push(`Auto-assigned port ${availablePort} (conflict resolved)`); return { resolved: true, port: availablePort, changes }; } catch (error) { return { resolved: false, port: this.defaultPort, changes: [`Failed to resolve port conflict: ${error instanceof Error ? error.message : String(error)}`] }; } } /** * Get the best port to use for the current environment */ async getBestPort() { // Check if we have a saved configuration const configuredPort = await this.getConfiguredPort(); if (configuredPort) { const isAvailable = await this.isPortAvailable(configuredPort); if (isAvailable) { return { port: configuredPort, reason: 'using saved configuration' }; } else { // Port is configured but in use - find alternative const availablePort = await this.findAvailablePort(); await this.savePortConfiguration(availablePort, true); return { port: availablePort, reason: `configured port ${configuredPort} in use, auto-selected alternative` }; } } // No configuration - find and save the best port const availablePort = await this.findAvailablePort(); await this.savePortConfiguration(availablePort, true); return { port: availablePort, reason: 'auto-detected and saved' }; } /** * Validate port configuration */ async validatePort(port) { if (port < 1024) { return { valid: false, error: 'Port must be 1024 or higher (system ports reserved)' }; } if (port > 65535) { return { valid: false, error: 'Port must be 65535 or lower' }; } const isAvailable = await this.isPortAvailable(port); if (!isAvailable) { const processInfo = await this.getProcessInfoForPort(port); return { valid: false, error: `Port ${port} is already in use by ${processInfo}` }; } return { valid: true }; } /** * Get simple port status report */ async getPortReport() { const currentPort = await this.getConfiguredPort(); const availablePorts = []; // Check a sample of ports in the range const samplePorts = [ this.defaultPort, ...Array.from({ length: 5 }, (_, i) => this.portRange.min + i) ]; for (const port of samplePorts) { if (await this.isPortAvailable(port)) { availablePorts.push(port); } } const configuration = await this.getPortConfiguration(); return { currentPort: currentPort || undefined, defaultPort: this.defaultPort, portRange: this.portRange, availablePorts, configuration: configuration || undefined }; } } export default MCPPortManager;