mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Load cloud-managed servers in MCP CLI commands (#36031)
## What changed - Load the cloud configuration bundle before `codex mcp list`, `get`, `login`, and `logout` so those commands can resolve enterprise-managed MCP servers. - Keep `codex mcp add` and `remove` scoped to user configuration, preventing managed server definitions from being copied into or deleted from `config.toml`. ## Testing - Add CLI integration coverage for listing and inspecting a managed server, completing its OAuth login and logout flow, and preserving user configuration during add and remove operations. GitOrigin-RevId: 9b64d70535aaba11303c42e44b9ad751c8745445
This commit is contained in:
+29
-43
@@ -39,6 +39,8 @@ use codex_utils_cli::format_env_display;
|
||||
|
||||
use crate::plugin_cmd::load_cli_auth_mode;
|
||||
|
||||
mod cloud_config;
|
||||
|
||||
/// Subcommands:
|
||||
/// - `list` — list configured servers (with `--json`)
|
||||
/// - `get` — show a single server (with `--json`)
|
||||
@@ -183,15 +185,19 @@ impl McpCli {
|
||||
} = self;
|
||||
|
||||
if loader_overrides.user_config_profile.is_some() {
|
||||
validate_profile_v2_migration(&config_overrides, loader_overrides).await?;
|
||||
validate_profile_v2_migration(&config_overrides, loader_overrides.clone()).await?;
|
||||
}
|
||||
|
||||
match subcommand {
|
||||
McpSubcommand::List(args) => {
|
||||
run_list(&config_overrides, args).await?;
|
||||
let config =
|
||||
cloud_config::load_mcp_config(&config_overrides, loader_overrides).await?;
|
||||
run_list(&config, args).await?;
|
||||
}
|
||||
McpSubcommand::Get(args) => {
|
||||
run_get(&config_overrides, args).await?;
|
||||
let config =
|
||||
cloud_config::load_mcp_config(&config_overrides, loader_overrides).await?;
|
||||
run_get(&config, args).await?;
|
||||
}
|
||||
McpSubcommand::Add(args) => {
|
||||
run_add(&config_overrides, args).await?;
|
||||
@@ -200,10 +206,14 @@ impl McpCli {
|
||||
run_remove(&config_overrides, args).await?;
|
||||
}
|
||||
McpSubcommand::Login(args) => {
|
||||
run_login(&config_overrides, args).await?;
|
||||
let config =
|
||||
cloud_config::load_mcp_config(&config_overrides, loader_overrides).await?;
|
||||
run_login(&config, args).await?;
|
||||
}
|
||||
McpSubcommand::Logout(args) => {
|
||||
run_logout(&config_overrides, args).await?;
|
||||
let config =
|
||||
cloud_config::load_mcp_config(&config_overrides, loader_overrides).await?;
|
||||
run_logout(&config, args).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,15 +480,9 @@ async fn load_mcp_manager(config: &Config) -> McpManager {
|
||||
McpManager::new(plugins_manager)
|
||||
}
|
||||
|
||||
async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let mcp_manager = load_mcp_manager(&config).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(&config).await;
|
||||
async fn run_login(config: &Config, login_args: LoginArgs) -> Result<()> {
|
||||
let mcp_manager = load_mcp_manager(config).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(config).await;
|
||||
|
||||
let LoginArgs { name, scopes } = login_args;
|
||||
|
||||
@@ -533,15 +537,9 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let mcp_manager = load_mcp_manager(&config).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(&config).await;
|
||||
async fn run_logout(config: &Config, logout_args: LogoutArgs) -> Result<()> {
|
||||
let mcp_manager = load_mcp_manager(config).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(config).await;
|
||||
|
||||
let LogoutArgs { name } = logout_args;
|
||||
|
||||
@@ -568,19 +566,13 @@ async fn run_logout(config_overrides: &CliConfigOverrides, logout_args: LogoutAr
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let mcp_manager = load_mcp_manager(&config).await;
|
||||
async fn run_list(config: &Config, list_args: ListArgs) -> Result<()> {
|
||||
let mcp_manager = load_mcp_manager(config).await;
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
|
||||
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ true).await;
|
||||
let auth = auth_manager.auth().await;
|
||||
let mcp_servers = mcp_manager.configured_servers(&config).await;
|
||||
let effective_mcp_servers = mcp_manager.effective_servers(&config, auth.as_ref()).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(config).await;
|
||||
let effective_mcp_servers = mcp_manager.effective_servers(config, auth.as_ref()).await;
|
||||
|
||||
let mut entries: Vec<_> = mcp_servers.iter().collect();
|
||||
entries.sort_by_key(|(name, _)| *name);
|
||||
@@ -835,15 +827,9 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Result<()> {
|
||||
let overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config = Config::load_with_cli_overrides(overrides)
|
||||
.await
|
||||
.context("failed to load configuration")?;
|
||||
let mcp_manager = load_mcp_manager(&config).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(&config).await;
|
||||
async fn run_get(config: &Config, get_args: GetArgs) -> Result<()> {
|
||||
let mcp_manager = load_mcp_manager(config).await;
|
||||
let mcp_servers = mcp_manager.configured_servers(config).await;
|
||||
|
||||
let Some(server) = mcp_servers.get(&get_args.name) else {
|
||||
bail!("No MCP server named '{name}' found.", name = get_args.name);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_cloud_config::cloud_config_bundle_loader_for_storage;
|
||||
use codex_config::CloudConfigBundleLoader;
|
||||
use codex_config::ConfigLoadOptions;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::LoaderOverrides;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_config_toml_with_layer_stack;
|
||||
use codex_core::config::resolve_bootstrap_auth_keyring_backend_kind;
|
||||
use codex_core::config::resolve_bootstrap_auth_route_config;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
|
||||
pub(super) async fn load_mcp_config(
|
||||
config_overrides: &CliConfigOverrides,
|
||||
loader_overrides: LoaderOverrides,
|
||||
) -> Result<Config> {
|
||||
let cli_overrides = config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?;
|
||||
let cwd = AbsolutePathBuf::current_dir().context("failed to resolve current directory")?;
|
||||
let bootstrap_config = load_config_toml_with_layer_stack(
|
||||
codex_home.as_path(),
|
||||
Some(&cwd),
|
||||
cli_overrides.clone(),
|
||||
ConfigLoadOptions {
|
||||
loader_overrides: loader_overrides.clone(),
|
||||
strict_config: false,
|
||||
cloud_config_bundle: CloudConfigBundleLoader::default(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.context("failed to load bootstrap configuration")?;
|
||||
let bootstrap_config_toml = &bootstrap_config.config_toml;
|
||||
let auth_route_config = resolve_bootstrap_auth_route_config(
|
||||
bootstrap_config_toml,
|
||||
bootstrap_config
|
||||
.config_layer_stack
|
||||
.requirements()
|
||||
.feature_requirements
|
||||
.as_ref(),
|
||||
)
|
||||
.context("failed to resolve cloud configuration authentication")?;
|
||||
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
|
||||
codex_home.to_path_buf(),
|
||||
/*enable_codex_api_key_env*/ false,
|
||||
bootstrap_config_toml
|
||||
.cli_auth_credentials_store
|
||||
.unwrap_or_default(),
|
||||
resolve_bootstrap_auth_keyring_backend_kind(&bootstrap_config)
|
||||
.context("failed to resolve cloud configuration credential storage")?,
|
||||
bootstrap_config_toml
|
||||
.chatgpt_base_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()),
|
||||
auth_route_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
ConfigBuilder::default()
|
||||
.codex_home(codex_home.to_path_buf())
|
||||
.cli_overrides(cli_overrides)
|
||||
.loader_overrides(loader_overrides)
|
||||
.cloud_config_bundle(cloud_config_bundle)
|
||||
.build()
|
||||
.await
|
||||
.context("failed to load configuration")
|
||||
}
|
||||
Reference in New Issue
Block a user