diff --git a/codex-rs/cli/src/mcp_cmd.rs b/codex-rs/cli/src/mcp_cmd.rs index 30c6fa21f..9035502af 100644 --- a/codex-rs/cli/src/mcp_cmd.rs +++ b/codex-rs/cli/src/mcp_cmd.rs @@ -241,6 +241,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re let new_entry = McpServerConfig { transport: transport.clone(), enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -448,6 +449,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> serde_json::json!({ "name": name, "enabled": cfg.enabled, + "disabled_reason": cfg.disabled_reason.as_ref().map(ToString::to_string), "transport": transport, "startup_timeout_sec": cfg .startup_timeout_sec @@ -492,11 +494,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> .map(|path| path.display().to_string()) .filter(|value| !value.is_empty()) .unwrap_or_else(|| "-".to_string()); - let status = if cfg.enabled { - "enabled".to_string() - } else { - "disabled".to_string() - }; + let status = format_mcp_status(cfg); let auth_status = auth_statuses .get(name.as_str()) .map(|entry| entry.auth_status) @@ -517,11 +515,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> bearer_token_env_var, .. } => { - let status = if cfg.enabled { - "enabled".to_string() - } else { - "disabled".to_string() - }; + let status = format_mcp_status(cfg); let auth_status = auth_statuses .get(name.as_str()) .map(|entry| entry.auth_status) @@ -691,6 +685,7 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re let output = serde_json::to_string_pretty(&serde_json::json!({ "name": get_args.name, "enabled": server.enabled, + "disabled_reason": server.disabled_reason.as_ref().map(ToString::to_string), "transport": transport, "enabled_tools": server.enabled_tools.clone(), "disabled_tools": server.disabled_tools.clone(), @@ -706,7 +701,11 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re } if !server.enabled { - println!("{} (disabled)", get_args.name); + if let Some(reason) = server.disabled_reason.as_ref() { + println!("{name} (disabled: {reason})", name = get_args.name); + } else { + println!("{name} (disabled)", name = get_args.name); + } return Ok(()); } @@ -828,3 +827,13 @@ fn validate_server_name(name: &str) -> Result<()> { bail!("invalid server name '{name}' (use letters, numbers, '-', '_')"); } } + +fn format_mcp_status(config: &McpServerConfig) -> String { + if config.enabled { + "enabled".to_string() + } else if let Some(reason) = config.disabled_reason.as_ref() { + format!("disabled: {reason}") + } else { + "disabled".to_string() + } +} diff --git a/codex-rs/cli/tests/mcp_list.rs b/codex-rs/cli/tests/mcp_list.rs index 400f53365..cb78644b1 100644 --- a/codex-rs/cli/tests/mcp_list.rs +++ b/codex-rs/cli/tests/mcp_list.rs @@ -89,6 +89,7 @@ async fn list_and_get_render_expected_output() -> Result<()> { { "name": "docs", "enabled": true, + "disabled_reason": null, "transport": { "type": "stdio", "command": "docs-server", diff --git a/codex-rs/core/src/config/edit.rs b/codex-rs/core/src/config/edit.rs index 0def0440a..fb6829bb9 100644 --- a/codex-rs/core/src/config/edit.rs +++ b/codex-rs/core/src/config/edit.rs @@ -1124,6 +1124,7 @@ gpt-5 = "gpt-5.1" cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: Some(vec!["one".to_string(), "two".to_string()]), @@ -1145,6 +1146,7 @@ gpt-5 = "gpt-5.1" env_http_headers: None, }, enabled: false, + disabled_reason: None, startup_timeout_sec: Some(std::time::Duration::from_secs(5)), tool_timeout_sec: None, enabled_tools: None, @@ -1209,6 +1211,7 @@ foo = { command = "cmd" } cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1252,6 +1255,7 @@ foo = { command = "cmd" } # keep me cwd: None, }, enabled: false, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1294,6 +1298,7 @@ foo = { command = "cmd", args = ["--flag"] } # keep me cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1337,6 +1342,7 @@ foo = { command = "cmd" } cwd: None, }, enabled: false, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 5e5d26c79..5694c9a25 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -2,6 +2,7 @@ use crate::auth::AuthCredentialsStoreMode; use crate::config::types::DEFAULT_OTEL_ENVIRONMENT; use crate::config::types::History; use crate::config::types::McpServerConfig; +use crate::config::types::McpServerDisabledReason; use crate::config::types::McpServerTransportConfig; use crate::config::types::Notice; use crate::config::types::Notifications; @@ -19,6 +20,7 @@ use crate::config_loader::ConfigRequirements; use crate::config_loader::LoaderOverrides; use crate::config_loader::McpServerIdentity; use crate::config_loader::McpServerRequirement; +use crate::config_loader::Sourced; use crate::config_loader::load_config_layers_state; use crate::features::Feature; use crate::features::FeatureOverrides; @@ -539,25 +541,32 @@ fn deserialize_config_toml_with_base( fn filter_mcp_servers_by_requirements( mcp_servers: &mut HashMap, - mcp_requirements: Option<&BTreeMap>, + mcp_requirements: Option<&Sourced>>, ) { let Some(allowlist) = mcp_requirements else { return; }; + let source = allowlist.source.clone(); for (name, server) in mcp_servers.iter_mut() { let allowed = allowlist + .value .get(name) .is_some_and(|requirement| mcp_server_matches_requirement(requirement, server)); - if !allowed { + if allowed { + server.disabled_reason = None; + } else { server.enabled = false; + server.disabled_reason = Some(McpServerDisabledReason::Requirements { + source: source.clone(), + }); } } } fn constrain_mcp_servers( mcp_servers: HashMap, - mcp_requirements: Option<&BTreeMap>, + mcp_requirements: Option<&Sourced>>, ) -> ConstraintResult>> { if mcp_requirements.is_none() { return Ok(Constrained::allow_any(mcp_servers)); @@ -1707,6 +1716,7 @@ mod tests { use crate::config::types::HistoryPersistence; use crate::config::types::McpServerTransportConfig; use crate::config::types::Notifications; + use crate::config_loader::RequirementSource; use crate::features::Feature; use super::*; @@ -1728,6 +1738,7 @@ mod tests { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1744,6 +1755,7 @@ mod tests { env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1976,9 +1988,9 @@ trust_level = "trusted" (MATCHED_URL_SERVER.to_string(), http_mcp(GOOD_URL)), (DIFFERENT_NAME_SERVER.to_string(), stdio_mcp("same-cmd")), ]); - filter_mcp_servers_by_requirements( - &mut servers, - Some(&BTreeMap::from([ + let source = RequirementSource::LegacyManagedConfigTomlFromMdm; + let requirements = Sourced::new( + BTreeMap::from([ ( MISMATCHED_URL_SERVER.to_string(), McpServerRequirement { @@ -2011,20 +2023,29 @@ trust_level = "trusted" }, }, ), - ])), + ]), + source.clone(), ); + filter_mcp_servers_by_requirements(&mut servers, Some(&requirements)); + let reason = Some(McpServerDisabledReason::Requirements { source }); assert_eq!( servers .iter() - .map(|(name, server)| (name.clone(), server.enabled)) - .collect::>(), + .map(|(name, server)| ( + name.clone(), + (server.enabled, server.disabled_reason.clone()) + )) + .collect::)>>(), HashMap::from([ - (MISMATCHED_URL_SERVER.to_string(), false), - (MISMATCHED_COMMAND_SERVER.to_string(), false), - (MATCHED_URL_SERVER.to_string(), true), - (MATCHED_COMMAND_SERVER.to_string(), true), - (DIFFERENT_NAME_SERVER.to_string(), false), + (MISMATCHED_URL_SERVER.to_string(), (false, reason.clone())), + ( + MISMATCHED_COMMAND_SERVER.to_string(), + (false, reason.clone()), + ), + (MATCHED_URL_SERVER.to_string(), (true, None)), + (MATCHED_COMMAND_SERVER.to_string(), (true, None)), + (DIFFERENT_NAME_SERVER.to_string(), (false, reason)), ]) ); } @@ -2041,11 +2062,14 @@ trust_level = "trusted" assert_eq!( servers .iter() - .map(|(name, server)| (name.clone(), server.enabled)) - .collect::>(), + .map(|(name, server)| ( + name.clone(), + (server.enabled, server.disabled_reason.clone()) + )) + .collect::)>>(), HashMap::from([ - ("server-a".to_string(), true), - ("server-b".to_string(), true), + ("server-a".to_string(), (true, None)), + ("server-b".to_string(), (true, None)), ]) ); } @@ -2057,16 +2081,22 @@ trust_level = "trusted" ("server-b".to_string(), http_mcp("https://example.com/b")), ]); - filter_mcp_servers_by_requirements(&mut servers, Some(&BTreeMap::new())); + let source = RequirementSource::LegacyManagedConfigTomlFromMdm; + let requirements = Sourced::new(BTreeMap::new(), source.clone()); + filter_mcp_servers_by_requirements(&mut servers, Some(&requirements)); + let reason = Some(McpServerDisabledReason::Requirements { source }); assert_eq!( servers .iter() - .map(|(name, server)| (name.clone(), server.enabled)) - .collect::>(), + .map(|(name, server)| ( + name.clone(), + (server.enabled, server.disabled_reason.clone()) + )) + .collect::)>>(), HashMap::from([ - ("server-a".to_string(), false), - ("server-b".to_string(), false), + ("server-a".to_string(), (false, reason.clone())), + ("server-b".to_string(), (false, reason)), ]) ); } @@ -2491,6 +2521,7 @@ trust_level = "trusted" cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(3)), tool_timeout_sec: Some(Duration::from_secs(5)), enabled_tools: None, @@ -2644,6 +2675,7 @@ bearer_token = "secret" cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -2712,6 +2744,7 @@ ZIG_VAR = "3" cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -2760,6 +2793,7 @@ ZIG_VAR = "3" cwd: Some(cwd_path.clone()), }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -2806,6 +2840,7 @@ ZIG_VAR = "3" env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(2)), tool_timeout_sec: None, enabled_tools: None, @@ -2868,6 +2903,7 @@ startup_timeout_sec = 2.0 )])), }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(2)), tool_timeout_sec: None, enabled_tools: None, @@ -2942,6 +2978,7 @@ X-Auth = "DOCS_AUTH" )])), }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(2)), tool_timeout_sec: None, enabled_tools: None, @@ -2969,6 +3006,7 @@ X-Auth = "DOCS_AUTH" env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -3034,6 +3072,7 @@ url = "https://example.com/mcp" )])), }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(2)), tool_timeout_sec: None, enabled_tools: None, @@ -3051,6 +3090,7 @@ url = "https://example.com/mcp" cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -3131,6 +3171,7 @@ url = "https://example.com/mcp" cwd: None, }, enabled: false, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -3173,6 +3214,7 @@ url = "https://example.com/mcp" cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: Some(vec!["allowed".to_string()]), diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index 13b201e84..981f9b541 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -3,11 +3,13 @@ // Note this file should generally be restricted to simple struct/enum // definitions that do not contain business logic. +use crate::config_loader::RequirementSource; pub use codex_protocol::config_types::AltScreenMode; pub use codex_protocol::config_types::WebSearchMode; use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::BTreeMap; use std::collections::HashMap; +use std::fmt; use std::path::PathBuf; use std::time::Duration; use wildmatch::WildMatchPattern; @@ -20,6 +22,23 @@ use serde::de::Error as SerdeError; pub const DEFAULT_OTEL_ENVIRONMENT: &str = "dev"; +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum McpServerDisabledReason { + Unknown, + Requirements { source: RequirementSource }, +} + +impl fmt::Display for McpServerDisabledReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + McpServerDisabledReason::Unknown => write!(f, "unknown"), + McpServerDisabledReason::Requirements { source } => { + write!(f, "requirements ({source})") + } + } + } +} + #[derive(Serialize, Debug, Clone, PartialEq)] pub struct McpServerConfig { #[serde(flatten)] @@ -29,6 +48,10 @@ pub struct McpServerConfig { #[serde(default = "default_enabled")] pub enabled: bool, + /// Reason this server was disabled after applying requirements. + #[serde(skip)] + pub disabled_reason: Option, + /// Startup timeout in seconds for initializing MCP server & initially listing tools. #[serde( default, @@ -160,6 +183,7 @@ impl<'de> Deserialize<'de> for McpServerConfig { startup_timeout_sec, tool_timeout_sec, enabled, + disabled_reason: None, enabled_tools, disabled_tools, }) diff --git a/codex-rs/core/src/config_loader/config_requirements.rs b/codex-rs/core/src/config_loader/config_requirements.rs index 0411b0916..a83398c71 100644 --- a/codex-rs/core/src/config_loader/config_requirements.rs +++ b/codex-rs/core/src/config_loader/config_requirements.rs @@ -44,7 +44,7 @@ impl fmt::Display for RequirementSource { pub struct ConfigRequirements { pub approval_policy: Constrained, pub sandbox_policy: Constrained, - pub mcp_servers: Option>, + pub mcp_servers: Option>>, } impl Default for ConfigRequirements { @@ -273,7 +273,7 @@ impl TryFrom for ConfigRequirements { Ok(ConfigRequirements { approval_policy, sandbox_policy, - mcp_servers: mcp_servers.map(|sourced| sourced.value), + mcp_servers, }) } } @@ -571,24 +571,27 @@ mod tests { assert_eq!( requirements.mcp_servers, - Some(BTreeMap::from([ - ( - "docs".to_string(), - McpServerRequirement { - identity: McpServerIdentity::Command { - command: "codex-mcp".to_string(), + Some(Sourced::new( + BTreeMap::from([ + ( + "docs".to_string(), + McpServerRequirement { + identity: McpServerIdentity::Command { + command: "codex-mcp".to_string(), + }, }, - }, - ), - ( - "remote".to_string(), - McpServerRequirement { - identity: McpServerIdentity::Url { - url: "https://example.com/mcp".to_string(), + ), + ( + "remote".to_string(), + McpServerRequirement { + identity: McpServerIdentity::Url { + url: "https://example.com/mcp".to_string(), + }, }, - }, - ), - ])) + ), + ]), + RequirementSource::Unknown, + )) ); Ok(()) } diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 7e6d4223c..9a8791eee 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -30,6 +30,7 @@ pub use config_requirements::McpServerIdentity; pub use config_requirements::McpServerRequirement; pub use config_requirements::RequirementSource; pub use config_requirements::SandboxModeRequirement; +pub use config_requirements::Sourced; pub use merge::merge_toml_values; pub(crate) use overrides::build_cli_overrides_layer; pub use state::ConfigLayerEntry; diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 6574437bd..0d638760f 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -1171,6 +1171,7 @@ mod tests { env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1215,6 +1216,7 @@ mod tests { env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 5001eead3..5abc39264 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -88,6 +88,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -225,6 +226,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -420,6 +422,7 @@ async fn stdio_image_completions_round_trip() -> anyhow::Result<()> { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -563,6 +566,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -717,6 +721,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> { env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -903,6 +908,7 @@ async fn streamable_http_with_oauth_round_trip() -> anyhow::Result<()> { env_http_headers: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, diff --git a/codex-rs/core/tests/suite/truncation.rs b/codex-rs/core/tests/suite/truncation.rs index a86489bdd..4ae095517 100644 --- a/codex-rs/core/tests/suite/truncation.rs +++ b/codex-rs/core/tests/suite/truncation.rs @@ -426,6 +426,7 @@ async fn mcp_tool_call_output_exceeds_limit_truncated_for_model() -> Result<()> cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(std::time::Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -517,6 +518,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, @@ -777,6 +779,7 @@ async fn mcp_tool_call_output_not_truncated_with_custom_limit() -> Result<()> { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: Some(std::time::Duration::from_secs(10)), tool_timeout_sec: None, enabled_tools: None, diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 8db7be6f6..cfafb6da3 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -1358,7 +1358,6 @@ pub(crate) fn new_mcp_tools_output( if tools.is_empty() { lines.push(" • No MCP tools available.".italic().into()); lines.push("".into()); - return PlainHistoryCell { lines }; } let mut servers: Vec<_> = config.mcp_servers.iter().collect(); @@ -1382,6 +1381,9 @@ pub(crate) fn new_mcp_tools_output( header.push(" ".into()); header.push("(disabled)".red()); lines.push(header.into()); + if let Some(reason) = cfg.disabled_reason.as_ref().map(ToString::to_string) { + lines.push(vec![" • Reason: ".into(), reason.dim()].into()); + } lines.push(Line::from("")); continue; } @@ -1836,6 +1838,7 @@ mod tests { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1856,6 +1859,7 @@ mod tests { env_http_headers: Some(env_headers), }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, diff --git a/codex-rs/tui2/src/history_cell.rs b/codex-rs/tui2/src/history_cell.rs index dc5ad0ee2..6f795a5be 100644 --- a/codex-rs/tui2/src/history_cell.rs +++ b/codex-rs/tui2/src/history_cell.rs @@ -1422,7 +1422,6 @@ pub(crate) fn new_mcp_tools_output( if tools.is_empty() { lines.push(" • No MCP tools available.".italic().into()); lines.push("".into()); - return PlainHistoryCell { lines }; } let mut servers: Vec<_> = config.mcp_servers.iter().collect(); @@ -1446,6 +1445,9 @@ pub(crate) fn new_mcp_tools_output( header.push(" ".into()); header.push("(disabled)".red()); lines.push(header.into()); + if let Some(reason) = cfg.disabled_reason.as_ref().map(ToString::to_string) { + lines.push(vec![" • Reason: ".into(), reason.dim()].into()); + } lines.push(Line::from("")); continue; } @@ -1971,6 +1973,7 @@ mod tests { cwd: None, }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None, @@ -1991,6 +1994,7 @@ mod tests { env_http_headers: Some(env_headers), }, enabled: true, + disabled_reason: None, startup_timeout_sec: None, tool_timeout_sec: None, enabled_tools: None,