MCP tools/list_changed notifications not honored in headless --print --output-format stream-json mode
Summary
When Claude Code runs in headless mode via claude --print --input-format stream-json --output-format stream-json --verbose and is driven as a long-running process (stdin kept open, multiple user events fed across turns), MCP servers' notifications/tools/list_changed notifications do not refresh the in-process tool registry. Subsequent turns' system/init envelopes continue to advertise the original tool snapshot.
The same MCP server, when connected from interactive claude (TUI mode), correctly handles list_changed and the session JSONL at ~/.claude/projects/.../<session-id>.jsonl records attachment.type=deferred_tools_delta events with the new tool names.
Environment
- Claude Code:
2.1.152 - OS: macOS (Darwin 25.5.0); also reproduces in a Linux container
- MCP transport: streamable HTTP (also expected to repro over stdio)
- MCP server: any server that emits
notifications/tools/list_changedmid-session (e.g. when a tenant/context switch dynamically expands the tool set)
Reproduction
- Configure an MCP server that exposes some base tools at
initializetime, then dynamically adds new tools mid-session (e.g. after a "select tenant/cluster/context" tool call) and emitsnotifications/tools/list_changed. - Start Claude Code in headless persistent mode:
``bash``
claude --print \\
--input-format stream-json \\
--output-format stream-json \\
--verbose \\
--mcp-config /path/to/mcp.json
- Feed two user events on stdin, one per \"turn\":
- Turn 1: ask Claude to call the tool that triggers
list_changed - Turn 2: ask Claude to list available tools, or to call one of the newly added tools
- Inspect the stdout stream-json transcript.
Expected
- Either: stream-json emits some
tool_list_updated/deferred_tools_deltaenvelope whenlist_changedarrives between turns - Or at minimum: the next turn's
system/initenvelope'stoolsarray includes the newly added tools andmcp_servers[].statusreflects the refreshed inventory
Actual
- No tool-update event is emitted on stdout between turns
- Every turn's
system/initre-declares the same tool list that existed at session start (post-initialize handshake); newly added tools never appear - Calling a newly added tool in Turn 2 fails with a \"tool not found\"-style error
Sample evidence
Across 5 system/init events in one persistent-session transcript (one per turn):
| init # | tool count | notes |
| --- | --- | --- |
| 0 | 30 | session start, MCP still pending |
| 1 | 64 | MCP connected, base tools loaded |
| 2 | 64 | after list_changed (should have grown) |
| 3 | 64 | unchanged |
| 4 | 64 | unchanged |
The same MCP server connected from interactive claude produces an attachment event of type deferred_tools_delta in ~/.claude/projects/.../<session-id>.jsonl containing the new tool names.
Workarounds tried
claude --print --resume <session-id> \"<prompt>\"per turn (one process per turn, resuming the IDE session JSONL): same issue, refreshed tools do not appear afterlist_changed- Sending an explicit \"please refresh tools\" prompt in the persistent session: no effect (Claude has no way to force a re-read of the MCP tool list)
- Tearing down and re-spawning the entire
claudeprocess between turns works, but defeats the purpose of a persistent session and loses prompt/KV cache locality
Related changelog hints
v2.1.121: \"MCP server reconfiguration works in headless mode\" — suggests headless MCP paths have had historical gapsv2.1.128: \"tool re-announcement summarized by server prefix\" — internal mechanism exists, but doesn't appear to surface in headless stream-json output
Ask
Either:
- Document that headless
--print --output-format stream-jsondoes not honorlist_changedand recommend a different entry point (Agent SDK?) for programmatic long-lived sessions with dynamic MCP tool sets, or - Make headless mode behave consistently with interactive mode — refresh the in-process tool registry and surface the change in the next turn's
system/init(and ideally as a discrete event on the stream-json output).
Happy to provide a minimal MCP server reproduction repo if helpful.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗