Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions client/bin/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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`);
Expand All @@ -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);
37 changes: 37 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<boolean> {
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}`);
Expand All @@ -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);