[BUG] codex exec: MCP tools from `tools/list` are not injected into the model's tool set
Open 💬 3 comments Opened Aug 15, 2026 by JCY2023
💡 Likely answer: A maintainer (github-actions[bot], contributor)
responded on this thread — see the highlighted reply below.
Environment
- codex-cli version:
codex-cli 0.147.0-alpha.6.6(alpha channel) - OS: Windows 11 (native, x64)
- MCP server type: streamable HTTP (ASP.NET / IIS), responses encoded as SSE (
Content-Type: text/event-stream), stateless (noMcp-Session-Idheader returned) - Server side verified healthy: direct
curlinitialize/notifications/initialized/tools/listreturns 200/202/200 with the full tool list
config.toml (minimal repro)
[mcp_servers.example-erp]
enabled = true
url = "http://<mcp-host>/mcp"
[mcp_servers.example-erp.http_headers]
Authorization = "Bearer <token>"
Steps to reproduce
- Configure a streamable HTTP MCP server as above (server returns tools via
tools/list). - Run:
````
codex exec --json --skip-git-repo-check --sandbox workspace-write -C <temp-dir> -m <model> -c notify=[] "Call the MCP tool <tool> and return the result."
- Observe the agent's available tool set.
Expected behavior
Tools returned by tools/list are registered and callable by the model — as in Codex desktop (ChatGPT app) with the same config, where the same tools are visible and usable.
Actual behavior
The model reports the tools do not exist (e.g. "no erp_describe / erp_query tool in this session"). The MCP connection itself works fine:
- codex sends
initialize→notifications/initialized→tools/listand receives 200 with all tools (verified by a local reverse proxy capturing the actual HTTP exchange). list_mcp_resourceswithserver=<id>reaches the server and returns-32601: Method 'resources/list' is not available— proving the connection and authentication are working (a wrong token would have produced 401).- The model's tool set only contains built-in tools (
shell_command,list_mcp_resources, ...); no tools fromtools/listappear.
Config variants tried (none changed the behavior)
http_headersstaticAuthorization(original)bearer_token_env_varinstead of the static header- explicit
enabled_toolsallowlist listing all server tools -c features.tool_call_mcp_elicitation=falseapproval_policy = { granular = { sandbox_approval=true, rules=true, mcp_elicitations=true, request_permissions=true, skill_approval=true } }--sandbox read-onlyvsworkspace-write; with/without-Cand--skip-git-repo-check; temp dir vs trusted project dir- direct connection vs local reverse proxy
Diagnostics already performed
codex mcp list/codex mcp get <id>/codex doctor: server shown as enabled, transportstreamable_http, auth "Bearer token", doctor reports MCP servers healthy (0 disabled, 0 failed).RUST_LOG=codex_core=trace,reqwest=trace: shows the HTTP connection to the MCP host and the completed handshake, but no tool registration from thetools/listresponse.- Feature flags in this build:
tool_call_mcp_elicitation= stable/true,mcp_2026_07_28= under development/false,non_prefixed_mcp_tool_names= under development/false.
Impact
This blocks headless/non-interactive codex exec usage with streamable HTTP MCP servers (agent cannot use any MCP tool), while interactive clients (desktop) work with the identical configuration.
Version
codex --version → codex-cli 0.147.0-alpha.6.6
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I have a possibly related macOS A/B reproduction, with one important difference: the MCP tool is injected and the call starts, but execution then fails with an empty error.
Error calling tool '<tool>':and no error textAn authenticated MCP initialize also succeeds independently. This suggests a version-specific failure after tool registration rather than the tool-injection failure described in this issue.
I filed the complete sanitized reproduction at: https://github.com/openai/codex/issues/38794
Traced this on
main@ 1f41cc5d92, and there's a mechanism that fits every one of your observations — including why yourRUST_LOGcapture showed a healthy handshake but no registration.Optional MCP servers get a 1-second startup grace before being silently omitted from the model's tool set. When the turn builds its tool binding, any non-required server whose startup hasn't completed is given
OPTIONAL_MCP_STARTUP_GRACE— hardcoded to 1s — and then dropped from the catalog with only a trace-level log:https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/codex-mcp/src/connection_manager/tool_catalog.rs#L175-L225
(
const OPTIONAL_MCP_STARTUP_GRACE: Duration = Duration::from_secs(1)at #L35; the omission logs"omitting pending optional MCP server".) User-configured servers are "optional" here —required_serversis a separate turn-level concept — so nothing forces the wait.Why
codex execfails while Desktop works with identical config: exec is one-shot — its only sampling request snapshots the binding immediately at session start, so a streamable-HTTP server doing initialize → initialized → tools/list over a real network on a cold start loses the 1-second race essentially every time. The wire exchange still completes (your reverse-proxy capture) — just after the binding was already taken, and exec never builds another one. Desktop sessions are long-lived: by the second turn (or the next MCP refresh) startup is complete and the tools appear.list_mcp_resourcesworking is also consistent: that goes through the live connection rather than the pre-snapshotted tool catalog.Why your trace didn't show it: the omission message is logged under the
codex_mcptarget at TRACE level; your filterRUST_LOG=codex_core=trace,reqwest=tracedoesn't include it. A directly actionable next step that would confirm this end-to-end on your setup:If the line appears, this is confirmed. (One alternative worth ruling out while you're in there: with a stateless server that returns no
Mcp-Session-Id, ifstartup_completenever flips — e.g. the client blocks establishing the follow-up SSE stream — you'd see the same omission on every snapshot; the trace will show whether startup eventually completes or hangs.)Fix outline:
codex exec(single-turn, cold cache), treat configured MCP servers as required for the first binding — or scale the grace with an explicit flag/config (--wait-for-mcp/mcp_startup_timeout) instead of a hardcoded 1s that no headless network server can reliably meet.example-erpwas still starting and was excluded from this turn") rather than a trace line — your report documents several hours of elimination work that a single warning would have prevented.-Ctemp dirs defeat it, which is exactly the headless CI/automation profile this issue is about.