Skip to main content
Glama
port-discovery.ts3.84 kB
import * as net from "net"; /** * Find an available port starting from a base port */ export async function findAvailablePort( startPort: number = 8080, ): Promise<number> { if (startPort < 1 || startPort > 65535) { throw new Error("Invalid port: must be between 1 and 65535"); } return new Promise((resolve, reject) => { function tryPort(port: number): void { if (port > 65534) { reject(new Error("No available ports found in range")); return; } const server = net.createServer(); server.listen(port, () => { const assignedPort = (server.address() as net.AddressInfo)?.port; server.close(() => { if (assignedPort) { resolve(assignedPort); } else { reject(new Error("Failed to get assigned port")); } }); }); server.on("error", (err: { code?: string } & Error) => { if (err.code === "EADDRINUSE") { // Port is in use, try next port tryPort(port + 1); } else { reject(err); } }); } tryPort(startPort); }); } /** * Manages port reservations for unified server architecture */ export class PortManager { private reservedPorts: Set<number> = new Set(); private portLock: Promise<number> = Promise.resolve(0); /** * Reserve a port, skipping already reserved ports */ async reservePort(preferredPort: number): Promise<number> { if (preferredPort < 1 || preferredPort > 65535) { throw new Error("Invalid port: must be between 1 and 65535"); } // Ensure sequential port reservation to avoid race conditions await this.portLock; this.portLock = this._reservePortInternal(preferredPort); return this.portLock; } private async _reservePortInternal(preferredPort: number): Promise<number> { let currentPort = preferredPort; while (currentPort <= 65535) { if (this.reservedPorts.has(currentPort)) { currentPort++; continue; } try { // Test if port is available await this.testPortAvailability(currentPort); this.reservedPorts.add(currentPort); return currentPort; } catch (error) { if ((error as { code?: string } & Error).code === "EADDRINUSE") { currentPort++; continue; } else { throw error; } } } throw new Error("No available ports found in range"); } /** * Reserve a port directly without checking if it's available (for testing) */ async reservePortDirectly(port: number): Promise<number> { if (port < 1 || port > 65535) { throw new Error("Invalid port: must be between 1 and 65535"); } if (this.reservedPorts.has(port)) { throw new Error("Port already reserved"); } this.reservedPorts.add(port); return port; } private testPortAvailability(port: number): Promise<void> { return new Promise((resolve, reject) => { const server = net.createServer(); server.listen(port, () => { server.close(() => resolve()); }); server.on("error", reject); }); } /** * Get a single port for unified server (MCP + Web + WebSocket) */ async getUnifiedPort(preferredPort: number = 8080): Promise<number> { return this.reservePort(preferredPort); } /** * Release a reserved port */ releasePort(port: number): void { this.reservedPorts.delete(port); } /** * Check if a port is reserved */ isReserved(port: number): boolean { return this.reservedPorts.has(port); } /** * Get all reserved ports */ getReservedPorts(): number[] { return Array.from(this.reservedPorts); } /** * Cleanup all reserved ports */ cleanup(): void { this.reservedPorts.clear(); } }

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/LightspeedDMS/ssh-mcp'

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