Skip to content

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

Description

@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:

    {
      "capabilities": {
        "tools": {
          "listChanged": true
        }
      }
    }
  2. On the initial tools/list, return only start_daemon.

  3. 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.
  4. 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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions