diff --git a/codex-rs/cli/src/mcp_cmd.rs b/codex-rs/cli/src/mcp_cmd.rs index 93f22e705c..44dca6e1a1 100644 --- a/codex-rs/cli/src/mcp_cmd.rs +++ b/codex-rs/cli/src/mcp_cmd.rs @@ -14,7 +14,6 @@ use codex_core::config::find_codex_home; use codex_core::config::load_global_mcp_servers; use codex_core::config::types::McpServerConfig; use codex_core::config::types::McpServerTransportConfig; -use codex_core::features::Feature; use codex_core::mcp::auth::compute_auth_statuses; use codex_core::protocol::McpAuthStatus; use codex_rmcp_client::delete_oauth_tokens; @@ -53,11 +52,9 @@ pub enum McpSubcommand { Remove(RemoveArgs), /// [experimental] Authenticate with a configured MCP server via OAuth. - /// Requires experimental_use_rmcp_client = true in config.toml. Login(LoginArgs), /// [experimental] Remove stored OAuth credentials for a server. - /// Requires experimental_use_rmcp_client = true in config.toml. Logout(LogoutArgs), } @@ -283,24 +280,17 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re { match supports_oauth_login(&url).await { Ok(true) => { - if !config.features.enabled(Feature::RmcpClient) { - println!( - "MCP server supports login. Add `experimental_use_rmcp_client = true` \ - to your config.toml and run `codex mcp login {name}` to login." - ); - } else { - println!("Detected OAuth support. Starting OAuth flow…"); - perform_oauth_login( - &name, - &url, - config.mcp_oauth_credentials_store_mode, - http_headers.clone(), - env_http_headers.clone(), - &Vec::new(), - ) - .await?; - println!("Successfully logged in."); - } + println!("Detected OAuth support. Starting OAuth flow…"); + perform_oauth_login( + &name, + &url, + config.mcp_oauth_credentials_store_mode, + http_headers.clone(), + env_http_headers.clone(), + &Vec::new(), + ) + .await?; + println!("Successfully logged in."); } Ok(false) => {} Err(_) => println!( @@ -353,12 +343,6 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) .await .context("failed to load configuration")?; - if !config.features.enabled(Feature::RmcpClient) { - bail!( - "OAuth login is only supported when [features].rmcp_client is true in config.toml. See https://github.com/openai/codex/blob/main/docs/config.md#feature-flags for details." - ); - } - let LoginArgs { name, scopes } = login_args; let Some(server) = config.mcp_servers.get(&name) else { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index dccf0556f1..fe35e16e5f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -251,10 +251,6 @@ pub struct Config { /// If set to `true`, used only the experimental unified exec tool. pub use_experimental_unified_exec_tool: bool, - /// If set to `true`, use the experimental official Rust MCP client. - /// https://github.com/modelcontextprotocol/rust-sdk - pub use_experimental_use_rmcp_client: bool, - /// Centralized feature flags; source of truth for feature gating. pub features: Features, @@ -718,7 +714,6 @@ pub struct ConfigToml { pub experimental_instructions_file: Option, pub experimental_compact_prompt_file: Option, pub experimental_use_unified_exec_tool: Option, - pub experimental_use_rmcp_client: Option, pub experimental_use_freeform_apply_patch: Option, pub experimental_sandbox_command_assessment: Option, /// Preferred OSS provider for local models, e.g. "lmstudio" or "ollama". @@ -1088,7 +1083,6 @@ impl Config { let include_apply_patch_tool_flag = features.enabled(Feature::ApplyPatchFreeform); let tools_web_search_request = features.enabled(Feature::WebSearchRequest); let use_experimental_unified_exec_tool = features.enabled(Feature::UnifiedExec); - let use_experimental_use_rmcp_client = features.enabled(Feature::RmcpClient); let experimental_sandbox_command_assessment = features.enabled(Feature::SandboxCommandAssessment); @@ -1241,7 +1235,6 @@ impl Config { tools_web_search_request, experimental_sandbox_command_assessment, use_experimental_unified_exec_tool, - use_experimental_use_rmcp_client, features, active_profile: active_profile_name, active_project, @@ -1809,7 +1802,6 @@ trust_level = "trusted" let codex_home = TempDir::new()?; let cfg = ConfigToml { experimental_use_unified_exec_tool: Some(true), - experimental_use_rmcp_client: Some(true), experimental_use_freeform_apply_patch: Some(true), ..Default::default() }; @@ -1822,12 +1814,10 @@ trust_level = "trusted" assert!(config.features.enabled(Feature::ApplyPatchFreeform)); assert!(config.features.enabled(Feature::UnifiedExec)); - assert!(config.features.enabled(Feature::RmcpClient)); assert!(config.include_apply_patch_tool); assert!(config.use_experimental_unified_exec_tool); - assert!(config.use_experimental_use_rmcp_client); Ok(()) } @@ -2994,7 +2984,6 @@ model_verbosity = "high" tools_web_search_request: false, experimental_sandbox_command_assessment: false, use_experimental_unified_exec_tool: false, - use_experimental_use_rmcp_client: false, features: Features::with_defaults(), active_profile: Some("o3".to_string()), active_project: ProjectConfig { trust_level: None }, @@ -3068,7 +3057,6 @@ model_verbosity = "high" tools_web_search_request: false, experimental_sandbox_command_assessment: false, use_experimental_unified_exec_tool: false, - use_experimental_use_rmcp_client: false, features: Features::with_defaults(), active_profile: Some("gpt3".to_string()), active_project: ProjectConfig { trust_level: None }, @@ -3157,7 +3145,6 @@ model_verbosity = "high" tools_web_search_request: false, experimental_sandbox_command_assessment: false, use_experimental_unified_exec_tool: false, - use_experimental_use_rmcp_client: false, features: Features::with_defaults(), active_profile: Some("zdr".to_string()), active_project: ProjectConfig { trust_level: None }, @@ -3232,7 +3219,6 @@ model_verbosity = "high" tools_web_search_request: false, experimental_sandbox_command_assessment: false, use_experimental_unified_exec_tool: false, - use_experimental_use_rmcp_client: false, features: Features::with_defaults(), active_profile: Some("gpt5".to_string()), active_project: ProjectConfig { trust_level: None }, diff --git a/codex-rs/core/src/config/profile.rs b/codex-rs/core/src/config/profile.rs index 5629465c40..3bedc2e8be 100644 --- a/codex-rs/core/src/config/profile.rs +++ b/codex-rs/core/src/config/profile.rs @@ -25,7 +25,6 @@ pub struct ConfigProfile { pub experimental_compact_prompt_file: Option, pub include_apply_patch_tool: Option, pub experimental_use_unified_exec_tool: Option, - pub experimental_use_rmcp_client: Option, pub experimental_use_freeform_apply_patch: Option, pub experimental_sandbox_command_assessment: Option, pub tools_web_search: Option, diff --git a/codex-rs/core/src/features.rs b/codex-rs/core/src/features.rs index 687e8b1627..7b63e0ec3f 100644 --- a/codex-rs/core/src/features.rs +++ b/codex-rs/core/src/features.rs @@ -125,7 +125,7 @@ impl Features { } pub fn enabled(&self, f: Feature) -> bool { - self.enabled.contains(&f) + f == Feature::RmcpClient || self.enabled.contains(&f) } pub fn enable(&mut self, f: Feature) -> &mut Self { @@ -134,7 +134,9 @@ impl Features { } pub fn disable(&mut self, f: Feature) -> &mut Self { - self.enabled.remove(&f); + if f != Feature::RmcpClient { + self.enabled.remove(&f); + } self } @@ -162,6 +164,18 @@ impl Features { pub fn apply_map(&mut self, m: &BTreeMap) { for (k, v) in m { match feature_for_key(k) { + Some(Feature::RmcpClient) => { + if k != Feature::RmcpClient.key() { + self.record_legacy_usage(k.as_str(), Feature::RmcpClient); + } + if !*v { + tracing::warn!( + "[features].{k} is ignored; the RMCP client is always enabled" + ); + continue; + } + self.enable(Feature::RmcpClient); + } Some(feat) => { if k != feat.key() { self.record_legacy_usage(k.as_str(), feat); @@ -190,7 +204,6 @@ impl Features { experimental_sandbox_command_assessment: cfg.experimental_sandbox_command_assessment, experimental_use_freeform_apply_patch: cfg.experimental_use_freeform_apply_patch, experimental_use_unified_exec_tool: cfg.experimental_use_unified_exec_tool, - experimental_use_rmcp_client: cfg.experimental_use_rmcp_client, tools_web_search: cfg.tools.as_ref().and_then(|t| t.web_search), tools_view_image: cfg.tools.as_ref().and_then(|t| t.view_image), ..Default::default() @@ -209,7 +222,6 @@ impl Features { .experimental_use_freeform_apply_patch, experimental_use_unified_exec_tool: config_profile.experimental_use_unified_exec_tool, - experimental_use_rmcp_client: config_profile.experimental_use_rmcp_client, tools_web_search: config_profile.tools_web_search, tools_view_image: config_profile.tools_view_image, }; @@ -275,6 +287,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Stable, default_enabled: true, }, + FeatureSpec { + id: Feature::RmcpClient, + key: "rmcp_client", + stage: Stage::Stable, + default_enabled: true, + }, // Unstable features. FeatureSpec { id: Feature::UnifiedExec, @@ -282,12 +300,6 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Experimental, default_enabled: false, }, - FeatureSpec { - id: Feature::RmcpClient, - key: "rmcp_client", - stage: Stage::Experimental, - default_enabled: false, - }, FeatureSpec { id: Feature::ApplyPatchFreeform, key: "apply_patch_freeform", @@ -343,3 +355,25 @@ pub const FEATURES: &[FeatureSpec] = &[ default_enabled: false, }, ]; + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + #[test] + fn rmcp_client_feature_is_always_enabled() { + let mut features = Features::with_defaults(); + assert!(features.enabled(Feature::RmcpClient)); + + let mut map = BTreeMap::new(); + map.insert("rmcp_client".to_string(), false); + features.apply_map(&map); + + assert!(features.enabled(Feature::RmcpClient)); + + features.disable(Feature::RmcpClient); + + assert!(features.enabled(Feature::RmcpClient)); + } +} diff --git a/codex-rs/core/src/features/legacy.rs b/codex-rs/core/src/features/legacy.rs index 4d59f2a9a3..803dd03058 100644 --- a/codex-rs/core/src/features/legacy.rs +++ b/codex-rs/core/src/features/legacy.rs @@ -51,7 +51,6 @@ pub struct LegacyFeatureToggles { pub experimental_sandbox_command_assessment: Option, pub experimental_use_freeform_apply_patch: Option, pub experimental_use_unified_exec_tool: Option, - pub experimental_use_rmcp_client: Option, pub tools_web_search: Option, pub tools_view_image: Option, } @@ -82,12 +81,6 @@ impl LegacyFeatureToggles { self.experimental_use_unified_exec_tool, "experimental_use_unified_exec_tool", ); - set_if_some( - features, - Feature::RmcpClient, - self.experimental_use_rmcp_client, - "experimental_use_rmcp_client", - ); set_if_some( features, Feature::WebSearchRequest, diff --git a/docs/config.md b/docs/config.md index 0f0761a301..3290975b7d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -42,7 +42,6 @@ Supported features: | Key | Default | Stage | Description | | ----------------------------------------- | :-----: | ------------ | ---------------------------------------------------- | | `unified_exec` | false | Experimental | Use the unified PTY-backed exec tool | -| `rmcp_client` | false | Experimental | Enable oauth support for streamable HTTP MCP servers | | `apply_patch_freeform` | false | Beta | Include the freeform `apply_patch` tool | | `view_image_tool` | true | Stable | Include the `view_image` tool | | `web_search_request` | false | Stable | Allow the model to issue web searches | @@ -53,6 +52,8 @@ Supported features: Notes: - Omit a key to accept its default. +- The RMCP client is always enabled; legacy `rmcp_client` and `experimental_use_rmcp_client` + toggles are ignored. - Legacy booleans such as `experimental_use_exec_command_tool`, `experimental_use_unified_exec_tool`, `include_apply_patch_tool`, and similar `experimental_use_*` keys are deprecated; setting the corresponding `[features].` avoids repeated warnings. ## Model selection @@ -464,13 +465,9 @@ http_headers = { "HEADER_NAME" = "HEADER_VALUE" } env_http_headers = { "HEADER_NAME" = "ENV_VAR" } ``` -Streamable HTTP connections always use the experimental Rust MCP client under the hood, so expect occasional rough edges. OAuth login flows are gated on the `experimental_use_rmcp_client = true` flag: - -```toml -experimental_use_rmcp_client = true -``` - -After enabling it, run `codex mcp login ` when the server supports OAuth. +Streamable HTTP connections always use the Rust MCP client under the hood. OAuth login +flows are supported by default; run `codex mcp login ` when the server +supports OAuth to store credentials. #### Other configuration options @@ -489,17 +486,6 @@ disabled_tools = ["search"] When both `enabled_tools` and `disabled_tools` are specified, Codex first restricts the server to the allow-list and then removes any tools that appear in the deny-list. -#### Experimental RMCP client - -This flag enables OAuth support for streamable HTTP servers. - -```toml -experimental_use_rmcp_client = true - -[mcp_servers.server_name] -… -``` - #### MCP CLI commands ```shell diff --git a/docs/example-config.md b/docs/example-config.md index 6061dc8830..2c04be614b 100644 --- a/docs/example-config.md +++ b/docs/example-config.md @@ -214,13 +214,13 @@ view_image = true [features] # Leave this table empty to accept defaults. Set explicit booleans to opt in/out. unified_exec = false -rmcp_client = false apply_patch_freeform = false view_image_tool = true web_search_request = false experimental_sandbox_command_assessment = false ghost_commit = false enable_experimental_windows_sandbox = false +# The RMCP client is always enabled; legacy toggles are ignored. ################################################################################ # Experimental toggles (legacy; prefer [features]) @@ -229,9 +229,6 @@ enable_experimental_windows_sandbox = false # Use experimental unified exec tool. Default: false experimental_use_unified_exec_tool = false -# Use experimental Rust MCP client (enables OAuth for HTTP MCP). Default: false -experimental_use_rmcp_client = false - # Include apply_patch via freeform editing path (affects default tool set). Default: false experimental_use_freeform_apply_patch = false @@ -320,7 +317,6 @@ experimental_use_freeform_apply_patch = false # experimental_compact_prompt_file = "compact_prompt.txt" # include_apply_patch_tool = false # experimental_use_unified_exec_tool = false -# experimental_use_rmcp_client = false # experimental_use_freeform_apply_patch = false # experimental_sandbox_command_assessment = false # tools_web_search = false