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);
});
Comment on lines +828 to +838
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkPort() currently resolves false for any listen error, which can misreport other failure modes (e.g., EACCES for privileged ports, invalid host) as "PORT IS IN USE". Capture the error and only treat EADDRINUSE as "in use"; for other err.code values either rethrow or surface a more accurate message. Also consider calling tester.close() in the error path to avoid leaving a handle around in edge cases.

Copilot uses AI. Check for mistakes.
}

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.`,
);
Comment on lines +841 to +846
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remediation text suggests running lsof ... | xargs kill -9, which is Unix-specific and conflicts with the stated goal of Windows compatibility. Consider making the message platform-aware (process.platform) or providing a more platform-neutral suggestion (e.g., "close the process using the port" + link/docs), with Windows equivalents where applicable.

Suggested change
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.`,
);
function getPortInUseRemediation(targetPort: number): string {
if (process.platform === "win32") {
return `💡 To fix: close the process using port ${targetPort} (for example, run "netstat -ano | findstr :${targetPort}" to find the PID, then "taskkill /PID <PID> /F"), or set SERVER_PORT to use a different port.`;
}
return `💡 To fix: close the process using port ${targetPort} (for example, run "lsof -ti:${targetPort} | xargs kill -9"), or set SERVER_PORT to use a different port.`;
}
const portFree = await checkPort(HOST, PORT);
if (!portFree) {
console.error(`❌ Proxy Server PORT IS IN USE at port ${PORT} ❌ `);
console.error(getPortInUseRemediation(PORT));

Copilot uses AI. Check for mistakes.
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);
Comment on lines +875 to +883
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shutdown() calls server.close() unconditionally. In Node, calling close() when the server isn't listening (or calling it twice if multiple signals fire) can throw ERR_SERVER_NOT_RUNNING. Add a guard (e.g., let shuttingDown = false + if (!server.listening) process.exit(0)) and consider clearing the force-exit timeout when close completes.

Copilot uses AI. Check for mistakes.
Loading