MCP tools permanently lost for a session when MCP startup is cancelled before completion
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Environment
- Codex version:
0.154.0(Linux x64) - Model/provider: custom Responses-compatible gateway (DeepSeek backend), custom model catalog
- MCP servers: three remote streamable-http servers configured in
config.toml - Usage: long-lived
codex-tuisession with multiple threads, frequent Esc/interrupt, sessions resumed viacodex resume
Summary
A thread can permanently lose all MCP tools when an MCP server's startup is cancelled before it completes. The /mcp panel still shows the server as connected (22 tools), but the model request contains no MCP tools and never recovers for that thread. A brand-new thread works fine.
Root cause (source-level, v0.154.0)
codex-rs/core/src/session/mod.rs,interrupt_task():
when the user interrupts and there is no active turn, it callscancel_mcp_startup().codex-rs/codex-mcp/src/connection_manager.rs,cancel_startup():
cancels the connection token whenever!startup_complete.codex-rs/codex-mcp/src/rmcp_client.rs,AsyncManagedClient::client():
awaits aManagedClientFuture(shared future). Once the startup future resolves with
StartupOutcomeError::Cancelled, every laterclient()call returns that same cached
Cancelledresult forever - there is no retry path.- Same file, startup closure:
startup_complete.store(true, Ordering::Release)runs unconditionally after the startup
future finishes, even when the outcome isCancelled. - Same file,
listed_tools():
only falls back tocached_tools()when!startup_complete. Becausestartup_complete
is nowtrue, it goes down the liveclient().awaitpath, which returnsCancelled
forever, so cached tools are never used. codex-rs/core/src/tools/mcp_tool_exposure.rs,append_mcp_tools():
rebuilds frombinding.tools()every turn, which is permanently empty for that
connection, so no MCP tool is registered into the model request.
Steps to reproduce
- Run Codex 0.154.0 with a remote MCP server that completes
initializebut drops the
connection duringtools/list(or interrupt the turn while MCP startup is in progress). - Observe the first turn is sent without waiting for MCP, and the request contains no MCP
tools; logs showomitting pending optional MCP server. - Continue/resume the session: subsequent turns still contain no MCP tools.
- Start a new thread with the same config while the server is healthy: tools are injected
normally.
Expected behavior
- An interrupted/cancelled MCP startup should be retried on later turns, or
listed_tools()should fall back to cached tools / rebuild the connection when the live
client returnsCancelled, orstartup_completeshould not be set totruefor a cancelled startup.
Actual behavior
The cancelled startup is permanently cached by the shared future and startup_complete=true
blocks the cache fallback, so the thread never sees MCP tools again.
Relevant logs
Service initialized as client ...
task cancelled
serve finished quit_reason=Cancelled
...
omitting pending optional MCP server
Model request (captured with a mock Responses API): only base tools, no mcp__... namespace,
no tool_search.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing interrupt_task() in codex-rs/core/src/session/mod.rs through cancel_startup(), AsyncManagedClient::client(), and listed_tools() in codex-rs/codex-mcp/src/rmcp_client.rs. Then inspect append_mcp_tools() in codex-rs/core/src/tools/mcp_tool_exposure.rs and reproduce the cancellation flow described in the issue. Done means a cancelled startup does not permanently remove MCP tools from later turns in the same thread.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100