Skip to content

codex exec on macOS initializes external MCP and lists tools but never sends tools/call #14115

@thiagohirai

Description

@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:
    • initialize
    • notifications/initialized
    • tools/list
  • The external MCP server never receives tools/call.
  • codex exec hangs 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/mcp as 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    execIssues related to the `codex exec` subcommand

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions