fix Apps cache identity isolation

This commit is contained in:
Rennie Song
2026-07-05 20:48:06 +00:00
parent ea0eba7955
commit 9f5f214d75
25 changed files with 552 additions and 126 deletions
@@ -226,6 +226,10 @@ impl AccountRequestProcessor {
);
}
}
if !thread_manager.list_thread_ids().await.is_empty() {
crate::mcp_refresh::queue_best_effort_refresh(thread_manager, config_manager).await;
}
}
fn spawn_effective_plugins_changed_task(
@@ -59,7 +59,7 @@ impl AppsRequestProcessor {
};
let mut config = self.load_latest_config(fallback_cwd).await?;
if let Some(thread) = thread {
if let Some(thread) = thread.as_ref() {
let _ = config
.features
.set_enabled(Feature::Apps, thread.enabled(Feature::Apps));
@@ -67,7 +67,10 @@ impl AppsRequestProcessor {
let auth = self.auth_manager.auth().await;
let mcp_manager = self.thread_manager.mcp_manager();
let mcp_config = mcp_manager.runtime_config(&config).await;
let mcp_config = match thread {
Some(thread) => thread.runtime_mcp_config(&config).await,
None => mcp_manager.runtime_config(&config).await,
};
if !codex_mcp::host_owned_codex_apps_enabled(&mcp_config, auth.as_ref()) {
return Ok(Some(AppsListResponse {
data: Vec::new(),
@@ -98,6 +101,7 @@ impl AppsRequestProcessor {
request,
params,
config,
mcp_config,
environment_manager,
mcp_manager,
plugins_manager,
@@ -111,17 +115,20 @@ impl AppsRequestProcessor {
self.shutdown_token.cancel();
}
#[allow(clippy::too_many_arguments)]
async fn apps_list_task(
outgoing: Arc<OutgoingMessageSender>,
request_id: ConnectionRequestId,
params: AppsListParams,
config: Config,
mcp_config: codex_mcp::McpConfig,
environment_manager: Arc<EnvironmentManager>,
mcp_manager: Arc<McpManager>,
plugins_manager: Arc<PluginsManager>,
) {
let retry_params = params.clone();
let retry_config = config.clone();
let retry_mcp_config = mcp_config.clone();
let retry_environment_manager = Arc::clone(&environment_manager);
let retry_mcp_manager = Arc::clone(&mcp_manager);
let retry_plugins_manager = Arc::clone(&plugins_manager);
@@ -129,6 +136,7 @@ impl AppsRequestProcessor {
&outgoing,
params,
config,
mcp_config,
environment_manager,
mcp_manager,
plugins_manager,
@@ -148,6 +156,7 @@ impl AppsRequestProcessor {
&outgoing,
retry_params,
retry_config,
retry_mcp_config,
retry_environment_manager,
retry_mcp_manager,
retry_plugins_manager,
@@ -163,6 +172,7 @@ impl AppsRequestProcessor {
outgoing: &Arc<OutgoingMessageSender>,
params: AppsListParams,
config: Config,
mcp_config: codex_mcp::McpConfig,
environment_manager: Arc<EnvironmentManager>,
mcp_manager: Arc<McpManager>,
plugins_manager: Arc<PluginsManager>,
@@ -189,47 +199,62 @@ impl AppsRequestProcessor {
loaded_plugins.capability_summaries(),
);
let plugin_apps = connector_snapshot.connector_ids().to_vec();
let extension_managed_apps_enabled =
codex_mcp::extension_managed_codex_apps_enabled(&mcp_config);
let load_cached_all_connectors = async {
if extension_managed_apps_enabled {
None
} else {
connectors::list_cached_all_connectors(&config, &plugin_apps).await
}
};
let (mut accessible_connectors, mut all_connectors) = tokio::join!(
connectors::list_cached_accessible_connectors_from_mcp_tools(
connectors::list_cached_accessible_connectors_from_mcp_tools_with_mcp_config(
&config,
mcp_manager.as_ref(),
&mcp_config,
),
connectors::list_cached_all_connectors(&config, &plugin_apps)
load_cached_all_connectors,
);
let cached_all_connectors = all_connectors.clone();
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let accessible_config = config.clone();
let accessible_mcp_config = mcp_config.clone();
let accessible_tx = tx.clone();
tokio::spawn(async move {
let result = connectors::list_accessible_connectors_from_mcp_tools_with_mcp_manager(
let result = connectors::list_accessible_connectors_from_mcp_tools_with_mcp_config(
&accessible_config,
force_refetch,
Arc::clone(&environment_manager),
mcp_manager,
accessible_mcp_config,
)
.await
.map_err(|err| format!("failed to load accessible apps: {err}"));
let _ = accessible_tx.send(AppListLoadResult::Accessible(result));
});
let all_config = config.clone();
let all_plugin_apps = plugin_apps.clone();
tokio::spawn(async move {
let result = connectors::list_all_connectors_with_options(
&all_config,
force_refetch,
&all_plugin_apps,
)
.await
.map_err(|err| format!("failed to list apps: {err}"));
let _ = tx.send(AppListLoadResult::Directory(result));
});
if !extension_managed_apps_enabled {
let all_config = config.clone();
let all_plugin_apps = plugin_apps.clone();
tokio::spawn(async move {
let result = connectors::list_all_connectors_with_options(
&all_config,
force_refetch,
&all_plugin_apps,
)
.await
.map_err(|err| format!("failed to list apps: {err}"));
let _ = tx.send(AppListLoadResult::Directory(result));
});
} else {
drop(tx);
}
let app_list_deadline = tokio::time::Instant::now() + APP_LIST_LOAD_TIMEOUT;
let mut accessible_loaded = false;
let mut all_loaded = false;
let mut all_loaded = extension_managed_apps_enabled;
let mut codex_apps_ready = true;
let mut last_notified_apps = None;
+5 -54
View File
@@ -1185,7 +1185,7 @@ async fn list_apps_force_refetch_preserves_previous_cache_on_failure() -> Result
}
#[tokio::test]
async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Result<()> {
async fn list_apps_force_refetch_does_not_reuse_extension_accessible_snapshot() -> Result<()> {
let initial_connectors = vec![
AppInfo {
id: "alpha".to_string(),
@@ -1245,6 +1245,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
let mut mcp = TestAppServer::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
// App server installs the hosted Apps MCP extension, whose catalog must not reuse the
// compatibility server's shared accessible-app snapshot.
let warm_request = mcp
.send_apps_list_request(AppsListParams {
limit: None,
@@ -1356,57 +1358,6 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
})
.await?;
let first_update = read_app_list_updated_notification(&mut mcp).await?;
assert_eq!(
first_update.data,
vec![
AppInfo {
id: "beta".to_string(),
name: "Beta App".to_string(),
description: Some("Beta v1".to_string()),
logo_url: None,
logo_url_dark: None,
icon_assets: None,
icon_dark_assets: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/beta-app/beta".to_string()),
is_accessible: true,
is_enabled: true,
plugin_display_names: Vec::new(),
},
AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha v1".to_string()),
logo_url: None,
logo_url_dark: None,
icon_assets: None,
icon_dark_assets: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
},
]
);
let maybe_second_update = timeout(
Duration::from_millis(150),
read_app_list_updated_notification(&mut mcp),
)
.await;
assert!(
maybe_second_update.is_err(),
"unexpected inaccessible-only app/list update during force refetch"
);
let expected_final = vec![AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
@@ -1424,8 +1375,8 @@ async fn list_apps_force_refetch_patches_updates_from_cached_snapshots() -> Resu
is_enabled: true,
plugin_display_names: Vec::new(),
}];
let second_update = read_app_list_updated_notification(&mut mcp).await?;
assert_eq!(second_update.data, expected_final);
let update = read_app_list_updated_notification(&mut mcp).await?;
assert_eq!(update.data, expected_final);
let refetch_response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
+2
View File
@@ -13,10 +13,12 @@ use codex_connectors::merge::merge_plugin_connectors;
use codex_core::config::Config;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_environment_manager;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_mcp_config;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_mcp_manager;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status;
pub use codex_core::connectors::list_cached_accessible_connectors_from_mcp_tools;
pub use codex_core::connectors::list_cached_accessible_connectors_from_mcp_tools_with_mcp_config;
pub use codex_core::connectors::with_app_enabled_state;
use codex_login::AuthManager;
use codex_login::CodexAuth;
@@ -40,6 +40,21 @@ pub struct CodexAppsToolsCacheKey {
pub(crate) is_workspace_account: bool,
}
impl CodexAppsToolsCacheKey {
/// Returns whether this key can safely partition process-shared Apps caches.
pub(crate) fn can_partition_shared_cache(&self) -> bool {
let has_account_id = self
.account_id
.as_deref()
.is_some_and(|id| !id.trim().is_empty());
let has_chatgpt_user_id = self
.chatgpt_user_id
.as_deref()
.is_some_and(|id| !id.trim().is_empty());
has_account_id && (!self.is_workspace_account || has_chatgpt_user_id)
}
}
/// Builds the CodexAuth-backed Codex Apps cache key.
pub fn codex_apps_tools_cache_key(auth: Option<&CodexAuth>) -> CodexAppsToolsCacheKey {
CodexAppsToolsCacheKey {
@@ -68,6 +83,10 @@ pub(crate) struct CodexAppsToolsCacheContext {
}
impl CodexAppsToolsCacheContext {
pub(crate) fn is_for_auth_key(&self, auth_key: &CodexAppsToolsCacheKey) -> bool {
self.entry.identity.auth_key == *auth_key
}
pub(crate) fn tools_cache_path(&self) -> PathBuf {
self.entry
.identity
+25 -11
View File
@@ -17,11 +17,14 @@ use crate::McpAuthStatusEntry;
use crate::codex_apps_cache::CodexAppsToolsCache;
use crate::codex_apps_cache::CodexAppsToolsCacheKey;
use crate::codex_apps_cache::CodexAppsToolsFetchSource;
use crate::codex_apps_cache::codex_apps_tools_cache_key;
use crate::elicitation::ElicitationRequestManager;
use crate::elicitation::ElicitationRequestRouter;
use crate::elicitation::ElicitationReviewerHandle;
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
use crate::mcp::ToolPluginProvenance;
use crate::mcp::codex_apps_server_uses_codex_auth_cache;
use crate::mcp::mcp_server_has_configured_request_auth;
use crate::rmcp_client::AsyncManagedClient;
use crate::rmcp_client::DEFAULT_STARTUP_TIMEOUT;
use crate::rmcp_client::MCP_TOOLS_FETCH_UNCACHED_DURATION_METRIC;
@@ -195,6 +198,9 @@ impl McpConnectionManager {
});
let shares_codex_apps_tools_cache = should_share_codex_apps_tools_cache(
&server_name,
&server,
auth,
&codex_apps_tools_cache_key,
uses_env_bearer_token,
enable_codex_apps_tools_cache,
);
@@ -202,14 +208,8 @@ impl McpConnectionManager {
codex_apps_tools_cache
.context(codex_home.clone(), codex_apps_tools_cache_key.clone())
});
// If Codex Apps has an env bearer token, that is its auth path. Do
// not also attach the ambient CodexAuth provider.
let runtime_auth_provider =
if server_name == CODEX_APPS_MCP_SERVER_NAME && uses_env_bearer_token {
None
} else {
chatgpt_auth_provider_for_server(&server, chatgpt_auth_provider.clone())
};
chatgpt_auth_provider_for_server(&server, chatgpt_auth_provider.clone());
let async_managed_client = AsyncManagedClient::new(
server_name.clone(),
startup_submit_id.clone(),
@@ -437,6 +437,14 @@ impl McpConnectionManager {
server_name == CODEX_APPS_MCP_SERVER_NAME && self.server_metadata.contains_key(server_name)
}
/// Returns whether the Apps client cache is pinned to this CodexAuth identity.
pub fn uses_shared_codex_apps_tools_cache_for(&self, auth: Option<&CodexAuth>) -> bool {
let auth_key = codex_apps_tools_cache_key(auth);
self.clients
.get(CODEX_APPS_MCP_SERVER_NAME)
.is_some_and(|client| client.uses_codex_apps_tools_cache_for(&auth_key))
}
pub fn set_approval_policy(&self, approval_policy: &Constrained<AskForApproval>) {
if let Ok(mut policy) = self.elicitation_requests.approval_policy.lock() {
*policy = approval_policy.value();
@@ -908,9 +916,9 @@ fn chatgpt_auth_provider_for_server(
server: &EffectiveMcpServer,
chatgpt_auth_provider: Option<SharedAuthProvider>,
) -> Option<SharedAuthProvider> {
if !server
.configured_config()
.is_some_and(|config| matches!(&config.auth, McpServerAuth::ChatGpt))
let config = server.configured_config()?;
if !matches!(&config.auth, McpServerAuth::ChatGpt)
|| mcp_server_has_configured_request_auth(config)
{
return None;
}
@@ -919,10 +927,16 @@ fn chatgpt_auth_provider_for_server(
fn should_share_codex_apps_tools_cache(
server_name: &str,
server: &EffectiveMcpServer,
auth: Option<&CodexAuth>,
cache_key: &CodexAppsToolsCacheKey,
uses_env_bearer_token: bool,
cache_enabled: bool,
) -> bool {
server_name == CODEX_APPS_MCP_SERVER_NAME && !uses_env_bearer_token && cache_enabled
server_name == CODEX_APPS_MCP_SERVER_NAME
&& !uses_env_bearer_token
&& cache_enabled
&& codex_apps_server_uses_codex_auth_cache(server, auth, cache_key)
}
async fn emit_update(
@@ -732,8 +732,14 @@ fn filter_tools_applies_per_server_filters() {
#[test]
fn codex_apps_env_bearer_token_bypasses_shared_tools_cache() {
let server = codex_apps_server_for_cache(McpServerAuth::ChatGpt);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let cache_key = codex_apps_tools_cache_key(Some(&auth));
assert!(!should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
Some(&auth),
&cache_key,
/*uses_env_bearer_token*/ true,
/*cache_enabled*/ true,
));
@@ -741,22 +747,189 @@ fn codex_apps_env_bearer_token_bypasses_shared_tools_cache() {
#[test]
fn extension_managed_codex_apps_bypass_shared_tools_cache() {
let server = codex_apps_server_for_cache(McpServerAuth::ChatGpt);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let cache_key = codex_apps_tools_cache_key(Some(&auth));
assert!(!should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
Some(&auth),
&cache_key,
/*uses_env_bearer_token*/ false,
/*cache_enabled*/ false,
));
}
#[test]
fn codex_apps_chatgpt_auth_uses_shared_tools_cache() {
assert!(should_share_codex_apps_tools_cache(
fn codex_apps_oauth_bypasses_shared_tools_cache() {
let server = codex_apps_server_for_cache(McpServerAuth::OAuth);
let cache_key = codex_apps_tools_cache_key(/*auth*/ None);
assert!(!should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
/*auth*/ None,
&cache_key,
/*uses_env_bearer_token*/ false,
/*cache_enabled*/ true,
));
}
#[test]
fn codex_apps_chatgpt_auth_uses_shared_tools_cache() {
let server = codex_apps_server_for_cache(McpServerAuth::ChatGpt);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let cache_key = codex_apps_tools_cache_key(Some(&auth));
assert!(should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
Some(&auth),
&cache_key,
/*uses_env_bearer_token*/ false,
/*cache_enabled*/ true,
));
}
#[test]
fn codex_apps_without_chatgpt_auth_bypasses_shared_tools_cache() {
let server = codex_apps_server_for_cache(McpServerAuth::ChatGpt);
let cache_key = codex_apps_tools_cache_key(/*auth*/ None);
assert!(!should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
/*auth*/ None,
&cache_key,
/*uses_env_bearer_token*/ false,
/*cache_enabled*/ true,
));
}
#[test]
fn codex_apps_unsafe_or_mismatched_cache_key_bypasses_shared_tools_cache() {
let server = codex_apps_server_for_cache(McpServerAuth::ChatGpt);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let cache_key = CodexAppsToolsCacheKey {
account_id: Some("workspace-id".to_string()),
chatgpt_user_id: None,
is_workspace_account: true,
};
let workspace_user_only_key = CodexAppsToolsCacheKey {
account_id: None,
chatgpt_user_id: Some("user-id".to_string()),
is_workspace_account: true,
};
let empty_identity_key = CodexAppsToolsCacheKey {
account_id: Some(" ".to_string()),
chatgpt_user_id: Some(String::new()),
is_workspace_account: true,
};
assert!(!cache_key.can_partition_shared_cache());
assert!(!workspace_user_only_key.can_partition_shared_cache());
assert!(!empty_identity_key.can_partition_shared_cache());
assert!(!should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
Some(&auth),
&cache_key,
/*uses_env_bearer_token*/ false,
/*cache_enabled*/ true,
));
}
#[test]
fn codex_apps_authorization_header_bypasses_shared_tools_cache() {
let mut config = crate::mcp::codex_apps_mcp_server_config(
"https://chatgpt.com",
/*apps_mcp_product_sku*/ None,
);
let McpServerTransportConfig::StreamableHttp { http_headers, .. } = &mut config.transport
else {
panic!("expected streamable HTTP Apps server");
};
*http_headers = Some(HashMap::from([(
"Authorization".to_string(),
"Bearer external".to_string(),
)]));
let server = EffectiveMcpServer::configured(config);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let cache_key = codex_apps_tools_cache_key(Some(&auth));
assert!(!should_share_codex_apps_tools_cache(
CODEX_APPS_MCP_SERVER_NAME,
&server,
Some(&auth),
&cache_key,
/*uses_env_bearer_token*/ false,
/*cache_enabled*/ true,
));
}
#[test]
fn configured_actor_auth_suppresses_ambient_chatgpt_provider() {
let mut config = crate::mcp::codex_apps_mcp_server_config(
"https://chatgpt.com",
/*apps_mcp_product_sku*/ None,
);
let McpServerTransportConfig::StreamableHttp { http_headers, .. } = &mut config.transport
else {
panic!("expected streamable HTTP Apps server");
};
*http_headers = Some(HashMap::from([(
"X-OpenAI-Actor-Authorization".to_string(),
"actor-biscuit".to_string(),
)]));
let server = EffectiveMcpServer::configured(config);
let provider = codex_model_provider::auth_provider_from_auth(
&CodexAuth::create_dummy_chatgpt_auth_for_testing(),
);
assert!(chatgpt_auth_provider_for_server(&server, Some(provider)).is_none());
}
#[tokio::test]
async fn shared_codex_apps_cache_requires_matching_auth_identity() {
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let codex_home = tempdir().expect("tempdir");
let cache_context = CodexAppsToolsCache::default().context(
codex_home.path().to_path_buf(),
codex_apps_tools_cache_key(Some(&auth)),
);
let mut client = create_ready_async_managed_client(Vec::new()).await;
client.is_codex_apps_mcp_server = true;
client.codex_apps_tools_cache_context = Some(cache_context);
let approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
let permission_profile = Constrained::allow_any(PermissionProfile::default());
let mut manager = McpConnectionManager::new_uninitialized(
&approval_policy,
&permission_profile,
/*prefix_mcp_tool_names*/ true,
);
manager
.clients
.insert(CODEX_APPS_MCP_SERVER_NAME.to_string(), client);
assert!(manager.uses_shared_codex_apps_tools_cache_for(Some(&auth)));
assert!(!manager.uses_shared_codex_apps_tools_cache_for(/*auth*/ None));
}
fn codex_apps_server_for_cache(auth: McpServerAuth) -> EffectiveMcpServer {
let mut config = crate::mcp::codex_apps_mcp_server_config(
"https://chatgpt.com",
/*apps_mcp_product_sku*/ None,
);
config.auth = auth;
let McpServerTransportConfig::StreamableHttp {
bearer_token_env_var,
..
} = &mut config.transport
else {
panic!("expected streamable HTTP Apps server");
};
*bearer_token_env_var = None;
EffectiveMcpServer::configured(config)
}
#[tokio::test]
async fn list_all_tools_uses_shared_codex_apps_cache_while_client_is_pending() {
let codex_home = tempdir().expect("tempdir");
+1
View File
@@ -40,6 +40,7 @@ pub use codex_apps_cache::CodexAppsToolsCache;
pub use codex_apps_cache::CodexAppsToolsCacheKey;
pub use codex_apps_cache::codex_apps_tools_cache_key;
pub use mcp::codex_apps_mcp_server_config;
pub use mcp::codex_apps_tools_cache_enabled;
pub use mcp::configured_mcp_servers;
pub use mcp::effective_mcp_servers;
pub use mcp::effective_mcp_servers_from_configured;
+79 -4
View File
@@ -46,6 +46,7 @@ use tokio_util::sync::CancellationToken;
use crate::McpServerSource;
use crate::ResolvedMcpCatalog;
use crate::codex_apps_cache::CodexAppsToolsCache;
use crate::codex_apps_cache::CodexAppsToolsCacheKey;
use crate::codex_apps_cache::codex_apps_tools_cache_key;
use crate::connection_manager::McpConnectionManager;
use crate::runtime::McpRuntimeContext;
@@ -248,10 +249,36 @@ pub fn extension_managed_codex_apps_enabled(config: &McpConfig) -> bool {
.server(CODEX_APPS_MCP_SERVER_NAME)
.is_some_and(|server| {
matches!(server.source(), McpServerSource::Extension { .. })
&& matches!(&server.config().auth, &McpServerAuth::OAuth)
&& (matches!(&server.config().auth, &McpServerAuth::OAuth)
|| mcp_server_has_configured_request_auth(server.config()))
})
}
pub(crate) fn mcp_server_has_configured_request_auth(server: &McpServerConfig) -> bool {
let McpServerTransportConfig::StreamableHttp {
bearer_token_env_var,
http_headers,
env_http_headers,
..
} = &server.transport
else {
return false;
};
bearer_token_env_var.is_some()
|| http_headers
.as_ref()
.is_some_and(|headers| headers.keys().any(|name| is_request_auth_header(name)))
|| env_http_headers
.as_ref()
.is_some_and(|headers| headers.keys().any(|name| is_request_auth_header(name)))
}
fn is_request_auth_header(name: &str) -> bool {
name.eq_ignore_ascii_case("authorization")
|| name.eq_ignore_ascii_case("x-openai-actor-authorization")
}
pub fn host_owned_codex_apps_enabled(config: &McpConfig, auth: Option<&CodexAuth>) -> bool {
extension_managed_codex_apps_enabled(config)
|| (config.apps_enabled && auth.is_some_and(CodexAuth::uses_codex_backend))
@@ -265,7 +292,55 @@ pub fn effective_mcp_servers(
config: &McpConfig,
auth: Option<&CodexAuth>,
) -> HashMap<String, EffectiveMcpServer> {
effective_mcp_servers_from_configured(configured_mcp_servers(config), config, auth)
let mut servers =
effective_mcp_servers_from_configured(configured_mcp_servers(config), config, auth);
if config
.mcp_server_catalog
.server(CODEX_APPS_MCP_SERVER_NAME)
.is_some_and(|server| mcp_server_has_configured_request_auth(server.config()))
&& let Some(server) = servers.get_mut(CODEX_APPS_MCP_SERVER_NAME)
{
server.disable_stored_oauth();
}
servers
}
/// Returns whether Codex Apps can safely use caches partitioned by CodexAuth.
pub fn codex_apps_tools_cache_enabled(config: &McpConfig, auth: Option<&CodexAuth>) -> bool {
if config.apps_mcp_product_sku.is_some() {
return false;
}
if !config
.mcp_server_catalog
.server(CODEX_APPS_MCP_SERVER_NAME)
.is_some_and(|server| matches!(server.source(), McpServerSource::Compatibility { .. }))
{
return false;
}
let cache_key = codex_apps_tools_cache_key(auth);
effective_mcp_servers(config, auth)
.get(CODEX_APPS_MCP_SERVER_NAME)
.is_some_and(|server| codex_apps_server_uses_codex_auth_cache(server, auth, &cache_key))
}
pub(crate) fn codex_apps_server_uses_codex_auth_cache(
server: &EffectiveMcpServer,
auth: Option<&CodexAuth>,
cache_key: &CodexAppsToolsCacheKey,
) -> bool {
let Some(server) = server.configured_config() else {
return false;
};
let McpServerTransportConfig::StreamableHttp { .. } = &server.transport else {
return false;
};
matches!(&server.auth, McpServerAuth::ChatGpt)
&& !mcp_server_has_configured_request_auth(server)
&& auth.is_some_and(CodexAuth::uses_codex_backend)
&& codex_apps_tools_cache_key(auth) == *cache_key
&& cache_key.can_partition_shared_cache()
}
/// Converts a materialized server map to its auth-gated runtime view.
@@ -348,7 +423,7 @@ pub async fn read_mcp_resource(
config.codex_home.clone(),
codex_apps_tools_cache,
codex_apps_tools_cache_key(auth),
/*enable_codex_apps_tools_cache*/ !extension_managed_codex_apps_enabled(config),
/*enable_codex_apps_tools_cache*/ codex_apps_tools_cache_enabled(config, auth),
config.prefix_mcp_tool_names,
config.client_elicitation_capability.clone(),
/*supports_openai_form_elicitation*/ false,
@@ -426,7 +501,7 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
config.codex_home.clone(),
codex_apps_tools_cache,
codex_apps_tools_cache_key(auth),
/*enable_codex_apps_tools_cache*/ !extension_managed_codex_apps_enabled(config),
/*enable_codex_apps_tools_cache*/ codex_apps_tools_cache_enabled(config, auth),
config.prefix_mcp_tool_names,
config.client_elicitation_capability.clone(),
/*supports_openai_form_elicitation*/ false,
+113 -1
View File
@@ -327,6 +327,94 @@ fn effective_mcp_servers_preserve_extension_owned_oauth_codex_apps_without_chatg
.and_then(EffectiveMcpServer::configured_config),
Some(&expected)
);
assert!(
!effective
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("extension Apps server")
.allows_stored_oauth()
);
assert!(!codex_apps_tools_cache_enabled(&config, /*auth*/ None));
}
#[test]
fn extension_owned_oauth_without_static_auth_can_use_stored_credentials() {
let mut config = test_mcp_config(PathBuf::new());
config.apps_enabled = true;
let mut extension_config =
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None);
extension_config.auth = McpServerAuth::OAuth;
let McpServerTransportConfig::StreamableHttp {
bearer_token_env_var,
..
} = &mut extension_config.transport
else {
panic!("expected streamable http transport for codex apps");
};
*bearer_token_env_var = None;
let mut catalog = ResolvedMcpCatalog::builder();
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!(
effective
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("extension Apps server")
.allows_stored_oauth()
);
assert!(!codex_apps_tools_cache_enabled(&config, /*auth*/ None));
}
#[test]
fn effective_mcp_servers_preserve_extension_owned_static_auth_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();
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)
);
assert!(!codex_apps_tools_cache_enabled(&config, /*auth*/ None));
}
#[test]
@@ -334,17 +422,32 @@ 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 compatibility_config =
codex_apps_mcp_server_config(&config.chatgpt_base_url, /*apps_mcp_product_sku*/ None);
let McpServerTransportConfig::StreamableHttp {
bearer_token_env_var,
..
} = &mut compatibility_config.transport
else {
panic!("expected streamable http transport for codex apps");
};
*bearer_token_env_var = None;
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),
compatibility_config,
));
config.mcp_server_catalog = catalog.build();
let effective = effective_mcp_servers(&config, /*auth*/ None);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
assert!(!effective.contains_key(CODEX_APPS_MCP_SERVER_NAME));
assert!(!codex_apps_tools_cache_enabled(&config, /*auth*/ None));
assert!(codex_apps_tools_cache_enabled(&config, Some(&auth)));
config.apps_mcp_product_sku = Some("sku".to_string());
assert!(!codex_apps_tools_cache_enabled(&config, Some(&auth)));
}
#[test]
@@ -363,8 +466,10 @@ fn effective_mcp_servers_hide_config_owned_oauth_codex_apps_without_chatgpt_auth
config.mcp_server_catalog = catalog.build();
let effective = effective_mcp_servers(&config, /*auth*/ None);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
assert!(!effective.contains_key(CODEX_APPS_MCP_SERVER_NAME));
assert!(!codex_apps_tools_cache_enabled(&config, Some(&auth)));
}
#[tokio::test]
@@ -437,6 +542,13 @@ async fn effective_mcp_servers_preserve_runtime_servers() {
config.mcp_server_catalog = catalog.build();
let effective = effective_mcp_servers(&config, Some(&auth));
assert!(!codex_apps_tools_cache_enabled(&config, Some(&auth)));
assert!(
effective
.get(CODEX_APPS_MCP_SERVER_NAME)
.expect("configured Apps server")
.allows_stored_oauth()
);
let sample = effective.get("sample").expect("user server should exist");
let docs = effective
+12
View File
@@ -22,6 +22,7 @@ use crate::codex_apps::normalize_codex_apps_callable_name;
use crate::codex_apps::normalize_codex_apps_callable_namespace;
use crate::codex_apps::normalize_codex_apps_tool_title;
use crate::codex_apps_cache::CodexAppsToolsCacheContext;
use crate::codex_apps_cache::CodexAppsToolsCacheKey;
use crate::codex_apps_cache::CodexAppsToolsFetchSource;
use crate::codex_apps_cache::load_startup_cached_codex_apps_server_info;
use crate::elicitation::ElicitationRequestManager;
@@ -498,6 +499,15 @@ impl AsyncManagedClient {
.is_some_and(CodexAppsToolsCacheContext::has_current_tools)
}
pub(crate) fn uses_codex_apps_tools_cache_for(
&self,
auth_key: &CodexAppsToolsCacheKey,
) -> bool {
self.codex_apps_tools_cache_context
.as_ref()
.is_some_and(|context| context.is_for_auth_key(auth_key))
}
fn cached_tools(&self) -> Option<Vec<ToolInfo>> {
self.codex_apps_tools_cache_context
.as_ref()
@@ -925,6 +935,7 @@ async fn make_rmcp_client(
runtime_context: McpRuntimeContext,
runtime_auth_provider: Option<SharedAuthProvider>,
) -> Result<RmcpClient, StartupOutcomeError> {
let allow_stored_oauth = server.allows_stored_oauth();
let config = match server.launch() {
McpServerLaunch::Configured(config) => config.as_ref().clone(),
};
@@ -995,6 +1006,7 @@ async fn make_rmcp_client(
env_http_headers,
store_mode,
keyring_backend_kind,
allow_stored_oauth,
http_client,
runtime_auth_provider,
)
+10
View File
@@ -14,15 +14,25 @@ pub(crate) enum McpServerLaunch {
#[derive(Debug, Clone)]
pub struct EffectiveMcpServer {
launch: McpServerLaunch,
allow_stored_oauth: bool,
}
impl EffectiveMcpServer {
pub fn configured(config: McpServerConfig) -> Self {
Self {
launch: McpServerLaunch::Configured(Box::new(config)),
allow_stored_oauth: true,
}
}
pub(crate) fn disable_stored_oauth(&mut self) {
self.allow_stored_oauth = false;
}
pub(crate) fn allows_stored_oauth(&self) -> bool {
self.allow_stored_oauth
}
pub(crate) fn launch(&self) -> &McpServerLaunch {
&self.launch
}
+35 -10
View File
@@ -33,14 +33,15 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::MCP_TOOL_CODEX_APPS_META_KEY;
use codex_mcp::McpConfig;
use codex_mcp::McpConnectionManager;
use codex_mcp::McpRuntimeContext;
use codex_mcp::ToolInfo;
use codex_mcp::ToolPluginProvenance;
use codex_mcp::codex_apps_tools_cache_enabled;
use codex_mcp::codex_apps_tools_cache_key;
use codex_mcp::compute_auth_statuses;
use codex_mcp::effective_mcp_servers;
use codex_mcp::extension_managed_codex_apps_enabled;
use codex_mcp::host_owned_codex_apps_enabled;
use codex_mcp::tool_plugin_provenance;
@@ -133,15 +134,22 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth(
pub async fn list_cached_accessible_connectors_from_mcp_tools(
config: &Config,
mcp_manager: &McpManager,
) -> Option<Vec<AppInfo>> {
let mcp_config = mcp_manager.runtime_config(config).await;
list_cached_accessible_connectors_from_mcp_tools_with_mcp_config(config, &mcp_config).await
}
pub async fn list_cached_accessible_connectors_from_mcp_tools_with_mcp_config(
config: &Config,
mcp_config: &McpConfig,
) -> Option<Vec<AppInfo>> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager.auth().await;
let mcp_config = mcp_manager.runtime_config(config).await;
if !host_owned_codex_apps_enabled(&mcp_config, auth.as_ref()) {
if !host_owned_codex_apps_enabled(mcp_config, auth.as_ref()) {
return Some(Vec::new());
}
if extension_managed_codex_apps_enabled(&mcp_config) {
if !codex_apps_tools_cache_enabled(mcp_config, auth.as_ref()) {
return None;
}
let cache_key = accessible_connectors_cache_key(config, auth.as_ref());
@@ -151,10 +159,10 @@ pub async fn list_cached_accessible_connectors_from_mcp_tools(
pub(crate) fn refresh_accessible_connectors_cache_from_mcp_tools(
config: &Config,
auth: Option<&CodexAuth>,
extension_managed_apps_enabled: bool,
uses_shared_codex_apps_tools_cache: bool,
mcp_tools: &[ToolInfo],
) {
if !config.features.enabled(Feature::Apps) || extension_managed_apps_enabled {
if !config.features.enabled(Feature::Apps) || !uses_shared_codex_apps_tools_cache {
return;
}
@@ -217,19 +225,36 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_mcp_manager(
force_refetch: bool,
environment_manager: Arc<EnvironmentManager>,
mcp_manager: Arc<McpManager>,
) -> anyhow::Result<AccessibleConnectorsStatus> {
let mcp_config = mcp_manager.runtime_config(config).await;
list_accessible_connectors_from_mcp_tools_with_mcp_config(
config,
force_refetch,
environment_manager,
mcp_manager,
mcp_config,
)
.await
}
pub async fn list_accessible_connectors_from_mcp_tools_with_mcp_config(
config: &Config,
force_refetch: bool,
environment_manager: Arc<EnvironmentManager>,
mcp_manager: Arc<McpManager>,
mcp_config: McpConfig,
) -> anyhow::Result<AccessibleConnectorsStatus> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager.auth().await;
let mcp_config = mcp_manager.runtime_config(config).await;
if !host_owned_codex_apps_enabled(&mcp_config, auth.as_ref()) {
return Ok(AccessibleConnectorsStatus {
connectors: Vec::new(),
codex_apps_ready: true,
});
}
let cache_key = (!extension_managed_codex_apps_enabled(&mcp_config))
.then(|| accessible_connectors_cache_key(config, auth.as_ref()));
let can_share_cache = codex_apps_tools_cache_enabled(&mcp_config, auth.as_ref());
let cache_key = can_share_cache.then(|| accessible_connectors_cache_key(config, auth.as_ref()));
let tool_plugin_provenance = tool_plugin_provenance(&mcp_config);
if !force_refetch
&& let Some(cache_key) = cache_key.as_ref()
@@ -282,7 +307,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_mcp_manager(
config.codex_home.to_path_buf(),
mcp_manager.codex_apps_tools_cache(),
codex_apps_tools_cache_key(auth.as_ref()),
/*enable_codex_apps_tools_cache*/ !extension_managed_codex_apps_enabled(&mcp_config),
/*enable_codex_apps_tools_cache*/ can_share_cache,
mcp_config.prefix_mcp_tool_names,
mcp_config.client_elicitation_capability,
/*supports_openai_form_elicitation*/ false,
+2 -2
View File
@@ -275,7 +275,7 @@ async fn refresh_accessible_connectors_cache_from_mcp_tools_writes_latest_instal
let cached = with_accessible_connectors_cache_cleared(|| {
refresh_accessible_connectors_cache_from_mcp_tools(
&config, /*auth*/ None, /*extension_managed_apps_enabled*/ false, &tools,
&config, /*auth*/ None, /*uses_shared_codex_apps_tools_cache*/ true, &tools,
);
read_cached_accessible_connectors(&cache_key).expect("cache should be populated")
});
@@ -340,7 +340,7 @@ async fn extension_managed_apps_do_not_write_accessible_connectors_cache() {
let cached = with_accessible_connectors_cache_cleared(|| {
refresh_accessible_connectors_cache_from_mcp_tools(
&config, /*auth*/ None, /*extension_managed_apps_enabled*/ true, &tools,
&config, /*auth*/ None, /*uses_shared_codex_apps_tools_cache*/ false, &tools,
);
read_cached_accessible_connectors(&cache_key)
});
+1 -1
View File
@@ -700,7 +700,7 @@ async fn refresh_codex_apps_after_connector_auth(
connectors::refresh_accessible_connectors_cache_from_mcp_tools(
&turn_context.config,
auth.as_ref(),
turn_context.extension_managed_apps_enabled,
manager.uses_shared_codex_apps_tools_cache_for(auth.as_ref()),
&mcp_tools,
);
}
+1 -1
View File
@@ -362,7 +362,7 @@ impl Session {
self.services.mcp_manager.codex_apps_tools_cache(),
codex_apps_tools_cache_key(auth.as_ref()),
/*enable_codex_apps_tools_cache*/
!codex_mcp::extension_managed_codex_apps_enabled(&mcp_config),
codex_mcp::codex_apps_tools_cache_enabled(&mcp_config, auth.as_ref()),
mcp_config.prefix_mcp_tool_names,
mcp_config.client_elicitation_capability.clone(),
self.services
+2 -1
View File
@@ -3239,7 +3239,8 @@ impl Session {
.push(PersonalitySpecInstructions::new(personality_message).render());
}
}
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
if turn_context.config.include_apps_instructions && turn_context.apps_enabled(mcp.config())
{
let accessible_and_enabled_connectors =
connectors::list_accessible_and_enabled_connectors_from_manager(
mcp.manager(),
-1
View File
@@ -111,7 +111,6 @@ 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,
+2 -1
View File
@@ -1207,8 +1207,9 @@ impl Session {
config.codex_home.to_path_buf(),
sess.services.mcp_manager.codex_apps_tools_cache(),
codex_apps_tools_cache_key(auth),
/*enable_codex_apps_tools_cache*/ !codex_mcp::extension_managed_codex_apps_enabled(
/*enable_codex_apps_tools_cache*/ codex_mcp::codex_apps_tools_cache_enabled(
&mcp_config,
auth,
),
config.prefix_mcp_tool_names(),
mcp_config.client_elicitation_capability.clone(),
+6 -5
View File
@@ -545,7 +545,8 @@ async fn build_skills_and_plugins(
let mentioned_plugins =
collect_explicit_plugin_mentions(&user_input, loaded_plugins.capability_summaries());
let connector_snapshot = step_context.mcp.config().connector_snapshot.clone();
let mcp_tools = if turn_context.apps_enabled() || !mentioned_plugins.is_empty() {
let apps_enabled = turn_context.apps_enabled(step_context.mcp.config());
let mcp_tools = if apps_enabled || !mentioned_plugins.is_empty() {
// Plugin mentions need raw MCP/app inventory even when app tools
// are normally hidden so we can describe the plugin's currently
// usable capabilities for this turn.
@@ -557,13 +558,13 @@ async fn build_skills_and_plugins(
.await
{
Ok(mcp_tools) => mcp_tools,
Err(_) if turn_context.apps_enabled() => return None,
Err(_) if apps_enabled => return None,
Err(_) => Vec::new(),
}
} else {
Vec::new()
};
let available_connectors = if turn_context.apps_enabled() {
let available_connectors = if apps_enabled {
let connectors = codex_connectors::merge::merge_plugin_connectors_with_accessible(
connector_snapshot
.connector_ids()
@@ -1171,7 +1172,7 @@ async fn run_sampling_request(
fields(
turn_id = %step_context.turn.sub_id,
model = %step_context.turn.model_info.slug,
apps_enabled = step_context.turn.apps_enabled()
apps_enabled = step_context.turn.apps_enabled(step_context.mcp.config())
)
)]
pub(crate) async fn built_tools(
@@ -1194,7 +1195,7 @@ pub(crate) async fn built_tools(
.await;
let connector_snapshot = step_context.mcp.config().connector_snapshot.clone();
let apps_enabled = turn_context.apps_enabled();
let apps_enabled = turn_context.apps_enabled(step_context.mcp.config());
let accessible_connectors =
apps_enabled.then(|| connectors::accessible_connectors_from_mcp_tools(&all_mcp_tools));
let accessible_connectors_with_enabled_state =
+4 -12
View File
@@ -105,7 +105,6 @@ 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,
@@ -195,15 +194,14 @@ impl TurnContext {
})
}
pub(crate) fn apps_enabled(&self) -> bool {
pub(crate) fn apps_enabled(&self, mcp_config: &codex_mcp::McpConfig) -> bool {
let uses_codex_backend = self
.auth_manager
.as_deref()
.is_some_and(AuthManager::current_auth_uses_codex_backend);
self.config
.features
.apps_enabled_for_auth(uses_codex_backend || self.extension_managed_apps_enabled)
&& self.config.orchestrator_mcp_enabled
self.config.features.apps_enabled_for_auth(
uses_codex_backend || codex_mcp::extension_managed_codex_apps_enabled(mcp_config),
) && self.config.orchestrator_mcp_enabled
}
pub(crate) async fn with_model(
@@ -254,7 +252,6 @@ 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
@@ -536,7 +533,6 @@ 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,
@@ -766,10 +762,6 @@ 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 {
@@ -468,7 +468,7 @@ async fn refresh_missing_requested_connectors(
connectors::refresh_accessible_connectors_cache_from_mcp_tools(
&turn.config,
auth,
turn.extension_managed_apps_enabled,
manager.uses_shared_codex_apps_tools_cache_for(auth),
&mcp_tools,
);
Some(accessible_connectors)
+5
View File
@@ -126,6 +126,7 @@ enum TransportRecipe {
env_http_headers: Option<HashMap<String, String>>,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
allow_stored_oauth: bool,
http_client: Arc<dyn HttpClient>,
auth_provider: Option<SharedAuthProvider>,
},
@@ -391,6 +392,7 @@ impl RmcpClient {
env_http_headers: Option<HashMap<String, String>>,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
allow_stored_oauth: bool,
http_client: Arc<dyn HttpClient>,
auth_provider: Option<SharedAuthProvider>,
) -> Result<Self> {
@@ -402,6 +404,7 @@ impl RmcpClient {
env_http_headers,
store_mode,
keyring_backend_kind,
allow_stored_oauth,
http_client,
auth_provider,
};
@@ -781,6 +784,7 @@ impl RmcpClient {
env_http_headers,
store_mode,
keyring_backend_kind,
allow_stored_oauth,
http_client,
auth_provider,
} => {
@@ -796,6 +800,7 @@ impl RmcpClient {
let initial_oauth_tokens = if bearer_token.is_none()
&& auth_provider.is_none()
&& !default_headers.contains_key(AUTHORIZATION)
&& *allow_stored_oauth
{
match load_oauth_tokens(server_name, url, *store_mode, *keyring_backend_kind) {
Ok(tokens) => tokens,
@@ -325,6 +325,7 @@ async fn oauth_startup_child() -> anyhow::Result<()> {
/*env_http_headers*/ None,
OAuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
/*allow_stored_oauth*/ true,
Environment::default_for_tests().get_http_client(),
/*auth_provider*/ None,
)
@@ -365,6 +366,7 @@ async fn expired_unrefreshable_startup_child() -> anyhow::Result<()> {
/*env_http_headers*/ None,
OAuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
/*allow_stored_oauth*/ true,
Environment::default_for_tests().get_http_client(),
/*auth_provider*/ None,
)
@@ -95,6 +95,7 @@ pub(crate) async fn create_client_with_http_client(
/*env_http_headers*/ None,
OAuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
/*allow_stored_oauth*/ true,
http_client,
/*auth_provider*/ None,
)
@@ -139,6 +140,7 @@ pub(crate) async fn create_remote_client(
/*env_http_headers*/ None,
OAuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
/*allow_stored_oauth*/ true,
Arc::new(http_client),
/*auth_provider*/ None,
)