Back to all posts
7 min read

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

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:

  1. I asked Claude to lint my article using Dictator’s MCP tools
  2. Claude called mcp__dictator__occupy — the initialization tool
  3. Dictator sent tools/listChanged notification to refresh the tool list
  4. Claude Code ignored it
  5. Claude noticed the tools did not appear
  6. Claude took initiative: ran dictator lint via Bash as a workaround
  7. Claude then offered: “Want to use the MCP tools? You’ll need to refresh”
  8. I ran /mcp to reconnect
  9. Only then did stalint and dictator tools 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:

  1. Ran occupy as expected
  2. Noticed tools did not refresh
  3. Informed me the spec was not being followed
  4. Waited for my manual /mcp reconnect
  5. 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:

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 listChanged capability 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:

  1. Start with minimal tools (setup, configuration)
  2. Wait for initialization
  3. Expose the actual working tools based on context

Dictator does exactly this:

PhaseAvailable Tools
No .dictate.tomloccupy 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:

  1. Server sends notification
  2. Client receives notification
  3. 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:

  1. Claude Code ignores tools/listChanged
  2. Server developers notice their dynamic tools do not work
  3. They give up and expose all tools at startup
  4. New developers see existing servers dumping 40 tools
  5. They copy the pattern
  6. “Best practice” becomes “dump everything”
  7. Nobody uses tools/listChanged anymore
  8. 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/listChanged anyway
  • 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 /mcp to 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:

Clienttools/listChangedNotes
Claude Code ≥ 2.1.0✅ FixedShipped in 2.1.0
Charm Crush✅ Always worked
Opencode✅ FixedPR #5913 merged
OpenAI Codex❌ Won’t accept the fixInvitation-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!

• Link to this post from your site• Share your thoughts via webmention• Join the IndieWeb conversation

Related Posts

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.

AILLMengineering