Skip to content

fix: pre-flight port check and graceful shutdown to prevent EADDRINUSE#1091

Open
btbowman wants to merge 1 commit intomodelcontextprotocol:mainfrom
btbowman:fix/port-race-condition-and-graceful-shutdown
Open

fix: pre-flight port check and graceful shutdown to prevent EADDRINUSE#1091
btbowman wants to merge 1 commit intomodelcontextprotocol:mainfrom
btbowman:fix/port-race-condition-and-graceful-shutdown

Conversation

@btbowman
Copy link

Summary

Fixes a race condition where the MCP Inspector reports "PORT IS IN USE" even when ports are confirmed free, and leaves ports in CLOSE_WAIT state after Ctrl+C — causing the error to persist across restarts.

Three bugs identified and fixed:

  1. No pre-flight port availability check — Both the client (client/bin/client.js) and proxy server (server/src/index.ts) attempted to bind directly via server.listen() without first verifying the port was free. When a stale process held the port, users received a confusing EADDRINUSE error with no actionable guidance.

  2. Missing process.exit(1) in client EADDRINUSE handler — The client's error handler logged the error but never called process.exit(1), leaving the process hanging indefinitely. Users had to manually kill the process.

  3. No SIGINT/SIGTERM graceful shutdown — Neither the client nor server registered signal handlers, so Ctrl+C left TCP sockets in CLOSE_WAIT state. The OS wouldn't release the port, causing the next launch to fail with EADDRINUSE — even though lsof showed no process on the port.

Changes

client/bin/client.js

  • Added import net from "net"
  • Added checkPort() function that attempts a test bind before the real server.listen()
  • Moved port and host declarations above the check so they're available for the pre-flight test
  • Added process.exit(1) to the existing EADDRINUSE error handler
  • Added SIGINT/SIGTERM handlers with server.close() for graceful shutdown (3s force-exit timeout)
  • Added actionable lsof/kill instructions in error messages

server/src/index.ts

  • Added import net from "net" with other imports at the top of the file
  • Added typed checkPort() function (Promise<boolean>) before app.listen()
  • Added SIGINT/SIGTERM handlers with server.close() for graceful shutdown (3s force-exit timeout)

How to test

  1. Start the Inspector: npx @modelcontextprotocol/inspector
  2. Ctrl+C to stop — port should be released immediately
  3. Restart immediately — should start cleanly without EADDRINUSE
  4. Start Inspector, then in another terminal start a second instance on the same port — should get a clear error with lsof/kill fix instructions and exit cleanly

Test plan

  • Verify Inspector starts normally on default ports (6274/6277)
  • Verify Ctrl+C releases ports immediately (no CLOSE_WAIT)
  • Verify immediate restart after Ctrl+C works without EADDRINUSE
  • Verify clear error + exit when port is genuinely in use
  • Verify CLIENT_PORT / SERVER_PORT env var overrides still work
  • Verify Windows compatibility (no platform-specific APIs used)

Resolves #1090

🤖 Generated with Claude Code

…INUSE race condition

Three bugs caused the Inspector to report "PORT IS IN USE" even when ports
were free, and to leave ports in CLOSE_WAIT after Ctrl+C:

1. No pre-flight port availability check — both the client (client.js) and
   server (index.ts) attempted to bind directly without first verifying the
   port was free, leading to confusing EADDRINUSE errors from stale processes.

2. Missing process.exit(1) in client EADDRINUSE handler — the client's
   error handler logged the error but never exited, leaving the process
   hanging indefinitely.

3. No SIGINT/SIGTERM graceful shutdown — neither process registered signal
   handlers, so Ctrl+C left TCP sockets in CLOSE_WAIT state. The next launch
   would then fail with EADDRINUSE because the OS hadn't released the port.

Fixes:
- Add checkPort() pre-flight check to both client/bin/client.js and
  server/src/index.ts that tests port availability before attempting to bind
- Add process.exit(1) to the client's EADDRINUSE error handler
- Add SIGINT/SIGTERM handlers with server.close() for graceful shutdown
  in both client and server, with a 3-second force-exit timeout
- Provide actionable error messages with lsof/kill instructions

Resolves modelcontextprotocol#1090

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition: 'PORT IS IN USE' error caused by Inspector's own process

1 participant

Comments