MCP stdio server fails to start at session init with no error surfaced (silent no-spawn)
Suggested title
MCP stdio server fails to start at session init with no error surfaced (silent no-spawn)
Repro environment
- Claude Code CLI, headless (
-p/ non-interactive) and interactive both affected. - One or more MCP servers configured as
stdiotransport in.mcp.json(e.g. a
uv run-wrapped Python server, an npx-wrapped Node server, or an ssh-proxy
stdio server).
Steps to reproduce
- Configure an MCP
stdioserver whose launch command will fail at startup —
the most realistic trigger is an invocation that resolves an interpreter
outside the server's dependency environment, e.g.
``jsonc``
// .mcp.json — command resolves a bare interpreter, not the project venv
{
"mcpServers": {
"playwright": {
"command": "uv",
"args": ["run", "--with", "mcp-server-playwright",
"python3.12", "/path/to/server.py"]
}
}
}
Here python3.12 is the system interpreter, so the child starts outside the
--with venv and dies immediately with ModuleNotFoundError: No module named. (Any startup crash reproduces the class of bug — bad path, missing
'mcp'
dependency, non-zero exit during handshake, etc.)
- Start a Claude Code session that uses this config.
- Ask the model to use a tool provided by that MCP server.
Expected behavior
Claude Code surfaces a clear, actionable diagnostic that the MCP server failed to
start — ideally the child process's captured stderr and a line such asMCP server "playwright" exited during startup (code 1): <stderr tail> — either
in the session UI, in --debug output, or in a session log file.
Actual behavior
The failure is silent. The session starts normally, the tools from that
server are simply absent from the tool list, and:
- No error is printed in the session.
- No warning is emitted in normal output.
- The model proceeds as if the server were never configured, often falling back
to other tools (e.g. shell grep/read) with no indication a capability was
lost.
From the operator's side the symptom is indistinguishable from "the model chose
not to use the tool," which makes it very hard to diagnose. The only reliable
detection we found is out of band: enumerate the Claude process's children
and check whether the expected server subprocess is present, e.g.
pgrep -P "$CLAUDE_PID" # MCP stdio servers appear as direct children
# cross-reference each child's cmdline against the configured server command;
# a configured server with no matching child = silent no-spawn.
Why this matters
A silent missing tool degrades the session invisibly. In automated / scheduled
runs there is no human watching the first turn, so the loss is only noticed much
later (or never), and every downstream result is produced with a silently reduced
toolset. A single startup-stderr line would turn a multi-hour misdiagnosis into a
five-second fix.
Suggested fix
- Capture the MCP child's stderr during the init handshake.
- On non-zero exit / handshake timeout / spawn failure, emit a visible
MCP server "<name>" failed to start: <reason> diagnostic (session UI +
--debug + any session log).
- Optionally expose configured-vs-running MCP status via a command
(/mcp status or similar) so operators can assert presence programmatically
instead of resorting to pgrep.
Detection pattern we deployed as a workaround
A periodic watchdog (10-min sweep) that, per live Claude session, diffs the set
of configured MCP servers against the set of actually-spawned child
processes (matched by a per-server cmdline marker, since wrappers likeuv/npx/ssh mean the child cmdline rarely equals the configured command).
A configured server with no matching child for longer than a startup grace
window is flagged as a silent no-spawn. This is a reasonable operator mitigation
but it should not be necessary — the runtime already knows the child exited.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗