[BUG] -p/--print: stdio MCP servers stay "pending" — tool snapshot races server startup and never refreshes (regression 2.1.143 → 2.1.162)
Summary
In headless print mode (claude -p), stdio MCP servers configured via --mcp-config are spawned but never become usable: the system/init envelope reports mcp_servers[].status: "pending" with zero MCP tools in the tool registry, and the registry never refreshes for the rest of the session, so the model runs the entire session without the configured MCP tools.
This is a regression between 2.1.143 (works: status: "connected", all server tools present at init) and 2.1.162 (broken). Still broken in 2.1.201.
Interactive mode and claude mcp list connect to the same server on the same config without issue — this is specific to -p.
Environment
- Claude Code: 2.1.162 and 2.1.201 (broken); 2.1.143 (works) — same machine, same config, same server binary
- OS: Linux 7.0.0-27-generic
- MCP transport: stdio (local binary)
- Server: a Rust MCP server (rust-mcp-sdk 0.9) exposing 24 tools;
initialize→notifications/initialized→tools/listround-trip completes in <1s when driven manually
Reproduction
mcp.json:
{
"mcpServers": {
"myserver": {
"command": "/path/to/mcp-server-binary",
"args": ["--mcp", "-w", "/path/to/workspace"]
}
}
}
printf 'which tools do you have? names only' | claude -p \
--output-format stream-json --verbose --model haiku \
--dangerously-skip-permissions --no-session-persistence \
--strict-mcp-config --mcp-config mcp.json
Inspect the system/init event.
Expected
mcp_servers: [{"name":"myserver","status":"connected"}] and the server's tools present in tools (this is exactly what 2.1.143 emits for the same command).
Actual (2.1.162, 2.1.201)
mcp_servers: [{"name":"myserver","status":"pending"}], zero MCP tools in tools, and the model answers the turn using only built-in tools. No error output even with --debug.
Evidence that the server is healthy and actually spawned
claude mcp list(same config via project.mcp.json):myserver: ... - ✓ Connectedon the same broken CLI versions.- Wrapping the server command in a logging shim shows
-pdoes spawn the process with correct args; the server's stderr is clean. - Driving the server manually with the full
initialize→notifications/initialized→tools/listsequence returns all 24 tools in well under a second. - Ruled out:
--bare, prompt via stdin vs argument,MCP_TIMEOUT, sanitized environment (env -i), working-directory trust state, config passed as file path vs inline JSON vs project-scope.mcp.json. With a project-scope.mcp.jsonand no--strict-mcp-config, every configured MCP server (five different ones, stdio and remote) showspendingin-pmode.
Version bisect (same machine, same config, same server binary)
| CLI | system/init mcp_servers[].status | server tools at init |
|---|---|---|
| 2.1.143 | connected | 24 |
| 2.1.162 | pending | 0 |
| 2.1.201 | pending | 0 |
Likely mechanism / related
It looks like -p mode now snapshots the tool registry before MCP connections complete, and nothing refreshes the snapshot afterwards. #62844 documents the no-refresh half (tools/list_changed not honored in --print mode); this report is about the initial connection racing the snapshot, which combined with the no-refresh means stdio MCP is effectively unusable in headless mode on affected versions.
Impact
Any headless/scripted claude -p pipeline that depends on stdio MCP tools silently degrades: sessions run and "succeed" with built-in tools only, with no error surfaced. We caught it only because a benchmark smoke gate asserted mcp_tool_calls > 0.
3 Comments
Confirming this affects HTTP-transport MCP servers too, on Windows, still present in 2.1.220 — but presenting as intermittent rather than permanently "pending", which fits the "tool snapshot races server startup" theory well: an HTTP handshake's timing is more variable than a local stdio spawn, so it sometimes wins the race and sometimes doesn't, rather than losing every time.
Setup:
--mcp-configwith two servers -- one HTTP-transport (remote, bearer auth), one stdio-transport (local Python script) -- plus--strict-mcp-config,--disallowedTools(deny-list built-ins),--permission-mode bypassPermissions,--no-session-persistence,--output-format text.Observed: repeated fresh
claude -pinvocations against the same prompt/config fail roughly 40-60% of the time to use the HTTP server's tool. The process exits 0; the model just answers in-band that it "hit a tool glitch" rather than the CLI surfacing an error.--debugoutput shows the MCP connection succeeding (Successfully connected (transport: http), capabilities reported) but notool_dispatch_startfor the tool follows in a failing run. Querying the same HTTP MCP endpoint directly (bypassing the CLI) responds correctly in ~150ms at the exact moment a-pcall is failing, ruling out server-side slowness.The local stdio server in the same config was reliable every time in my testing (Python/FastMCP, fast startup) -- consistent with the race-condition theory: it wins the startup race often enough on my machine/server not to show the "always pending" symptom this issue describes, while the HTTP server's connection timing is variable enough to lose the race intermittently.
Ruled out on my end as contributing factors: entity/data volume behind the HTTP server, auth method (long-lived token vs. interactive session credentials both reproduce it), and
ToolSearch/deferred-tool-discovery on vs. off. A long-running interactive session against the same MCP server/account is reliable every time -- this is specific to fresh-pprocesses, matching this issue's "specific to -p" note.Workaround in use: detect the characteristic "trouble reaching / tool glitch" apology text and retry the whole
-pinvocation (up to 3x). Meaningfully improves the odds but doesn't eliminate the failure, and adds real latency.(Separately, I also found
--tools <list>silently dropsmcp__<server>-style entries when given an explicit list -- results in zero MCP tools available with no error.--disallowedToolsdeny-listing doesn't have this problem and was used as a workaround. Happy to file that as its own issue if useful, since it seems unrelated to the race condition here.)Thanks for the detailed bisect. This was fixed in 2.1.221 — print mode (
-p) now waits for--mcp-configservers to finish connecting before the first turn (previously it snapshotted the tool list after a short 2-second budget and skipped servers slated for deferred loading, which is why single-turn headless runs sawpendingand no MCP tools). Changelog: https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md (see 2.1.221).I re-ran your exact command on the current release, 2.1.233, against a stdio MCP server (and again with a server that deliberately sleeps 4 seconds before starting):
system/initreportsmcp_servers: [{"name":"myserver","status":"connected"}]and the server'smcp__myserver__*tools appear intoolsevery time.@bkempen3 — your intermittent HTTP case was on 2.1.220, just before the fix; please retry on 2.1.221+ and open a new issue if it still happens (and yes, please file the
--tools mcp__…dropping separately).Closing as fixed — reply here if you still see
pendingon 2.1.221 or later and we'll reopen.🤖 Generated with Claude Code
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.