MCP tools/list_changed notification does not invalidate deferred tool cache or refetch tools/list

Open 💬 1 comment Opened Jul 15, 2026 by RainbowXie

Environment

  • Codex CLI/core: 0.144.1
  • Platform: Linux x86_64 (Linux Mint 22.3 / kernel 6.8)
  • MCP transport: stdio
  • Relevant enabled feature: tool_search_always_defer_mcp_tools
  • Reproducer server: RainbowXie/headless-ida-mcp-server, stdio stub entry point headless-ida-mcp-stub

What issue are you seeing?

Codex does not refresh an MCP server's tool registry after the server sends the standard notifications/tools/list_changed notification.

The MCP server initially exposes one cheap bootstrap tool, start_daemon, to avoid starting idalib and exposing roughly 89 tools when IDA is not being used. After start_daemon completes, the stdio server:

  1. fetches and caches the daemon's real tool list;
  2. sends notifications/tools/list_changed;
  3. returns the full tool list on every subsequent tools/list request.

Codex calls start_daemon successfully, but never issues another tools/list request. The deferred tool_search surface remains based on the initial one-tool response, so none of the newly available tools can be discovered or called.

This also affects MCP servers with plugin systems: enabling or disabling a plugin changes the tool surface and emits tools/list_changed, but Codex continues using the stale cached surface.

Steps to reproduce

Use an MCP stdio server with this behavior:

  1. During initialization, advertise:

``json
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
``

  1. On the initial tools/list, return only start_daemon.
  1. When tools/call invokes start_daemon:
  • change the server's tool list so it now contains additional tools;
  • send notifications/tools/list_changed;
  • return success.
  1. Ask Codex to use one of the newly added tools, or inspect the MCP requests made by Codex.

Observed Codex request sequence:

initialize
tools/list                 # returns only start_daemon
tools/call start_daemon    # server sends notifications/tools/list_changed
                           # no second tools/list request

The same server was verified end-to-end with:

  • an in-memory MCP client with a warm daemon;
  • an in-memory MCP client with a cold daemon;
  • a real stdio subprocess using the Python MCP client;
  • a direct live call against the configured server.

Those clients receive the notification, refetch tools/list, and see all 89 tools.

Historical Codex sessions using the same server design before the deferred/lazy tool surface successfully called tools such as decompile, lookup_funcs, and server_health. Current sessions only see/call start_daemon.

Expected behavior

When Codex receives notifications/tools/list_changed, it should:

  1. invalidate the cached tool list for that MCP server;
  2. refetch tools/list;
  3. rebuild or update the deferred tool_search index for that server;
  4. make newly added tools discoverable and callable;
  5. remove tools that are no longer returned.

Repeated notifications can be debounced or coalesced, but the resulting tool registry must eventually reflect the latest server response.

Additional investigation

The server-side notification path appears correct. The Python MCP clients refetch successfully, and the server advertises tools.listChanged=true.

Codex embeds rmcp. In rmcp, ToolListChangedNotification is dispatched to the application's notification handler; rmcp does not automatically invalidate or refetch the tool list. Codex's application layer appears to cache MCP tools and build the deferred tool_search surface from that cache. My current hypothesis is that the notification handler does not invalidate that cache or trigger rediscovery.

The MCP protocol only lets a server request a refetch; it cannot push a replacement tool list. Therefore, a server cannot preserve a small lazy initial surface or expose dynamic plugin tools reliably if the client ignores this notification.

Related but distinct issues:

  • #19425: tools returned by the initial tools/list are not exposed to Desktop threads.
  • #21503: an already indexed deferred tool can be missed by tool_search ranking.

This issue occurs earlier: Codex never refetches the changed list, so the new tools never enter the deferred index at all.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗