allow extension-managed Codex Apps auth

This commit is contained in:
Rennie Song
2026-07-03 22:34:59 +00:00
parent 319d03056e
commit be3eb41118
5 changed files with 108 additions and 2 deletions
+1
View File
@@ -43,6 +43,7 @@ pub use mcp::codex_apps_mcp_server_config;
pub use mcp::configured_mcp_servers;
pub use mcp::effective_mcp_servers;
pub use mcp::effective_mcp_servers_from_configured;
pub use mcp::extension_managed_codex_apps_enabled;
pub use mcp::host_owned_codex_apps_enabled;
pub use mcp::hosted_plugin_runtime_mcp_server_config;
pub use mcp::tool_plugin_provenance;
+16 -1
View File
@@ -43,6 +43,7 @@ use rmcp::model::ReadResourceResult;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use crate::McpServerSource;
use crate::ResolvedMcpCatalog;
use crate::codex_apps_cache::CodexAppsToolsCache;
use crate::codex_apps_cache::codex_apps_tools_cache_key;
@@ -238,8 +239,22 @@ impl ToolPluginProvenance {
}
}
pub fn extension_managed_codex_apps_enabled(config: &McpConfig) -> bool {
// Trusted host extensions may replace the ChatGPT-backed server with one
// that supplies its own request authentication.
config.apps_enabled
&& config
.mcp_server_catalog
.server(CODEX_APPS_MCP_SERVER_NAME)
.is_some_and(|server| {
matches!(server.source(), McpServerSource::Extension { .. })
&& matches!(&server.config().auth, &McpServerAuth::OAuth)
})
}
pub fn host_owned_codex_apps_enabled(config: &McpConfig, auth: Option<&CodexAuth>) -> bool {
config.apps_enabled && auth.is_some_and(CodexAuth::uses_codex_backend)
extension_managed_codex_apps_enabled(config)
|| (config.apps_enabled && auth.is_some_and(CodexAuth::uses_codex_backend))
}
pub fn configured_mcp_servers(config: &McpConfig) -> HashMap<String, McpServerConfig> {
+82
View File
@@ -285,6 +285,88 @@ fn codex_apps_server_config_forwards_configured_product_sku_header() {
}
}
#[test]
fn effective_mcp_servers_preserve_extension_owned_oauth_codex_apps_without_chatgpt_auth() {
let mut config = test_mcp_config(PathBuf::new());
config.apps_enabled = true;
let compatibility_config =
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None);
let mut extension_config = compatibility_config.clone();
extension_config.auth = McpServerAuth::OAuth;
let McpServerTransportConfig::StreamableHttp { http_headers, .. } =
&mut extension_config.transport
else {
panic!("expected streamable http transport for codex apps");
};
*http_headers = Some(HashMap::from([(
"X-OpenAI-Actor-Authorization".to_string(),
"actor-biscuit".to_string(),
)]));
let expected = extension_config.clone();
let mut catalog = ResolvedMcpCatalog::builder();
catalog.register(McpServerRegistration::from_compatibility(
CODEX_APPS_MCP_SERVER_NAME.to_string(),
"legacy_codex_apps".to_string(),
compatibility_config,
));
catalog.register(McpServerRegistration::from_extension(
CODEX_APPS_MCP_SERVER_NAME.to_string(),
"host_managed_apps".to_string(),
/*contribution_order*/ 0,
extension_config,
));
config.mcp_server_catalog = catalog.build();
let effective = effective_mcp_servers(&config, /*auth*/ None);
assert_eq!(
effective
.get(CODEX_APPS_MCP_SERVER_NAME)
.and_then(EffectiveMcpServer::configured_config),
Some(&expected)
);
}
#[test]
fn effective_mcp_servers_hide_chatgpt_codex_apps_without_chatgpt_auth() {
let mut config = test_mcp_config(PathBuf::new());
config.apps_enabled = true;
let mut catalog = ResolvedMcpCatalog::builder();
catalog.register(McpServerRegistration::from_compatibility(
CODEX_APPS_MCP_SERVER_NAME.to_string(),
"legacy_codex_apps".to_string(),
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None),
));
config.mcp_server_catalog = catalog.build();
let effective = effective_mcp_servers(&config, /*auth*/ None);
assert!(!effective.contains_key(CODEX_APPS_MCP_SERVER_NAME));
}
#[test]
fn effective_mcp_servers_hide_config_owned_oauth_codex_apps_without_chatgpt_auth() {
let mut config = test_mcp_config(PathBuf::new());
config.apps_enabled = true;
let mut server_config =
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None);
server_config.auth = McpServerAuth::OAuth;
let mut catalog = ResolvedMcpCatalog::builder();
catalog.register(McpServerRegistration::from_config(
CODEX_APPS_MCP_SERVER_NAME.to_string(),
server_config,
));
config.mcp_server_catalog = catalog.build();
let effective = effective_mcp_servers(&config, /*auth*/ None);
assert!(!effective.contains_key(CODEX_APPS_MCP_SERVER_NAME));
}
#[tokio::test]
async fn effective_mcp_servers_preserve_runtime_servers() {
let codex_home = tempfile::tempdir().expect("tempdir");
+1
View File
@@ -111,6 +111,7 @@ pub(super) async fn spawn_review_thread(
realtime_active: parent_turn_context.realtime_active,
config: per_turn_config,
auth_manager: auth_manager_for_context,
extension_managed_apps_enabled: parent_turn_context.extension_managed_apps_enabled,
model_info: model_info.clone(),
session_telemetry: session_telemetry_for_context,
provider: provider_for_context,
+8 -1
View File
@@ -105,6 +105,7 @@ pub struct TurnContext {
pub(crate) realtime_active: bool,
pub config: Arc<Config>,
pub(crate) auth_manager: Option<Arc<AuthManager>>,
pub(crate) extension_managed_apps_enabled: bool,
pub(crate) model_info: ModelInfo,
pub(crate) session_telemetry: SessionTelemetry,
pub(crate) provider: SharedModelProvider,
@@ -201,7 +202,7 @@ impl TurnContext {
.is_some_and(AuthManager::current_auth_uses_codex_backend);
self.config
.features
.apps_enabled_for_auth(uses_codex_backend)
.apps_enabled_for_auth(uses_codex_backend || self.extension_managed_apps_enabled)
&& self.config.orchestrator_mcp_enabled
}
@@ -253,6 +254,7 @@ impl TurnContext {
realtime_active: self.realtime_active,
config: Arc::new(config),
auth_manager: self.auth_manager.clone(),
extension_managed_apps_enabled: self.extension_managed_apps_enabled,
model_info: model_info.clone(),
session_telemetry: self
.session_telemetry
@@ -534,6 +536,7 @@ impl Session {
realtime_active: false,
config: per_turn_config,
auth_manager: auth_manager_for_context,
extension_managed_apps_enabled: false,
model_info,
session_telemetry: session_telemetry_for_context,
provider: provider_for_context,
@@ -763,6 +766,10 @@ impl Session {
sub_id,
skills_snapshot,
);
turn_context.extension_managed_apps_enabled =
codex_mcp::extension_managed_codex_apps_enabled(
self.services.latest_mcp_runtime().config(),
);
turn_context.realtime_active = self.conversation.running_state().await.is_some();
if let Some(final_schema) = final_output_json_schema {