mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +00:00
Hide app-only MCP tools from model
This commit is contained in:
@@ -59,6 +59,22 @@ impl ToolInfo {
|
||||
pub fn canonical_tool_name(&self) -> ToolName {
|
||||
ToolName::namespaced(self.callable_namespace.clone(), self.callable_name.clone())
|
||||
}
|
||||
|
||||
/// Returns whether MCP Apps metadata allows this tool to be presented to the model.
|
||||
pub fn is_model_visible(&self) -> bool {
|
||||
self.tool
|
||||
.meta
|
||||
.as_deref()
|
||||
.and_then(|meta| meta.get(META_UI))
|
||||
.and_then(JsonValue::as_object)
|
||||
.and_then(|ui| ui.get(META_UI_VISIBILITY))
|
||||
.and_then(JsonValue::as_array)
|
||||
.is_none_or(|visibility| {
|
||||
visibility
|
||||
.iter()
|
||||
.any(|value| value.as_str() == Some(META_UI_VISIBILITY_MODEL))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declared_openai_file_input_param_names(
|
||||
@@ -261,6 +277,9 @@ const MCP_TOOL_NAME_DELIMITER: &str = "__";
|
||||
const MAX_TOOL_NAME_LENGTH: usize = 64;
|
||||
const CALLABLE_NAME_HASH_LEN: usize = 12;
|
||||
const META_OPENAI_FILE_PARAMS: &str = "openai/fileParams";
|
||||
const META_UI: &str = "ui";
|
||||
const META_UI_VISIBILITY: &str = "visibility";
|
||||
const META_UI_VISIBILITY_MODEL: &str = "model";
|
||||
|
||||
fn callable_namespace_with_prefix(namespace: &str, prefix_mcp_tool_names: bool) -> String {
|
||||
if !prefix_mcp_tool_names || namespace.starts_with(LEGACY_MCP_TOOL_NAME_PREFIX) {
|
||||
|
||||
@@ -20,10 +20,15 @@ pub(crate) fn build_mcp_tool_exposure(
|
||||
config: &Config,
|
||||
search_tool_enabled: bool,
|
||||
) -> McpToolExposure {
|
||||
let mut deferred_tools = filter_non_codex_apps_mcp_tools_only(all_mcp_tools);
|
||||
let model_visible_tools = all_mcp_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.is_model_visible())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let mut deferred_tools = filter_non_codex_apps_mcp_tools_only(&model_visible_tools);
|
||||
if let Some(connectors) = connectors {
|
||||
deferred_tools.extend(filter_codex_apps_mcp_tools(
|
||||
all_mcp_tools,
|
||||
&model_visible_tools,
|
||||
connectors,
|
||||
config,
|
||||
));
|
||||
|
||||
@@ -7,6 +7,7 @@ use codex_mcp::ToolInfo;
|
||||
use codex_tools::ToolName;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::Meta;
|
||||
use rmcp::model::Tool;
|
||||
|
||||
use super::*;
|
||||
@@ -79,6 +80,20 @@ fn numbered_mcp_tools(count: usize) -> Vec<ToolInfo> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn with_ui_visibility(mut tool: ToolInfo, visibility: &[&str]) -> ToolInfo {
|
||||
tool.tool.meta = Some(Meta(
|
||||
serde_json::json!({
|
||||
"ui": {
|
||||
"visibility": visibility,
|
||||
},
|
||||
})
|
||||
.as_object()
|
||||
.expect("object")
|
||||
.clone(),
|
||||
));
|
||||
tool
|
||||
}
|
||||
|
||||
fn tool_names(tools: &[ToolInfo]) -> HashSet<ToolName> {
|
||||
tools
|
||||
.iter()
|
||||
@@ -86,6 +101,52 @@ fn tool_names(tools: &[ToolInfo]) -> HashSet<ToolName> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hides_app_only_tools_from_model_exposure() {
|
||||
let config = test_config().await;
|
||||
let public_tool = make_mcp_tool(
|
||||
"rmcp",
|
||||
"public_tool",
|
||||
"mcp__rmcp",
|
||||
"public_tool",
|
||||
/*connector_id*/ None,
|
||||
/*connector_name*/ None,
|
||||
);
|
||||
let app_only_tool = with_ui_visibility(
|
||||
make_mcp_tool(
|
||||
"rmcp",
|
||||
"app_only_tool",
|
||||
"mcp__rmcp",
|
||||
"app_only_tool",
|
||||
/*connector_id*/ None,
|
||||
/*connector_name*/ None,
|
||||
),
|
||||
&["app"],
|
||||
);
|
||||
let model_only_tool = with_ui_visibility(
|
||||
make_mcp_tool(
|
||||
"rmcp",
|
||||
"model_only_tool",
|
||||
"mcp__rmcp",
|
||||
"model_only_tool",
|
||||
/*connector_id*/ None,
|
||||
/*connector_name*/ None,
|
||||
),
|
||||
&["model"],
|
||||
);
|
||||
let mcp_tools = vec![public_tool.clone(), app_only_tool, model_only_tool.clone()];
|
||||
|
||||
let exposure = build_mcp_tool_exposure(
|
||||
&mcp_tools, /*connectors*/ None, &config, /*search_tool_enabled*/ true,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tool_names(&exposure.direct_tools),
|
||||
tool_names(&[public_tool, model_only_tool])
|
||||
);
|
||||
assert!(exposure.deferred_tools.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn directly_exposes_small_effective_tool_sets() {
|
||||
let config = test_config().await;
|
||||
|
||||
Reference in New Issue
Block a user