codex - 💡(How to fix) Fix mcp-server: exec/patch approval elicitations default to Denied for MCP-compliant responses [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openai/codex#18268Fetched 2026-04-18 05:56:47
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4subscribed ×1unlabeled ×1

Root Cause

(content is empty because the requested_schema sent by codex is {"type":"object","properties":{}} — no declared fields.)

Fix Action

Fix / Workaround

codex mcp-server deserializes elicitation responses as flat structs (ExecApprovalResponse { decision } / PatchApprovalResponse { decision }) instead of MCP ElicitResult ({action, content}).

A spec-compliant MCP client responds to exec/patch approval elicitations with:

The same pattern exists in patch_approval.rs.

Code Example

{"action": "accept", "content": {}}

---

// TODO(mbolin): ExecApprovalResponse does not conform to ElicitResult. See:
// - https://github.com/modelcontextprotocol/modelcontextprotocol/blob/f962dc1780fa5eed7fb7c8a0232f1fc83ef220cd/schema/2025-06-18/schema.json#L617-L636
// - https://modelcontextprotocol.io/specification/draft/client/elicitation#protocol-messages
// It should have "action" and "content" fields.
RAW_BUFFERClick to expand / collapse

What issue are you seeing?

codex mcp-server deserializes elicitation responses as flat structs (ExecApprovalResponse { decision } / PatchApprovalResponse { decision }) instead of MCP ElicitResult ({action, content}).

A spec-compliant MCP client responds to exec/patch approval elicitations with:

{"action": "accept", "content": {}}

(content is empty because the requested_schema sent by codex is {"type":"object","properties":{}} — no declared fields.)

Deserialization fails because the response lacks a top-level decision field, so the server falls back to ReviewDecision::Denied. Net effect: every MCP-compliant approval is silently denied, and the user's "accept" never takes effect.

There is a self-documented TODO acknowledging the non-conformance:

https://github.com/openai/codex/blob/main/codex-rs/mcp-server/src/exec_approval.rs

// TODO(mbolin): ExecApprovalResponse does not conform to ElicitResult. See:
// - https://github.com/modelcontextprotocol/modelcontextprotocol/blob/f962dc1780fa5eed7fb7c8a0232f1fc83ef220cd/schema/2025-06-18/schema.json#L617-L636
// - https://modelcontextprotocol.io/specification/draft/client/elicitation#protocol-messages
// It should have "action" and "content" fields.

The same pattern exists in patch_approval.rs.

The bug is also baked into the server's own integration tests, which respond with the flat shape (codex-rs/mcp-server/tests/suite/codex_tool.rs:138-146 and :306-314). So MCP-compliant clients cannot currently drive those approvals successfully, and the test suite does not exercise the spec shape.

What steps can reproduce the bug?

  1. Start codex mcp-server and connect from any MCP client that implements the elicitation/create request/response per the MCP 2025-06-18 spec (Claude Code's Elicitation hook is a convenient example).
  2. Open a codex session with sandbox=read-only, approval-policy=on-request.
  3. Prompt codex to run a command that requires escalation, e.g. date > /tmp/x.txt.
  4. codex-mcp sends elicitation/create to the client.
  5. Client responds with spec-compliant {"action": "accept", "content": {}} (or with content.decision = "approved" since codex's Rust enum serializes to snake_case; both should work in principle, neither does today).
  6. Observed: codex reports "rejected by you" and refuses to run the command.
  7. Expected: codex runs the command.

Verified against main at fe7c959e90d46abb8311e4a0b369e6cb32bf337e (2026-04-16).

What is the expected behavior?

The server should parse MCP ElicitResult semantics correctly:

  • action: "accept" → treat as Approved (optionally promote to ApprovedForSession if content.decision == "approved_for_session")
  • action: "decline" → treat as Denied
  • action: "cancel" → treat as Abort (or nearest non-approval state)
  • content.decision should be read only when present AND the server has a reason to support richer decisions than a generic MCP client can produce

That is: action is the primary protocol signal, content is a capability extension.

Two orthogonal design notes:

  1. The current requested_schema is {"type":"object","properties":{}}, which tells generic clients "no content expected." If codex wants richer decisions (approved_for_session, policy amendments), it should encode that in requested_schema rather than assuming clients will invent a decision field.
  2. The flat deserialization defaulting to Denied on any parse failure is a silent-fail path that masks this bug from operators. A warning log on the deserialize failure path would have surfaced it earlier.

Additional information

Commit verified: fe7c959e90d46abb8311e4a0b369e6cb32bf337e (main, 2026-04-16).

Affected files:

  • codex-rs/mcp-server/src/exec_approval.rs:41-48 — flat ExecApprovalResponse struct
  • codex-rs/mcp-server/src/exec_approval.rs:127-135 — silent deny on deserialize failure
  • codex-rs/mcp-server/src/patch_approval.rs:38-40 — flat PatchApprovalResponse
  • codex-rs/mcp-server/src/patch_approval.rs:126-130 — silent deny on deserialize failure

Tests currently encode the non-compliant shape:

  • codex-rs/mcp-server/tests/suite/codex_tool.rs:138-146
  • codex-rs/mcp-server/tests/suite/codex_tool.rs:306-314

So a fix likely wants to update tests to use spec-compliant ElicitResult shape alongside the server-side change.

Related issues (adjacent, not duplicates):

  • #11816 — codex mcp-server hangs indefinitely when command approval requires elicitation but MCP client cannot answer
  • #13405 — Elicitation support (codex as MCP client side)

This issue is specifically about the server side responding to elicitation-based approvals from a compliant MCP client.

Context: Found while building an auto-approval hook on the client side. The hook emits spec-compliant ElicitResult responses (verified against MCP 2025-06-18 schema); codex denies every one. Happy to supply MCP wire captures, additional repro detail, or test cases if that helps maintainers.

extent analysis

TL;DR

Update the ExecApprovalResponse and PatchApprovalResponse structs to conform to the MCP ElicitResult shape, with action and content fields.

Guidance

  • Identify the non-compliant code in exec_approval.rs and patch_approval.rs and update the structs to match the MCP ElicitResult shape.
  • Modify the deserialization logic to handle the new shape and parse the action and content fields correctly.
  • Update the integration tests in codex_tool.rs to use the spec-compliant ElicitResult shape.
  • Consider adding a warning log on the deserialize failure path to prevent similar silent-fail issues in the future.

Example

// Updated ExecApprovalResponse struct
struct ExecApprovalResponse {
    action: String,
    content: serde_json::Value,
}

// Updated deserialization logic
impl<'de> Deserialize<'de> for ExecApprovalResponse {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = serde_json::Value::deserialize(deserializer)?;
        let action = value.get("action").ok_or(serde::de::Error::custom("missing action field"))?.as_str().unwrap().to_string();
        let content = value.get("content").ok_or(serde::de::Error::custom("missing content field"))?.clone();
        Ok(ExecApprovalResponse { action, content })
    }
}

Notes

The fix requires updating the server-side code to conform to the MCP specification, as well as updating the integration tests to use the correct shape. Additionally, consideration should be given to adding logging to prevent similar issues in the future.

Recommendation

Apply the workaround by updating the ExecApprovalResponse and PatchApprovalResponse structs to conform to the MCP ElicitResult shape, and modify the deserialization logic to handle the new shape. This will allow the server to correctly parse the action and content fields and respond accordingly.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING