openai / openai/codex

MCP tools permanently lost for a session when MCP startup is cancelled before completion

Open
#46,399 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI mcp
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-tui session with multiple threads, frequent Esc/interrupt, sessions resumed via codex 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)
  1. codex-rs/core/src/session/mod.rs, interrupt_task():
    when the user interrupts and there is no active turn, it calls cancel_mcp_startup().
  2. codex-rs/codex-mcp/src/connection_manager.rs, cancel_startup():
    cancels the connection token whenever !startup_complete.
  3. codex-rs/codex-mcp/src/rmcp_client.rs, AsyncManagedClient::client():
    awaits a ManagedClientFuture (shared future). Once the startup future resolves with
    StartupOutcomeError::Cancelled, every later client() call returns that same cached
    Cancelled result forever - there is no retry path.
  4. Same file, startup closure:
    startup_complete.store(true, Ordering::Release) runs unconditionally after the startup
    future finishes, even when the outcome is Cancelled.
  5. Same file, listed_tools():
    only falls back to cached_tools() when !startup_complete. Because startup_complete
    is now true, it goes down the live client().await path, which returns Cancelled
    forever, so cached tools are never used.
  6. codex-rs/core/src/tools/mcp_tool_exposure.rs, append_mcp_tools():
    rebuilds from binding.tools() every turn, which is permanently empty for that
    connection, so no MCP tool is registered into the model request.
Steps to reproduce
  1. Run Codex 0.154.0 with a remote MCP server that completes initialize but drops the
    connection during tools/list (or interrupt the turn while MCP startup is in progress).
  2. Observe the first turn is sent without waiting for MCP, and the request contains no MCP
    tools; logs show omitting pending optional MCP server.
  3. Continue/resume the session: subsequent turns still contain no MCP tools.
  4. 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 returns Cancelled, or
  • startup_complete should not be set to true for 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.