Add opt-in MCP user-verification transport (#43352)

## Why

Device-authenticated verification needs an app response outside automated approval and review. Previously, MCP user-verification requests were always cancelled.

## What changed

- Add typed `openai/userVerification` elicitations carrying a title, description, and challenge through core and app-server.
- Route verification to one connection enabled by trusted host activation. Restrict responses to that owner, exclude requests from replay, and cancel on disconnect or authentication changes, including account switches away and back.
- Return proofs in `content`, discard response metadata, and cancel malformed acceptances. Suppress verification response logging and analytics, and send only a generic verification notice to realtime.
- Keep the mode experimental and exclude it from stable schema exports. Capability advertisement remains disabled, and the TUI cancels verification requests.

## Testing

Add coverage for owner isolation, disconnect and authentication races, proof response handling, automated-review bypass, inactive-host cancellation, stable schema filtering, and realtime payload exclusion. Update the MCP integration test to verify that an activated request holds the tool until the client responds.

GitOrigin-RevId: c8c7b6691a3f7756682bbcca9def1df0d1feb906
This commit is contained in:
riley-oai authored and copyberry committed 2026-09-07 04:33:22 +00:00
1 parent 121f91fd5d
commit 555b82afa9
31 files changed
+1244 -64

No files matched your search

@@ -1,6 +1,7 @@
use super::shared::v2_enum_from_core;
use crate::JsonSchema;
use crate::TS;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
use codex_protocol::items::McpToolCallError as CoreMcpToolCallError;
use codex_protocol::mcp::CallToolResult as CoreMcpCallToolResult;
@@ -379,7 +380,7 @@ impl From<rmcp::model::ElicitationAction> for McpServerElicitationAction {
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerElicitationRequestParams {
@@ -393,6 +394,7 @@ pub struct McpServerElicitationRequestParams {
pub turn_id: Option<String>,
pub server_name: String,
#[serde(flatten)]
#[experimental(nested)]
pub request: McpServerElicitationRequest,
// TODO: When core can correlate an elicitation with an MCP tool call, expose the associated
// McpToolCall item id here as an optional field. The current core event does not carry that
@@ -744,11 +746,20 @@ pub struct McpElicitationConstOption {
pub title: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(tag = "mode", rename_all = "camelCase")]
#[ts(tag = "mode")]
#[ts(export_to = "v2/")]
pub enum McpServerElicitationRequest {
/// A device-authenticated approval; accepted responses contain the proof in `content`.
#[experimental("mcpServer/elicitation/request.userVerification")]
#[serde(rename = "openai/userVerification", rename_all = "camelCase")]
#[ts(rename = "openai/userVerification", rename_all = "camelCase")]
UserVerification {
title: String,
description: String,
challenge: String,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Form {
@@ -794,6 +805,15 @@ impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {
fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error> {
match value {
CoreElicitationRequest::UserVerification {
title,
description,
challenge,
} => Ok(Self::UserVerification {
title,
description,
challenge,
}),
CoreElicitationRequest::Form {
meta,
message,