MCP tools/list_changed notifications not honored in headless --print --output-format stream-json mode

Open 💬 4 comments Opened May 27, 2026 by freeznet

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_changed mid-session (e.g. when a tenant/context switch dynamically expands the tool set)

Reproduction

  1. Configure an MCP server that exposes some base tools at initialize time, then dynamically adds new tools mid-session (e.g. after a "select tenant/cluster/context" tool call) and emits notifications/tools/list_changed.
  2. 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
``

  1. 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
  1. Inspect the stdout stream-json transcript.

Expected

  • Either: stream-json emits some tool_list_updated / deferred_tools_delta envelope when list_changed arrives between turns
  • Or at minimum: the next turn's system/init envelope's tools array includes the newly added tools and mcp_servers[].status reflects the refreshed inventory

Actual

  • No tool-update event is emitted on stdout between turns
  • Every turn's system/init re-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 after list_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 claude process 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 gaps
  • v2.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:

  1. Document that headless --print --output-format stream-json does not honor list_changed and recommend a different entry point (Agent SDK?) for programmatic long-lived sessions with dynamic MCP tool sets, or
  2. 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.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗