Repository navigation
mcpdo: run the daemon test suites on Windows over named pipes - #2977
Conversation
…2922) The daemon test harness derives the endpoint from getDaemonSocketPath instead of a hard-coded <dir>/mcpdod.sock, keys fake-daemon tokens by directory, passes dir alongside a pipe endpoint, and tears a spawned daemon down by its token/lock (present on every platform) with an EBUSY-tolerant rmSync. unixDaemonOnly is gone; the few remaining win32 skips are POSIX-only behaviour (socket files, sun_path, mode bits), each with its reason at the site. mcpdo's per-file coverage threshold is enforced on Windows again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
…ing its dir (#2922) The daemon chdir()s into its directory and exits only once its event loop drains, after it has already removed its token and lock. Windows refuses to remove a directory that is a live process's cwd (EBUSY), so teardown now waits on the pid the daemon reports in ping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
…drains (#2922) A daemon that stopped on its own (idle reap after disconnect, or a test's daemon stop) has already removed its token, so teardown has no pid to wait on while the process still has the directory as its cwd. Retry the removal on EBUSY/EPERM/ENOTEMPTY for a bounded time instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
With mcpdo's per-file gate enforced on Windows, the POSIX arms of paths.ts (socket-file path, sun_path limits, assertTrustedPrivateRoot's checks) need covering there too. Fake the platform (and stand in getuid where Windows has none) instead of skipping; the sun_path case now runs on every host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
There was a problem hiding this comment.
🟡 Changes recommended
Closed fake servers leave mcpdod.token behind, causing teardown to wait the full eight seconds even when nothing is listening; fake-server cleanup should remove the token while preserving real-daemon waits.
1 open issue
What changed in this PR
Ports mcpdo’s daemon test harness to Windows named pipes, addressing #2922 without changing production code. The daemon suites now run on Windows, and mcpdo’s per-file 90% coverage gate is restored there.
Changes:
- Adds shared endpoint and teardown helpers for Unix sockets and Windows named pipes.
- Removes broad Windows skips, retaining only cases that require POSIX behavior.
- Updates fake-daemon token handling, path tests, and quality-gate documentation.
| File | Description |
|---|---|
docs/quality-gate.md |
Documents Windows daemon tests and restored coverage enforcement. |
clients/mcpdo/vitest.config.ts |
Enforces coverage thresholds on every platform. |
clients/mcpdo/__tests__/skills-budget-hint.test.ts |
Enables Windows execution with shared teardown. |
clients/mcpdo/__tests__/rate-limit-envelope.test.ts |
Uses portable endpoints and token lookup. |
clients/mcpdo/__tests__/oauth-error-envelope.test.ts |
Enables Windows tests and shared directory cleanup. |
clients/mcpdo/__tests__/mcp-elicitation.test.ts |
Enables Windows elicitation tests with shared teardown. |
clients/mcpdo/__tests__/mcp-coverage.test.ts |
Enables daemon cases and platform-neutral liveness checks. |
clients/mcpdo/__tests__/mcp-connection.test.ts |
Enables Windows connection tests with shared teardown. |
clients/mcpdo/__tests__/load.test.ts |
Enables daemon-backed load tests on Windows. |
clients/mcpdo/__tests__/helpers/platform.ts |
Replaces the broad skip flag with isWindows. |
clients/mcpdo/__tests__/helpers/fake-daemon.ts |
Publishes fake tokens by daemon directory. |
clients/mcpdo/__tests__/helpers/daemon-endpoint.ts |
Adds portable endpoint, shutdown, and cleanup helpers. |
clients/mcpdo/__tests__/elicitation-client.test.ts |
Uses portable endpoints and explicit token directories. |
clients/mcpdo/__tests__/directory-read.test.ts |
Enables Windows directory-read tests with shared teardown. |
clients/mcpdo/__tests__/daemon-stream.test.ts |
Ports streaming tests and isolates stale-file coverage. |
clients/mcpdo/__tests__/daemon-private.test.ts |
Enables private-daemon tests with explicit directories. |
clients/mcpdo/__tests__/daemon-paths.test.ts |
Exercises platform branches and Windows temporary-directory handling. |
clients/mcpdo/__tests__/daemon-handshake.test.ts |
Runs handshake tests over platform-native endpoints. |
clients/mcpdo/__tests__/daemon-coverage.test.ts |
Ports daemon coverage cases and adopts shared teardown. |
Review effort: Balanced
A closed fake server leaves its published token behind, so stopDaemonIn waited out its full budget in every fake-server teardown. A daemon answers ping even while stopping, so an unanswered ping means there is no shutdown to wait for; a process still draining is removeDaemonDir's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot round 1 — 1 finding, fixed in 2090e96: |
|
Copilot review loop closed: round 2 was clean (no inline comments, no suppressed findings, approval recommended). 2 rounds total. |

Closes #2922
Summary
mcpdo's daemon test suites now run on the
windowsCI job against the daemon's real named pipe, and mcpdo's per-file 90% coverage gate is enforced on Windows again.Windows CI proof
windowsjob on the PR head (2090e96): https://github.com/modelcontextprotocol/inspector/actions/runs/43178529126/job/131048543890 (11m24s, green; the same counts as the earlier run at f9dcfc9, https://github.com/modelcontextprotocol/inspector/actions/runs/43175250060/job/131037805564)coverage:mcpdoon WindowsThe 7 remaining skips are all POSIX-only behaviour, each with its reason at the site: 3 pre-existing exec-bit cases in
resolve-command, plus 4 from the old daemon skip set that test something Windows does not have — a stale socket file (daemon-stream,daemon-coverage), thesun_pathlimit through the CLI (mcp-coverage), and0700mode tightening (daemon-paths).What changed
__tests__/helpers/daemon-endpoint.ts(new). The harness derives the endpoint fromgetDaemonSocketPath(dir)instead of a hard-coded<dir>/mcpdod.sock.stopDaemonIn(dir)stops a spawned daemon and waits for its token/lock (present on every platform, unlike a socket file) and for the pid it reports inpingto exit.removeDaemonDir(dir)retries the removal onEBUSY/EPERM/ENOTEMPTYwith a bounded budget. Two Windows findings drove those two waits, both from CI runs on this branch:chdir()s into its directory and exits only once its event loop drains, after removing its token and lock. Windows refuses to remove a directory that is a live process's cwd, so waiting for the files alone was not enough.disconnect, or a test's owndaemon stop) leaves no token to ask for its pid, so the removal itself retries while that process drains.publishFakeDaemonToken(dir)is keyed by the daemon directory rather than the endpoint, and everycallDaemon/streamDaemonin the fakes passesdiralongsidesocketPath— a pipe has no directory, so the client cannot find the token from it (daemonTokenDir).helpers/platform.ts:unixDaemonOnlyis gone;isWindowsnames the remaining POSIX-only skips.daemon-paths: the POSIX arms ofpaths.ts(socket-file path, bothsun_pathlimits,assertTrustedPrivateRoot's checks) are reached on every host by fakingprocess.platform(and standing ingetuidwhere Windows has none) rather than skipping, since the gate now runs there. Thesun_pathcase is no longer skipped anywhere.createPrivateDaemonDir's layout case setsTEMP/TMPtoo, which is whatos.tmpdir()reads on Windows.clients/mcpdo/vitest.config.ts: the win32 threshold carve-out is removed.docs/quality-gate.md: thewindowsjob paragraph no longer says mcpdo's daemon suites and gate are off there.No production code changed; macOS/Linux keep the Unix-socket path unchanged (
npm run local:gategreen on macOS).Follow-up filed
clients/cliload-interrupt-e2eSIGTERM case exits by signal under machine load. Unrelated to this change (it touches noclients/clicode); it failed one local gate run here and is now tracked.🤖 Generated with Claude Code