diff --git a/client/bin/client.js b/client/bin/client.js index 2a7419e66..28c31d1bb 100755 --- a/client/bin/client.js +++ b/client/bin/client.js @@ -5,10 +5,40 @@ import { join, dirname } from "path"; import { fileURLToPath } from "url"; import handler from "serve-handler"; import http from "http"; +import net from "net"; const __dirname = dirname(fileURLToPath(import.meta.url)); const distPath = join(__dirname, "../dist"); +const port = parseInt(process.env.CLIENT_PORT || "6274", 10); +const host = process.env.HOST || "localhost"; + +// Check port availability before attempting to bind. +// Prevents confusing EADDRINUSE errors from stale processes. +function checkPort(targetHost, targetPort) { + return new Promise((resolve) => { + const tester = net.createServer(); + tester.once("error", (err) => { + resolve(false); + }); + tester.once("listening", () => { + tester.close(() => resolve(true)); + }); + tester.listen(targetPort, targetHost); + }); +} + +const portFree = await checkPort(host, port); +if (!portFree) { + console.error( + `āŒ MCP Inspector PORT IS IN USE at http://${host}:${port} āŒ `, + ); + console.error( + `šŸ’” To fix: run "lsof -ti:${port} | xargs kill -9" to free the port, or set CLIENT_PORT to use a different port.`, + ); + process.exit(1); +} + const server = http.createServer((request, response) => { const handlerOptions = { public: distPath, @@ -40,8 +70,6 @@ const server = http.createServer((request, response) => { return handler(request, response, handlerOptions); }); -const port = parseInt(process.env.CLIENT_PORT || "6274", 10); -const host = process.env.HOST || "localhost"; server.on("listening", () => { const url = process.env.INSPECTOR_URL || `http://${host}:${port}`; console.log(`\nšŸš€ MCP Inspector is up and running at:\n ${url}\n`); @@ -58,5 +86,20 @@ server.on("error", (err) => { } else { throw err; } + process.exit(1); }); + +// Graceful shutdown: properly close the HTTP server so the port is +// released immediately instead of lingering in CLOSE_WAIT state. +function shutdown() { + server.close(() => { + process.exit(0); + }); + // Force exit if close takes too long (e.g. hanging connections) + setTimeout(() => process.exit(0), 3000); +} + +process.on("SIGINT", shutdown); +process.on("SIGTERM", shutdown); + server.listen(port, host); diff --git a/server/src/index.ts b/server/src/index.ts index 4d1fffa29..2b0934e73 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -29,6 +29,7 @@ import rateLimit from "express-rate-limit"; import { findActualExecutable } from "spawn-rx"; import mcpProxy from "./mcpProxy.js"; import { randomUUID, randomBytes, timingSafeEqual } from "node:crypto"; +import net from "net"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import { readFileSync } from "fs"; @@ -822,6 +823,30 @@ const PORT = parseInt( ); const HOST = process.env.HOST || "localhost"; +// Check port availability before attempting to bind. +// Prevents confusing EADDRINUSE errors from stale processes. +function checkPort(targetHost: string, targetPort: number): Promise { + return new Promise((resolve) => { + const tester = net.createServer(); + tester.once("error", () => { + resolve(false); + }); + tester.once("listening", () => { + tester.close(() => resolve(true)); + }); + tester.listen(targetPort, targetHost); + }); +} + +const portFree = await checkPort(HOST, PORT); +if (!portFree) { + console.error(`āŒ Proxy Server PORT IS IN USE at port ${PORT} āŒ `); + console.error( + `šŸ’” To fix: run "lsof -ti:${PORT} | xargs kill -9" to free the port, or set SERVER_PORT to use a different port.`, + ); + process.exit(1); +} + const server = app.listen(PORT, HOST); server.on("listening", () => { console.log(`āš™ļø Proxy server listening on ${HOST}:${PORT}`); @@ -844,3 +869,15 @@ server.on("error", (err) => { } process.exit(1); }); + +// Graceful shutdown: properly close the HTTP server so the port is +// released immediately instead of lingering in CLOSE_WAIT state. +function shutdown() { + server.close(() => { + process.exit(0); + }); + setTimeout(() => process.exit(0), 3000); +} + +process.on("SIGINT", shutdown); +process.on("SIGTERM", shutdown);