[app-server][core] Add connector-level Guardian reviewer overrides (#25167)

Context: https://openai.slack.com/archives/C0B4JAF0Q2C/p1779912328647229

```
approvals_reviewer = "auto_review"

[apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
enabled = true
approvals_reviewer = "user"
default_tools_approval_mode = "prompt"
```

<img width="230" height="84" alt="Screenshot 2026-05-31 at 11 56 34 AM"
src="https://github.com/user-attachments/assets/e319f8f7-0983-42a7-98cd-3302732fa406"
/>

<img width="841" height="233" alt="Screenshot 2026-05-31 at 11 52 42 AM"
src="https://github.com/user-attachments/assets/7ac76645-4e90-4d00-8242-f031146a22a5"
/>

-------

```
approvals_reviewer = "user"

[apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
enabled = true
approvals_reviewer = "auto_review"
default_tools_approval_mode = "prompt"
```
<img width="195" height="83" alt="Screenshot 2026-05-31 at 12 02 27 PM"
src="https://github.com/user-attachments/assets/3d374dc8-8aa2-466f-a13f-e4ed8567aa2e"
/>
<img width="771" height="207" alt="Screenshot 2026-05-31 at 12 05 42 PM"
src="https://github.com/user-attachments/assets/105c2575-68d6-4ca6-8e69-dc8c82da36a2"
/>



## Summary
- add `apps.<connector_id>.approvals_reviewer` to override Guardian or
user review routing per connected app
- apply overrides across direct app MCP calls, delegated MCP prompts,
and app-server MCP elicitation review while preserving global behavior
for non-app MCP servers
- expose and document the config through app-server v2 and generated
schemas, while honoring global managed reviewer requirements

---------

Co-authored-by: jif-oai <jif@openai.com>
This commit is contained in:
Alex Zamoshchinandjif-oai authored and GitHub committed 2026-06-02 17:04:11 +02:00
1 parent c097ad3e9e
commit 4d80d808b4
22 files changed
+337 -21

No files matched your search

+70
View File
@@ -2,6 +2,7 @@ use super::*;
use crate::mcp_tool_call::MCP_TOOL_APPROVAL_DECLINE_SYNTHETIC;
use crate::mcp_tool_call::MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX;
use async_channel::bounded;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::ResponseItem;
@@ -445,3 +446,72 @@ async fn delegated_mcp_guardian_abort_returns_synthetic_decline_answer() {
})
);
}
#[tokio::test]
async fn delegated_mcp_user_reviewer_waits_for_metadata_lookup() {
let (parent_session, parent_ctx, _rx_events) =
crate::session::tests::make_session_and_context_with_rx().await;
let pending_mcp_invocations = Arc::new(Mutex::new(HashMap::from([(
"call-1".to_string(),
McpInvocation {
server: CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool: "dangerous_tool".to_string(),
arguments: None,
},
)])));
let cancel_token = CancellationToken::new();
let manager = Arc::clone(&parent_session.services.mcp_connection_manager);
let (manager_locked_tx, manager_locked_rx) = std::sync::mpsc::sync_channel(0);
let (release_manager_tx, release_manager_rx) = std::sync::mpsc::sync_channel(0);
let manager_lock = tokio::task::spawn_blocking(move || {
let _manager_guard = manager.blocking_write();
manager_locked_tx
.send(())
.expect("manager lock receiver should remain open");
release_manager_rx
.recv()
.expect("manager lock release sender should remain open");
});
manager_locked_rx
.recv_timeout(Duration::from_secs(1))
.expect("manager write lock should be acquired");
let event = RequestUserInputEvent {
call_id: "call-1".to_string(),
turn_id: "child-turn-1".to_string(),
questions: vec![RequestUserInputQuestion {
id: format!("{MCP_TOOL_APPROVAL_QUESTION_ID_PREFIX}_call-1"),
header: "Approve app tool call?".to_string(),
question: "Allow this app tool?".to_string(),
is_other: false,
is_secret: false,
options: None,
}],
};
let response = maybe_auto_review_mcp_request_user_input(
&parent_session,
&parent_ctx,
&pending_mcp_invocations,
&event,
&cancel_token,
);
tokio::pin!(response);
assert!(
timeout(Duration::from_millis(100), &mut response)
.await
.is_err(),
"manual reviewer should wait for MCP metadata"
);
release_manager_tx
.send(())
.expect("manager lock holder should remain open");
manager_lock
.await
.expect("manager lock task should not panic");
assert_eq!(
timeout(Duration::from_secs(1), response)
.await
.expect("manual reviewer should finish after MCP metadata lookup"),
None
);
}