Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions codex-rs/app-server/src/mcp_refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ async fn build_refresh_config(
config.mcp_oauth_credentials_store_mode,
)
.map_err(io::Error::other)?,
auth_keyring_backend_kind: serde_json::to_value(config.auth_keyring_backend_kind())
.map_err(io::Error::other)?,
})
}

Expand Down Expand Up @@ -104,6 +106,7 @@ mod tests {
use codex_config::ThreadConfigLoadErrorCode;
use codex_config::ThreadConfigLoader;
use codex_config::ThreadConfigSource;
use codex_config::types::AuthKeyringBackendKind;
use codex_core::config::ConfigOverrides;
use codex_core::init_state_db;
use codex_core::thread_store_from_config;
Expand Down Expand Up @@ -142,6 +145,38 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn refresh_config_uses_latest_auth_keyring_backend() -> anyhow::Result<()> {
let (temp_dir, thread_manager, config_manager, _loader) = refresh_test_state().await?;
std::fs::write(
temp_dir.path().join(codex_config::CONFIG_TOML_FILE),
"[features]\nsecret_auth_storage = true\n",
)?;

let mut good_thread = None;
for thread_id in thread_manager.list_thread_ids().await {
let thread = thread_manager.get_thread(thread_id).await?;
let thread_config = thread.config().await;
if thread_config.cwd.ends_with("good") {
good_thread = Some(thread);
break;
}
}
let thread = good_thread.expect("good test thread should exist");

let refresh_config = build_refresh_config(thread.as_ref(), &config_manager).await?;
let backend = serde_json::from_value::<AuthKeyringBackendKind>(
refresh_config.auth_keyring_backend_kind,
)?;

assert_eq!(
thread.config().await.auth_keyring_backend_kind(),
AuthKeyringBackendKind::Direct
);
assert_eq!(backend, AuthKeyringBackendKind::Secrets);
Ok(())
}

async fn refresh_test_state() -> anyhow::Result<(
TempDir,
Arc<ThreadManager>,
Expand All @@ -153,6 +188,10 @@ mod tests {
let bad_cwd = temp_dir.path().join("bad");
std::fs::create_dir_all(&good_cwd)?;
std::fs::create_dir_all(&bad_cwd)?;
std::fs::write(
temp_dir.path().join(codex_config::CONFIG_TOML_FILE),
"[features]\nsecret_auth_storage = false\n",
)?;

let initial_config_manager =
ConfigManager::without_managed_config_for_tests(temp_dir.path().to_path_buf());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ impl McpRequestProcessor {
&name,
&url,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
http_headers,
env_http_headers,
&resolved_scopes.scopes,
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/app-server/src/request_processors/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,7 @@ impl PluginRequestProcessor {
);

let store_mode = config.mcp_oauth_credentials_store_mode;
let keyring_backend_kind = config.auth_keyring_backend_kind();
let callback_port = config.mcp_oauth_callback_port;
let callback_url = config.mcp_oauth_callback_url.clone();
let outgoing = Arc::clone(&self.outgoing);
Expand All @@ -1712,6 +1713,7 @@ impl PluginRequestProcessor {
&name,
&oauth_config.url,
store_mode,
keyring_backend_kind,
oauth_config.http_headers.clone(),
oauth_config.env_http_headers.clone(),
&resolved_scopes.scopes,
Expand All @@ -1728,6 +1730,7 @@ impl PluginRequestProcessor {
&name,
&oauth_config.url,
store_mode,
keyring_backend_kind,
oauth_config.http_headers,
oauth_config.env_http_headers,
&[],
Expand Down
13 changes: 12 additions & 1 deletion codex-rs/cli/src/mcp_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ async fn perform_oauth_login_retry_without_scopes(
name: &str,
url: &str,
store_mode: codex_config::types::OAuthCredentialsStoreMode,
keyring_backend_kind: codex_config::types::AuthKeyringBackendKind,
http_headers: Option<HashMap<String, String>>,
env_http_headers: Option<HashMap<String, String>>,
resolved_scopes: &ResolvedMcpOAuthScopes,
Expand All @@ -223,6 +224,7 @@ async fn perform_oauth_login_retry_without_scopes(
name,
url,
store_mode,
keyring_backend_kind,
http_headers.clone(),
env_http_headers.clone(),
&resolved_scopes.scopes,
Expand All @@ -240,6 +242,7 @@ async fn perform_oauth_login_retry_without_scopes(
name,
url,
store_mode,
keyring_backend_kind,
http_headers,
env_http_headers,
&[],
Expand Down Expand Up @@ -384,6 +387,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
&name,
&oauth_config.url,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
oauth_config.http_headers,
oauth_config.env_http_headers,
&resolved_scopes,
Expand Down Expand Up @@ -478,6 +482,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
&name,
&url,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
http_headers,
env_http_headers,
&resolved_scopes,
Expand Down Expand Up @@ -514,7 +519,12 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr
_ => bail!("OAuth logout is only supported for streamable_http transports."),
};

match delete_oauth_tokens(&name, &url, config.mcp_oauth_credentials_store_mode) {
match delete_oauth_tokens(
&name,
&url,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
) {
Ok(true) => println!("Removed OAuth credentials for '{name}'."),
Ok(false) => println!("No OAuth credentials stored for '{name}'."),
Err(err) => return Err(anyhow!("failed to delete OAuth credentials: {err}")),
Expand All @@ -541,6 +551,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
let auth_statuses = compute_auth_statuses(
effective_mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
/*auth*/ None,
)
.await;
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/codex-mcp/src/connection_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use anyhow::anyhow;
use async_channel::Sender;
use codex_config::Constrained;
use codex_config::McpServerTransportConfig;
use codex_config::types::AuthKeyringBackendKind;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_login::CodexAuth;
use codex_protocol::mcp::CallToolResult;
Expand Down Expand Up @@ -119,6 +120,7 @@ impl McpConnectionManager {
pub async fn new(
mcp_servers: &HashMap<String, EffectiveMcpServer>,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
auth_entries: HashMap<String, McpAuthStatusEntry>,
approval_policy: &Constrained<AskForApproval>,
submit_id: String,
Expand Down Expand Up @@ -198,6 +200,7 @@ impl McpConnectionManager {
server_name.clone(),
server,
store_mode,
keyring_backend_kind,
cancel_token.clone(),
tx_event.clone(),
elicitation_requests.clone(),
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/codex-mcp/src/connection_manager_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::tools::normalize_tools_for_model_with_prefix;
use crate::tools::tool_with_model_visible_input_schema;
use codex_config::Constrained;
use codex_config::McpServerConfig;
use codex_config::types::AuthKeyringBackendKind;
use codex_exec_server::EnvironmentManager;
use codex_protocol::ToolName;
use codex_protocol::mcp::McpServerInfo;
Expand Down Expand Up @@ -1213,6 +1214,7 @@ async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() {
let manager = McpConnectionManager::new(
&mcp_servers,
OAuthCredentialsStoreMode::default(),
AuthKeyringBackendKind::default(),
HashMap::new(),
&approval_policy,
String::new(),
Expand Down
14 changes: 13 additions & 1 deletion codex-rs/codex-mcp/src/mcp/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::HashMap;
use anyhow::Result;
use codex_config::McpServerConfig;
use codex_config::McpServerTransportConfig;
use codex_config::types::AuthKeyringBackendKind;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_login::CodexAuth;
use codex_protocol::protocol::McpAuthStatus;
Expand Down Expand Up @@ -130,6 +131,7 @@ pub fn should_retry_without_scopes(scopes: &ResolvedMcpOAuthScopes, error: &anyh
pub async fn compute_auth_statuses<'a, I>(
servers: I,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
auth: Option<&CodexAuth>,
) -> HashMap<String, McpAuthStatusEntry>
where
Expand All @@ -152,7 +154,15 @@ where
async move {
let auth_status = match config.as_ref() {
Some(config) => {
match compute_auth_status(&name, config, store_mode, has_runtime_auth).await {
match compute_auth_status(
&name,
config,
store_mode,
keyring_backend_kind,
has_runtime_auth,
)
.await
{
Ok(status) => status,
Err(error) => {
warn!(
Expand All @@ -179,6 +189,7 @@ async fn compute_auth_status(
server_name: &str,
config: &McpServerConfig,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
has_runtime_auth: bool,
) -> Result<McpAuthStatus> {
if !config.enabled {
Expand All @@ -204,6 +215,7 @@ async fn compute_auth_status(
http_headers.clone(),
env_http_headers.clone(),
store_mode,
keyring_backend_kind,
)
.await
}
Expand Down
7 changes: 7 additions & 0 deletions codex-rs/codex-mcp/src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use codex_config::Constrained;
use codex_config::McpServerConfig;
use codex_config::McpServerTransportConfig;
use codex_config::types::AppToolApproval;
use codex_config::types::AuthKeyringBackendKind;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_login::CodexAuth;
use codex_plugin::PluginCapabilitySummary;
Expand Down Expand Up @@ -114,6 +115,8 @@ pub struct McpConfig {
pub codex_home: PathBuf,
/// Preferred credential store for MCP OAuth tokens.
pub mcp_oauth_credentials_store_mode: OAuthCredentialsStoreMode,
/// Backend used when MCP OAuth storage is configured for keyring-backed persistence.
pub auth_keyring_backend_kind: AuthKeyringBackendKind,
/// Optional fixed localhost callback port for MCP OAuth login.
pub mcp_oauth_callback_port: Option<u16>,
/// Optional OAuth redirect URI override for MCP login.
Expand Down Expand Up @@ -262,6 +265,7 @@ pub async fn read_mcp_resource(
let auth_statuses = compute_auth_statuses(
mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind,
auth,
)
.await;
Expand All @@ -271,6 +275,7 @@ pub async fn read_mcp_resource(
let manager = McpConnectionManager::new(
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind,
auth_statuses,
&config.approval_policy,
String::new(),
Expand Down Expand Up @@ -330,6 +335,7 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
let auth_status_entries = compute_auth_statuses(
mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind,
auth,
)
.await;
Expand All @@ -343,6 +349,7 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
let mcp_connection_manager = McpConnectionManager::new(
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind,
auth_status_entries.clone(),
&config.approval_policy,
submit_id,
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/codex-mcp/src/mcp/mod_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::*;
use crate::McpServerRegistration;
use codex_config::Constrained;
use codex_config::types::AppToolApproval;
use codex_config::types::AuthKeyringBackendKind;
use codex_login::CodexAuth;
use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
Expand All @@ -20,6 +21,7 @@ fn test_mcp_config(codex_home: PathBuf) -> McpConfig {
apps_mcp_product_sku: None,
codex_home,
mcp_oauth_credentials_store_mode: OAuthCredentialsStoreMode::default(),
auth_keyring_backend_kind: AuthKeyringBackendKind::default(),
mcp_oauth_callback_port: None,
mcp_oauth_callback_url: None,
skill_mcp_dependency_install_enabled: true,
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/codex-mcp/src/rmcp_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use codex_async_utils::CancelErr;
use codex_async_utils::OrCancelExt;
use codex_config::McpServerConfig;
use codex_config::McpServerTransportConfig;
use codex_config::types::AuthKeyringBackendKind;
use codex_config::types::OAuthCredentialsStoreMode;
use codex_exec_server::HttpClient;
use codex_exec_server::ReqwestHttpClient;
Expand Down Expand Up @@ -141,6 +142,7 @@ impl AsyncManagedClient {
server_name: String,
server: EffectiveMcpServer,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
cancel_token: CancellationToken,
tx_event: Sender<Event>,
elicitation_requests: ElicitationRequestManager,
Expand Down Expand Up @@ -179,6 +181,7 @@ impl AsyncManagedClient {
&server_name,
server.clone(),
store_mode,
keyring_backend_kind,
runtime_context,
runtime_auth_provider,
)
Expand Down Expand Up @@ -577,6 +580,7 @@ async fn make_rmcp_client(
server_name: &str,
server: EffectiveMcpServer,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
runtime_context: McpRuntimeContext,
runtime_auth_provider: Option<SharedAuthProvider>,
) -> Result<RmcpClient, StartupOutcomeError> {
Expand Down Expand Up @@ -648,6 +652,7 @@ async fn make_rmcp_client(
http_headers,
env_http_headers,
store_mode,
keyring_backend_kind,
http_client,
runtime_auth_provider,
)
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,7 @@ impl Config {
apps_mcp_product_sku: self.apps_mcp_product_sku.clone(),
codex_home: self.codex_home.to_path_buf(),
mcp_oauth_credentials_store_mode: self.mcp_oauth_credentials_store_mode,
auth_keyring_backend_kind: self.auth_keyring_backend_kind(),
mcp_oauth_callback_port: self.mcp_oauth_callback_port,
mcp_oauth_callback_url: self.mcp_oauth_callback_url.clone(),
skill_mcp_dependency_install_enabled: self
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/core/src/connectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_mcp_manager(
let auth_status_entries = compute_auth_statuses(
mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth.as_ref(),
)
.await;
Expand All @@ -291,6 +292,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_mcp_manager(
let mcp_connection_manager = McpConnectionManager::new(
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth_status_entries,
&config.permissions.approval_policy,
INITIAL_SUBMIT_ID.to_owned(),
Expand Down
Loading
Loading