[BUG] Desktop app never rebuilds its deferred-tool pool: tools/list_changed and RefreshMcpTools are both no-ops (CLI 2.1.237 works with the identical server — carve-out from #66084)

Status Open
Reported on v2.1.229
Maintainer reply None cached
Activity 0 comments · opened Aug 21, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

In Claude Code Desktop app sessions, the deferred-tool pool that backs ToolSearch is snapshotted at session start and never rebuilt. A server that changes its tool surface at runtime and emits notifications/tools/list_changed has no effect on the session: ToolSearch keeps returning tools the server has already removed, and calls fail because the host and the server have diverged.

The explicit escape hatch does not help either. RefreshMcpTools (behind CLAUDE_CODE_ENABLE_REFRESH_MCP_TOOLS) returns a failure for every connected server, including the first-party servers the app ships with.

This is filed as a carve-out from #66084 at the maintainer's suggestion there. #66084 was diagnosed by @bcherny on 2026-08-15 as a capability-advertisement bug: the client only subscribes to tools/list_changed when the server advertises capabilities.tools.listChanged: true, which FastMCP does not do by default. That diagnosis is correct for the native CLI, and his comment explicitly flagged the Desktop-app reports as possibly out of scope:

Note the Desktop-app report above (2.1.229) is a different host path and may not be covered by the same fix.

This issue confirms that. The Desktop failure is not capability-related: the server here does advertise listChanged, and the same server on the same machine works correctly under the native CLI on the same day.

Closely related and independently reported: #86284 — the Desktop app ignores mcpServers.alwaysLoad: true while the CLI honours the identical config. Different symptom (initial load vs. runtime refresh), same divergence between the Desktop host's deferred-tool pool and the CLI's.

What Should Happen?

Two things, in order of importance:

  1. A tools/list_changed notification from a server that advertises the capability should rebuild the deferred-tool pool in Desktop sessions, as it already does in the native CLI.
  2. RefreshMcpTools should reach the deferred-tool pool. As an explicit, user-triggered refresh it is the natural fallback when the notification path fails, and today it fails for every server.

Error Messages/Logs

Calling a tool that ToolSearch still offers after the server removed it:

{"error":{"kind":"toolset_not_loaded","tool":"search_templates","toolset":"templates"},
 "message":"Tool 'search_templates' is in toolset 'templates' which is not currently loaded."}

RefreshMcpTools with no argument, covering all 10 connected servers in that session. Every one returned:

refreshed the server, but the live tool pool was not updated — the server may have been
removed or disconnected while the refresh was in flight, or its tools are not managed in
this session mode; if it is still configured, the refreshed list applies on the next pool
rebuild

Steps to Reproduce

Any stdio MCP server that changes its tool surface at runtime and advertises capabilities.tools.listChanged: true reproduces this. Used here: Konnect v0.6.1, which exposes 203 tools across 19 toolsets and can load/unload them at runtime.

In a Desktop app session:

  1. unload_toolset("templates") — server confirms, emits notifications/tools/list_changed
  2. ToolSearch for the removed tools — they are still listed (all 4 of them)
  3. Call one of them — fails with toolset_not_loaded
  4. RefreshMcpTools — returns the failure quoted above, for every server
  5. ToolSearch again — unchanged

The same probe against the native CLI, same day, same 2.1.237 binary, same server:

claude -p "<probe>" --mcp-config mcp.json --strict-mcp-config --debug-file debug.log

| Step | Desktop app | Native CLI |
|---|---|---|
| unload_toolset("templates") | ok | ok |
| ToolSearch for search_templates | still FOUND | NOTFOUND |
| load_toolset("templates") | ok | ok |
| ToolSearch for search_templates | FOUND | FOUND |
| call search_templates(query="ldo") | toolset_not_loaded | returns ldo_3v3 |

The CLI debug log shows the notification being honoured, twice, once per toolset change:

[DEBUG] MCP server "konnect": Received tools/list_changed notification, refreshing tools

No equivalent handling is observable on the Desktop side.

Claude Model

Opus

Is this a regression?

No — it has behaved this way in every Desktop build tested (2.1.229 on 2026-08-14 and 2026-08-18, 2.1.237 on 2026-08-21).

Last Working Version

_No response_

Claude Code Version

2.1.237, bundled with Desktop app 1.34493.1.0

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other — Claude Code Desktop app (there is no Desktop option in this dropdown; the CLI half of the A/B ran in PowerShell)

Additional Information

Why there is no workaround for this class of server. #66084 lists "/mcp reconnect or session restart" as the workaround. That does not apply to a server that lazy-loads its own tools: restarting the session restarts the server process, which comes back with only its small starter kit, and re-running load_toolset reproduces the bug immediately. The only way to use such a server in the Desktop app is to force it to register everything before the first tools/list — 203 tool schemas, roughly 25K tokens, which is precisely what the lazy-loading design exists to avoid.

Scope note. The RefreshMcpTools result covering all 10 servers — including first-party ones — suggests this is a property of the Desktop host's session mode rather than anything server-specific. Together with #86284, where the same host ignores alwaysLoad: true on a config the CLI honours, it points at the Desktop app's deferred-tool pool as the common component.

Suggested labels: area:desktop, area:mcp, platform:windows.

View original on GitHub ↗