[MCP] Prompt mcp login when adding a streamable HTTP server that supports oauth (#5193)
1. If Codex detects that a `codex mcp add -url …` server supports oauth, it will auto-initiate the login flow. 2. If the TUI starts and a MCP server supports oauth but isn't logged in, it will give the user an explicit warning telling them to log in.
This commit is contained in:
@@ -18,6 +18,7 @@ use codex_core::mcp::auth::compute_auth_statuses;
|
|||||||
use codex_core::protocol::McpAuthStatus;
|
use codex_core::protocol::McpAuthStatus;
|
||||||
use codex_rmcp_client::delete_oauth_tokens;
|
use codex_rmcp_client::delete_oauth_tokens;
|
||||||
use codex_rmcp_client::perform_oauth_login;
|
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.
|
/// [experimental] Launch Codex as an MCP server or manage configured MCP servers.
|
||||||
///
|
///
|
||||||
@@ -190,7 +191,10 @@ impl McpCli {
|
|||||||
|
|
||||||
async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Result<()> {
|
async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Result<()> {
|
||||||
// Validate any provided overrides even though they are not currently applied.
|
// 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 {
|
let AddArgs {
|
||||||
name,
|
name,
|
||||||
@@ -226,17 +230,21 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AddMcpTransportArgs {
|
AddMcpTransportArgs {
|
||||||
streamable_http: Some(streamable_http),
|
streamable_http:
|
||||||
|
Some(AddMcpStreamableHttpArgs {
|
||||||
|
url,
|
||||||
|
bearer_token_env_var,
|
||||||
|
}),
|
||||||
..
|
..
|
||||||
} => McpServerTransportConfig::StreamableHttp {
|
} => McpServerTransportConfig::StreamableHttp {
|
||||||
url: streamable_http.url,
|
url,
|
||||||
bearer_token_env_var: streamable_http.bearer_token_env_var,
|
bearer_token_env_var,
|
||||||
},
|
},
|
||||||
AddMcpTransportArgs { .. } => bail!("exactly one of --command or --url must be provided"),
|
AddMcpTransportArgs { .. } => bail!("exactly one of --command or --url must be provided"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_entry = McpServerConfig {
|
let new_entry = McpServerConfig {
|
||||||
transport,
|
transport: transport.clone(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
startup_timeout_sec: None,
|
startup_timeout_sec: None,
|
||||||
tool_timeout_sec: None,
|
tool_timeout_sec: None,
|
||||||
@@ -249,6 +257,17 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
|
|||||||
|
|
||||||
println!("Added global MCP server '{name}'.");
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use codex_apply_patch::ApplyPatchAction;
|
|||||||
use codex_protocol::ConversationId;
|
use codex_protocol::ConversationId;
|
||||||
use codex_protocol::protocol::ConversationPathResponseEvent;
|
use codex_protocol::protocol::ConversationPathResponseEvent;
|
||||||
use codex_protocol::protocol::ExitedReviewModeEvent;
|
use codex_protocol::protocol::ExitedReviewModeEvent;
|
||||||
|
use codex_protocol::protocol::McpAuthStatus;
|
||||||
use codex_protocol::protocol::ReviewRequest;
|
use codex_protocol::protocol::ReviewRequest;
|
||||||
use codex_protocol::protocol::RolloutItem;
|
use codex_protocol::protocol::RolloutItem;
|
||||||
use codex_protocol::protocol::SessionSource;
|
use codex_protocol::protocol::SessionSource;
|
||||||
@@ -372,10 +373,25 @@ impl Session {
|
|||||||
);
|
);
|
||||||
let default_shell_fut = shell::default_user_shell();
|
let default_shell_fut = shell::default_user_shell();
|
||||||
let history_meta_fut = crate::message_history::history_metadata(&config);
|
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.
|
// Join all independent futures.
|
||||||
let (rollout_recorder, mcp_res, default_shell, (history_log_id, history_entry_count)) =
|
let (
|
||||||
tokio::join!(rollout_fut, mcp_fut, default_shell_fut, history_meta_fut);
|
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| {
|
let rollout_recorder = rollout_recorder.map_err(|e| {
|
||||||
error!("failed to initialize rollout recorder: {e:#}");
|
error!("failed to initialize rollout recorder: {e:#}");
|
||||||
@@ -402,11 +418,24 @@ impl Session {
|
|||||||
// Surface individual client start-up failures to the user.
|
// Surface individual client start-up failures to the user.
|
||||||
if !failed_clients.is_empty() {
|
if !failed_clients.is_empty() {
|
||||||
for (server_name, err) in failed_clients {
|
for (server_name, err) in failed_clients {
|
||||||
let message = format!("MCP client for `{server_name}` failed to start: {err:#}");
|
let log_message =
|
||||||
error!("{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."
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log_message
|
||||||
|
};
|
||||||
post_session_configured_error_events.push(Event {
|
post_session_configured_error_events.push(Event {
|
||||||
id: INITIAL_SUBMIT_ID.to_owned(),
|
id: INITIAL_SUBMIT_ID.to_owned(),
|
||||||
msg: EventMsg::Error(ErrorEvent { message }),
|
msg: EventMsg::Error(ErrorEvent {
|
||||||
|
message: display_message,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ pub async fn determine_streamable_http_auth_status(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to determine whether a streamable HTTP MCP server advertises OAuth login.
|
/// 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 base_url = Url::parse(url)?;
|
||||||
let client = Client::builder().timeout(DISCOVERY_TIMEOUT).build()?;
|
let client = Client::builder().timeout(DISCOVERY_TIMEOUT).build()?;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ mod rmcp_client;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use auth_status::determine_streamable_http_auth_status;
|
pub use auth_status::determine_streamable_http_auth_status;
|
||||||
|
pub use auth_status::supports_oauth_login;
|
||||||
pub use codex_protocol::protocol::McpAuthStatus;
|
pub use codex_protocol::protocol::McpAuthStatus;
|
||||||
pub use oauth::OAuthCredentialsStoreMode;
|
pub use oauth::OAuthCredentialsStoreMode;
|
||||||
pub use oauth::StoredOAuthTokens;
|
pub use oauth::StoredOAuthTokens;
|
||||||
|
|||||||
Reference in New Issue
Block a user