Skip to content

Close MCP transport gracefully on shutdown#131

Open
20syldev wants to merge 1 commit into
vercel:mainfrom
20syldev:fix/graceful-shutdown-mcp-exit
Open

Close MCP transport gracefully on shutdown#131
20syldev wants to merge 1 commit into
vercel:mainfrom
20syldev:fix/graceful-shutdown-mcp-exit

Conversation

@20syldev
Copy link
Copy Markdown

@20syldev 20syldev commented Apr 9, 2026

Problem

When the MCP host (e.g. Claude Code) sends SIGTERM to terminate the server on exit, users see:

1 MCP server failed

even though everything worked correctly. The exit code is 0, but the host interprets the shutdown as a failure.

Root cause

Two issues were combining to cause this:

1. MCP transport was never closed before process.exit()

index.ts's shutdown() called process.exit(0) immediately after spawning the telemetry flush process. The MCP Server object was never told to close, so the stdio transport received an abrupt EOF instead of a clean close notification. The host saw the broken pipe and reported a failure.

2. Duplicate signal handlers racing each other

browser-eval-manager.ts registers its own SIGINT/SIGTERM handlers that both stop the Playwright connection and call process.exit(0). These fired concurrently with the handlers in index.ts, causing process.exit(0) to be called before index.ts had a chance to flush telemetry or close the MCP transport.

Fix

src/index.ts — make shutdown() async and call await server.close() before process.exit():

- const shutdown = () => {
+ const shutdown = async () => {
    log('Server terminated')
+   try {
+     await server.close()
+   } catch {
+     // Ignore close errors during shutdown
+   }
    // ... telemetry flush ...
    process.exit(0)
  }

src/_internal/browser-eval-manager.ts — remove process.exit(0) from the signal handlers (owned exclusively by index.ts):

  process.on("SIGINT", async () => {
    await stopBrowserEvalMCP()
-   process.exit(0)
  })
  process.on("SIGTERM", async () => {
    await stopBrowserEvalMCP()
-   process.exit(0)
  })

Fixes #126, relates to #113

When the MCP host (e.g. Claude Code) sends SIGTERM to terminate the
server, the shutdown handler was calling process.exit(0) without first
closing the MCP server transport.  The host received an abrupt EOF on
the stdio channel and interpreted it as a server failure, reporting
"1 MCP server failed" even though the exit code was 0.

Two related fixes:

1. Call await server.close() in index.ts shutdown() before process.exit()
   so the MCP SDK can send a proper close notification over the transport.

2. Remove process.exit(0) from browser-eval-manager.ts signal handlers.
   Those handlers fired concurrently with the ones in index.ts, causing
   process.exit(0) to be called before index.ts could flush telemetry or
   close the MCP transport. Signal handling and process exit are now
   owned exclusively by index.ts.

Fixes vercel#126
Relates to vercel#113
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.

MCP server failed on exit

1 participant