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
2026-09-07 04:33:22 +00:00
committed by copyberry
parent 121f91fd5d
commit 555b82afa9
31 changed files with 1244 additions and 64 deletions
+16 -4
View File
@@ -288,6 +288,7 @@ impl MessageProcessor {
plugin_startup_tasks,
} = args;
let thread_state_manager = ThreadStateManager::new();
outgoing.watch_user_verification_auth(Arc::clone(&auth_manager));
// The thread store is intentionally process-scoped. Config reloads can
// affect per-thread behavior, but they must not move newly started,
// resumed, or forked threads to a different persistence backend/root.
@@ -804,6 +805,9 @@ impl MessageProcessor {
session_state: &ConnectionSessionState,
) {
session_state.rpc_gate.close().await;
self.outgoing
.disconnect_user_verification_connection(connection_id)
.await;
session_state.mcp_event_streams.clear().await;
if timeout(
CONNECTION_RPC_DRAIN_TIMEOUT,
@@ -835,14 +839,22 @@ impl MessageProcessor {
}
/// Handle a standalone JSON-RPC response originating from the peer.
pub(crate) async fn process_response(&self, response: JSONRPCResponse) {
pub(crate) async fn process_response(
&self,
connection_id: ConnectionId,
response: JSONRPCResponse,
) {
let JSONRPCResponse { id, result, .. } = response;
self.outgoing.notify_client_response(id, result).await
self.outgoing
.notify_client_response(connection_id, id, result)
.await
}
/// Handle an error object received from the peer.
pub(crate) async fn process_error(&self, err: JSONRPCError) {
self.outgoing.notify_client_error(err.id, err.error).await;
pub(crate) async fn process_error(&self, connection_id: ConnectionId, err: JSONRPCError) {
self.outgoing
.notify_client_error(connection_id, err.id, err.error)
.await;
}
async fn handle_client_request(