mcp-server: exec/patch approval elicitations default to Denied for MCP-compliant responses

Open 💬 1 comment Opened Apr 17, 2026 by prodan-s

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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗