Skip to content

CLI's long-lived stream path can crash with an uncaught EPIPE #2412

Closed

Description

@CloneOfAlex

Which version line?

v2 — current (@modelcontextprotocol/inspector@latest)

Which client?

CLI

Inspector version

2.7.0 (git tag) — static code-review finding, not run locally

Node version

N/A — static code review, no live run performed

Operating system (and browser, for the web client)

N/A — static code review

Transport

stdio

MCP server under inspection

N/A — this is a static code-review finding against the 2.7.0 tag source (clients/cli/src/handlers/consume-outcome.ts), not a live reproduction against a running MCP server.

Steps to reproduce

Found via static review of the 2.7.0 tag source, not a live run.

  1. clients/cli/src/handlers/consume-outcome.ts carries its own TODO(#1432) acknowledging the stream path doesn't yet handle EPIPE/stdout errors.
  2. A very ordinary shell habit — piping CLI output to head, grep, or less — closes stdout early.
  3. Against a subscribe/stream method, this can throw an uncaught EPIPE and crash the CLI process instead of exiting cleanly.

I have not run mcp-inspector --cli <server> --method <a subscribe/stream method> | head -n 5 live to observe the crash; flagging based on the code's own acknowledged TODO. I reviewed issue #1432 and this looks like a narrower, still-open gap within that broader CLI-v2 epic, not a duplicate of it.

Expected behavior

Piping CLI output into a program that exits early (head, grep, less) against a subscribe/stream method causes the CLI to exit cleanly — the same common pattern that's already handled for non-streaming output.

Actual behavior

The long-lived stream consumer path (clients/cli/src/handlers/consume-outcome.ts) does not yet handle EPIPE/stdout errors, per its own TODO(#1432), so it can throw an uncaught EPIPE and crash instead of exiting gracefully.

Suggested fix: handle EPIPE on stdout (a common pattern: swallow it and exit 0) in the long-lived stream consumer path referenced by TODO(#1432).

Logs, errors, or screenshots

No response

Already prototyped a fix?

No response

Before you submit

  • I searched existing issues and this is not a duplicate.
  • This is not a security vulnerability report (those go through the private advisory process).

Activity

  1. cliffhall commented on Sep 18, 2026

    @cliffhall
    Member

    Triage: Priority Medium (total 7)

    • Severity 3 — a real crash on an ordinary shell habit (piping to head/less); the workaround is to avoid piping
    • Urgency 3 — the code carries its own TODO(#1432), so it has been known and deferred
    • Bonuses: +1 bug label

    Labels applied at triage: v2.

    Board: #28, Status Incoming (awaiting maintainer review — no milestone yet).

  2. SeojunKim-pumisj commented on Sep 21, 2026

    @SeojunKim-pumisj

    I prototyped this locally from v2/main, following the repo's issues-only contribution policy (no PR opened or branch pushed).

    Prompt used to drive the local prototype

    Work on MCP Inspector issue #2412 on v2/main. Reproduce the long-lived CLI stdout EPIPE path, implement a minimal scoped fix that treats EPIPE as clean completion while propagating non-EPIPE stdout errors, clean up the subscription plus SIGINT/SIGTERM/stdout listeners on every exit, and add CLI tests that meet the repository's per-file 90% coverage gate. Follow the repository contribution policy and do not open a PR.

    Before

    The long-lived stream branch registers only signal handlers. A closed downstream stdout pipe has no error listener, so an EPIPE can become an uncaught stream error.

    Prototype behavior

    • Register the stdout error listener before starting the subscription.
    • On EPIPE, stop the subscription, remove the signal/stdout listeners, and resolve normally.
    • On another stdout error, perform the same cleanup and reject into the CLI's existing error path.
    • If subscription setup throws, remove all newly registered listeners before propagating the failure.

    I reproduced the failure path at unit level by emitting an EPIPE on process.stdout; the regression test verifies clean completion, one unsubscribe call, and listener cleanup. Separate tests cover non-EPIPE propagation and setup failure cleanup.

    Verification (Windows, Node 22.20.0)

    • CLI format/lint/typecheck: pass
    • CLI coverage suite: 29 files / 391 tests pass
    • consume-outcome.ts: 97.36% statements, 90% branches, 100% functions, 100% lines
    • Root npm run format: pass with no unrelated source changes

    I also attempted npm run local:gate. It reached the root script tests, then failed/hung in unrelated Windows scripts/gate-lease.test.mjs cases (for example, a test expected POSIX /x/y but received C:\x\y, and signal-exit assertions expected numeric POSIX exit codes). I stopped that stalled gate after capturing the failures; the CLI-scoped checks and coverage above are green.

  3. added this to the v2.9.0 milestone on Sep 23, 2026
  4. self-assigned this
    on Sep 24, 2026
  5. cliffhall commented on Sep 24, 2026

    @cliffhall
    Member

    Fixed by #2479 (merged to v2/main in eec0349).

  6. added a commit that references this issue on Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingv2Issues and PRs for v2

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions