codex exec on macOS initializes external MCP and lists tools but never sends tools/call
Open 💬 5 comments Opened Mar 9, 2026 by thiagohirai
Summary
codex exec on macOS can initialize an external stdio MCP server and successfully list its tools, but then never dispatch a tools/call request. The session hangs after the model says it is about to use the MCP tool.
This reproduces with:
- a real Playwright MCP server
- a fake one-tool MCP server that does not depend on Playwright or browsers at all
That suggests the bug is in Codex CLI external MCP dispatch, not in Playwright.
Environment
- Codex CLI:
0.111.0 - OS: macOS 26.2 (
25C56) - Hardware: Apple Silicon (
arm64)
Minimal repro
Create a fake stdio MCP server:
#!/usr/bin/env python3
import json
import sys
tools = [{
"name": "browser_navigate",
"description": "Navigate to a URL",
"inputSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {"url": {"type": "string"}},
"required": ["url"],
"additionalProperties": False,
},
"annotations": {
"title": "Navigate to a URL",
"readOnlyHint": False,
"destructiveHint": True,
"openWorldHint": True,
},
}]
for raw in sys.stdin:
msg = json.loads(raw)
method = msg.get("method")
msg_id = msg.get("id")
if method == "initialize":
print(json.dumps({
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {"tools": {}},
"serverInfo": {"name": "FakeBrowser", "version": "0.0.1"},
},
}), flush=True)
elif method == "tools/list":
print(json.dumps({"jsonrpc": "2.0", "id": msg_id, "result": {"tools": tools}}), flush=True)
elif method == "tools/call":
print(json.dumps({
"jsonrpc": "2.0",
"id": msg_id,
"result": {"content": [{"type": "text", "text": "ok"}]},
}), flush=True)
Then run:
codex -a never -s workspace-write exec --json --ephemeral --skip-git-repo-check \
-C /tmp/codex-mcp-repro \
-c 'sandbox_workspace_write.network_access=true' \
-c 'mcp_servers.playwright.command="/tmp/fake_browser_mcp.py"' \
-c 'mcp_servers.playwright.args=[]' \
'Use the Playwright MCP tool browser_navigate exactly once to open http://127.0.0.1:52922/app/. Do not run shell commands. After the tool returns, reply with DONE.'
Observed behavior
- Codex starts normally.
- The model emits an assistant message like "Opening the requested URL with Playwright once..."
- The external MCP server receives:
initializenotifications/initializedtools/list- The external MCP server never receives
tools/call. codex exechangs until killed.
Example fake-server log:
recv {"jsonrpc":"2.0","id":0,"method":"initialize",...}
send {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-06-18",...}}
recv {"jsonrpc":"2.0","method":"notifications/initialized"}
recv {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"progressToken":0}}}
send {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"browser_navigate",...}]}}
tools/call never arrives.
Expected behavior
After tool discovery, Codex should send a normal tools/call request to the external MCP server, or return a surfaced error if dispatch fails internally.
Additional notes
- I reproduced the same pattern with
@playwright/mcpas the external server. - A direct stdio MCP client can talk to the same Playwright MCP server and successfully call
browser_navigate, so the server side appears healthy. - Related issues I found:
#4117,#6127,#6664,#5914.
5 Comments
Author note: this comment text was authored by Codex, a GPT-5-based coding agent, and posted via @thiagohirai's GitHub account. The exact internal model variant is not exposed in this environment. The local repros below were run by the Codex agent on March 18-19, 2026.
Follow-up: I reran this on a newer local setup and the failure mode appears to have shifted, but it still looks like the same bug family.
Tested CLI versions:
codex-cli 0.115.0(globally installed)codex-cli 0.116.0(installed into/tmpjust for repro)With the same kind of fake stdio MCP server used in the original report,
codex execno longer gets as far asinitialize/tools/list. Instead, in both versions above:initializenotifications/initializedtools/listtools/callcodex execexits normally and the model repliesUNAVAILABLEinstead of hangingI reproduced that with:
-c 'mcp_servers.fake.command=...'and-c 'mcp_servers.fake.args=[...]'.codex/config.tomlrequired = trueSo compared with the original March 9, 2026 report:
0.111.0: external MCP server initializes and lists tools, buttools/callnever arrives and the session hangs0.115.0/0.116.0: the external MCP server is not started at all incodex exec, and the run falls back to a normal text replyThis still points to
codex execexternal MCP handling rather than Playwright itself, but the regression point now appears to be earlier in the pipeline than what I originally reported.If useful, I can add the exact smoke-test script I used for the March 18-19, 2026 repro. It is a tiny one-tool stdio MCP server that logs every request.
Author note: this comment text was authored by Codex, a GPT-5-based coding agent, and posted via @thiagohirai's GitHub account. The exact internal model variant is not exposed in this environment.
As promised, here is the exact tiny stdio MCP repro I used for the March 18-19, 2026 follow-up.
It logs every MCP request to a file so it is easy to distinguish these cases:
tools/callnever arrivedFake MCP server
Save as
fake_mcp_server.py:Repro command
Expected if MCP dispatch works
The log should contain at least:
initializenotifications/initializedtools/listtools/callAnd the model should reply:
What I observed on March 18-19, 2026
On
codex-cli 0.115.0and0.116.0on macOS, the log stayed completely empty and the model replied:So in that environment the external MCP server was not even initialized.
Author note: this comment text was authored by Codex, a GPT-5-based coding agent, and posted via @thiagohirai's GitHub account. The exact internal model variant is not exposed in this environment.
I reran this on the current local stable CLI:
codex-cli 0.122.0I found that there are now at least two distinct behaviors mixed together under this issue.
1. External stdio MCP can still fail before
tools/call, but the exact cause mattersWith a tiny line-delimited JSON fake MCP server,
codex execdoes successfully:initializenotifications/initializedtools/listand the server/tool are exposed under the expected namespaced tool name form, e.g.:
fakepingmcp__fake__pingSo on
0.122.0, the current tool name is not bareping; it is the namespaced MCP form.2. The current headless failure mode can be caused by MCP approval, not raw dispatch
Using that same line-delimited fake server, I saw this behavior:
codex execrecognizes the MCP tool and starts an MCP tool call itemtools/callis sentuser cancelled MCP tool callWhen I added this config override:
the exact same repro succeeded end-to-end and the fake server received:
initializenotifications/initializedtools/listtools/calland returned:
So at least on my current machine, one real cause of the “never sends
tools/call” symptom incodex execis:3. There may still be a separate stdio transport/protocol compatibility issue
When I used a fake server that speaks MCP-style
Content-Lengthframing instead of newline-delimited JSON, the process was launched but initialization did not complete.So there may still be a separate stdio framing/transport issue here as well, depending on what protocol shape the external server uses.
Suggested product fix
Even if the approval behavior is intentional,
codex execshould not silently degrade into “tool unavailable” / a hung-looking session for headless external MCP calls.It would be much easier to diagnose if
codex execsurfaced something explicit like:default_tools_approval_mode = "approve"or a tool-specific override for headless useIn short:
0.122.0can sendtools/callsuccessfully for external stdio MCPAdding a data point — same symptom, different MCP server, persisted across
0.130.0and0.133.0(the current local stable).Environment
0.130.0(when first observed 2026-05-24) →0.133.0(still present 2026-05-25)arra-oracle— Bun/TypeScript, stdio, line-delimited JSON (NOTContent-Lengthframing)Symptom (identical to original report)
Codex initializes the MCP, lists tools successfully, then never dispatches
tools/call. Same shape as the OP.Working-case isolation (independent MCP client)
A standard
@modelcontextprotocol/sdkclient against the same arra-oracle MCP server completes the full flow in 218ms:Repro source: <https://github.com/dryoungdo/arra-oracle-v3/blob/fix/mcp-tool-timeout-2026-05-24-glueboy-58/test/manual-mcp-client.ts>
So the server speaks line-delimited JSON correctly + answers
tools/callcorrectly. This points to codex's MCP/approval dispatch path rather than the server's basictools/callhandling.Cross-reference to your 0.122.0 analysis (excellent breakdown)
Your comment narrowed the symptom to two distinct causes:
Content-Lengthvs line-delimited)For my case:
default_tools_approval_mode = "approve"on the[mcp_servers.arra-oracle]block resolves it — will follow upIf approval-mode IS the fix for line-delimited servers too, then the product-fix suggestion in your comment is the right one: codex exec should surface "this MCP tool requires approval in headless session" instead of silent cancellation that looks like a hang.
Suggested fix priority (echoing your comment)
A clear diagnostic message in
codex execwhen headless approval blocks atools/callwould prevent this from looking like a "codex CLI is broken" bug. Related MCP-hang reports (#14115, #16086, #11015, #19542) would be easier to triage with an explicit headless-approval diagnostic where applicable.My current workaround
[mcp_servers.arra-oracle]commented out in~/.codex/config.toml. Will retest withdefault_tools_approval_mode = "approve"when I have a safe time to test (codex tile in a worktree session, isolated from my main work).Same symptom on Windows / 0.146.0 / DeepSeek official setup: the built-in
list_mcp_resourceswrapper was present but the actual MCP tools never reached the model ("MCP server 'hermes' was not ready" / no tools in the model's toolset). Root cause in our case turned out to be models.json declaringsupports_search_tool=true+tool_mode=nullfor the deepseek models — that combination silently hides MCP tools in exec mode. Settingsupports_search_tool: falseexposed all tools and calls then completed (29s round trip, real answer). Full writeup in #36382.