Capture MCP Tools Changed notifications - notifications/prompts/list_changed notifications

Resolved 💬 28 comments Opened Jul 22, 2025 by AchintyaAshok Closed May 23, 2026
💡 Likely answer: A maintainer (localden, collaborator) responded on this thread — see the highlighted reply below.

Requesting to add support for https://modelcontextprotocol.io/specification/2024-11-05/server/tools#list-changed-notification.

Goal: when the MCP server emits a notification that its tools have changed, I would like Claude to refresh the tool list from the server and reflect the change in the /mcp menu for that server.

View original on GitHub ↗

29 Comments

coygeek · 10 months ago

Hey there,

This is a great suggestion. It would be a huge quality-of-life improvement for anyone using MCP servers that have dynamic tools.

I took a look through the latest documentation you provided to see if there was a manual workaround. From what I can tell, the current implementation seems to discover tools when the server is first connected.

The /mcp slash command lets you manage the connection and view the server's tools, but the docs don't mention a way to manually trigger a refresh of the tool list after the initial connection.

The MCP documentation page (en/docs/claude-code/mcp) mentions:

MCP prompts are dynamically discovered from connected servers.

And:

MCP commands are automatically available when: An MCP server is connected and active The server exposes prompts through the MCP protocol * The prompts are successfully retrieved during connection

This seems to confirm that the tool/prompt discovery happens at connection time. I couldn't find any mention of the notifications/tools/list_changed event specifically.

For now, I guess the only way to force a refresh would be to remove and re-add the server with claude mcp remove <server-name> and claude mcp add .... Not ideal, but it might get the job done if you know the tool list has changed.

Having Claude Code listen for that list_changed notification and automatically refresh would be the perfect solution. It would make integrations feel much more seamless.

Fingers crossed the dev team sees this and adds it to the roadmap

chipgpt · 10 months ago

I would like to see this as well. Currently there is support for dynamic tools in Cursor and VSCode but not in Claude Code.

stefancplace · 10 months ago

It would be amazing to use this feature 👍

kolkov · 9 months ago

Expert Implementation Approach for Dynamic MCP Tool Management

TL;DR

We've successfully implemented a comprehensive solution for this exact problem in our GODA MCP server (Go-based). Here's what we learned and recommend for Claude Code implementation.

Current Problem Analysis

Based on our research and implementation experience:

  1. Claude Code uses conversation-level caching - mcp_list_tools items persist for entire conversations
  2. notifications/tools/list_changed are sent but ignored by Claude Desktop/Code
  3. Current workaround requires manual /mcp reconnection or app restart
  4. VSCode and Cursor support this, but Claude Code doesn't

Our Working Solution

We implemented a server-side intelligent categorization system that works around Claude Code's limitations:

Architecture Overview

Server-Side (Go):
├── Tool Categorization (6 priority-based categories)
├── Dynamic Handler Registration/Deregistration
├── Environment-driven Configuration
└── MCP Notifications (for future compatibility)

Client Interaction:
├── set_tool_categories - Dynamic switching
├── get_tool_categories - Current status
├── Tool Profiles - Workflow-based presets
└── Proper handler cleanup on category changes

Key Technical Insights

1. Handler Persistence vs Tool Discovery

// CRITICAL: Must clear both tools AND handlers
s.tools = make(map[string]Tool)
s.handlers = make(map[string]HandlerFunc) // ← This is key!

2. Conversation-Level Cache Compatibility

// Send notification even though Claude Code ignores it (future-proof)
func (s *Server) notifyToolsListChanged() {
    notification := NewNotification("notifications/tools/list_changed", map[string]interface{}{})
    s.encoder.Encode(notification)
}

3. Token Optimization By Design

// Default: 38/81 tools (53% reduction), Minimal: 22/81 tools (75% reduction)
func GetEnabledCategories() []string {
    if envCategories == "" {
        return []string{"core", "checkpoint", "lighthouse"} // Smart defaults
    }
    // ... category parsing
}

Recommended Implementation for Claude Code

Phase 1: Basic Notification Support

// Listen for MCP notifications
onNotification('notifications/tools/list_changed', async () => {
    await this.refreshToolsList();
    this.invalidateConversationCache(); // Clear mcp_list_tools
});

Phase 2: Advanced Cache Strategy

interface MCPCacheStrategy {
    type: 'conversation' | 'session' | 'none';
    ttl?: number;
    invalidateOnNotification: boolean;
}

// Allow users to configure caching behavior
const config: MCPCacheStrategy = {
    type: 'session', // Cache per session, not conversation
    invalidateOnNotification: true
};

Phase 3: Smart Tool Management

// Server-side categorization support
interface ToolCategory {
    name: string;
    priority: number;
    enabled: boolean;
}

// Client-side filtering UI
const toolFilter = {
    categories: ['core', 'checkpoint'],
    maxTools: 25, // Performance limit
    autoOptimize: true
};

Real-World Performance Impact

Our implementation achieves:

  • 53% token reduction with lighthouse included (38/81 tools)
  • 75% token reduction in minimal mode (22/81 tools)
  • Immediate tool deactivation when categories disabled
  • Zero server restarts required for configuration changes

Testing Strategy

We verified handler deregistration works correctly:

# Tools available with lighthouse category
✅ mcp__goda__get_web_vitals() -> Success

# Switch to minimal categories
✅ set_tool_categories("core,checkpoint") -> 22 tools loaded

# Verify handler deregistration
❌ mcp__goda__get_web_vitals() -> "MCP error -32601: Tool not found"

Migration Path for Existing Servers

  1. Implement category system in your MCP server
  2. Add set_tool_categories command for dynamic switching
  3. Clear handlers alongside tools during category changes
  4. Send notifications for future Claude Code compatibility

Community Impact

This approach solves the broader MCP ecosystem problem:

  • Reduces context bloat (50k+ tokens issue mentioned in related issues)
  • Improves UX with workflow-based tool profiles
  • Maintains backward compatibility with existing MCP clients
  • Future-proofs for when Claude Code implements proper notification support

Code References

Full implementation available in our GODA project (Go):

  • Categories System: internal/mcp/categories.go
  • Handler Management: internal/mcp/server.go
  • MCP Notifications: notifyToolsListChanged() method
  • GitHub: Coming soon (will be open-sourced after initial release)

---

Would be happy to share more implementation details or collaborate on getting this into Claude Code core!

Quick Demo

If anyone wants to test this approach:

Option 1: Binary Download

# GODA binary with MCP categorization will be available soon
# Available for Windows, macOS, Linux
# Download link: TBD (after open-source release)

Option 2: Build from Source

# Source code will be available after initial release
# Requirements: Go 1.21+
# Build: go build -o goda ./cmd/goda/
# Run: ./goda mcp -serve

Configuration Example:

{
  "mcpServers": {
    "goda": {
      "command": "goda",
      "args": ["mcp", "-serve", "-headless=false"],
      "env": {
        "GODA_TOOL_CATEGORIES": "core,checkpoint,lighthouse"
      }
    }
  }
}

Try Dynamic Categorization:

# Tools change instantly, no restart needed
mcp__goda__set_tool_categories(categories: "core,lighthouse")
mcp__goda__set_tool_categories(categories: "*")  # All tools
mcp__goda__set_tool_categories(categories: "core,checkpoint")  # Minimal

The key insight: fix the problem at the server level while Claude Code catches up architecturally.

tbuechner · 9 months ago

@kolkov This requires a reconnect after modifying the categories, right?

bowlofarugula · 8 months ago

It's incredibly sad and confusing that Anthropic's own MCP clients don't support the basic discovery features of the protocol they created. Especially when those clients are proprietary so even if the community wanted to contribute that support, they can't.

How could MCP ever be taken seriously when only a fraction of the spec is supported even by its own authors?

It's not like this is a new feature. It was in the very first MCP spec version, front and center under the Tools documentation.

mattdholloway · 7 months ago

Just to bump this, this is something me and my colleagues working on the GitHub MCP Server would find very useful as, In our deployments, we occasionally change the tools list (eg. during tool consolidation.) At the moment Claude Code does not support this, but this would be a really useful addition to make the experience smoother for users with long running sessions so the LLM doesn't attempt to call non-existent/changed tools.

NubeBuster · 7 months ago

The LLM tool landscape truly is a developing wild west at this point.

Even the best tools available each have their own shortcomings of failures to implement basic standards.

I hope next year I can look back and laugh at this. I probably will.

temujin9 · 7 months ago

Independently discovered this, and offered similar suggestions for fix. Here's hoping the extra noise increases the chances.

boxabirds · 6 months ago

It'd be really great to see this part of the spec implemented by Claude Code because there are solutions out there (including my own) where descriptions of tools do change fairly regularly -- part of the problem is that they're called descriptions but they're tool spec PROMPTS and as such are subject to prompt engineering lifecycles rather than some static guidance that no one reads or pays attention to.

Combined with the fact that people tend to sit on Claude Code sessions for days if not weeks, well, lots of confused debugging can result (like today when I couldn't figure out why CC was using an old version of a description; I thought it was a caching bug, didn't expect it to be a CC caching bug!)

pablozamudio · 6 months ago

Hi, are there any updates on this issue? This limitation is a significant blocker for enabling dynamic tool discovery. It also poses a major challenge in preventing context pollution when MCP Servers manage a large number of tools.

pablozamudio · 5 months ago

Updated claude-code and now it seems to be working.
Also version 2.1.0 changelog notes support for list_changed notifications.

afucher · 5 months ago

It just works for the following prompt, if during the current prompt iteration the LLM needs to call a tool that was updated, Claude is not being able to see that tool and returns an error: Error: No such tool available: <YOUR_NEW_TOOL>
If I try to interact again with Claude, now it is able to see the tool and call it.

PhilippeKhalife · 5 months ago

Adding another real-world use case to support this request.

Scenario: MCP gateway that dynamically discovers agents from a message broker. Agents register via heartbeats after the gateway connects to the broker.

Problem: By the time agent discovery callbacks fire (milliseconds after init), Claude Code has already cached an empty tool list and ignores the tools/list_changed notification. Users see zero tools.

Current workaround: MCP server authors must implement polling/delay logic to wait for agents before completing initialization. This adds 10-15+ seconds of latency to every connection and is fragile.

Key point: Users who don't control the MCP server source code have NO workaround. They're stuck with broken tool discovery.

This is a fundamental MCP protocol feature - would be great to see it supported.

lpmwfx · 3 months ago

Real-world impact: MCP proxy/multiplexer rendered unable to dynamically manage servers

I built mcp-proxy — a Rust-based MCP multiplexer that sits between Claude Code and multiple downstream MCP servers. It aggregates tools from all servers with namespace prefixes, handles crash recovery, binary hot-reload, and graceful shutdown.

The original design included on-demand server loading: a catalogue of configured servers where Claude could start/stop individual servers mid-session via IPC. This was fully implemented and tested — the IPC works, servers spawn correctly, tools/list_changed is sent — but the new tools never appear in the session because of this exact bug.

What happens:

  1. mcp-proxy starts with 2 of 10 configured servers
  2. Claude requests loading a third server via IPC
  3. Server spawns successfully, tools/list_changed notification sent on stdout
  4. Claude Code silently ignores the notification
  5. New server's tools are invisible until session restart

Result: The entire catalogue/on-demand architecture had to be scrapped. All servers must now be loaded at startup, defeating the purpose of dynamic management. With the 128-tool hard cap and growing MCP ecosystems, this is a significant limitation.

A working tools/list_changed handler would unlock MCP multiplexers, lazy-loading proxies, and any server that dynamically registers new tools at runtime.

GrantSparks · 3 months ago

Confirming this is still an issue as of today. I maintain an MCP server that uses dynamic tool promotion — tools start deferred and are surfaced on demand via tools/list_changed. Server-side everything works correctly (tool list updates, notification fires, tools are callable via call_tool), but ToolSearch never picks up the newly promoted tools. The only workaround is bypassing list_tools() entirely.

The partial fix in 2.1.0 doesn't help this case. The ToolSearch index appears to be a static snapshot from conversation start that's never refreshed.

boxabirds · 3 months ago

My workaround is to Piggyback on all of my tool calls I message to the user
to say “tools have changed if you want to see them, restart Claude.

One more example of how young Claude is really. So many laughable
inadequacies.

  • Hooks are completely isolated from everything else specifically no access

to MCP Conte

  • It’s impossible to register a plug-in and get auto updates unless your

anthropic yourself

  • The discoverability for MCP auth is so bad no one uses it because no one

will find it

On Fri, 20 Mar 2026 at 05:30, Grant Sparks @.***> wrote:

GrantSparks left a comment (anthropics/claude-code#4118) <https://github.com/anthropics/claude-code/issues/4118#issuecomment-4095708916> Confirming this is still an issue as of today. I maintain an MCP server that uses dynamic tool promotion — tools start deferred and are surfaced on demand via tools/list_changed. Server-side everything works correctly (tool list updates, notification fires, tools are callable via call_tool), but ToolSearch never picks up the newly promoted tools. The only workaround is bypassing list_tools() entirely. The partial fix in 2.1.0 doesn't help this case. The ToolSearch index appears to be a static snapshot from conversation start that's never refreshed. — Reply to this email directly, view it on GitHub <https://github.com/anthropics/claude-code/issues/4118?email_source=notifications&email_token=AABD62JPWHWP5MS6UB4K37D4RTJPFA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIMBZGU3TAOBZGE3KM4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-4095708916>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AABD62NWCRVBQWZMNP777GD4RTJPFAVCNFSM6AAAAACCBPKHMSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DAOJVG4YDQOJRGY> . You are receiving this because you commented.Message ID: @.***>
Serg2000Mr · 3 months ago

Test results: notifications/tools/list_changed in VS Code extension v2.1.86

I tested this end-to-end with a C# MCP stdio server (ModelContextProtocol SDK 1.1.0) on Windows.

Setup

  • Server declares listChanged: true capability
  • Server sends notifications/tools/list_changed via McpServer.SendNotificationAsync() after startup
  • Confirmed notification is sent successfully (no errors in server logs)

Results

| Scenario | Tool visible? |
|---|---|
| New tool added → rebuild → same chat ToolSearch | No |
| New tool added → rebuild → new chat ToolSearch | Yes |
| New tool added → rebuild → /mcp → Reconnect → same chat | Yes |

Analysis of extension.js (v2.1.86)

The MCP SDK layer does handle the notification correctly:

  • _setupListChangedHandler registers a debounced handler (300ms)
  • On notification → calls tools/list → updates cacheToolMetadata
  • Calls onChanged callback

However, the application layer does not propagate the updated tool list into the active agent conversation's deferred tools. The tools are refreshed at the SDK/connection level but the running chat session retains its stale tool cache.

Conclusion

The CHANGELOG for v2.1.0 says "Added support for MCP list_changed notifications" — this is true at the SDK level. But the active conversation does not see newly added tools until the user opens a new chat or manually reconnects via /mcp.

This makes the feature effectively incomplete for the main use case: iterating on MCP server development while keeping the same chat open.

Environment: VS Code extension anthropic.claude-code@2.1.86-win32-x64, Windows 10, C# MCP server via stdio.

yurukusa · 3 months ago

A PostToolUse hook can detect when MCP tool usage patterns change:

INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
SEEN_FILE="/tmp/cc-mcp-tools-seen.txt"
if echo "$TOOL" | grep -q '__'; then
    if ! grep -qF "$TOOL" "$SEEN_FILE" 2>/dev/null; then
        echo "$TOOL" >> "$SEEN_FILE"
        echo "📌 New MCP tool detected: $TOOL" >&2
    fi
fi
exit 0

For monitoring MCP server health:

COUNTER="/tmp/cc-mcp-health-counter"
COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
COUNT=$((COUNT + 1))
echo "$COUNT" > "$COUNTER"
[ $((COUNT % 50)) -ne 0 ] && exit 0
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
if echo "$TOOL" | grep -q '__'; then
    OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // empty')
    if echo "$OUTPUT" | grep -qiE '(connection refused|timeout|not connected|ECONNREFUSED)'; then
        MCP_NAME=$(echo "$TOOL" | cut -d'_' -f3)
        echo "⚠️ MCP server $MCP_NAME may be disconnected: $OUTPUT" >&2
    fi
fi
exit 0

Note: Hooks can't currently trigger an MCP tool list refresh — that requires a native implementation of the notifications/tools/list_changed protocol handler. The hooks above provide visibility into MCP tool state changes as a partial workaround.

hortovanyi · 3 months ago

found this issue also with Claude Desktop via claude.ai and Claude Code #41123

Serg2000Mr · 3 months ago

Workaround that actually reconnects — patch extension.js directly

Since the conversation-level tool cache isn't refreshed on list_changed, I patched extension.js to watch a flag file and call reconnectMcpServer automatically. Build script touches the flag → all open chats reconnect, no manual /mcp needed.

apply_patch.py (tested on v2.1.87, Windows):

"""Inject MCP auto-reconnect IIFE into Claude Code extension.js"""
import sys, glob, os, re

# ---- configure these ----
SERVER_NAME = "your-mcp-server-name"
FLAG_FILE   = "C:/path/to/your/.mcp-reconnect"   # build script touches this file
# -------------------------

EXT_DIR = os.path.expandvars(r"%USERPROFILE%\.vscode\extensions")
candidates = glob.glob(os.path.join(EXT_DIR, "anthropic.claude-code-*-win32-x64"))
if not candidates:
    sys.exit(f"ERROR: extension not found in {EXT_DIR}")

def version_key(p):
    m = re.search(r'(\d+)\.(\d+)\.(\d+)', os.path.basename(p))
    return tuple(int(x) for x in m.groups()) if m else (0,0,0)

EXT_JS = os.path.join(max(candidates, key=version_key), "extension.js")
print(f"Target: {EXT_JS}")

# Anchor: right after Z = new WQ(...) is pushed to subscriptions.
# Z holds .allComms (Set of active chats). Update this when extension updates:
# search for a line containing both 'allComms' and 'onDidChangeConfiguration'.
ANCHOR = "K.subscriptions.push(Z),K.subscriptions.push(O6.workspace.onDidChangeConfiguration"

FLAG_JS = FLAG_FILE.replace("\\", "\\\\").replace("/", "\\\\")

PATCH = (
    '/* --- MCP auto-reconnect patch --- */'
    '(function(){'
    'var fs=require("fs");'
    f'var flagPath="{FLAG_JS}";'
    f'var serverName="{SERVER_NAME}";'
    'var lastTs=0;'
    'try{lastTs=fs.statSync(flagPath).mtimeMs}catch(e){}'
    'var inFlight=false,pendingTs=0,retries=0;'
    'var h=setInterval(function(){'
    'if(inFlight)return;'
    'try{'
    'var ts=fs.statSync(flagPath).mtimeMs;'
    'if(ts<=lastTs)return;'
    'var comms=Array.from(Z.allComms);'
    'if(!comms.length)return;'
    'if(ts!==pendingTs){pendingTs=ts;retries=0;}'
    'inFlight=true;'
    'var p=[];'
    'comms.forEach(function(c){'
    'if(c.channels&&c.channels.size)'
    'c.channels.forEach(function(_,id){p.push(c.reconnectMcpServer(id,serverName));});'
    '});'
    'Promise.allSettled(p).then(function(r){'
    'inFlight=false;'
    'var fail=r.filter(function(x){return x.status==="rejected";}).length;'
    'if(!fail){lastTs=pendingTs;console.log("[patch] All "+r.length+" channels reconnected OK");}'
    'else if(++retries>=3){lastTs=pendingTs;}'
    '});'
    '}catch(e){}'
    '},2000);'
    'K.subscriptions.push({dispose:function(){clearInterval(h);}});'
    '})();'
    '/* --- end patch --- */'
)

MARKER = "MCP auto-reconnect patch"
content = open(EXT_JS, encoding="utf-8").read()
if MARKER in content:
    sys.exit("Already patched")
if ANCHOR not in content:
    sys.exit(
        "ERROR: anchor not found — extension was updated, need to find new anchor.\n"
        "Search extension.js for a line containing both 'allComms' and 'onDidChangeConfiguration'."
    )

open(EXT_JS, "w", encoding="utf-8").write(content.replace(ANCHOR, PATCH + ANCHOR, 1))
print("Done. Reload VSCode: Ctrl+Shift+P → Developer: Reload Window")

How it works:

  • setInterval(2000ms) watches FLAG_FILE mtime
  • On change → iterates Z.allComms (Set of active chats) → calls reconnectMcpServer(channelId, serverName) for each channel
  • Build script does touch .mcp-reconnect after successful rebuild → all open chats reconnect automatically

Stable identifiers (not minified, survive extension updates): allComms (17 occurrences in v2.1.87), reconnectMcpServer (5 occurrences)

What breaks on extension update: the injection anchor uses minified variable names (K, Z, O6). The script prints a clear error when the anchor isn't found and tells you where to look. Adapting to a new version takes a few minutes.

Works on Windows. The flag file approach is build-tool agnostic — any script can trigger reconnect with a simple touch.

---

Detecting extension updates

The patch breaks silently when Claude Code auto-updates. Simple check:

python -c "
import glob, os, re
d = os.path.expandvars(r'%USERPROFILE%\.vscode\extensions')
c = glob.glob(os.path.join(d, 'anthropic.claude-code-*-win32-x64'))
def vk(p): m=re.search(r'(\d+)\.(\d+)\.(\d+)',os.path.basename(p)); return tuple(int(x) for x in m.groups()) if m else (0,0,0)
f = os.path.join(max(c,key=vk),'extension.js')
print('OK' if 'MCP auto-reconnect patch' in open(f).read() else 'NEEDS PATCH: '+f)
"

Add this to your build script — if it prints NEEDS PATCH, run apply_patch.py + reload VSCode.

---

Update: tested on v2.1.88

Extension updated silently overnight — the anchor broke as expected. Adapted in a few minutes:

  • O6M6 (one minified variable renamed)
  • allComms, reconnectMcpServer, Z, K — unchanged

New anchor for v2.1.88:

K.subscriptions.push(Z),K.subscriptions.push(M6.workspace.onDidChangeConfiguration

The detection script above caught the update immediately on the next build. Patch reapplied, working.

---

Update: tested on v2.1.89

Same anchor as v2.1.88 — no changes needed. allComms, reconnectMcpServer, Z, K, M6 all unchanged. Patch applied cleanly.

Also worth checking before each re-patch: scan the changelog for a native fix (gh api repos/anthropics/claude-code/releases/latest --jq '.body' | grep -i mcp). v2.1.89 has no mention of notifications/tools/list_changed — patch still required.

---

Repository: https://github.com/Serg2000Mr/claude-code-mcp-reconnect — generic version of the script (configurable SERVER_NAME / FLAG_FILE).

christianromeni · 3 months ago

@ollie-anthropic In the labels it says "enhancement" but this is more of type "bug" .. Its been open für almost a year... Would it be possible to get some kind of feedback or an ETA.. This would open up many many doors..

esinecan · 2 months ago

The bug is still there on claude desktop. Say I am developing a MCP tool. I have dynamic discovery with a tool I named discover_tools. It works by registering newly enabled tools at runtime and emitting the MCP protocol's notifications/tools/list_changed event. The protocol defines this event ( https://modelcontextprotocol.io/specification/2024-11-05/server/tools#list-changed-notification ) and Anthropic's own TypeScript SDK provides sendToolsListChanged() for emitting it.

However Claude Code itself does not honor it. The Zod schema for the notification is defined in cli.js, but no handler is registered, so the dynamically-enabled tool never reaches the model's tool surface. It's baffling that this has been open nearly for a year.

petercoxphoto · 2 months ago

Adding a real-world cost data point in case it helps weigh the not-planned ruling on #46426.

I run ~30 MCP connectors against my single-operator business stack (Shopify, Xero, Klaviyo, GA4, GSC, Google Ads, Pinterest, Bluesky, LinkedIn, Home Assistant, etc.). Iterating on a connector — adding a tool, fixing a schema, renaming an action — is a tight loop: edit, rebuild container, redeploy. Every iteration currently costs a full Claude Code restart, which means losing the conversation, the loaded context, and any in-progress investigation.

The MCP server already sends notifications/tools/list_changed after the rebuild. Claude Code receives the notification (per #13646) but discards it because no handler is registered. The protocol-level work is done on both sides; only the client handler is missing.

/mcp reconnect re-establishes transport without re-pulling tools/list, which is arguably worse than no reconnect at all — it gives the appearance of a refresh without performing one.

The stated efficiency argument (tool schemas baked into the cached system prompt) is real, but ENABLE_TOOL_SEARCH=true already breaks the "all tools at session start" model — deferred tools are loaded on demand and injected into the conversation, not the prompt. The same mechanism could deliver a tools/list_changed refresh without invalidating the prompt cache.

A /mcp refresh <server> slash command that re-fetches a single server's tool list and surfaces the diff would solve 95% of the iteration pain even without automatic handling of the notification.

hamzanajeeb1 · 1 month ago

The bug is still there on Claude Desktop, I keep seeing this when building my own MCP server for an app!

edobry · 1 month ago

+1 — Minsky (https://github.com/edobry/minsky), a multi-agent task system, ships infrastructure that depends on this. Our architecture: a stdio supervisor (minsky mcp proxy) sits between Claude Code and the inner MCP server, transparently respawns the inner when source updates, and emits notifications/tools/list_changed upstream after each respawn so Claude Code can refresh its tool cache without a /mcp reconnect step.

End-to-end test (Claude Code 2.1.146):

  1. debug.listMethods on the inner returns 210 tools (e.g., git.branch, ai.chat, session.inspect).
  2. ToolSearch select:mcp__minsky__git_branch returns empty (the deferred-tools surface doesn't have it).
  3. Proxy invokes __proxy_restart_server → emits a spec-conformant notifications/tools/list_changed (advertised capability, valid JSON-RPC, ping-gated readiness probe).
  4. ToolSearch select:mcp__minsky__git_branch still returns empty.
  5. Manual /mcp reconnect → ToolSearch immediately finds it.

Interestingly, the binary at 2.1.146 contains both "Received tools/list_changed notification, refreshing tools" and a tengu_mcp_list_changed telemetry event — so a handler DOES exist (vs the 2.0.65 reported in #13646). But the deferred-tools surface that powers ToolSearch doesn't reflect any refresh — only manual /mcp reconnect closes the gap. The handler may update the SDK-internal tools list without propagating to the higher-level deferred-tools cache.

Anything that closes this gap would be appreciated. Happy to share repro details.

(Had Claude help draft this comment.)

hamzanajeeb1 · 1 month ago

@edobry Thanks for jumping on this guys! Just wanted to point out that I'm seeing this issue on claude desktop, so if that too could be taken care of - that would be amazing!

localden collaborator · 1 month ago

Thanks for the report. Handling for notifications/tools/list_changed shipped in 2.1.0 and is active in current versions. If you are still seeing the tool list not refresh on a current version (especially in print mode or with deferred tool search), please open a new issue with the version and a repro.

cesarlai-alt · 15 days ago

Built MCP Salad on top of this — a CLI + gateway that uses notifications/tools/list_changed to hot-swap servers into a running Claude Code session without restart.

The implementation: gateway listens on a Unix socket for salad enable <server>, updates the server pool, fires the notification. On Claude Code ≥2.1.0 the tool list updates instantly — no restart, no new chat.

One gap we noticed: the notification works cleanly, but the UX problem is discovery — knowing which server to enable mid-session. We added salad suggest "<description>" which keyword-searches the 14k official MCP registry and returns ranked suggestions, so you can go from "I need a patent search tool" to live tools in a running session in a few commands.

Might be useful context for anyone building on this notification.