codex mcp-server: `-c` config overrides are accepted but never applied to sessions
Codex CLI version: 0.145.0
Subscription: ChatGPT Pro
Model: gpt-5.6-sol
Platform: macOS 26.5.0, arm64
Terminal: ghostty 1.3.1
What issue are you seeing?
codex mcp-server advertises the same -c, --config <key=value> flag as the other
subcommands, and it parses without complaint, but the override never reaches the sessions
the server creates. Those sessions fall back to whatever is in ~/.codex/config.toml.
Nothing warns you, so a config that didn't apply looks identical to one that did.
I ran into this setting approvals_reviewer for an MCP client that wrapscodex mcp-server. Starting the server with -c approvals_reviewer=user left sessions onguardian_subagent from my config.toml. Passing the same setting through the codex
tool's per-call config argument works, so only the CLI flag is affected.
model is the easiest way to see it, since session_configured reports the model it
resolved.
What steps can reproduce the bug?
With model = "gpt-5.6-sol" in ~/.codex/config.toml:
- Start
codex mcp-server -c model="gpt-5.2" - Send
initialize, thennotifications/initialized - Call the
codextool with any prompt, e.g.
{"cwd": "/tmp", "sandbox": "read-only", "approval-policy": "never", "prompt": "Reply with only: OK"}
- Read
modelfrom thesession_configuredevent
Observed model=gpt-5.6-sol, expected gpt-5.2. Running it again with no -c gives the
same value, which is how I convinced myself the flag is doing nothing at all rather than
being partially applied.
<details>
<summary>Script that runs both cases and prints them side by side</summary>
"""Compare the model resolved by `codex mcp-server` with and without -c."""
import json, subprocess, threading, time
def run(extra_args, label):
proc = subprocess.Popen(["codex", "mcp-server", *extra_args],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
text=True, bufsize=1, cwd="/tmp")
done, seen = {}, {}
def send(o):
proc.stdin.write(json.dumps(o) + "\n"); proc.stdin.flush()
def reader():
for line in proc.stdout:
try:
m = json.loads(line)
except json.JSONDecodeError:
continue
if m.get("method") == "codex/event":
msg = (m.get("params") or {}).get("msg") or {}
if msg.get("type") == "session_configured":
seen["model"] = msg.get("model")
elif m.get("id") is not None and ("result" in m or "error" in m):
done[m["id"]] = m
threading.Thread(target=reader, daemon=True).start()
send({"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "repro", "version": "1.0"}}})
t = time.monotonic() + 30
while 1 not in done and time.monotonic() < t:
time.sleep(0.2)
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
send({"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": "codex", "arguments": {
"cwd": "/tmp", "sandbox": "read-only", "approval-policy": "never",
"prompt": "Reply with only: OK"}}})
deadline = time.monotonic() + 180
while 2 not in done and time.monotonic() < deadline:
time.sleep(0.25)
proc.kill()
print(f" {label:<42} model={seen.get('model')}")
run([], "no -c flag (config.toml default)")
run(["-c", 'model="gpt-5.2"'], '-c model="gpt-5.2"')
Output:
no -c flag (config.toml default) model=gpt-5.6-sol
-c model="gpt-5.2" model=gpt-5.6-sol
</details>
What is the expected behavior?
Either the override applies to the sessions the server creates, or codex mcp-server
rejects or warns about -c so it's obvious it had no effect. The silent success is the
part that cost me time.
Additional information
codex-rs/mcp-server/src/lib.rs does parse the overrides and build a Config from them,
so this looks like a propagation gap rather than intended behavior:
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| { ... })?;
let config = ConfigBuilder::default()
.cli_overrides(cli_kv_overrides)
.strict_config(strict_config)
.build()
.await
As far as I can tell that config is used for the server's own setup (otel, state db,EnvironmentManager, codex_home, installation id) and isn't what the tool-call sessions
resolve from. I didn't trace where it gets dropped.
Closest existing issues I could find, both different: #35780 is about -c mis-splitting
quoted keys containing periods, and #13243 is about --help formatting for --config.
2 Comments
I traced this to the per-session config rebuild in
CodexToolCallParam:run_mainparses the root-cvalues and uses them for the MCP server's baseConfig, but eachcodextool call creates a freshConfigBuilderfrom only the call'sconfigand typed fields.I have a focused local patch that retains the parsed startup override list in
MessageProcessorand merges it in this order for ordinary user-controlled keys:config.toml < codex mcp-server -c < tool-call config < typed tool fieldsManaged configuration and requirements retain their existing authority. The patch preserves the public
CodexToolCallParam::into_config(Arg0DispatchPaths)signature and does not change--strict-config, schemas, dependencies,codex-core, orcodex-reply.The regression test launches the real top-level
codex mcp-serverand checks threesession_configuredevents: startup overrides, request-level overrides, and a typedmodeloverride. It isolatesCODEX_HOME,CODEX_SQLITE_HOME, proxies, and host-managed config; a missing test-only provider key stops submission before model transport/socket I/O.The test failed before the production change with
config-modelinstead ofstartup-model. Local validation now passes:just test -p codex-mcp-server— 15/15just test -p codex-cli --test mcp_server— 1/1just test -p codex-cli— 339/339cargo clippy -p codex-mcp-server --tests --profile release -- -D warningsjust fix -p codex-mcp-serverjust fix -p codex-clijust fmtI have kept the branch local and have not opened a PR because external code contributions are invitation-only. If this approach aligns with the intended fix, would a maintainer be willing to invite a PR? I can submit the tested patch promptly.
Note: We've announced that the mcp-server is deprecated and will be removed in a future release. We recommend using the app server interface instead.