[MCP] Add configuration options to enable or disable specific tools (#5367)
Some MCP servers expose a lot of tools. In those cases, it is reasonable to allow/denylist tools for Codex to use so it doesn't get overwhelmed with too many tools. The new configuration options available in the `mcp_server` toml table are: * `enabled_tools` * `disabled_tools` Fixes #4796
This commit is contained in:
@@ -484,6 +484,16 @@ pub fn write_global_mcp_servers(
|
||||
entry["tool_timeout_sec"] = toml_edit::value(timeout.as_secs_f64());
|
||||
}
|
||||
|
||||
if let Some(enabled_tools) = &config.enabled_tools {
|
||||
entry["enabled_tools"] =
|
||||
TomlItem::Value(enabled_tools.iter().collect::<TomlArray>().into());
|
||||
}
|
||||
|
||||
if let Some(disabled_tools) = &config.disabled_tools {
|
||||
entry["disabled_tools"] =
|
||||
TomlItem::Value(disabled_tools.iter().collect::<TomlArray>().into());
|
||||
}
|
||||
|
||||
doc["mcp_servers"][name.as_str()] = TomlItem::Table(entry);
|
||||
}
|
||||
}
|
||||
@@ -1923,6 +1933,8 @@ approve_all = true
|
||||
enabled: true,
|
||||
startup_timeout_sec: Some(Duration::from_secs(3)),
|
||||
tool_timeout_sec: Some(Duration::from_secs(5)),
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2059,6 +2071,8 @@ bearer_token = "secret"
|
||||
enabled: true,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
|
||||
@@ -2121,6 +2135,8 @@ ZIG_VAR = "3"
|
||||
enabled: true,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
|
||||
@@ -2163,6 +2179,8 @@ ZIG_VAR = "3"
|
||||
enabled: true,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
|
||||
@@ -2204,6 +2222,8 @@ ZIG_VAR = "3"
|
||||
enabled: true,
|
||||
startup_timeout_sec: Some(Duration::from_secs(2)),
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
|
||||
@@ -2261,6 +2281,8 @@ startup_timeout_sec = 2.0
|
||||
enabled: true,
|
||||
startup_timeout_sec: Some(Duration::from_secs(2)),
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
||||
@@ -2330,6 +2352,8 @@ X-Auth = "DOCS_AUTH"
|
||||
enabled: true,
|
||||
startup_timeout_sec: Some(Duration::from_secs(2)),
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
|
||||
@@ -2351,6 +2375,8 @@ X-Auth = "DOCS_AUTH"
|
||||
enabled: true,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
);
|
||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
||||
@@ -2410,6 +2436,8 @@ url = "https://example.com/mcp"
|
||||
enabled: true,
|
||||
startup_timeout_sec: Some(Duration::from_secs(2)),
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
@@ -2425,6 +2453,8 @@ url = "https://example.com/mcp"
|
||||
enabled: true,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
),
|
||||
]);
|
||||
@@ -2499,6 +2529,8 @@ url = "https://example.com/mcp"
|
||||
enabled: false,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
},
|
||||
)]);
|
||||
|
||||
@@ -2518,6 +2550,49 @@ url = "https://example.com/mcp"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_global_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
env: None,
|
||||
env_vars: Vec::new(),
|
||||
cwd: None,
|
||||
},
|
||||
enabled: true,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
enabled_tools: Some(vec!["allowed".to_string()]),
|
||||
disabled_tools: Some(vec!["blocked".to_string()]),
|
||||
},
|
||||
)]);
|
||||
|
||||
write_global_mcp_servers(codex_home.path(), &servers)?;
|
||||
|
||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||
let serialized = std::fs::read_to_string(&config_path)?;
|
||||
assert!(serialized.contains(r#"enabled_tools = ["allowed"]"#));
|
||||
assert!(serialized.contains(r#"disabled_tools = ["blocked"]"#));
|
||||
|
||||
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
||||
let docs = loaded.get("docs").expect("docs entry");
|
||||
assert_eq!(
|
||||
docs.enabled_tools.as_ref(),
|
||||
Some(&vec!["allowed".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
docs.disabled_tools.as_ref(),
|
||||
Some(&vec!["blocked".to_string()])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn persist_model_selection_updates_defaults() -> anyhow::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
Reference in New Issue
Block a user