Need way to hot-load local MCP changes into existing Codex thread
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:
- 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
- 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
portfolio_state
Right now that reports version 1.31.0 with 107` tools.
- Update the MCP surface on disk. For example, add a new
portfolio_statetool intools/portfolio_state_mcp_server.py, add it to the controller allowlist inLOCAL_PORTFOLIO_STATE_MCP_TOOL_NAMES, update shape snapshots/changelog, and bump the MCP server version.
- 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.
- 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.
- 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_refreshorstop_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:
- Open Codex thread with portfolio_state attached.
- Add a new MCP tool locally and update the server allowlist/version.
- Fresh local probe sees the new tool.
- Without starting a new Codex thread, ask the existing thread to discover/call that new tool.
- Expected fixed behavior: the existing thread sees and can call the new tool automatically.
Additional information
_No response_
6 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
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_changedthroughcodex-rmcp-clientintocodex-mcp, marks the cached tools stale, and refreshestools/listthe next time an existing thread reads its tool surface. It passes:cargo build -p codex-clicargo test -p codex-rmcp-clientcargo test -p codex-mcpcargo clippy --fix --tests --allow-dirty -p codex-rmcp-client -p codex-mcpI 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-mcpManagedClienttool cache path from #19725.@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:
notifications/tools/list_changedthroughcodex-rmcp-clientintocodex-mcp, marks the cached tool list stale, and refreshestools/listthe next time the existing session reads MCP tools.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 watchestools/portfolio_state_mcp_server.pyandtools/. That covers common Python/Node script-based MCP servers without recursively watching the whole repo.It should work for things like:
Caveats:
You (or another maintainer) may want to decide whether broader project config watching, dependency-root watching, or an explicit MCP
watch_rootsconfig would be the preferred product shape.I ran:
cargo fmt -- --config imports_granularity=Itemcargo test -p codex-rmcp-clientcargo test -p codex-mcpcargo test -p codex-core mcp_server_watcher --libcargo test -p codex-core reload_user_config_layer_updates_mcp_servers --libcargo test -p codex-core queue_current_mcp_server_refresh_uses_reloaded_config --libcargo test -p codex-core refresh_mcp_servers_is_deferred_until_next_turn --libcargo clippy --fix --tests --allow-dirty -p codex-rmcp-client -p codex-mcpcargo clippy --fix --tests --allow-dirty -p codex-coreWould 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?
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.
Additional exact reproduction from Codex Desktop on macOS arm64 with
codex-cli 0.144.0and a local stdio MCP server:gitnexusMCP namespace after the server/config was repaired.codex mcp listandcodex mcp get gitnexusboth reported the server enabled.initializefollowed bytools/listsucceeded and returned 13 read-only tools.codex exectask then called the same server'slist_repostool successfully and received 34 repositories.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.
Windows/AppX reproduction and operational impact
This is also reproducible on current Windows Codex Desktop
26.803.5235.0.Our live control comparison shows:
mcp__family_gramps__*tools and a read-onlyget_metadatacall succeeds;mcp__github__*tools appear;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:
This makes MCP diagnosis and configuration disproportionately expensive for users, especially when working with remote or authenticated MCP servers.
Suggested acceptance criteria: