Skip to content

Commit 7117457

Browse files
authored
Add server-level approval defaults for custom MCP servers (#17843)
## Summary - Add `default_tools_approval_mode` support for custom MCP server configs, matching the existing `codex_apps` behavior - Apply approval precedence as per-tool override, then server default, then `auto` - Update config serialization, CLI display, schema generation, docs, and tests ## Testing - `cargo check -p codex-config` - `cargo check -p codex-core` - `just write-config-schema` - `just fmt` - `cargo test -p codex-config` - Targeted `codex-core` tests for config parsing, config writes, and MCP approval precedence - `just fix -p codex-config -p codex-core`
1 parent 206dd13 commit 7117457

27 files changed

+231
-12
lines changed

codex-rs/cli/src/mcp_cmd.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use anyhow::Result;
66
use anyhow::anyhow;
77
use anyhow::bail;
88
use clap::ArgGroup;
9+
use codex_config::types::AppToolApproval;
910
use codex_config::types::McpServerConfig;
1011
use codex_config::types::McpServerTransportConfig;
1112
use codex_core::McpManager;
@@ -304,6 +305,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
304305
disabled_reason: None,
305306
startup_timeout_sec: None,
306307
tool_timeout_sec: None,
308+
default_tools_approval_mode: None,
307309
enabled_tools: None,
308310
disabled_tools: None,
309311
scopes: None,
@@ -879,6 +881,14 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re
879881
if let Some(timeout) = server.tool_timeout_sec {
880882
println!(" tool_timeout_sec: {}", timeout.as_secs_f64());
881883
}
884+
if let Some(approval_mode) = server.default_tools_approval_mode {
885+
let approval_mode = match approval_mode {
886+
AppToolApproval::Auto => "auto",
887+
AppToolApproval::Prompt => "prompt",
888+
AppToolApproval::Approve => "approve",
889+
};
890+
println!(" default_tools_approval_mode: {approval_mode}");
891+
}
882892
println!(" remove: codex mcp remove {}", get_args.name);
883893

884894
Ok(())

codex-rs/codex-mcp/src/mcp/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ fn codex_apps_mcp_server_config(config: &McpConfig, auth: Option<&CodexAuth>) ->
276276
disabled_reason: None,
277277
startup_timeout_sec: Some(Duration::from_secs(30)),
278278
tool_timeout_sec: None,
279+
default_tools_approval_mode: None,
279280
enabled_tools: None,
280281
disabled_tools: None,
281282
scopes: None,

codex-rs/codex-mcp/src/mcp/mod_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
200200
disabled_reason: None,
201201
startup_timeout_sec: None,
202202
tool_timeout_sec: None,
203+
default_tools_approval_mode: None,
203204
enabled_tools: None,
204205
disabled_tools: None,
205206
scopes: None,
@@ -223,6 +224,7 @@ async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
223224
disabled_reason: None,
224225
startup_timeout_sec: None,
225226
tool_timeout_sec: None,
227+
default_tools_approval_mode: None,
226228
enabled_tools: None,
227229
disabled_tools: None,
228230
scopes: None,

codex-rs/codex-mcp/src/mcp/skill_dependencies.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ fn mcp_dependency_to_server_config(
126126
disabled_reason: None,
127127
startup_timeout_sec: None,
128128
tool_timeout_sec: None,
129+
default_tools_approval_mode: None,
129130
enabled_tools: None,
130131
disabled_tools: None,
131132
scopes: None,
@@ -154,6 +155,7 @@ fn mcp_dependency_to_server_config(
154155
disabled_reason: None,
155156
startup_timeout_sec: None,
156157
tool_timeout_sec: None,
158+
default_tools_approval_mode: None,
157159
enabled_tools: None,
158160
disabled_tools: None,
159161
scopes: None,

codex-rs/codex-mcp/src/mcp/skill_dependencies_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ fn collect_missing_respects_canonical_installed_key() {
4646
disabled_reason: None,
4747
startup_timeout_sec: None,
4848
tool_timeout_sec: None,
49+
default_tools_approval_mode: None,
4950
enabled_tools: None,
5051
disabled_tools: None,
5152
scopes: None,
@@ -98,6 +99,7 @@ fn collect_missing_dedupes_by_canonical_key_but_preserves_original_name() {
9899
disabled_reason: None,
99100
startup_timeout_sec: None,
100101
tool_timeout_sec: None,
102+
default_tools_approval_mode: None,
101103
enabled_tools: None,
102104
disabled_tools: None,
103105
scopes: None,

codex-rs/codex-mcp/src/mcp_connection_manager_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ fn mcp_init_error_display_prompts_for_github_pat() {
799799
disabled_reason: None,
800800
startup_timeout_sec: None,
801801
tool_timeout_sec: None,
802+
default_tools_approval_mode: None,
802803
enabled_tools: None,
803804
disabled_tools: None,
804805
scopes: None,
@@ -850,6 +851,7 @@ fn mcp_init_error_display_reports_generic_errors() {
850851
disabled_reason: None,
851852
startup_timeout_sec: None,
852853
tool_timeout_sec: None,
854+
default_tools_approval_mode: None,
853855
enabled_tools: None,
854856
disabled_tools: None,
855857
scopes: None,

codex-rs/config/src/mcp_edit.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem {
189189
if let Some(timeout) = config.tool_timeout_sec {
190190
entry["tool_timeout_sec"] = value(timeout.as_secs_f64());
191191
}
192+
if let Some(approval_mode) = config.default_tools_approval_mode {
193+
entry["default_tools_approval_mode"] = value(match approval_mode {
194+
AppToolApproval::Auto => "auto",
195+
AppToolApproval::Prompt => "prompt",
196+
AppToolApproval::Approve => "approve",
197+
});
198+
}
192199
if let Some(enabled_tools) = &config.enabled_tools
193200
&& !enabled_tools.is_empty()
194201
{

codex-rs/config/src/mcp_edit_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ async fn replace_mcp_servers_serializes_per_tool_approval_overrides() -> anyhow:
2929
disabled_reason: None,
3030
startup_timeout_sec: None,
3131
tool_timeout_sec: None,
32+
default_tools_approval_mode: Some(AppToolApproval::Auto),
3233
enabled_tools: None,
3334
disabled_tools: None,
3435
scopes: None,
@@ -62,6 +63,7 @@ async fn replace_mcp_servers_serializes_per_tool_approval_overrides() -> anyhow:
6263
r#"[mcp_servers.docs]
6364
command = "docs-server"
6465
supports_parallel_tool_calls = true
66+
default_tools_approval_mode = "auto"
6567
6668
[mcp_servers.docs.tools]
6769

codex-rs/config/src/mcp_types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ pub struct McpServerConfig {
9393
#[serde(default, with = "option_duration_secs")]
9494
pub tool_timeout_sec: Option<Duration>,
9595

96+
/// Approval mode for tools in this server unless a tool override exists.
97+
#[serde(default, skip_serializing_if = "Option::is_none")]
98+
pub default_tools_approval_mode: Option<AppToolApproval>,
99+
96100
/// Explicit allow-list of tools exposed from this server. When set, only these tools will be registered.
97101
#[serde(default, skip_serializing_if = "Option::is_none")]
98102
pub enabled_tools: Option<Vec<String>>,
@@ -158,6 +162,8 @@ pub struct RawMcpServerConfig {
158162
#[serde(default)]
159163
pub supports_parallel_tool_calls: Option<bool>,
160164
#[serde(default)]
165+
pub default_tools_approval_mode: Option<AppToolApproval>,
166+
#[serde(default)]
161167
pub enabled_tools: Option<Vec<String>>,
162168
#[serde(default)]
163169
pub disabled_tools: Option<Vec<String>>,
@@ -194,6 +200,7 @@ impl TryFrom<RawMcpServerConfig> for McpServerConfig {
194200
enabled,
195201
required,
196202
supports_parallel_tool_calls,
203+
default_tools_approval_mode,
197204
enabled_tools,
198205
disabled_tools,
199206
scopes,
@@ -260,6 +267,7 @@ impl TryFrom<RawMcpServerConfig> for McpServerConfig {
260267
required: required.unwrap_or_default(),
261268
supports_parallel_tool_calls: supports_parallel_tool_calls.unwrap_or_default(),
262269
disabled_reason: None,
270+
default_tools_approval_mode,
263271
enabled_tools,
264272
disabled_tools,
265273
scopes,

codex-rs/config/src/mcp_types_tests.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,38 @@ fn deserialize_server_config_with_parallel_tool_calls() {
258258
assert!(cfg.supports_parallel_tool_calls);
259259
}
260260

261+
#[test]
262+
fn deserialize_server_config_with_default_tool_approval_mode() {
263+
let cfg: McpServerConfig = toml::from_str(
264+
r#"
265+
command = "echo"
266+
default_tools_approval_mode = "approve"
267+
268+
[tools.search]
269+
approval_mode = "prompt"
270+
"#,
271+
)
272+
.expect("should deserialize default tool approval mode");
273+
274+
assert_eq!(
275+
cfg.default_tools_approval_mode,
276+
Some(AppToolApproval::Approve)
277+
);
278+
assert_eq!(
279+
cfg.tools.get("search"),
280+
Some(&McpServerToolConfig {
281+
approval_mode: Some(AppToolApproval::Prompt),
282+
})
283+
);
284+
285+
let serialized = toml::to_string(&cfg).expect("should serialize MCP config");
286+
assert!(serialized.contains("default_tools_approval_mode = \"approve\""));
287+
288+
let round_tripped: McpServerConfig =
289+
toml::from_str(&serialized).expect("should deserialize serialized MCP config");
290+
assert_eq!(round_tripped, cfg);
291+
}
292+
261293
#[test]
262294
fn serialize_round_trips_server_config_with_parallel_tool_calls() {
263295
let cfg: McpServerConfig = toml::from_str(
@@ -304,6 +336,7 @@ fn deserialize_ignores_unknown_server_fields() {
304336
disabled_reason: None,
305337
startup_timeout_sec: None,
306338
tool_timeout_sec: None,
339+
default_tools_approval_mode: None,
307340
enabled_tools: None,
308341
disabled_tools: None,
309342
scopes: None,

0 commit comments

Comments
 (0)