mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Honor app link settings for MCP tool approvals (#42056)
## What changed - Pass the resolved app `link_id` into MCP tool approval policy evaluation. - Apply link-specific `default_tools_approval_mode` and `approvals_reviewer` settings before app and global defaults, while preserving per-tool and managed policy precedence. ## Testing - Cover link-specific approval prompts, selected account links, reviewer routing, fallback behavior, and managed-policy overrides. GitOrigin-RevId: caa07ec3e73c87aad9522bd69808cd1c0b1686f5
This commit is contained in:
1 parent
0e37d834d4
commit
3a04482645
11 files changed
+429
-39
No files matched your search
@@ -22,10 +22,11 @@ impl Default for AppToolPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Connector-owned metadata used to evaluate one app tool.
|
||||
/// App and account metadata used to evaluate one tool.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AppToolPolicyInput<'a> {
|
||||
pub connector_id: Option<&'a str>,
|
||||
pub link_id: Option<&'a str>,
|
||||
pub tool_name: &'a str,
|
||||
pub tool_title: Option<&'a str>,
|
||||
pub destructive_hint: Option<bool>,
|
||||
@@ -186,6 +187,12 @@ fn app_tool_policy_from_apps_config(
|
||||
});
|
||||
let approval = managed_approval
|
||||
.or_else(|| tool_config.and_then(|tool| tool.approval_mode))
|
||||
.or_else(|| {
|
||||
input
|
||||
.link_id
|
||||
.and_then(|link_id| app?.links.as_ref()?.links.get(link_id))
|
||||
.and_then(|link| link.default_tools_approval_mode)
|
||||
})
|
||||
.or_else(|| app.and_then(|app| app.default_tools_approval_mode))
|
||||
.or_else(|| {
|
||||
input
|
||||
|
||||
@@ -488,6 +488,112 @@ fn managed_approval_overrides_user_tool_approval() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_privacy_mode_overrides_app_default_and_preserves_tool_settings() {
|
||||
let apps_config = serde_json::from_value(serde_json::json!({
|
||||
"calendar": {
|
||||
"default_tools_approval_mode": "auto",
|
||||
"tools": { "events/create": { "approval_mode": "writes" } },
|
||||
"links": {
|
||||
"link_calendar": { "default_tools_approval_mode": "approve" },
|
||||
"link_other": { "default_tools_approval_mode": "prompt" },
|
||||
"link_without_privacy": {},
|
||||
},
|
||||
},
|
||||
"drive": {
|
||||
"links": { "link_drive": { "default_tools_approval_mode": "prompt" } },
|
||||
},
|
||||
"without_links": {
|
||||
"default_tools_approval_mode": "writes",
|
||||
},
|
||||
"empty_links": {
|
||||
"default_tools_approval_mode": "prompt",
|
||||
"links": {},
|
||||
},
|
||||
}))
|
||||
.expect("apps config");
|
||||
let evaluator = AppToolPolicyEvaluator::from_parts(
|
||||
Some(apps_config),
|
||||
/*requirements_apps_config*/ None,
|
||||
);
|
||||
|
||||
for (link_id, approval) in [
|
||||
(Some("link_calendar"), AppToolApproval::Approve),
|
||||
(Some("link_other"), AppToolApproval::Prompt),
|
||||
(Some("link_without_privacy"), AppToolApproval::Auto),
|
||||
(Some("link_drive"), AppToolApproval::Auto),
|
||||
(None, AppToolApproval::Auto),
|
||||
] {
|
||||
assert_eq!(
|
||||
evaluator.policy(AppToolPolicyInput {
|
||||
link_id,
|
||||
..input("events/list", /*tool_title*/ None)
|
||||
}),
|
||||
AppToolPolicy {
|
||||
enabled: true,
|
||||
approval,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
evaluator.policy(AppToolPolicyInput {
|
||||
link_id: Some("link_calendar"),
|
||||
..input("events/create", /*tool_title*/ None)
|
||||
}),
|
||||
AppToolPolicy {
|
||||
enabled: true,
|
||||
approval: AppToolApproval::Writes,
|
||||
}
|
||||
);
|
||||
|
||||
for (connector_id, approval) in [
|
||||
("without_links", AppToolApproval::Writes),
|
||||
("empty_links", AppToolApproval::Prompt),
|
||||
] {
|
||||
assert_eq!(
|
||||
evaluator.policy(AppToolPolicyInput {
|
||||
connector_id: Some(connector_id),
|
||||
link_id: Some("link_calendar"),
|
||||
..input("events/list", /*tool_title*/ None)
|
||||
}),
|
||||
AppToolPolicy {
|
||||
enabled: true,
|
||||
approval,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_privacy_mode_preserves_managed_connector_requirements() {
|
||||
let apps_config = serde_json::from_value(serde_json::json!({
|
||||
"calendar": {
|
||||
"links": { "link_calendar": { "default_tools_approval_mode": "approve" } },
|
||||
},
|
||||
}))
|
||||
.expect("apps config");
|
||||
let mut requirements =
|
||||
app_tool_requirements("calendar", "events/create", AppToolApproval::Prompt);
|
||||
requirements
|
||||
.apps
|
||||
.get_mut("calendar")
|
||||
.expect("calendar requirement")
|
||||
.enabled = Some(false);
|
||||
let evaluator = AppToolPolicyEvaluator::from_parts(Some(apps_config), Some(&requirements));
|
||||
|
||||
assert_eq!(
|
||||
evaluator.policy(AppToolPolicyInput {
|
||||
link_id: Some("link_calendar"),
|
||||
..input("events/create", /*tool_title*/ None)
|
||||
}),
|
||||
AppToolPolicy {
|
||||
enabled: false,
|
||||
approval: AppToolApproval::Prompt,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_tool_enable_overrides_app_level_hints() {
|
||||
let apps_config = AppsConfigToml {
|
||||
@@ -705,6 +811,7 @@ fn evaluator_matches_tool_title_for_user_config() {
|
||||
fn input<'a>(tool_name: &'a str, tool_title: Option<&'a str>) -> AppToolPolicyInput<'a> {
|
||||
AppToolPolicyInput {
|
||||
connector_id: Some("calendar"),
|
||||
link_id: None,
|
||||
tool_name,
|
||||
tool_title,
|
||||
destructive_hint: Some(true),
|
||||
@@ -795,6 +902,7 @@ fn policy_from_config_parts(
|
||||
};
|
||||
AppToolPolicyEvaluator::new(&config_layer_stack).policy(AppToolPolicyInput {
|
||||
connector_id,
|
||||
link_id: None,
|
||||
tool_name,
|
||||
tool_title,
|
||||
destructive_hint,
|
||||
|
||||
@@ -69,6 +69,7 @@ pub fn installed_connector_runtime<'a>(
|
||||
let policy_allows_tool = policy
|
||||
.policy(AppToolPolicyInput {
|
||||
connector_id: Some(connector_id),
|
||||
link_id: None,
|
||||
tool_name: tool.tool_name,
|
||||
tool_title: tool.tool_title,
|
||||
destructive_hint: tool.destructive_hint,
|
||||
|
||||
Reference in new issue
Block a user