Repository navigation
The MCP client using a tool launched with uvx does not terminate. #320
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't workingneeds confirmationNeeds confirmation that the PR is actually required or needed.Needs confirmation that the PR is actually required or needed.potentially closeBot recommends closing — needs maintainer reviewBot recommends closing — needs maintainer review
Description
Activity
- addedneeds confirmationNeeds confirmation that the PR is actually required or needed.Needs confirmation that the PR is actually required or needed.
on Oct 13, 2025 Hi @ynaoto thanks for reporting this - just checking whether this issue is still affecting you as it's been some time since your initial report?
fixed in PR #1200 (ace9b62, merged Dec 1 2025). tested on main (b8886e7) and v1.x — both have the fix.
old
close()usedAbortController.abort()which didn't reliably kill uvx-launched processes that don't exit on stdin close. newclose()does:stdin.end()→ 2 s wait → SIGTERM → 2 s wait → SIGKILL.repro script + output
// repro.ts — run: npx tsx repro.ts import { spawn } from "node:child_process"; import { StdioClientTransport } from "@modelcontextprotocol/client"; const TIMEOUT_MS = 10_000; // minimal server that stays alive even after stdin closes (simulates uvx behavior) const hangingServerScript = ` const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin }); rl.on("line", (line) => { try { const msg = JSON.parse(line); if (msg.method === "initialize") { const resp = { jsonrpc: "2.0", id: msg.id, result: { protocolVersion: "2024-11-05", serverInfo: { name: "hang-server", version: "0.0.1" }, capabilities: {} } }; process.stdout.write(JSON.stringify(resp) + "\\n"); } } catch {} }); rl.on("close", () => { setInterval(() => {}, 100_000); }); // stay alive setInterval(() => {}, 100_000); `; async function main() { console.log("Starting repro for issue #320..."); const transport = new StdioClientTransport({ command: "node", args: ["-e", hangingServerScript] }); await transport.start(); const childPid = transport.pid; console.log(`Child process spawned with PID: ${childPid}`); try { process.kill(childPid!, 0); console.log("Child is alive before close() — OK"); } catch { console.error("ERROR: child already dead before close()"); process.exit(1); } const closeStart = Date.now(); console.log("Calling transport.close()..."); await transport.close(); const elapsed = Date.now() - closeStart; console.log(`close() returned after ${elapsed}ms`); let childAlive = false; try { process.kill(childPid!, 0); childAlive = true; } catch { childAlive = false; } if (childAlive) { console.error(`BUG REPRODUCED: child process ${childPid} is still alive after close()`); process.exit(2); } else { console.log(`Child process ${childPid} is dead after close() — PASS`); } } const timer = setTimeout(() => { console.error("TIMEOUT — event loop is hung"); process.exit(3); }, TIMEOUT_MS); timer.unref(); main().catch(err => { console.error(err); process.exit(1); });
$ npx tsx repro.ts Starting repro for issue #320... Child process spawned with PID: 943 Child is alive before close() — OK Calling transport.close()... close() returned after 2011ms Child process 943 is dead after close() — PASS Process should exit cleanly now.The 2011 ms delay is expected: the server ignores stdin close, so
close()waits the 2 s grace period then sends SIGTERM, which kills it.reopen if still seeing this on the current release.
- addedpotentially closeBot recommends closing — needs maintainer reviewBot recommends closing — needs maintainer review
on Apr 17, 2026
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingneeds confirmationNeeds confirmation that the PR is actually required or needed.Needs confirmation that the PR is actually required or needed.potentially closeBot recommends closing — needs maintainer reviewBot recommends closing — needs maintainer review
Describe the bug
As shown in the attached sample, a client that uses a tool launched with uvx does not terminate.
While it is possible to forcefully terminate the process using something like process.exit(0), doing so leaves the uvx cache in a state where it appears to still be in use by the MCP client process.
This issue does not seem to occur when using npx instead.
A similar issue also occurs on Windows Claude Desktop as well.
To Reproduce
Run the attached sample.
Expected behavior
Exit the client process.
==========