@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
JavaScript
/**
* 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;