-
Notifications
You must be signed in to change notification settings - Fork 10.4k
codex exec on macOS initializes external MCP and lists tools but never sends tools/call #14115
Copy link
Copy link
Open
Labels
execIssues related to the `codex exec` subcommandIssues related to the `codex exec` subcommand
Description
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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
execIssues related to the `codex exec` subcommandIssues related to the `codex exec` subcommand
Type
Fields
Give feedbackNo fields configured for issues without a type.