Route MCP OAuth through configured HTTP clients (#35806)

## What changed

- Pass resolved, route-aware HTTP clients through MCP OAuth discovery and login so CLI commands, plugin installation, and skill dependency setup honor configured proxies and execution environments.
- Apply per-plugin MCP server configuration and requirements before starting OAuth during installation, and skip disabled servers or servers assigned to unowned environments.
- Preserve configured MCP server policies when merging remotely installed plugin metadata.

## Testing

- Cover proxy-routed OAuth for `codex mcp add`, `codex mcp login`, plugin installation, and skill MCP dependencies.
- Cover plugin-install OAuth filtering for disabled servers, plugin requirements, and unowned environments.

GitOrigin-RevId: f84c88820e24a627faa78d6bed1b371682ecdc2f
This commit is contained in:
Celia Chen
2026-07-28 18:25:29 +00:00
committed by copyberry
parent 8f00b9a04c
commit 9ea975a2dc
11 changed files with 947 additions and 45 deletions
+40 -10
View File
@@ -19,16 +19,19 @@ use codex_core::config::find_codex_home;
use codex_core::config::load_global_mcp_servers;
use codex_core_plugins::PluginsManager;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::HttpClient;
use codex_exec_server::RouteAwareHttpClient;
use codex_login::AuthManager;
use codex_mcp::McpOAuthLoginSupport;
use codex_mcp::McpRuntimeContext;
use codex_mcp::ResolvedMcpOAuthScopes;
use codex_mcp::compute_auth_statuses;
use codex_mcp::discover_supported_scopes;
use codex_mcp::oauth_login_support;
use codex_mcp::discover_supported_scopes_with_http_client;
use codex_mcp::oauth_login_support_with_http_client;
use codex_mcp::resolve_oauth_scopes;
use codex_mcp::should_retry_without_scopes;
use codex_protocol::protocol::McpAuthStatus;
use codex_rmcp_client::OAuthDiscoveryTimeout;
use codex_rmcp_client::delete_oauth_tokens;
use codex_rmcp_client::perform_oauth_login;
use codex_utils_cli::CliConfigOverrides;
@@ -224,6 +227,7 @@ async fn perform_oauth_login_retry_without_scopes(
oauth_resource: Option<&str>,
callback_port: Option<u16>,
callback_url: Option<&str>,
http_client: Arc<dyn HttpClient>,
) -> Result<()> {
match perform_oauth_login(
name,
@@ -237,6 +241,7 @@ async fn perform_oauth_login_retry_without_scopes(
oauth_resource,
callback_port,
callback_url,
Arc::clone(&http_client),
)
.await
{
@@ -255,6 +260,7 @@ async fn perform_oauth_login_retry_without_scopes(
oauth_resource,
callback_port,
callback_url,
http_client,
)
.await
}
@@ -381,7 +387,17 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
println!("Added global MCP server '{name}'.");
match oauth_login_support(&transport).await {
// `mcp add` assigns new servers to the local environment, so its immediate
// OAuth discovery uses the local route-aware HTTP client.
let http_client: Arc<dyn HttpClient> =
Arc::new(RouteAwareHttpClient::new(config.http_client_factory()));
let login_support = oauth_login_support_with_http_client(
&transport,
Arc::clone(&http_client),
OAuthDiscoveryTimeout::LOCAL,
)
.await;
match login_support {
McpOAuthLoginSupport::Supported(oauth_config) => {
println!("Detected OAuth support. Starting OAuth flow…");
let resolved_scopes = resolve_oauth_scopes(
@@ -401,6 +417,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
oauth_resource.as_deref(),
config.mcp_oauth_callback_port,
config.mcp_oauth_callback_url.as_deref(),
http_client,
)
.await?;
println!("Successfully logged in.");
@@ -479,9 +496,18 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
_ => bail!("OAuth login is only supported for streamable HTTP servers."),
};
// Standalone `mcp login` runs OAuth from the local CLI process; execution
// environment routing belongs to app-server and session MCP flows.
let http_client: Arc<dyn HttpClient> =
Arc::new(RouteAwareHttpClient::new(config.http_client_factory()));
let explicit_scopes = (!scopes.is_empty()).then_some(scopes);
let discovered_scopes = if explicit_scopes.is_none() && server.scopes.is_none() {
discover_supported_scopes(&server.transport).await
discover_supported_scopes_with_http_client(
&server.transport,
Arc::clone(&http_client),
OAuthDiscoveryTimeout::LOCAL,
)
.await
} else {
None
};
@@ -500,6 +526,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
server.oauth_resource.as_deref(),
config.mcp_oauth_callback_port,
config.mcp_oauth_callback_url.as_deref(),
http_client,
)
.await?;
println!("Successfully logged in to MCP server '{name}'.");
@@ -557,17 +584,20 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
let mut entries: Vec<_> = mcp_servers.iter().collect();
entries.sort_by_key(|(name, _)| *name);
// Standalone `mcp list` only discovers auth through local HTTP; it has
// never initialized or routed through remote execution environments.
let runtime_context = McpRuntimeContext::new(
Arc::new(EnvironmentManager::without_environments(
config.http_client_factory(),
)),
config.cwd.to_path_buf(),
);
let auth_statuses = compute_auth_statuses(
effective_mcp_servers.iter(),
config.mcp_oauth_credentials_store_mode,
config.auth_keyring_backend_kind(),
auth.as_ref(),
&McpRuntimeContext::new(
Arc::new(EnvironmentManager::without_environments(
config.http_client_factory(),
)),
config.cwd.to_path_buf(),
),
&runtime_context,
)
.await;