diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 300716dc1..3d091006c 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1258,9 +1258,7 @@ "type": "object" }, "RawMcpServerConfig": { - "additionalProperties": { - "$ref": "#/definitions/McpServerToolConfig" - }, + "additionalProperties": false, "properties": { "args": { "default": null, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 7d2f6c573..949906ac4 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -1991,28 +1991,19 @@ approval_mode = "approve" } #[test] -fn mcp_servers_toml_parses_legacy_flattened_per_tool_approval_overrides() { +fn mcp_servers_toml_ignores_unknown_server_fields() { let config = toml::from_str::( r#" [mcp_servers.docs] command = "docs-server" - -[mcp_servers.docs.search] -approval_mode = "approve" +trust_level = "trusted" "#, ) - .expect("legacy TOML deserialization should succeed"); - let tool = config - .mcp_servers - .get("docs") - .and_then(|server| server.tools.get("search")) - .expect("docs/search tool config exists"); + .expect("unknown MCP server fields should be ignored"); assert_eq!( - tool, - &McpServerToolConfig { - approval_mode: Some(AppToolApproval::Approve), - } + config.mcp_servers.get("docs"), + Some(&stdio_mcp("docs-server")) ); } diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index a69fec343..05fbaaaa4 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -163,10 +163,6 @@ pub(crate) struct RawMcpServerConfig { pub _name: Option, #[serde(default)] pub tools: Option>, - /// Legacy flattened per-tool approval settings accepted for backward compatibility. - #[serde(default)] - #[serde(flatten)] - pub legacy_tools: HashMap, } impl<'de> Deserialize<'de> for McpServerConfig { @@ -191,10 +187,7 @@ impl<'de> Deserialize<'de> for McpServerConfig { let disabled_tools = raw.disabled_tools.clone(); let scopes = raw.scopes.clone(); let oauth_resource = raw.oauth_resource.clone(); - let mut tools = raw.legacy_tools.clone(); - if let Some(nested_tools) = raw.tools.clone() { - tools.extend(nested_tools); - } + let tools = raw.tools.clone().unwrap_or_default(); fn throw_if_set(transport: &str, field: &str, value: Option<&T>) -> Result<(), E> where diff --git a/codex-rs/core/src/config/types_tests.rs b/codex-rs/core/src/config/types_tests.rs index dde0cd18f..cba01e787 100644 --- a/codex-rs/core/src/config/types_tests.rs +++ b/codex-rs/core/src/config/types_tests.rs @@ -243,6 +243,40 @@ fn deserialize_server_config_with_tool_filters() { assert_eq!(cfg.disabled_tools, Some(vec!["blocked".to_string()])); } +#[test] +fn deserialize_ignores_unknown_server_fields() { + let cfg: McpServerConfig = toml::from_str( + r#" + command = "echo" + trust_level = "trusted" + "#, + ) + .expect("should ignore unknown server fields"); + + assert_eq!( + cfg, + McpServerConfig { + transport: McpServerTransportConfig::Stdio { + command: "echo".to_string(), + args: vec![], + env: None, + env_vars: Vec::new(), + cwd: None, + }, + enabled: true, + required: false, + disabled_reason: None, + startup_timeout_sec: None, + tool_timeout_sec: None, + enabled_tools: None, + disabled_tools: None, + scopes: None, + oauth_resource: None, + tools: HashMap::new(), + } + ); +} + #[test] fn deserialize_skill_config_with_name_selector() { let cfg: SkillConfig = toml::from_str(