MCP: tools list is not refreshed on notifications/tools/list_changed (stale tools until session restart)
Summary
Claude Code caches the MCP tools/list result at connection time and never refreshes it, even when the MCP server explicitly sends a notifications/tools/list_changed notification. Tools added to a server after the session started remain invisible for the whole session. The only workaround is to start a brand new chat.
This defeats much of the purpose of an MCP aggregating gateway, whose main value is that backends can be added and removed at runtime without restarting any client.
Environment
- Claude Code:
2.1.215 - OS: Windows 11 Pro (10.0.28000)
- MCP server: MCPJungle
v0.4.5(aggregating gateway), transportstreamable_http, endpointhttp://127.0.0.1:18080/mcp - Server capabilities as reported in
initialize:{"tools":{"listChanged":true}}
Expected behavior
Per the MCP specification, a server that declares tools.listChanged: true may send notifications/tools/list_changed when its tool set changes. A client receiving this notification is expected to re-issue tools/list, so newly available tools become usable in the current session.
Actual behavior
The notification is delivered by the server but has no effect on the client: the tool set stays frozen at whatever it was when the session connected. New tools are not callable until the user opens a new chat.
Verification that the server side is correct
The notification really is sent — measured directly, bypassing Claude Code, with plain curl against the same gateway endpoint the client uses.
- Open a session and complete the handshake:
SESSION=$(curl -s -i -X POST http://127.0.0.1:18080/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
| tr -d '\r' | grep -i '^Mcp-Session-Id:' | cut -d' ' -f2)
curl -s -X POST http://127.0.0.1:18080/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SESSION" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
- Listen on the SSE channel:
timeout 40 curl -s -N -X GET http://127.0.0.1:18080/mcp \
-H "Accept: text/event-stream" -H "Mcp-Session-Id: $SESSION" > sse-out.txt
- While the listener is running, register a new backend server in the gateway.
Result: 12 notifications arrived (one per tool exposed by the newly registered server):
event: message
data: {"jsonrpc":"2.0","method":"notifications/tools/list_changed","params":{}}
... (12 total)
Control run: the same listener with no registry change produced 0 notifications over 20 seconds — so the server is not spamming, it emits strictly on actual change.
Conclusion: the gateway declares listChanged: true and does deliver the notification. The client does not act on it.
Steps to reproduce
- Connect Claude Code to an MCP server that declares
tools.listChanged: true(any aggregating gateway; MCPJungle v0.4.5 was used here). - Start a Claude Code session and confirm the current tool list.
- Without touching Claude Code, add a new backend/tool to that MCP server, so it emits
notifications/tools/list_changed. - In the same session, try to use the new tool, or search for it.
Expected: the new tool becomes available in the running session.
Actual: the tool is not found; it only appears after starting a new chat.
Additional notes
- Calling the new tool through a direct JSON-RPC
tools/callto the gateway (outside the client's tool layer) works fine, which confirms the gateway and its routing are healthy — only the client's cached tool list is stale. - This matters most for gateways that manage backends dynamically. In our setup, 1C databases are connected to a proxy at arbitrary times under user-chosen channel names, and the gateway registers each one automatically. Every such addition currently requires every open agent session to be restarted before the new database can be used.
Suggested fix
Subscribe to notifications/tools/list_changed on servers that advertise tools.listChanged: true, and re-issue tools/list when it fires (debounced — as shown above, a single registration can emit one notification per tool).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗