Codex never tells MCP servers to stop after tool call interrupt/timeout

Open 💬 2 comments Opened Jun 8, 2026 by a9lim

What version of Codex CLI is running?

codex-cli 0.137.0

What subscription do you have?

Pro 20x

Which model were you using?

gpt-5.5

What platform is your computer?

Darwin 25.6.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

Terminal.app

Codex doctor report

What issue are you seeing?

When using Codex with tools from MCP servers, interrupting or timing out a tool call doesn't notify the server. Codex will drop the request future locally but sends nothing back, so MCP servers will try to execute the tool to completion anyways.

What steps can reproduce the bug?

  1. Register a stdio MCP server that runs a tool call and logs whether it receives notifications/cancelled.
  2. Ask Codex to call that tool then interrupt it or let it time out.
  3. The server never gets notifications/cancelled and tries to keep running the tool even though its output will never be seen.

What is the expected behavior?

Codex should send notifications/cancelled when tool calls are cancelled so the server can stop executing it.

Additional information

I had Claude fix the bug at a9lim/codex@fix/mcp-client-cancellation

View original on GitHub ↗

2 Comments

wgu9 · 2 months ago

I checked current main and this looks like a real lifecycle gap in the MCP tool-call path, not just a UI interruption issue.

The cancellation token exists at the generic tool runtime layer, but it does not make it down to the MCP request:

  • codex-rs/core/src/tools/parallel.rs passes a CancellationToken into each tool dispatch and aborts/drops the task when the turn is cancelled, unless the handler opts into waiting for runtime cleanup.
  • codex-rs/core/src/mcp_tool_call.rs::execute_mcp_tool_call() then awaits sess.call_tool(...) directly.
  • codex-rs/core/src/session/mcp.rs::call_tool() forwards to mcp_connection_manager.call_tool(...) without any cancellation token or request-id handle.
  • I only see CancelledNotificationParam handled as an inbound server-to-client notification in codex-rs/rmcp-client/src/logging_client_handler.rs; I do not see an outbound client-to-server cancellation path tied to a running tool request.

So the likely failure mode is: Codex cancels/aborts the local future, but the MCP server still owns a live JSON-RPC request and never receives MCP notifications/cancelled for that request id. That matches the reproduction where the server keeps executing after the result has become unobservable to Codex.

Patch shape I would expect:

  1. Keep a cancellation-aware handle around each outbound MCP tool request, including the actual MCP request id used by rmcp.
  2. When the Codex tool/turn CancellationToken fires before the MCP response resolves, send MCP notifications/cancelled to the server with that request id and a reason such as user interrupt/timeout.
  3. Prefer waiting for the cancellation send/cleanup path rather than simply aborting the local task, similar to how shell tools now distinguish runtime cleanup from immediate task abort.
  4. Add a stdio MCP integration test with a tool that blocks until it receives notifications/cancelled, then assert Codex sends that notification on interrupt/timeout.

This is especially important for tools with external side effects: dropping the local future is not equivalent to cancelling the server-side operation.

alexchexes · 2 months ago

This also occurs through Code mode: the model can terminate a pending nested MCP call, but the MCP server receives no notifications/cancelled. The server therefore continues waiting and cannot distinguish the abandoned request from a legitimate long-running call. #29122 contains the Code-mode reproducer.