codex exec: no way to allow MCP tool calls non-interactively without --dangerously-bypass-approvals-and-sandbox
Summary
In codex exec (non-interactive), MCP tool calls are auto-cancelled because stdin is closed and no config key I can find suppresses the approval prompt. The only working bypass is --dangerously-bypass-approvals-and-sandbox, which also disables -s read-only sandboxing. Asking: what's the right way to authorize MCP tool calls in codex exec while keeping -s read-only in place?
Environment
codexv0.130.0 (Homebrew, macOS arm64)- Auth: ChatGPT subscription via
codex login
Repro
Minimal STDIO MCP server with one tool:
# /tmp/spike_mcp_server.py
from mcp.server.fastmcp import FastMCP
server = FastMCP("spike")
@server.tool()
def echo_hello(name: str) -> str:
return f"hello {name}"
server.run()
Register it and call it from codex exec:
SPIKE_HOME=$(mktemp -d)
cp ~/.codex/auth.json "$SPIKE_HOME/"
codex --help >/dev/null # ensure binary works
CODEX_HOME="$SPIKE_HOME" codex mcp add spike -- /opt/homebrew/bin/python3 /tmp/spike_mcp_server.py
# Verify registered
CODEX_HOME="$SPIKE_HOME" codex mcp list
# Name Command Args Env Cwd Status Auth
# spike /opt/homebrew/bin/python3 /tmp/spike_mcp_server.py - - enabled Unsupported
# Call it
CODEX_HOME="$SPIKE_HOME" codex exec --color never \
--skip-git-repo-check -s read-only \
"Use the spike echo_hello tool with name=test and report what it returned." \
</dev/null
Observed
mcp: spike/echo_hello started
mcp: spike/echo_hello (failed)
user cancelled MCP tool call
The MCP child process IS spawned (verified via RUST_LOG=info: MCP server stderr (/opt/homebrew/bin/python3): [spike_mcp_server] startup pid=...), but the tool call is auto-cancelled — presumably because stdin is closed so the approval prompt's reader gets EOF and codex interprets that as "user declined".
What I tried (none of these suppress the approval)
Tried each of these in $CODEX_HOME/config.toml:
approval_policy = "never"
default_tools_approval_mode = "never"
tools_require_approval = false
mcp_approval_policy = "never"
trusted_mcp_servers = ["spike"]
[projects."<spike-cwd>"]
trust_level = "trusted"
[mcp_servers.spike]
approval_policy = "never"
(approval_policy and default_tools_approval_mode are accepted by codex without errors — session header shows approval: never — but neither prevents the MCP-call cancellation. The other keys are silently ignored.)
I also tried --ignore-user-config + -c mcp_servers.spike.command=... CLI overrides; the MCP server didn't even register that way.
What does work
--dangerously-bypass-approvals-and-sandbox makes the call succeed:
mcp: spike/echo_hello (completed)
{"result":"hello test"}
But this flag also disables -s read-only, so it's not a viable production posture for a daemon that needs to keep filesystem reads sandboxed.
What I'm trying to do
I'm migrating a personal-AI daemon (codebase: github.com/gsingal/hanuman, issue #143) from claude -p to codex exec so it doesn't burn API credits. The general-agent layer needs to call our first-party STDIO MCP server (read-only Google tools + a frozen-URL allowlist enforced server-side in Python) while keeping -s read-only codex sandboxing so the model can't read arbitrary files outside the per-call scratch dir.
Ask
What's the supported config to authorize MCP tool calls in codex exec without --dangerously-bypass-approvals-and-sandbox?
Specifically: is there a way to say "trust this MCP server's tool calls without prompting" that works alongside -s read-only?
If this isn't currently supported, would you accept a config key proposal like [mcp_servers.<name>] auto_approve_tools = true (or a global default_mcp_tools_approval_mode = "never") for the same use case other agent-framework users will hit when calling codex non-interactively?
Why I think this matters beyond my use case
Any non-interactive use of codex exec with custom MCP servers (cron jobs, headless daemons, CI-driven agents, evaluation harnesses) hits this same wall. The --dangerously-bypass-approvals-and-sandbox flag's name (correctly) discourages using it in production, but right now it's the only path to a working non-interactive MCP-tool call.
Happy to provide more repro detail, RUST_LOG output, or test a candidate fix.
4 Comments
Proposed Solution: MCP-Specific Approval Config for Non-Interactive Mode
Root Cause
MCP tool calls go through the same approval prompt as other tools. When stdin is closed (non-interactive), EOF is interpreted as "user declined". No config key bypasses MCP approval while keeping sandbox active.
Fix
[mcp_servers.<name>] auto_approve_tools = trueconfig keyapproval_policy = "never"for MCP calls (currently only affects non-MCP)--dangerously-bypass-approvals-and-sandboxinto--bypass-approvals+--bypass-sandboxFull solution:
solutions/codex-24135-mcp-tool-calls-non-interactive-fix.mdI think there is one safety detail worth making explicit if Codex adds an MCP-specific non-interactive approval path.
For headless
codex exec, I would avoid making this only a broad boolean likeauto_approve_tools = true. The production-safe shape is closer to:spike.echo_hello, not just raw tool nametools/listThat would preserve the thing this issue is asking for: first-party MCP tools can run in
codex exec -s read-onlywithout stdin approval prompts, while the user does not have to use the broader dangerous bypass flag.I made a small free helper for this exact review artifact while testing MCP launch/security workflows:
https://github.com/kayalopez/ai-agent-launch-tools/releases/tag/v0.1.7
It turns non-sensitive
tools/listJSON into an allow / ask / deny matrix and emits a snapshot digest plus per-tool metadata digests. It can also print a Codexconfig.tomlreview snippet: allow ->approval_mode = "approve", ask ->approval_mode = "prompt", and deny ->disabled_tools, without changing sandbox settings. The useful bit for this issue is the acceptance-test pattern: run discovery, approve exact tool keys, then verify a changedtools/listsnapshot forces review instead of inheriting approval silently.No tool calls are executed by the helper; it only classifies pasted/exported metadata.
For non-interactive
codex exec, I would keep sandbox policy and MCP approval policy completely separate.The unsafe shape is a broad switch that means "auto-approve MCP tools" and accidentally becomes equivalent to bypassing the sandbox. The safer shape is a scoped MCP approval policy that can be evaluated without stdin:
Then headless execution can work like this:
--dangerously-bypass-approvals-and-sandboxthe only path to non-interactive MCP.Regression tests I would want:
That gives automation a safe path without making approval bypass and sandbox bypass the same lever.
This is exactly the kind of problem I'm trying to validate.
I've been building a small Python authorization layer that sits outside the agent and evaluates individual tool calls before execution, rather than having to choose between interactive approval and effectively bypassing the safety layer.
The interesting use case seems to be headless/CI agents where you still want deterministic permissions and possibly approval policies without a human sitting at stdin.
If you've still got the use case around, I'd be happy to give you the prototype to test against your MCP setup.
I'd be especially interested in whether it actually solves the gap or just adds another layer you wouldn't want in the stack.