Accept MCP elicitation requests so headless MCP tool calls work - #641
Accept MCP elicitation requests so headless MCP tool calls work#641principalwater wants to merge 2 commits into
Conversation
Codex gates MCP tool calls behind an MCP elicitation (`mcpServer/elicitation/request`) rather than the exec/patch approval channel, so the `approvalPolicy: "never"` that every thread started by this client already declares does not cover them. `handleServerRequest` answered every server-initiated request with JSON-RPC -32601, which left the elicitation unresolved. Core maps a missing response to `ReviewDecision::Abort`, surfaced to the model as "user rejected MCP tool call". Because runs driven by this client are headless, nobody can accept interactively, so every MCP tool call in a `task` or `review` run failed. The job log only showed "Tool <server>/<tool> failed.", which points the user at a networking or MCP-config problem that does not exist. Answer the elicitation with `action: "accept"` instead. The response logic moves into an exported `buildServerRequestResponse()` so it can be tested directly; `AppServerClientBase` is not exported. Fixes openai#640 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 36abd9cc08
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
`mcpServer/elicitation/request` also carries ordinary form and URL elicitations from configured MCP servers, not just Codex tool-approval prompts. Accepting those with empty content would hand the server bogus consent or invalid input. Accept only when `params._meta.codex_approval_kind` is `mcp_tool_call` (`APPROVAL_KIND_KEY` / `APPROVAL_KIND_MCP_TOOL_CALL` in codex-rs/protocol/src/mcp_approval_meta.rs) and decline everything else, which a headless client can neither render nor validate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3f39556 to
2fb936e
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Fixes #640.
Problem
Every MCP tool call inside a plugin-driven run (
/codex:rescue,codex-companion.mjs task) fails immediately. The job log shows only:The rollout under
~/.codex/sessions/has the real cause:{"type": "mcp_tool_call_end", "invocation": {"server": "my-server", "tool": "my.tool", ...}, "duration": {"secs": 0, "nanos": 0}, "result": {"Err": "user rejected MCP tool call"}}durationis 0 — the call never leaves the client.Cause
Codex gates MCP tool calls behind an MCP elicitation (
mcpServer/elicitation/request), not the exec/patch approval channel. So theapprovalPolicy: "never"thatbuildThreadParamsalready sets for every thread this client starts does not cover them.handleServerRequestanswered every server-initiated request with-32601:That leaves the elicitation unresolved, and
codex-rs/core/src/mcp_tool_call.rsmaps a missing response toReviewDecision::Abort, surfaced asuser rejected MCP tool call. Runs driven by this client are headless, so nobody can accept interactively and every MCP tool call fails.The user-visible symptom is misleading: the agent concludes its MCP servers are unreachable and works without them. In my case that was a memory server the agent is instructed to consult before non-trivial work — it silently proceeded without project context and reported the memory as unavailable, which sent me looking for a networking problem that did not exist.
Change
Answer
mcpServer/elicitation/requestwithaction: "accept", perMcpServerElicitationRequestResponseincodex-rs/app-server-protocol. Everything else still gets-32601.The response logic moves into an exported pure function
buildServerRequestResponse()so it can be tested directly —AppServerClientBaseis not exported, so there was no way to reach the old method from a test.handleServerRequestis now one line.Verification
Unit — new
tests/app-server.test.mjs, 3 cases, all passing: elicitation is accepted, unimplemented server requests still get-32601, request id type is preserved.End to end — same plugin task calling an MCP memory server, before and after the change:
FAIL user rejected MCP tool callOK 1user rejected MCP tool callin job logNote:
tests/state.test.mjshas one failing case (resolveStateDir uses a temp-backed per-workspace directory) that reproduces on pristinemainand is unrelated to this change.Note for maintainers
Independently of this fix, propagating the real error text into the job log and
/codex:statuswould help a lot.Tool <server>/<tool> failed.gave no hint that approval was involved — diagnosing this required reading raw rollout JSONL.