mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
Resolve enterprise-managed MCP registrations in the catalog (#45459)
## What changed - Retain the trusted enterprise identity provider in runtime configuration and bind winning MCP registrations during catalog finalization. Require `features.use_xaa` and a configured identity provider for activation, while preserving existing server restrictions. - Apply plugin `ema_auth` client, issuer, resource, and scope settings to installed and selected plugins. Disable registrations with mismatched endpoints or empty resources without rewriting plugin endpoints. - Preserve enterprise auth policy across catalog rebuilds and rebind registrations when materialized server settings change. Keep registration rejection separate from persistent server-name vetoes so it does not disable replacement hosted apps. ## Testing Add coverage for activation gates, configuration ownership, plugin endpoint validation, catalog rebuilds, and skipping interactive OAuth during installation of enterprise-managed plugins. Stabilize the sandbox network proxy test by reading request headers before closing the loopback connection. GitOrigin-RevId: 3374f507d120835b285767cedbbb511fc7b0fba2
This commit is contained in:
@@ -969,8 +969,8 @@ async fn load_plugin(
|
||||
|
||||
fn apply_plugin_mcp_server_policy(config: &mut McpServerConfig, policy: &PluginMcpServerConfig) {
|
||||
config.enabled = policy.enabled;
|
||||
if policy.ema_auth.is_some() {
|
||||
config.auth = codex_config::McpServerAuth::EmaAuth;
|
||||
if let Some(ema) = &policy.ema_auth {
|
||||
ema.apply(config);
|
||||
}
|
||||
if let Some(approval_mode) = policy.default_tools_approval_mode {
|
||||
config.default_tools_approval_mode = Some(approval_mode);
|
||||
@@ -1438,11 +1438,11 @@ pub fn apply_configured_plugin_mcp_server_policies(
|
||||
) {
|
||||
for (name, server) in servers {
|
||||
if let Some(policy) = policies.get(name) {
|
||||
if policy.ema_auth.is_some() {
|
||||
server.auth = codex_config::McpServerAuth::EmaAuth;
|
||||
server.enabled &= policy.enabled;
|
||||
if let Some(ema) = &policy.ema_auth {
|
||||
ema.apply(server);
|
||||
}
|
||||
let declared_approval_mode = server.default_tools_approval_mode.unwrap_or_default();
|
||||
server.enabled &= policy.enabled;
|
||||
|
||||
if let Some(approval_mode) = policy.default_tools_approval_mode {
|
||||
server.default_tools_approval_mode =
|
||||
|
||||
@@ -27,6 +27,102 @@ fn user_layer(path: AbsolutePathBuf, config: &str) -> ConfigLayerEntry {
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ema_policy_overlays_native_and_agent_plugins_without_changing_endpoints() {
|
||||
let temp_dir = TempDir::new().expect("tempdir");
|
||||
let policy_config = r#"
|
||||
[plugins."sample@test".mcp_servers.example]
|
||||
enabled = true
|
||||
[plugins."sample@test".mcp_servers.example.ema_auth]
|
||||
url = "https://resource.example/mcp"
|
||||
client_id = "resource-client"
|
||||
authorization_server_issuer = "https://as.example"
|
||||
resource = "https://resource.example"
|
||||
scopes = ["tools"]
|
||||
"#;
|
||||
let stack = ConfigLayerStack::new(
|
||||
vec![ConfigLayerEntry::new(
|
||||
ConfigLayerSource::EnterpriseManaged {
|
||||
id: "ema-policy".to_string(),
|
||||
name: "EMA policy".to_string(),
|
||||
},
|
||||
toml::from_str(policy_config).expect("managed policy toml"),
|
||||
)],
|
||||
ConfigRequirements::default(),
|
||||
ConfigRequirementsToml::default(),
|
||||
)
|
||||
.expect("valid trusted policy stack");
|
||||
let policies = configured_plugins_from_stack(&stack, temp_dir.path())
|
||||
.remove("sample@test")
|
||||
.expect("configured plugin")
|
||||
.mcp_servers;
|
||||
|
||||
for (name, manifest_path, manifest, mcp_path, mcp) in [
|
||||
(
|
||||
"native",
|
||||
".codex-plugin/plugin.json",
|
||||
r#"{"name":"native"}"#,
|
||||
".mcp.json",
|
||||
r#"{"mcpServers":{"example":{"url":"https://resource.example/mcp"}}}"#,
|
||||
),
|
||||
(
|
||||
"agent",
|
||||
"plugin.json",
|
||||
r#"{"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","name":"agent"}"#,
|
||||
"mcp.json",
|
||||
r#"{"$schema":"https://agent-plugins.org/schemas/1.0.0/mcp.schema.json","mcpServers":{"example":{"type":"streamable-http","url":"https://resource.example/mcp"}}}"#,
|
||||
),
|
||||
] {
|
||||
for endpoint in [
|
||||
"https://resource.example/mcp",
|
||||
"https://resource.example/mcp/other",
|
||||
"https://resource.example/mcp?tenant=other",
|
||||
"https://other.example/mcp",
|
||||
] {
|
||||
let root = temp_dir.path().join(name);
|
||||
write_file(&root.join(manifest_path), manifest);
|
||||
write_file(
|
||||
&root.join(mcp_path),
|
||||
&mcp.replace("https://resource.example/mcp", endpoint),
|
||||
);
|
||||
let declared = load_plugin_mcp_servers(&root, /*auth_mode*/ None).await;
|
||||
let transport = declared["example"].transport.clone();
|
||||
let installed = load_plugin_mcp_servers_with_policy(
|
||||
&root,
|
||||
/*auth_mode*/ None,
|
||||
Some(&policies),
|
||||
)
|
||||
.await;
|
||||
let mut selected = declared;
|
||||
apply_configured_plugin_mcp_server_policies(&policies, &mut selected);
|
||||
assert_eq!(installed, selected);
|
||||
let server = &selected["example"];
|
||||
assert_eq!(server.transport, transport);
|
||||
assert_eq!(server.enabled, endpoint == "https://resource.example/mcp");
|
||||
assert_eq!(server.auth, codex_config::McpServerAuth::EmaAuth);
|
||||
assert_eq!(
|
||||
server.oauth,
|
||||
Some(codex_config::McpServerOAuthConfig {
|
||||
client_id: Some("resource-client".into()),
|
||||
authorization_server_issuer: Some("https://as.example".into()),
|
||||
ema_registration_error: (endpoint != "https://resource.example/mcp")
|
||||
.then_some("plugin endpoint does not match its EMA registration"),
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
server.oauth_resource.as_deref(),
|
||||
Some("https://resource.example")
|
||||
);
|
||||
assert_eq!(
|
||||
server.scopes.as_deref(),
|
||||
Some(["tools".to_string()].as_slice())
|
||||
);
|
||||
assert_eq!(server.oauth_idp(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn agent_plugin_overlay_apps_are_not_runtime_active() {
|
||||
let temp_dir = TempDir::new().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user