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
29 changes: 24 additions & 5 deletions codex-rs/cli/src/mcp_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use codex_core::mcp::auth::compute_auth_statuses;
use codex_core::protocol::McpAuthStatus;
use codex_rmcp_client::delete_oauth_tokens;
use codex_rmcp_client::perform_oauth_login;
use codex_rmcp_client::supports_oauth_login;

/// [experimental] Launch Codex as an MCP server or manage configured MCP servers.
///
Expand Down Expand Up @@ -189,7 +190,10 @@ impl McpCli {

async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Result<()> {
// Validate any provided overrides even though they are not currently applied.
config_overrides.parse_overrides().map_err(|e| anyhow!(e))?;
let overrides = config_overrides.parse_overrides().map_err(|e| anyhow!(e))?;
let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default())
.await
.context("failed to load configuration")?;

let AddArgs {
name,
Expand Down Expand Up @@ -225,17 +229,21 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
}
}
AddMcpTransportArgs {
streamable_http: Some(streamable_http),
streamable_http:
Some(AddMcpStreamableHttpArgs {
url,
bearer_token_env_var,
}),
..
} => McpServerTransportConfig::StreamableHttp {
url: streamable_http.url,
bearer_token_env_var: streamable_http.bearer_token_env_var,
url,
bearer_token_env_var,
},
AddMcpTransportArgs { .. } => bail!("exactly one of --command or --url must be provided"),
};

let new_entry = McpServerConfig {
transport,
transport: transport.clone(),
enabled: true,
startup_timeout_sec: None,
tool_timeout_sec: None,
Expand All @@ -248,6 +256,17 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re

println!("Added global MCP server '{name}'.");

if let McpServerTransportConfig::StreamableHttp {
url,
bearer_token_env_var: None,
} = transport
&& matches!(supports_oauth_login(&url).await, Ok(true))
{
println!("Detected OAuth support. Starting OAuth flow…");
perform_oauth_login(&name, &url, config.mcp_oauth_credentials_store_mode).await?;
println!("Successfully logged in.");
}

Ok(())
}

Expand Down
39 changes: 34 additions & 5 deletions codex-rs/core/src/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use codex_apply_patch::ApplyPatchAction;
use codex_protocol::ConversationId;
use codex_protocol::protocol::ConversationPathResponseEvent;
use codex_protocol::protocol::ExitedReviewModeEvent;
use codex_protocol::protocol::McpAuthStatus;
use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SessionSource;
Expand Down Expand Up @@ -370,10 +371,25 @@ impl Session {
);
let default_shell_fut = shell::default_user_shell();
let history_meta_fut = crate::message_history::history_metadata(&config);
let auth_statuses_fut = compute_auth_statuses(
config.mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
);

// Join all independent futures.
let (rollout_recorder, mcp_res, default_shell, (history_log_id, history_entry_count)) =
tokio::join!(rollout_fut, mcp_fut, default_shell_fut, history_meta_fut);
let (
rollout_recorder,
mcp_res,
default_shell,
(history_log_id, history_entry_count),
auth_statuses,
) = tokio::join!(
rollout_fut,
mcp_fut,
default_shell_fut,
history_meta_fut,
auth_statuses_fut
);

let rollout_recorder = rollout_recorder.map_err(|e| {
error!("failed to initialize rollout recorder: {e:#}");
Expand All @@ -400,11 +416,24 @@ impl Session {
// Surface individual client start-up failures to the user.
if !failed_clients.is_empty() {
for (server_name, err) in failed_clients {
let message = format!("MCP client for `{server_name}` failed to start: {err:#}");
error!("{message}");
let log_message =
format!("MCP client for `{server_name}` failed to start: {err:#}");
error!("{log_message}");
let display_message = if matches!(
auth_statuses.get(&server_name),
Some(McpAuthStatus::NotLoggedIn)
) {
format!(
"The {server_name} MCP server is not logged in. Run `codex mcp login {server_name}` to log in."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for a followup, but it would be nice if we could do /mcp login {server_name} to avoid having to quit / reopen.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

)
} else {
log_message
};
post_session_configured_error_events.push(Event {
id: INITIAL_SUBMIT_ID.to_owned(),
msg: EventMsg::Error(ErrorEvent { message }),
msg: EventMsg::Error(ErrorEvent {
message: display_message,
}),
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/rmcp-client/src/auth_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub async fn determine_streamable_http_auth_status(
}

/// Attempt to determine whether a streamable HTTP MCP server advertises OAuth login.
async fn supports_oauth_login(url: &str) -> Result<bool> {
pub async fn supports_oauth_login(url: &str) -> Result<bool> {
let base_url = Url::parse(url)?;
let client = Client::builder().timeout(DISCOVERY_TIMEOUT).build()?;

Expand Down
1 change: 1 addition & 0 deletions codex-rs/rmcp-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod rmcp_client;
mod utils;

pub use auth_status::determine_streamable_http_auth_status;
pub use auth_status::supports_oauth_login;
pub use codex_protocol::protocol::McpAuthStatus;
pub use oauth::OAuthCredentialsStoreMode;
pub use oauth::StoredOAuthTokens;
Expand Down
Loading