From f259c5046230ccabacf966b7638d6553cf493603 Mon Sep 17 00:00:00 2001 From: 20syldev Date: Thu, 9 Apr 2026 20:25:22 +0200 Subject: [PATCH] fix: close MCP transport gracefully on shutdown 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 #126 Relates to #113 --- src/_internal/browser-eval-manager.ts | 7 ++++--- src/index.ts | 12 +++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/_internal/browser-eval-manager.ts b/src/_internal/browser-eval-manager.ts index dd6dea6..fd480ef 100644 --- a/src/_internal/browser-eval-manager.ts +++ b/src/_internal/browser-eval-manager.ts @@ -122,15 +122,16 @@ export async function stopBrowserEvalMCP(): Promise { } /** - * Cleanup on process exit + * Cleanup on process exit. + * Signal handling and process.exit() are intentionally left to src/index.ts + * so there is a single, ordered shutdown sequence. These listeners only close + * the Playwright MCP connection; they do not call process.exit() themselves. */ process.on("SIGINT", async () => { await stopBrowserEvalMCP() - process.exit(0) }) process.on("SIGTERM", async () => { await stopBrowserEvalMCP() - process.exit(0) }) diff --git a/src/index.ts b/src/index.ts index ed9ff03..04e4870 100644 --- a/src/index.ts +++ b/src/index.ts @@ -303,9 +303,19 @@ async function main() { log('Server started') - const shutdown = () => { + const shutdown = async () => { log('Server terminated') + // Close the MCP server transport before exiting so the host (e.g. Claude + // Code) receives a clean EOF on the stdio channel instead of an abrupt + // disconnect. Without this the host reports "MCP server failed" even + // though the exit code is 0. + try { + await server.close() + } catch { + // Ignore close errors during shutdown + } + const aggregationJSON = getSessionAggregationJSON() if (aggregationJSON) {