vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
516 lines (515 loc) • 20.9 kB
JavaScript
import { createServer } from 'net';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import logger from '../logger.js';
const DEFAULT_INSTANCE_TRACKING_DIR = path.join(os.tmpdir(), 'vibe-coder-instances');
const INSTANCE_FILE_PREFIX = 'instance-';
const EXCLUDED_PORT_RANGES = [
{ start: 1, end: 1024, reason: 'System/privileged ports' },
{ start: 5060, end: 5061, reason: 'SIP' },
{ start: 3306, end: 3306, reason: 'MySQL' },
{ start: 5432, end: 5432, reason: 'PostgreSQL' },
{ start: 6379, end: 6379, reason: 'Redis' },
{ start: 27017, end: 27017, reason: 'MongoDB' }
];
export class PortAllocator {
static config = {};
static get INSTANCE_TRACKING_DIR() {
return this.config.instanceTrackingDir || DEFAULT_INSTANCE_TRACKING_DIR;
}
static initialize(config) {
this.config = config;
if (config.instanceTrackingDir) {
logger.info({
instanceTrackingDir: config.instanceTrackingDir
}, 'PortAllocator initialized with custom instance tracking directory');
}
else {
logger.debug({
instanceTrackingDir: DEFAULT_INSTANCE_TRACKING_DIR
}, 'PortAllocator using default instance tracking directory');
}
}
static getConfig() {
return { ...this.config };
}
static isPortExcluded(port) {
for (const range of EXCLUDED_PORT_RANGES) {
if (port >= range.start && port <= range.end) {
logger.debug({ port, reason: range.reason }, 'Port excluded from allocation');
return true;
}
}
return false;
}
static async isPortUsedByVibeCoderInstance(port) {
try {
const net = await import('net');
return new Promise((resolve) => {
const client = net.createConnection({ port }, () => {
client.end();
logger.debug({ port }, 'Port is in use by another process');
resolve(true);
});
client.on('error', () => {
logger.debug({ port }, 'Port is not in use');
resolve(false);
});
client.setTimeout(1000, () => {
client.destroy();
resolve(false);
});
});
}
catch (error) {
logger.error({ err: error, port }, 'Error checking port usage');
return false;
}
}
static async findAvailablePort(port) {
const startTime = Date.now();
logger.debug({ port, operation: 'port_check_start' }, 'Starting port availability check');
if (port < 0 || port > 65535) {
logger.debug({
port,
available: false,
error: 'Invalid port range',
operation: 'port_check_complete'
}, 'Port availability check: invalid port');
return false;
}
return new Promise((resolve) => {
const server = createServer();
server.listen(port, () => {
server.close(() => {
const duration = Date.now() - startTime;
logger.debug({
port,
available: true,
duration,
operation: 'port_check_complete'
}, 'Port availability check: available');
resolve(true);
});
});
server.on('error', (err) => {
const duration = Date.now() - startTime;
if (err.code === 'EADDRINUSE') {
logger.debug({
port,
available: false,
error: err.code,
duration,
operation: 'port_check_complete'
}, 'Port availability check: in use');
resolve(false);
}
else {
logger.debug({
port,
available: false,
error: err.message,
duration,
operation: 'port_check_complete'
}, 'Port availability check: error');
resolve(false);
}
});
});
}
static async findAvailablePortInRange(range) {
const attempted = [];
logger.debug({
service: range.service,
start: range.start,
end: range.end
}, 'Starting port allocation for service');
for (let port = range.start; port <= range.end; port++) {
if (this.isPortExcluded(port)) {
logger.debug({
port,
service: range.service,
reason: 'excluded_port',
operation: 'port_skip'
}, 'Skipping excluded port');
continue;
}
attempted.push(port);
logger.debug({
port,
service: range.service,
attempt: attempted.length,
remaining: range.end - port,
operation: 'port_attempt'
}, 'Attempting port allocation');
const isAvailable = await this.findAvailablePort(port);
if (isAvailable) {
logger.debug({
service: range.service,
port,
attempted: attempted.length,
efficiency: `${attempted.length}/${range.end - range.start + 1}`,
operation: 'range_allocation_success'
}, 'Port allocated successfully');
return {
port,
service: range.service,
attempted,
success: true
};
}
else {
logger.debug({
port,
service: range.service,
attempt: attempted.length,
operation: 'port_conflict'
}, 'Port conflict detected, trying next port');
}
}
const error = `No available ports in range ${range.start}-${range.end} for service ${range.service}`;
logger.warn({
service: range.service,
range: `${range.start}-${range.end}`,
attempted
}, error);
return {
port: -1,
service: range.service,
attempted,
success: false,
error
};
}
static parsePortRange(envVar, defaultRange) {
if (!envVar || envVar.trim() === '') {
logger.debug({ defaultRange }, 'Empty environment variable, using default range');
return defaultRange;
}
if (!envVar.includes('-')) {
const port = parseInt(envVar.trim(), 10);
if (isNaN(port) || port <= 0 || port > 65535) {
logger.warn({ envVar, defaultRange }, 'Invalid single port, using default range');
return defaultRange;
}
logger.debug({ port, service: defaultRange.service }, 'Parsed single port as range');
return {
start: port,
end: port,
service: defaultRange.service
};
}
const parts = envVar.split('-');
if (parts.length !== 2) {
logger.warn({ envVar, defaultRange }, 'Invalid port range format, using default range');
return defaultRange;
}
const start = parseInt(parts[0].trim(), 10);
const end = parseInt(parts[1].trim(), 10);
if (isNaN(start) || isNaN(end) || start <= 0 || end <= 0 || start > 65535 || end > 65535) {
logger.warn({ envVar, start, end, defaultRange }, 'Invalid port numbers, using default range');
return defaultRange;
}
if (start > end) {
logger.warn({ envVar, start, end, defaultRange }, 'Start port greater than end port, using default range');
return defaultRange;
}
logger.debug({ start, end, service: defaultRange.service }, 'Successfully parsed port range');
return {
start,
end,
service: defaultRange.service
};
}
static async detectPortConflicts(ports) {
const conflicts = new Map();
logger.debug({ ports }, 'Checking for port conflicts with other instances');
for (const port of ports) {
const inUse = await this.isPortUsedByVibeCoderInstance(port);
conflicts.set(port, inUse);
if (inUse) {
logger.warn({ port }, 'Port conflict detected with another instance');
}
}
const conflictCount = Array.from(conflicts.values()).filter(v => v).length;
logger.info({
portsChecked: ports.length,
conflictsFound: conflictCount
}, 'Port conflict detection complete');
return conflicts;
}
static async allocatePortsForServices(ranges) {
const allocations = new Map();
const totalAttempted = [];
const successful = [];
const conflicts = [];
const errors = [];
const batchStartTime = Date.now();
logger.info({
serviceCount: ranges.length,
services: ranges.map(r => r.service),
totalPortsInRanges: ranges.reduce((sum, r) => sum + (r.end - r.start + 1), 0),
operation: 'batch_allocation_start'
}, 'Starting batch port allocation for services');
for (const range of ranges) {
try {
const result = await this.findAvailablePortInRange(range);
allocations.set(range.service, result);
totalAttempted.push(...result.attempted);
if (result.success) {
successful.push(result.port);
logger.info({
service: range.service,
port: result.port
}, 'Service port allocated successfully');
}
else {
conflicts.push(...result.attempted);
if (result.error) {
errors.push(result.error);
}
logger.warn({
service: range.service,
attempted: result.attempted.length
}, 'Service port allocation failed');
}
}
catch (error) {
const errorMsg = `Failed to allocate port for service ${range.service}: ${error}`;
errors.push(errorMsg);
logger.error({ service: range.service, error }, 'Port allocation error');
allocations.set(range.service, {
port: -1,
service: range.service,
attempted: [],
success: false,
error: errorMsg
});
}
}
const summary = {
allocations,
totalAttempted: [...new Set(totalAttempted)],
success: successful.length === ranges.length
};
const batchDuration = Date.now() - batchStartTime;
const successRate = ranges.length > 0 ? (successful.length / ranges.length * 100).toFixed(1) : '0';
logger.info({
totalServices: ranges.length,
successfulAllocations: successful.length,
failedAllocations: errors.length,
totalPortsAttempted: summary.totalAttempted.length,
uniquePortsAttempted: [...new Set(summary.totalAttempted)].length,
successRate: `${successRate}%`,
duration: batchDuration,
averageTimePerService: ranges.length > 0 ? Math.round(batchDuration / ranges.length) : 0,
operation: 'batch_allocation_complete'
}, 'Batch port allocation completed');
logger.debug('=== Batch Allocation Results ===');
for (const [serviceName, result] of allocations) {
if (result.success) {
logger.debug({
service: serviceName,
port: result.port,
attempts: result.attempted.length,
status: 'success',
operation: 'service_allocation_result'
}, `Service allocation successful: ${serviceName}`);
}
else {
logger.debug({
service: serviceName,
attempts: result.attempted.length,
attemptedPorts: result.attempted,
error: result.error,
status: 'failed',
operation: 'service_allocation_result'
}, `Service allocation failed: ${serviceName}`);
}
}
logger.debug('=== End Batch Allocation Results ===');
return summary;
}
static async cleanupOrphanedPorts() {
const cleanupStartTime = Date.now();
logger.info({ operation: 'cleanup_start' }, 'Starting port cleanup for orphaned processes');
const cleanedCount = 0;
let checkedCount = 0;
let occupiedCount = 0;
const commonPortRanges = [
{ start: 8080, end: 8090, service: 'websocket' },
{ start: 3001, end: 3020, service: 'http' },
{ start: 3000, end: 3010, service: 'sse' }
];
const occupiedPorts = [];
try {
logger.debug({
ranges: commonPortRanges,
totalPortsToCheck: commonPortRanges.reduce((sum, r) => sum + (r.end - r.start + 1), 0),
operation: 'cleanup_scan_start'
}, 'Starting port cleanup scan');
for (const range of commonPortRanges) {
logger.debug({
service: range.service,
start: range.start,
end: range.end,
operation: 'cleanup_range_start'
}, `Scanning ${range.service} port range`);
for (let port = range.start; port <= range.end; port++) {
if (this.isPortExcluded(port)) {
logger.debug({
port,
service: range.service,
reason: 'excluded',
operation: 'cleanup_port_skip'
}, 'Skipping excluded port during cleanup');
continue;
}
checkedCount++;
const isAvailable = await this.findAvailablePort(port);
if (!isAvailable) {
occupiedCount++;
occupiedPorts.push({ port, service: range.service });
logger.debug({
port,
service: range.service,
operation: 'cleanup_port_occupied'
}, 'Port in use - checking if orphaned');
logger.debug({
port,
service: range.service,
operation: 'cleanup_port_analysis'
}, 'Port occupied by process');
}
else {
logger.debug({
port,
service: range.service,
operation: 'cleanup_port_available'
}, 'Port available during cleanup scan');
}
}
logger.debug({
service: range.service,
portsChecked: range.end - range.start + 1,
operation: 'cleanup_range_complete'
}, `Completed ${range.service} port range scan`);
}
const cleanupDuration = Date.now() - cleanupStartTime;
logger.info({
cleanedCount,
checkedCount,
occupiedCount,
occupiedPorts,
duration: cleanupDuration,
averageTimePerPort: checkedCount > 0 ? Math.round(cleanupDuration / checkedCount) : 0,
operation: 'cleanup_complete'
}, 'Port cleanup completed');
return cleanedCount;
}
catch (error) {
const cleanupDuration = Date.now() - cleanupStartTime;
logger.error({
error,
cleanedCount,
checkedCount,
occupiedCount,
duration: cleanupDuration,
operation: 'cleanup_error'
}, 'Error during port cleanup');
return cleanedCount;
}
}
static async registerInstance(port, service) {
try {
await fs.mkdir(this.INSTANCE_TRACKING_DIR, { recursive: true });
const instanceInfo = {
pid: process.pid,
port,
service,
startTime: Date.now()
};
const instanceFile = path.join(this.INSTANCE_TRACKING_DIR, `${INSTANCE_FILE_PREFIX}${process.pid}-${port}.json`);
await fs.writeFile(instanceFile, JSON.stringify(instanceInfo, null, 2));
logger.info({ pid: process.pid, port, service }, 'Instance registered');
}
catch (error) {
logger.error({ err: error, port, service }, 'Failed to register instance');
}
}
static async unregisterInstance(port) {
try {
const instanceFile = path.join(this.INSTANCE_TRACKING_DIR, `${INSTANCE_FILE_PREFIX}${process.pid}-${port}.json`);
await fs.unlink(instanceFile);
logger.info({ pid: process.pid, port }, 'Instance unregistered');
}
catch (error) {
if (error.code !== 'ENOENT') {
logger.error({ err: error, port }, 'Failed to unregister instance');
}
}
}
static async getActiveInstances() {
const instances = [];
try {
await fs.mkdir(this.INSTANCE_TRACKING_DIR, { recursive: true });
const files = await fs.readdir(this.INSTANCE_TRACKING_DIR);
for (const file of files) {
if (file.startsWith(INSTANCE_FILE_PREFIX)) {
try {
const filePath = path.join(this.INSTANCE_TRACKING_DIR, file);
const content = await fs.readFile(filePath, 'utf-8');
const instanceInfo = JSON.parse(content);
if (this.isProcessRunning(instanceInfo.pid)) {
instances.push(instanceInfo);
}
else {
await fs.unlink(filePath);
logger.debug({ pid: instanceInfo.pid, port: instanceInfo.port }, 'Cleaned up stale instance file');
}
}
catch (error) {
logger.error({ err: error, file }, 'Failed to read instance file');
}
}
}
}
catch (error) {
logger.error({ err: error }, 'Failed to get active instances');
}
return instances;
}
static isProcessRunning(pid) {
try {
process.kill(pid, 0);
return true;
}
catch {
return false;
}
}
static async cleanupInstanceTracking() {
try {
try {
await fs.access(this.INSTANCE_TRACKING_DIR);
}
catch {
logger.debug({ dir: this.INSTANCE_TRACKING_DIR }, 'Instance tracking directory does not exist, skipping cleanup');
return;
}
const files = await fs.readdir(this.INSTANCE_TRACKING_DIR);
const prefix = `${INSTANCE_FILE_PREFIX}${process.pid}-`;
for (const file of files) {
if (file.startsWith(prefix)) {
const filePath = path.join(this.INSTANCE_TRACKING_DIR, file);
await fs.unlink(filePath);
logger.debug({ file }, 'Cleaned up instance tracking file');
}
}
}
catch (error) {
logger.error({ err: error }, 'Failed to cleanup instance tracking');
}
}
}