tools/listChanged Is a Bug, Not a Feature: What Claude Code Gets Wrong

TL;DR: While writing Part 1 of this series, I watched Claude Code ignore the MCP spec in real-time. Dictator sent tools/listChanged. Claude Code did nothing. I had to manually run /mcp to reconnect. The spec says clients SHOULD refresh their tool list. Claude Code says “nah.” This is not a missing feature — it is a bug dressed up as an implementation choice.
The Live Demonstration¶
Here is what happened while I was writing Part 1:
- I asked Claude to lint my article using Dictator’s MCP tools
- Claude called
mcp__dictator__occupy— the initialization tool - Dictator sent
tools/listChangednotification to refresh the tool list - Claude Code ignored it
- Claude noticed the tools did not appear
- Claude took initiative: ran
dictator lintvia Bash as a workaround - Claude then offered: “Want to use the MCP tools? You’ll need to refresh”
- I ran
/mcpto reconnect - Only then did
stalintanddictatortools appear
This is not theoretical. This happened in the same conversation where I was explaining why the ecosystem ignores the spec. The irony writes itself.
Credit Where Due: Claude Adapted¶
Here is what Claude did not do: file a JIRA ticket saying “taking day off, tools did not show up.”
Instead, Claude:
- Ran
occupyas expected - Noticed tools did not refresh
- Informed me the spec was not being followed
- Waited for my manual
/mcpreconnect - Completed the task once tools appeared
Claude understood the expectation (it had just read my previous MCP posts). It recognized the failure. It worked around it. No hallucination. No giving up. No blaming the server.
This is the difference between a smart model and a dumb client. The model adapted. The client failed.
And if Dictator had been like the gh CLI — requiring authentication that failed — Claude would have adapted there too. Generate a token, pass it via env vars, fall back to curl calling the GitHub API directly. Opus would handle it elegantly. Haiku would brute-force it. Either way: task completed.
The model is not the problem. The client ignoring tools/listChanged is the problem.
What the Spec Says¶
This is not a new feature. tools/listChanged has been in the MCP spec since November 2024 — over a year ago:
- 2024-11-05 — The Original Voyager
- 2025-03-26
- 2025-06-18
- 2025-11-25 — Current
- Draft — Still there
Five spec versions. Same capability. Still ignored.
From the current spec:
Capabilities declaration:
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
List Changed Notification:
When the list of available tools changes, servers that declared the
listChangedcapability SHOULD send a notification:
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
The Message Flow (straight from the spec):
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Updates
Server--)Client: tools/list_changed
Client->>Server: tools/list
Server-->>Client: Updated tools
The spec is explicit. Server sends tools/list_changed. Client sends tools/list. Client gets updated tools. Three steps. Drawn in a diagram. In the official spec.
Why Dynamic Tools Matter¶
The whole point of tools/listChanged is progressive disclosure. Instead of dumping 40 tools on the LLM at startup (burning context, confusing the model), a well-designed MCP server can:
- Start with minimal tools (setup, configuration)
- Wait for initialization
- Expose the actual working tools based on context
Dictator does exactly this:
| Phase | Available Tools |
|---|---|
No .dictate.toml | occupy only |
After occupy (config exists) | stalint, dictator, stalint_watch |
This is not complexity for complexity’s sake. It is:
- Token efficiency: The LLM does not see tools it cannot use yet
- Context awareness: Tools appear when they are relevant
- Safety: Destructive tools only appear after git repo is confirmed
- Cleaner UX: Less noise, more signal
The Claude Code Issues¶
This is not an unknown problem. I have filed issues myself:
Claude Code (all still open):
- #3315 - MCP roots capability advertised but roots/list not implemented
- #3174 - MCP notifications/message received but not displayed
- #3141 - MCP Resources Pagination Support
OpenAI Codex:
- #7635 - MCP tools don’t respect sandboxing (closed)
The pattern: file issue, wait, silence, workaround. OpenAI closed mine. Anthropic’s are still pending.
Meanwhile, Anthropic ships Skills and the ecosystem celebrates markdown files while the underlying protocol remains half-implemented.
What Charm Got Right¶
Charm’s Crush implements tools/listChanged correctly. When the server sends a notification, Crush refreshes the tool list. No manual reconnect. No user intervention. Just… working as specified.
You can log in with your Claude account through Crush and use the same MCP servers. The difference: Crush respects the spec.
This is not a moonshot feature. It is event handling 101:
- Server sends notification
- Client receives notification
- Client refreshes state
Three steps. Charm implemented it. Anthropic’s own client did not.
The Real Cost¶
When Claude Code ignores tools/listChanged:
For users:
- Manual reconnects break flow
- Confusion when tools do not appear
- “Is my server broken?” debugging sessions
- Workarounds become muscle memory
For server developers:
- Dynamic tool registration becomes useless
- Progressive disclosure is impossible
- Every server must expose everything upfront
- Token efficiency goes out the window
For the ecosystem:
- Servers designed for dumb clients only
- Innovation stalls at lowest common denominator
- The spec becomes aspirational, not operational
The Vibe-Coded MCP Circle¶
Here is how the ecosystem perpetuates bad patterns:
- Claude Code ignores
tools/listChanged - Server developers notice their dynamic tools do not work
- They give up and expose all tools at startup
- New developers see existing servers dumping 40 tools
- They copy the pattern
- “Best practice” becomes “dump everything”
- Nobody uses
tools/listChangedanymore - Claude Code team: “See? Nobody uses it. Low priority.”
This is how specs die. Not with a bang, but with a thousand workarounds.
The Fix Is Simple¶
// When server sends tools/listChanged
client.on('notifications/tools/list_changed', async () => {
const tools = await client.request('tools/list');
updateAvailableTools(tools);
});
That is it. Event listener. Request. Update state. This is not distributed systems complexity. This is not consensus algorithms. This is a notification handler.
If Charm can do it, Anthropic can do it.
What You Can Do¶
If you are a server developer:
- Implement
tools/listChangedanyway - Document that Claude Code users need to manually reconnect
- File issues, even if they go ignored
- Support clients that actually implement the spec
If you are a Claude Code user:
- When tools are missing, try
/mcpto reconnect - Know this is a client bug, not your server
- Star the relevant GitHub issues
- Use Crush if you need proper MCP support
If you work at Anthropic:
- Read your own spec
- Implement the notification handler
- It is literally a few lines of code
- Stop shipping Skills while the foundation crumbles
Conclusion¶
The MCP spec is well-designed. It handles dynamic tool registration elegantly. It enables progressive disclosure. It supports context-aware tool exposure.
Claude Code ignores this.
Not because it is technically hard. Not because the spec is unclear. But because shipping new features (Skills!) is sexier than fixing existing ones.
Meanwhile, I am here manually running /mcp every time I want to use a properly-designed MCP server. Because the reference client does not follow the reference spec.
tools/listChanged is not a feature request. It is a bug report.
Fix it.
This is Part 2 of the MCP series. Part 1: Skills Are Not Skills
Update — February 2026¶
Claude Code fixed it. As of version 2.1.0, Claude Code now handles notifications/tools/list_changed correctly. No more manual /mcp reconnects. The bug is gone.
OpenAI Codex is a different story.
I submitted PR #12449 implementing the exact same fix — an OnToolListChanged callback with atomic tool list updates via Arc<RwLock<>>. The implementation was correct, included integration tests, and addressed a real spec gap.
It was closed. Not because the code was wrong. Because “all code contributions are by invitation only.” The maintainer noted they prioritize work by community upvotes — the linked issue had zero at the time.
So to summarize the current state:
| Client | tools/listChanged | Notes |
|---|---|---|
| Claude Code ≥ 2.1.0 | ✅ Fixed | Shipped in 2.1.0 |
| Charm Crush | ✅ Always worked | — |
| Opencode | ✅ Fixed | PR #5913 merged |
| OpenAI Codex | ❌ Won’t accept the fix | Invitation-only contributions |
Anthropic shipped the fix. Opencode merged the PR within days. OpenAI closed theirs — same feature, same spec, different answer. The spec has been around since November 2024.
🔗 Interstellar Communications
No transmissions detected yet. Be the first to establish contact!
Related Posts
Skills Are Not Skills: The MCP Misunderstanding Nobody Wants to Correct
Skills are tutorials. MCP servers are executables. One tells Claude what to do. The other does it. The difference matters, and the ecosystem is lying to you about it.
The Lobotomy Pipeline: What Happens When AI Removes All Friction
LLMs are exoskeletons. They amplify what you already have. The problem: people are using them as a replacement for building anything at all. No friction, no learning, no muscle — just confident parrots shipping half-tested code.
Helmsman: Stop Writing AGENTS.md That Lies to Half Your Models
Your static instruction file works for Claude Opus and breaks for Claude Haiku. Helmsman serves model-aware instructions that adapt to capability tiers, environment, and project context.