MCP 2026-07-28: conforming `input_required` result on tools/call fails with "Unexpected response type", although the client declared `elicitation.url`

Open 💬 1 comment Opened Aug 25, 2026 by juliankolbe

Summary

Codex declares elicitation: { url: {}, form: {} } in the per-request _meta.io.modelcontextprotocol/clientCapabilities envelope on the 2026-07-28 stateless lane — but when a server takes it at its word and answers a tools/call with a spec-conforming InputRequiredResult (MRTR, SEP-2322) carrying a url-mode elicitation/create, the tool call fails client-side with:

tool call error: tool call failed for `acme/send_message`

Caused by:
    Unexpected response type

The elicitation is never surfaced to the user. In my case the input_required was a human-in-the-loop approval gate (a server-side policy requiring confirmation before a send-type action), so the practical effect was: the user never saw the approval prompt, and the agent treated the governed tool as broken and routed around it.

Environment

  • codex-mcp-client 0.149.0-alpha.4.3 (0.147.0 behaves the same way)
  • Streamable HTTP, protocol 2026-07-28 (stateless _meta envelope), against a 2026-07-28-conforming MCP server
  • The client's envelope declared elicitation: { url: {}, form: {} } — which is exactly what invites the server to send the url-mode ask (per MRTR server requirement #7, servers MUST NOT embed input requests the client didn't declare — so a client that did NOT declare it would have received the server's fallback carrier instead).

Wire flow

  1. tools/call → server-side policy requires human confirmation.
  2. Server responds 200 with (shape abbreviated):
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "approval": {
        "method": "elicitation/create",
        "params": {
          "mode": "url",
          "url": "https://mcp.example.com/approve?token=…",
          "message": "This action needs your approval. Review and approve it here, then it will continue."
        }
      }
    }
  }
}
  1. Codex errors with Unexpected response type instead of presenting the URL consent flow and retrying the call.

Notably, this payload shape is essentially identical to the one your own MRTR test server emits (codex-rs/rmcp-client/src/bin/test_mcp_2026_stdio_server.rsresultType: "input_required", inputRequests.approval.method = "elicitation/create"), and codex-rs/rmcp-client/tests/mcp_2026_mrtr.rs asserts it is handled. So the server response appears conformant by codex's own fixtures.

Where it breaks (as far as I can trace from source)

  • The outer error string comes from codex-rs/codex-mcp/src/connection_manager.rs ("tool call failed for {server}/{tool}"), which calls the typed client.call_tool(...) and converts via call_tool_result_from_rmcp(result).
  • The inner Unexpected response type is rmcp's ServiceError::UnexpectedResponse (crates/rmcp/src/service.rs), raised by the strict typed-response match: ServerResult::CallToolResult(r) => Ok(r), _ => Err(ServiceError::UnexpectedResponse). An input_required result is (correctly) not a CallToolResult, so the typed helper rejects it before any MRTR handling can run.
  • i.e. the MRTR-aware path in codex-rs/rmcp-client exists and is tested, but the codex-mcp connection-manager path used for these tool calls doesn't route through it — capability declaration and result handling are out of sync.

Why this matters more than the sibling decode issues

Related open issues hit the same UnexpectedResponse arm via decode strictness (#29002 — valid result decodes as CustomResult; #38979 — non-integer priority annotation). This one is different in kind: the client advertises elicitation.url per-request, which per spec invites servers to send url-mode input requests — and then hard-fails on the conforming response. Until the handling path catches up, declaring the capability makes behavior strictly worse than not declaring it.

A second-order safety effect worth flagging: when the human-approval elicitation errors out, the agent's natural recovery is to look for an equivalent tool that isn't behind the gate and try again — i.e. the failure mode actively steers agents around human-in-the-loop approvals.

Suggested fix directions

  • Route tools/call results on the codex-mcp path through the same input_required-aware handling rmcp-client already has (or extend call_tool_result_from_rmcp to accept the MRTR variant), or
  • until then, stop declaring elicitation.url in the _meta capabilities on paths that can't fulfill it, so conforming servers use their fallback carriers.

Happy to provide full wire captures or test against a fix.

View original on GitHub ↗

1 Comment

juliankolbe · 2 days ago

Reproduced in both the desktop app and the CLI — identical error either way, which is consistent with the trace above: both surfaces route tool calls through the same codex-mcp connection-manager path.