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:
Nick Steele
2026-09-14 16:30:00 +00:00
committed by copyberry
parent b876f88981
commit 374c4b2d82
15 changed files with 792 additions and 47 deletions
+5 -5
View File
@@ -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 =
+96
View File
@@ -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");