Need way to hot-load local MCP changes into existing Codex thread

Open 💬 6 comments Opened May 1, 2026 by devinbost
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

Version 26.422.20832 (2025)

What subscription do you have?

Pro

What platform is your computer?

Darwin 25.4.0 arm64 arm

What issue are you seeing?

Whenever my local MCP server has updates to its tools, the existing Codex UI thread can't access any of the updates, requiring a human to create a new thread in the UI. This is a huge blocker becuase if I'm using Codex to automatically update the MCP surface overnight, every other automation is blocked because existing threads cannot access the updated MCP surface. There needs to be a way to hot-load local MCP changes into existing threads.

What steps can reproduce the bug?

Uploaded thread: 019de35b-8816-72d3-b69b-773269fd8e01

The repro is:

  1. Start with a Codex thread that has the local MCP server attached.

In this repo, portfolio_state is built from [tools/prompt_wave_controller.py](/Users/devin.bost/proj/repos/thespaceplace/tools/prompt_wave_controller.py:45) and launched as:
``bash
python3 -u tools/portfolio_state_mcp_server.py serve
``

  1. In that existing thread, record what it can see: server version, tool count, and tool names. Current fresh local probe is:

``bash
cd /Users/devin.bost/proj/repos/thespaceplace
python3 tools/probe_local_orchestrator_mcp_servers.py --server portfolio_state --json
`
Right now that reports
portfolio_state version 1.31.0 with 107` tools.

  1. Update the MCP surface on disk. For example, add a new portfolio_state tool in tools/portfolio_state_mcp_server.py, add it to the controller allowlist in LOCAL_PORTFOLIO_STATE_MCP_TOOL_NAMES, update shape snapshots/changelog, and bump the MCP server version.
  1. Fresh-probe the local server in a new process:

``bash
python3 tools/probe_local_orchestrator_mcp_servers.py --server portfolio_state --json
``
This should show the new version/tool count/tool name, because the probe starts a fresh stdio MCP process.

  1. Return to the already-open Codex thread and try to call or discover the new tool. The existing thread should still fail to see it or be unable to call it, because its MCP attachment/tool surface was captured before the update. Existing long-lived MCP server processes also do not hot-reload Python module changes.
  1. Confirm the mismatch with the repo’s stale-attachment logic: compare the existing thread’s attached observation against the fresh probe. The expected status is attachment_stale_needs_refresh or stop_attachment_stale; the relevant fail-closed guidance is in [tools/kappability_multi_codex_portfolio_supervisor.py](/Users/devin.bost/proj/repos/thespaceplace/tools/kappability_multi_codex_portfolio_supervisor.py:723), and old contracts explicitly say to refresh the Codex MCP attachment before relying on new behavior in [tools/portfolio_state_mcp_server.py](/Users/devin.bost/proj/repos/thespaceplace/tools/portfolio_state_mcp_server.py:139).

The key observation: a fresh local probe can see the updated MCP surface, but an already-created Codex thread cannot. The fix is to start a new Codex thread/session or otherwise refresh the MCP attachment.

What is the expected behavior?

An already-open Codex thread should automatically refresh its attached local MCP surface after the local MCP server/config changes, without requiring a new thread or manual restart.

Concretely:

  • After a local MCP tool is added, renamed, removed, or its schema/version changes, the existing thread should detect the changed surface.
  • On the next tool-discovery or tool-call opportunity, the thread should use a fresh MCP tools/list result instead of the stale attached snapshot.
  • Newly added tools should become discoverable and callable from the existing thread.
  • Updated tool schemas/output contracts should be reflected in the thread’s callable metadata.
  • Removed tools should disappear or fail with a clear “tool no longer available” error, not linger as stale callable tools.
  • MCP identity/freshness checks should report the refreshed version/tool count/source hashes as current.
  • Repo trust checks such as get_go_loop_mcp_attachment_freshness should return an aligned/usable state rather than attachment_stale_needs_refresh or stop_attachment_stale.
  • If automatic refresh fails, Codex should fail closed with a clear refresh error and recovery hint, not silently keep using stale MCP metadata.

A crisp acceptance-test version:

  1. Open Codex thread with portfolio_state attached.
  2. Add a new MCP tool locally and update the server allowlist/version.
  3. Fresh local probe sees the new tool.
  4. Without starting a new Codex thread, ask the existing thread to discover/call that new tool.
  5. Expected fixed behavior: the existing thread sees and can call the new tool automatically.

Additional information

_No response_

View original on GitHub ↗

6 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #19155

Powered by Codex Action

devinbost · 3 months ago

Hi @etraut-openai, I think #19155 may be in the MCP refresh/tool-list area you recently touched.

I put together a small demonstration branch here:
https://github.com/openai/codex/compare/main...devinbost:feat/mcp-tool-list-refresh?expand=1

The change wires MCP notifications/tools/list_changed through codex-rmcp-client into codex-mcp, marks the cached tools stale, and refreshes tools/list the next time an existing thread reads its tool surface. It passes:

  • cargo build -p codex-cli
  • cargo test -p codex-rmcp-client
  • cargo test -p codex-mcp
  • cargo clippy --fix --tests --allow-dirty -p codex-rmcp-client -p codex-mcp

I know external PRs are invitation-only, so I’m not trying to bypass that process. Would you be the right person to say whether this is the intended direction, or to invite/open an internal PR if it looks right?

CC @aibrahim-oai since the patch also touches the codex-mcp ManagedClient tool cache path from #19725.

devinbost · 3 months ago

@etraut-openai, I updated the demonstration branch to cover the local-stdio hot-load part of #20605 in addition to #19155:

https://github.com/openai/codex/compare/main...devinbost:feat/mcp-tool-list-refresh?expand=1

The branch now has two pieces:

  1. For #19155, it wires MCP notifications/tools/list_changed through codex-rmcp-client into codex-mcp, marks the cached tool list stale, and refreshes tools/list the next time the existing session reads MCP tools.
  1. For #20605, it adds a core watcher for local stdio MCP surfaces. Existing sessions watch the user MCP config plus path-like stdio command/args, reload MCP config when those files change, and queue a deferred MCP server refresh so the stdio server is restarted and the tool surface is rebuilt on the next turn/tool-list request.

The implementation is intentionally conservative: for local stdio MCP servers, it watches files referenced directly by the configured command/args, plus the immediate parent directory of those files. For example, if the MCP config launches a server as python3 -u tools/portfolio_state_mcp_server.py serve, Codex watches tools/portfolio_state_mcp_server.py and tools/. That covers common Python/Node script-based MCP servers without recursively watching the whole repo.

It should work for things like:

node tools/server.mjs
bun tools/server.ts
deno run tools/server.ts
ruby tools/server.rb
go run ./cmd/mcp-server
cargo run --manifest-path tools/mcp/Cargo.toml
./target/debug/my-mcp-server

Caveats:

  • It watches files/directories referenced directly in command or args.
  • It also watches the immediate parent directory of referenced files.
  • It will not reliably catch deeper dependency changes outside those watched paths.
  • It will not catch changes hidden behind a package script like npm run mcp unless the watched config/entrypoint also changes.

You (or another maintainer) may want to decide whether broader project config watching, dependency-root watching, or an explicit MCP watch_roots config would be the preferred product shape.

I ran:

  • cargo fmt -- --config imports_granularity=Item
  • cargo test -p codex-rmcp-client
  • cargo test -p codex-mcp
  • cargo test -p codex-core mcp_server_watcher --lib
  • cargo test -p codex-core reload_user_config_layer_updates_mcp_servers --lib
  • cargo test -p codex-core queue_current_mcp_server_refresh_uses_reloaded_config --lib
  • cargo test -p codex-core refresh_mcp_servers_is_deferred_until_next_turn --lib
  • cargo clippy --fix --tests --allow-dirty -p codex-rmcp-client -p codex-mcp
  • cargo clippy --fix --tests --allow-dirty -p codex-core

Would you be the right person to advise whether this is close to the intended architecture, or should I ping someone else for the MCP/session refresh path?

Kaexburli · 2 months ago

Same need here from a slightly different runtime shape.

We can verify out-of-band that the MCP server has the updated/real tool catalog available, and the server logs a Codex MCP client connection. But the already-open Codex thread still has the stale MCP tool attachment captured earlier and cannot call the real tools until the session/thread is restarted.

So the missing capability is exactly a hot refresh of the thread-attached MCP tool surface after the underlying server/catalog changes or recovers.

100yenadmin · 1 month ago

Additional exact reproduction from Codex Desktop on macOS arm64 with codex-cli 0.144.0 and a local stdio MCP server:

  • The already-open long-running task did not expose the configured gitnexus MCP namespace after the server/config was repaired.
  • codex mcp list and codex mcp get gitnexus both reported the server enabled.
  • A direct fresh stdio initialize followed by tools/list succeeded and returned 13 read-only tools.
  • A fresh ephemeral codex exec task then called the same server's list_repos tool successfully and received 34 repositories.
  • The original task still could not acquire the namespace dynamically.

This isolates the failure to the existing task's attached tool surface rather than server startup, config parsing, or MCP protocol behavior. A full Desktop restart was not required for a newly created task; creating a new task was sufficient. The missing capability is a current-task MCP reconnect/reload exactly as described here.

No credentials or private configuration values are included in this report.

TeaShaman-cyber · 20 days ago

Windows/AppX reproduction and operational impact

This is also reproducible on current Windows Codex Desktop 26.803.5235.0.

Our live control comparison shows:

  • after a full Desktop restart, an external Gramps MCP server exposes 57 mcp__family_gramps__* tools and a read-only get_metadata call succeeds;
  • an authenticated remote GitHub MCP server remains unavailable and no mcp__github__* tools appear;
  • the GitHub discovery/exposure details are tracked in #19425.

The missing capability is an in-process/live MCP manifest refresh. When an external server is started, repaired, re-authenticated, or its tools change, the current task should be able to request a bounded refresh of its attached MCP catalog without restarting the whole Desktop app.

A full restart is not a cheap workaround for this problem. On Windows, restarting Codex can require reopening and reconstructing historical tasks, while existing reports also describe eager history hydration, UI stalls, repeated history replay, and MCP process startup during task navigation:

  • #29590 — slow or unresponsive previous-conversation loading and eager history hydration;
  • #32154 — MCP process generation and history replay while navigating existing Windows/WSL2 chats.

This makes MCP diagnosis and configuration disproportionately expensive for users, especially when working with remote or authenticated MCP servers.

Suggested acceptance criteria:

  • from an existing task, invoke an explicit “Refresh MCP tools” action or equivalent;
  • refresh only the selected server or current task attachment;
  • report success/failure and the resulting tool count or server version;
  • keep the current task alive without a full app restart and history hydration.