[codex] Support ui visibility meta for tools (#24700)

## Summary

Adds support for the same ui.visibility metadata as resources

[spec](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#resource-discovery)
This commit is contained in:
Gabriel Peal
2026-05-28 10:24:03 -07:00
committed by GitHub
Unverified
parent 2264fdd4a2
commit 577ec03bf8
8 changed files with 418 additions and 2 deletions
+7 -1
View File
@@ -3,6 +3,7 @@ use std::collections::HashSet;
use codex_features::Feature;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::ToolInfo as McpToolInfo;
use codex_mcp::tool_is_model_visible;
use crate::config::Config;
use crate::connectors;
@@ -51,7 +52,9 @@ pub(crate) fn build_mcp_tool_exposure(
fn filter_non_codex_apps_mcp_tools_only(mcp_tools: &[McpToolInfo]) -> Vec<McpToolInfo> {
mcp_tools
.iter()
.filter(|tool| tool.server_name != CODEX_APPS_MCP_SERVER_NAME)
.filter(|tool| {
tool.server_name != CODEX_APPS_MCP_SERVER_NAME && tool_is_model_visible(tool)
})
.cloned()
.collect()
}
@@ -72,6 +75,9 @@ fn filter_codex_apps_mcp_tools(
if tool.server_name != CODEX_APPS_MCP_SERVER_NAME {
return false;
}
if !tool_is_model_visible(tool) {
return false;
}
let Some(connector_id) = tool.connector_id.as_deref() else {
return false;
};
@@ -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::*;
@@ -80,6 +81,16 @@ fn tool_names(tools: &[ToolInfo]) -> HashSet<ToolName> {
.collect()
}
fn with_visibility(mut tool: ToolInfo, visibility: &[&str]) -> ToolInfo {
tool.tool.meta = Some(Meta(
serde_json::json!({ "ui": { "visibility": visibility } })
.as_object()
.expect("metadata object")
.clone(),
));
tool
}
#[tokio::test]
async fn directly_exposes_small_effective_tool_sets() {
let config = test_config().await;
@@ -93,6 +104,84 @@ async fn directly_exposes_small_effective_tool_sets() {
assert!(exposure.deferred_tools.is_none());
}
#[tokio::test]
async fn excludes_tools_hidden_from_model_exposure() {
let config = test_config().await;
let visible_tool = make_mcp_tool(
"rmcp",
"visible_tool",
"mcp__rmcp",
"visible_tool",
/*connector_id*/ None,
/*connector_name*/ None,
);
let hidden_tool = with_visibility(
make_mcp_tool(
"rmcp",
"hidden_tool",
"mcp__rmcp",
"hidden_tool",
/*connector_id*/ None,
/*connector_name*/ None,
),
&["app"],
);
let empty_visibility_tool = with_visibility(
make_mcp_tool(
"rmcp",
"empty_visibility_tool",
"mcp__rmcp",
"empty_visibility_tool",
/*connector_id*/ None,
/*connector_name*/ None,
),
&[],
);
let visible_app_tool = with_visibility(
make_mcp_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_read",
"mcp__codex_apps__calendar",
"read",
Some("calendar"),
Some("Calendar"),
),
&["app", "model"],
);
let hidden_app_tool = with_visibility(
make_mcp_tool(
CODEX_APPS_MCP_SERVER_NAME,
"calendar_open",
"mcp__codex_apps__calendar",
"open",
Some("calendar"),
Some("Calendar"),
),
&["app"],
);
let mcp_tools = vec![
visible_tool.clone(),
hidden_tool,
empty_visibility_tool,
visible_app_tool.clone(),
hidden_app_tool,
];
let connectors = vec![make_connector("calendar", "Calendar")];
let exposure = build_mcp_tool_exposure(
&mcp_tools,
Some(connectors.as_slice()),
&config,
/*search_tool_enabled*/ false,
);
assert_eq!(
tool_names(&exposure.direct_tools),
tool_names(&[visible_tool, visible_app_tool])
);
assert!(exposure.deferred_tools.is_none());
}
#[tokio::test]
async fn searches_large_effective_tool_sets() {
let config = test_config().await;