[BUG] -p/--print: stdio MCP servers stay "pending" — tool snapshot races server startup and never refreshes (regression 2.1.143 → 2.1.162)

Status Fixed / completed
Reported on v2.1.143
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jul 6, 2026 · closed Aug 16, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

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/list round-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: ... - ✓ Connected on the same broken CLI versions.
  • Wrapping the server command in a logging shim shows -p does spawn the process with correct args; the server's stderr is clean.
  • Driving the server manually with the full initialize → notifications/initialized → tools/list sequence 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.json and no --strict-mcp-config, every configured MCP server (five different ones, stdio and remote) shows pending in -p mode.

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.

View original on GitHub ↗

3 Comments

bkempen3 · 2 months ago

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-config with 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 -p invocations 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. --debug output shows the MCP connection succeeding (Successfully connected (transport: http), capabilities reported) but no tool_dispatch_start for 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 -p call 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 -p processes, matching this issue's "specific to -p" note.

Workaround in use: detect the characteristic "trouble reaching / tool glitch" apology text and retry the whole -p invocation (up to 3x). Meaningfully improves the odds but doesn't eliminate the failure, and adds real latency.

(Separately, I also found --tools <list> silently drops mcp__<server>-style entries when given an explicit list -- results in zero MCP tools available with no error. --disallowedTools deny-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.)

bcherny collaborator · 1 month ago

Thanks for the detailed bisect. This was fixed in 2.1.221 — print mode (-p) now waits for --mcp-config servers 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 saw pending and 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/init reports mcp_servers: [{"name":"myserver","status":"connected"}] and the server's mcp__myserver__* tools appear in tools every 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 pending on 2.1.221 or later and we'll reopen.

🤖 Generated with Claude Code

github-actions[bot] · 10 days ago

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.