mirror of
https://github.com/openai/codex.git
synced 2026-05-03 02:46:39 +00:00
[mcp] Expand tool search to custom MCPs. (#16944)
- [x] Expand tool search to custom MCPs.
- [x] Rename several variables/fields to be more generic.
Updated tool & server name lifecycles:
**Raw Identity**
ToolInfo.server_name is raw MCP server name.
ToolInfo.tool.name is raw MCP tool name.
MCP calls route back to raw via parse_tool_name() returning
(tool.server_name, tool.tool.name).
mcpServerStatus/list now groups by raw server and keys tools by
Tool.name: mod.rs:599
App-server just forwards that grouped raw snapshot:
codex_message_processor.rs:5245
**Callable Names**
On list-tools, we create provisional callable_namespace / callable_name:
mcp_connection_manager.rs:1556
For non-app MCP, provisional callable name starts as raw tool name.
For codex-apps, provisional callable name is sanitized and strips
connector name/id prefix; namespace includes connector name.
Then qualify_tools() sanitizes callable namespace + name to ASCII alnum
/ _ only: mcp_tool_names.rs:128
Note: this is stricter than Responses API. Hyphen is currently replaced
with _ for code-mode compatibility.
**Collision Handling**
We do initially collapse example-server and example_server to the same
base.
Then qualify_tools() detects distinct raw namespace identities behind
the same sanitized namespace and appends a hash to the callable
namespace: mcp_tool_names.rs:137
Same idea for tool-name collisions: hash suffix goes on callable tool
name.
Final list_all_tools() map key is callable_namespace + callable_name:
mcp_connection_manager.rs:769
**Direct Model Tools**
Direct MCP tool declarations use the full qualified sanitized key as the
Responses function name.
The raw rmcp Tool is converted but renamed for model exposure.
**Tool Search / Deferred**
Tool search result namespace = final ToolInfo.callable_namespace:
tool_search.rs:85
Tool search result nested name = final ToolInfo.callable_name:
tool_search.rs:86
Deferred tool handler is registered as "{namespace}:{name}":
tool_registry_plan.rs:248
When a function call comes back, core recombines namespace + name, looks
up the full qualified key, and gets the raw server/tool for MCP
execution: codex.rs:4353
**Separate Legacy Snapshot**
collect_mcp_snapshot_from_manager_with_detail() still returns a map
keyed by qualified callable name.
mcpServerStatus/list no longer uses that; it uses
McpServerStatusSnapshot, which is raw-inventory shaped.
This commit is contained in:
@@ -101,12 +101,12 @@ pub use tool_discovery::DiscoverableToolType;
|
||||
pub use tool_discovery::TOOL_SEARCH_DEFAULT_LIMIT;
|
||||
pub use tool_discovery::TOOL_SEARCH_TOOL_NAME;
|
||||
pub use tool_discovery::TOOL_SUGGEST_TOOL_NAME;
|
||||
pub use tool_discovery::ToolSearchAppInfo;
|
||||
pub use tool_discovery::ToolSearchAppSource;
|
||||
pub use tool_discovery::ToolSearchResultSource;
|
||||
pub use tool_discovery::ToolSearchSource;
|
||||
pub use tool_discovery::ToolSearchSourceInfo;
|
||||
pub use tool_discovery::ToolSuggestEntry;
|
||||
pub use tool_discovery::collect_tool_search_app_infos;
|
||||
pub use tool_discovery::collect_tool_search_output_tools;
|
||||
pub use tool_discovery::collect_tool_search_source_infos;
|
||||
pub use tool_discovery::collect_tool_suggest_entries;
|
||||
pub use tool_discovery::create_tool_search_tool;
|
||||
pub use tool_discovery::create_tool_suggest_tool;
|
||||
@@ -116,7 +116,7 @@ pub use tool_registry_plan_types::ToolHandlerKind;
|
||||
pub use tool_registry_plan_types::ToolHandlerSpec;
|
||||
pub use tool_registry_plan_types::ToolNamespace;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlan;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanAppTool;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanDeferredTool;
|
||||
pub use tool_registry_plan_types::ToolRegistryPlanParams;
|
||||
pub use tool_spec::ConfiguredToolSpec;
|
||||
pub use tool_spec::ResponsesApiWebSearchFilters;
|
||||
|
||||
@@ -16,13 +16,13 @@ pub const TOOL_SEARCH_DEFAULT_LIMIT: usize = 8;
|
||||
pub const TOOL_SUGGEST_TOOL_NAME: &str = "tool_suggest";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ToolSearchAppInfo {
|
||||
pub struct ToolSearchSourceInfo {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ToolSearchAppSource<'a> {
|
||||
pub struct ToolSearchSource<'a> {
|
||||
pub server_name: &'a str,
|
||||
pub connector_name: Option<&'a str>,
|
||||
pub connector_description: Option<&'a str>,
|
||||
@@ -30,6 +30,7 @@ pub struct ToolSearchAppSource<'a> {
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ToolSearchResultSource<'a> {
|
||||
pub server_name: &'a str,
|
||||
pub tool_namespace: &'a str,
|
||||
pub tool_name: &'a str,
|
||||
pub tool: &'a rmcp::model::Tool,
|
||||
@@ -143,11 +144,14 @@ pub struct ToolSuggestEntry {
|
||||
pub app_connector_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn create_tool_search_tool(app_tools: &[ToolSearchAppInfo], default_limit: usize) -> ToolSpec {
|
||||
pub fn create_tool_search_tool(
|
||||
searchable_sources: &[ToolSearchSourceInfo],
|
||||
default_limit: usize,
|
||||
) -> ToolSpec {
|
||||
let properties = BTreeMap::from([
|
||||
(
|
||||
"query".to_string(),
|
||||
JsonSchema::string(Some("Search query for apps tools.".to_string())),
|
||||
JsonSchema::string(Some("Search query for MCP tools.".to_string())),
|
||||
),
|
||||
(
|
||||
"limit".to_string(),
|
||||
@@ -157,22 +161,22 @@ pub fn create_tool_search_tool(app_tools: &[ToolSearchAppInfo], default_limit: u
|
||||
),
|
||||
]);
|
||||
|
||||
let mut app_descriptions = BTreeMap::new();
|
||||
for app_tool in app_tools {
|
||||
app_descriptions
|
||||
.entry(app_tool.name.clone())
|
||||
let mut source_descriptions = BTreeMap::new();
|
||||
for source in searchable_sources {
|
||||
source_descriptions
|
||||
.entry(source.name.clone())
|
||||
.and_modify(|existing: &mut Option<String>| {
|
||||
if existing.is_none() {
|
||||
*existing = app_tool.description.clone();
|
||||
*existing = source.description.clone();
|
||||
}
|
||||
})
|
||||
.or_insert(app_tool.description.clone());
|
||||
.or_insert(source.description.clone());
|
||||
}
|
||||
|
||||
let app_descriptions = if app_descriptions.is_empty() {
|
||||
let source_descriptions = if source_descriptions.is_empty() {
|
||||
"None currently enabled.".to_string()
|
||||
} else {
|
||||
app_descriptions
|
||||
source_descriptions
|
||||
.into_iter()
|
||||
.map(|(name, description)| match description {
|
||||
Some(description) => format!("- {name}: {description}"),
|
||||
@@ -183,7 +187,7 @@ pub fn create_tool_search_tool(app_tools: &[ToolSearchAppInfo], default_limit: u
|
||||
};
|
||||
|
||||
let description = format!(
|
||||
"# Apps (Connectors) tool discovery\n\nSearches over apps/connectors tool metadata with BM25 and exposes matching tools for the next model call.\n\nYou have access to all the tools of the following apps/connectors:\n{app_descriptions}\nSome of the tools may not have been provided to you upfront, and you should use this tool (`{TOOL_SEARCH_TOOL_NAME}`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `{TOOL_SEARCH_TOOL_NAME}` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery."
|
||||
"# MCP tool discovery\n\nSearches over MCP tool metadata with BM25 and exposes matching tools for the next model call.\n\nYou have access to tools from the following MCP servers/connectors:\n{source_descriptions}\nSome of the tools may not have been provided to you upfront, and you should use this tool (`{TOOL_SEARCH_TOOL_NAME}`) to search for the required MCP tools. For MCP tool discovery, always use `{TOOL_SEARCH_TOOL_NAME}` instead of `list_mcp_resources` or `list_mcp_resource_templates`."
|
||||
);
|
||||
|
||||
ToolSpec::ToolSearch {
|
||||
@@ -201,9 +205,12 @@ pub fn collect_tool_search_output_tools<'a>(
|
||||
tool_sources: impl IntoIterator<Item = ToolSearchResultSource<'a>>,
|
||||
) -> Result<Vec<ToolSearchOutputTool>, serde_json::Error> {
|
||||
let grouped = tool_sources.into_iter().fold(
|
||||
BTreeMap::<&'a str, Vec<ToolSearchResultSource<'a>>>::new(),
|
||||
BTreeMap::<String, Vec<(String, ToolSearchResultSource<'a>)>>::new(),
|
||||
|mut grouped, tool| {
|
||||
grouped.entry(tool.tool_namespace).or_default().push(tool);
|
||||
grouped
|
||||
.entry(tool.tool_namespace.to_string())
|
||||
.or_default()
|
||||
.push((tool.tool_name.to_string(), tool));
|
||||
grouped
|
||||
},
|
||||
);
|
||||
@@ -215,20 +222,28 @@ pub fn collect_tool_search_output_tools<'a>(
|
||||
};
|
||||
|
||||
let description = first_tool
|
||||
.1
|
||||
.connector_description
|
||||
.map(str::to_string)
|
||||
.or_else(|| {
|
||||
first_tool
|
||||
.1
|
||||
.connector_name
|
||||
.map(str::trim)
|
||||
.filter(|connector_name| !connector_name.is_empty())
|
||||
.map(|connector_name| format!("Tools for working with {connector_name}."))
|
||||
})
|
||||
.or_else(|| {
|
||||
Some(format!(
|
||||
"Tools from the {} MCP server.",
|
||||
first_tool.1.server_name
|
||||
))
|
||||
});
|
||||
|
||||
let tools = tools
|
||||
.iter()
|
||||
.map(|tool| {
|
||||
mcp_tool_to_deferred_responses_api_tool(tool.tool_name.to_string(), tool.tool)
|
||||
mcp_tool_to_deferred_responses_api_tool(tool.0.clone(), tool.1.tool)
|
||||
.map(ResponsesApiNamespaceTool::Function)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
@@ -243,25 +258,36 @@ pub fn collect_tool_search_output_tools<'a>(
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn collect_tool_search_app_infos<'a>(
|
||||
app_tools: impl IntoIterator<Item = ToolSearchAppSource<'a>>,
|
||||
codex_apps_server_name: &str,
|
||||
) -> Vec<ToolSearchAppInfo> {
|
||||
app_tools
|
||||
pub fn collect_tool_search_source_infos<'a>(
|
||||
searchable_tools: impl IntoIterator<Item = ToolSearchSource<'a>>,
|
||||
) -> Vec<ToolSearchSourceInfo> {
|
||||
searchable_tools
|
||||
.into_iter()
|
||||
.filter(|tool| tool.server_name == codex_apps_server_name)
|
||||
.filter_map(|tool| {
|
||||
let name = tool
|
||||
if let Some(name) = tool
|
||||
.connector_name
|
||||
.map(str::trim)
|
||||
.filter(|connector_name| !connector_name.is_empty())?
|
||||
.to_string();
|
||||
let description = tool
|
||||
.connector_description
|
||||
.map(str::trim)
|
||||
.filter(|connector_description| !connector_description.is_empty())
|
||||
.map(str::to_string);
|
||||
Some(ToolSearchAppInfo { name, description })
|
||||
.filter(|connector_name| !connector_name.is_empty())
|
||||
{
|
||||
return Some(ToolSearchSourceInfo {
|
||||
name: name.to_string(),
|
||||
description: tool
|
||||
.connector_description
|
||||
.map(str::trim)
|
||||
.filter(|description| !description.is_empty())
|
||||
.map(str::to_string),
|
||||
});
|
||||
}
|
||||
|
||||
let name = tool.server_name.trim();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ToolSearchSourceInfo {
|
||||
name: name.to_string(),
|
||||
description: None,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -26,23 +26,23 @@ fn mcp_tool(name: &str, description: &str) -> Tool {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_tool_search_tool_deduplicates_and_renders_enabled_apps() {
|
||||
fn create_tool_search_tool_deduplicates_and_renders_enabled_sources() {
|
||||
assert_eq!(
|
||||
create_tool_search_tool(
|
||||
&[
|
||||
ToolSearchAppInfo {
|
||||
ToolSearchSourceInfo {
|
||||
name: "Google Drive".to_string(),
|
||||
description: Some(
|
||||
"Use Google Drive as the single entrypoint for Drive, Docs, Sheets, and Slides work."
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
ToolSearchAppInfo {
|
||||
ToolSearchSourceInfo {
|
||||
name: "Google Drive".to_string(),
|
||||
description: None,
|
||||
},
|
||||
ToolSearchAppInfo {
|
||||
name: "Slack".to_string(),
|
||||
ToolSearchSourceInfo {
|
||||
name: "docs".to_string(),
|
||||
description: None,
|
||||
},
|
||||
],
|
||||
@@ -50,7 +50,7 @@ fn create_tool_search_tool_deduplicates_and_renders_enabled_apps() {
|
||||
),
|
||||
ToolSpec::ToolSearch {
|
||||
execution: "client".to_string(),
|
||||
description: "# Apps (Connectors) tool discovery\n\nSearches over apps/connectors tool metadata with BM25 and exposes matching tools for the next model call.\n\nYou have access to all the tools of the following apps/connectors:\n- Google Drive: Use Google Drive as the single entrypoint for Drive, Docs, Sheets, and Slides work.\n- Slack\nSome of the tools may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required tools and load them for the apps mentioned above. For the apps mentioned above, always use `tool_search` instead of `list_mcp_resources` or `list_mcp_resource_templates` for tool discovery.".to_string(),
|
||||
description: "# MCP tool discovery\n\nSearches over MCP tool metadata with BM25 and exposes matching tools for the next model call.\n\nYou have access to tools from the following MCP servers/connectors:\n- Google Drive: Use Google Drive as the single entrypoint for Drive, Docs, Sheets, and Slides work.\n- docs\nSome of the tools may not have been provided to you upfront, and you should use this tool (`tool_search`) to search for the required MCP tools. For MCP tool discovery, always use `tool_search` instead of `list_mcp_resources` or `list_mcp_resource_templates`.".to_string(),
|
||||
parameters: JsonSchema::object(BTreeMap::from([
|
||||
(
|
||||
"limit".to_string(),
|
||||
@@ -61,7 +61,7 @@ fn create_tool_search_tool_deduplicates_and_renders_enabled_apps() {
|
||||
),
|
||||
(
|
||||
"query".to_string(),
|
||||
JsonSchema::string(Some("Search query for apps tools.".to_string()),),
|
||||
JsonSchema::string(Some("Search query for MCP tools.".to_string()),),
|
||||
),
|
||||
]), Some(vec!["query".to_string()]), Some(false.into())),
|
||||
}
|
||||
@@ -141,9 +141,11 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
let calendar_create_event = mcp_tool("calendar-create-event", "Create a calendar event.");
|
||||
let gmail_read_email = mcp_tool("gmail-read-email", "Read an email.");
|
||||
let calendar_list_events = mcp_tool("calendar-list-events", "List calendar events.");
|
||||
let docs_search = mcp_tool("search", "Search docs.");
|
||||
|
||||
let tools = collect_tool_search_output_tools([
|
||||
ToolSearchResultSource {
|
||||
server_name: "codex_apps",
|
||||
tool_namespace: "mcp__codex_apps__calendar",
|
||||
tool_name: "_create_event",
|
||||
tool: &calendar_create_event,
|
||||
@@ -151,6 +153,7 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
connector_description: Some("Plan events"),
|
||||
},
|
||||
ToolSearchResultSource {
|
||||
server_name: "codex_apps",
|
||||
tool_namespace: "mcp__codex_apps__gmail",
|
||||
tool_name: "_read_email",
|
||||
tool: &gmail_read_email,
|
||||
@@ -158,12 +161,21 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
connector_description: Some("Read mail"),
|
||||
},
|
||||
ToolSearchResultSource {
|
||||
server_name: "codex_apps",
|
||||
tool_namespace: "mcp__codex_apps__calendar",
|
||||
tool_name: "_list_events",
|
||||
tool: &calendar_list_events,
|
||||
connector_name: Some("Calendar"),
|
||||
connector_description: Some("Plan events"),
|
||||
},
|
||||
ToolSearchResultSource {
|
||||
server_name: "docs",
|
||||
tool_namespace: "mcp__docs__",
|
||||
tool_name: "search",
|
||||
tool: &docs_search,
|
||||
connector_name: None,
|
||||
connector_description: None,
|
||||
},
|
||||
])
|
||||
.expect("collect tool search output tools");
|
||||
|
||||
@@ -216,6 +228,22 @@ fn collect_tool_search_output_tools_groups_results_by_namespace() {
|
||||
output_schema: None,
|
||||
})],
|
||||
}),
|
||||
ToolSearchOutputTool::Namespace(ResponsesApiNamespace {
|
||||
name: "mcp__docs__".to_string(),
|
||||
description: "Tools from the docs MCP server.".to_string(),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: "search".to_string(),
|
||||
description: "Search docs.".to_string(),
|
||||
strict: false,
|
||||
defer_loading: Some(true),
|
||||
parameters: JsonSchema::object(
|
||||
Default::default(),
|
||||
/*required*/ None,
|
||||
/*additional_properties*/ None
|
||||
),
|
||||
output_schema: None,
|
||||
})],
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -225,6 +253,7 @@ fn collect_tool_search_output_tools_falls_back_to_connector_name_description() {
|
||||
let gmail_batch_read_email = mcp_tool("gmail-batch-read-email", "Read multiple emails.");
|
||||
|
||||
let tools = collect_tool_search_output_tools([ToolSearchResultSource {
|
||||
server_name: "codex_apps",
|
||||
tool_namespace: "mcp__codex_apps__gmail",
|
||||
tool_name: "_batch_read_email",
|
||||
tool: &gmail_batch_read_email,
|
||||
|
||||
@@ -8,13 +8,13 @@ use crate::TOOL_SUGGEST_TOOL_NAME;
|
||||
use crate::ToolHandlerKind;
|
||||
use crate::ToolRegistryPlan;
|
||||
use crate::ToolRegistryPlanParams;
|
||||
use crate::ToolSearchAppSource;
|
||||
use crate::ToolSearchSource;
|
||||
use crate::ToolSpec;
|
||||
use crate::ToolsConfig;
|
||||
use crate::ViewImageToolOptions;
|
||||
use crate::WebSearchToolOptions;
|
||||
use crate::collect_code_mode_tool_definitions;
|
||||
use crate::collect_tool_search_app_infos;
|
||||
use crate::collect_tool_search_source_infos;
|
||||
use crate::collect_tool_suggest_entries;
|
||||
use crate::create_apply_patch_freeform_tool;
|
||||
use crate::create_apply_patch_json_tool;
|
||||
@@ -250,24 +250,24 @@ pub fn build_tool_registry_plan(
|
||||
}
|
||||
|
||||
if config.search_tool
|
||||
&& let Some(app_tools) = params.app_tools
|
||||
&& let Some(deferred_mcp_tools) = params.deferred_mcp_tools
|
||||
{
|
||||
let search_app_infos = collect_tool_search_app_infos(
|
||||
app_tools.iter().map(|tool| ToolSearchAppSource {
|
||||
server_name: tool.server_name,
|
||||
connector_name: tool.connector_name,
|
||||
connector_description: tool.connector_description,
|
||||
}),
|
||||
params.codex_apps_mcp_server_name,
|
||||
);
|
||||
let search_source_infos =
|
||||
collect_tool_search_source_infos(deferred_mcp_tools.iter().map(|tool| {
|
||||
ToolSearchSource {
|
||||
server_name: tool.server_name,
|
||||
connector_name: tool.connector_name,
|
||||
connector_description: tool.connector_description,
|
||||
}
|
||||
}));
|
||||
plan.push_spec(
|
||||
create_tool_search_tool(&search_app_infos, TOOL_SEARCH_DEFAULT_LIMIT),
|
||||
create_tool_search_tool(&search_source_infos, TOOL_SEARCH_DEFAULT_LIMIT),
|
||||
/*supports_parallel_tool_calls*/ true,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
plan.register_handler(TOOL_SEARCH_TOOL_NAME, ToolHandlerKind::ToolSearch);
|
||||
|
||||
for tool in app_tools {
|
||||
for tool in deferred_mcp_tools {
|
||||
plan.register_handler(
|
||||
format!("{}:{}", tool.tool_namespace, tool.tool_name),
|
||||
ToolHandlerKind::Mcp,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::ResponsesApiWebSearchFilters;
|
||||
use crate::ResponsesApiWebSearchUserLocation;
|
||||
use crate::ToolHandlerSpec;
|
||||
use crate::ToolNamespace;
|
||||
use crate::ToolRegistryPlanAppTool;
|
||||
use crate::ToolRegistryPlanDeferredTool;
|
||||
use crate::ToolsConfigParams;
|
||||
use crate::WaitAgentTimeoutOptions;
|
||||
use crate::mcp_call_tool_result_output_schema;
|
||||
@@ -60,7 +60,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
||||
let (tools, _) = build_specs(
|
||||
&config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -162,7 +162,7 @@ fn test_build_specs_collab_tools_enabled() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -202,7 +202,7 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -345,7 +345,7 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -380,7 +380,7 @@ fn view_image_tool_omits_detail_without_original_detail_feature() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
@@ -411,7 +411,7 @@ fn view_image_tool_includes_detail_with_original_detail_feature() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let view_image = find_tool(&tools, VIEW_IMAGE_TOOL_NAME);
|
||||
@@ -453,7 +453,7 @@ fn disabled_environment_omits_environment_backed_tools() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -489,7 +489,7 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -526,7 +526,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let request_user_input_tool = find_tool(&tools, REQUEST_USER_INPUT_TOOL_NAME);
|
||||
@@ -549,7 +549,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let request_user_input_tool = find_tool(&tools, REQUEST_USER_INPUT_TOOL_NAME);
|
||||
@@ -577,7 +577,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert_lacks_tool_name(&tools, "request_permissions");
|
||||
@@ -597,7 +597,7 @@ fn request_permissions_requires_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let request_permissions_tool = find_tool(&tools, "request_permissions");
|
||||
@@ -626,7 +626,7 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -652,7 +652,7 @@ fn js_repl_requires_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -686,7 +686,7 @@ fn js_repl_enabled_adds_tools() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -717,7 +717,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
let (default_tools, _) = build_specs(
|
||||
&default_tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
@@ -740,7 +740,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
let (supported_tools, _) = build_specs(
|
||||
&supported_tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert_contains_tool_names(&supported_tools, &["image_generation"]);
|
||||
@@ -766,7 +766,7 @@ fn image_generation_tools_require_feature_and_supported_model() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
@@ -796,7 +796,7 @@ fn web_search_mode_cached_sets_external_web_access_false() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -832,7 +832,7 @@ fn web_search_mode_live_sets_external_web_access_true() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -882,7 +882,7 @@ fn web_search_config_is_forwarded_to_tool_spec() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -923,7 +923,7 @@ fn web_search_tool_type_text_and_image_sets_search_content_types() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -958,7 +958,7 @@ fn mcp_resource_tools_are_hidden_without_mcp_servers() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -989,7 +989,7 @@ fn mcp_resource_tools_are_included_when_mcp_servers_are_present() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::new()),
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1023,7 +1023,7 @@ fn test_parallel_support_flags() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1050,7 +1050,7 @@ fn test_test_model_info_includes_sync_tool() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1098,7 +1098,7 @@ fn test_build_specs_mcp_tools_converted() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1181,7 +1181,12 @@ fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
),
|
||||
]);
|
||||
|
||||
let (tools, _) = build_specs(&tools_config, Some(tools_map), /*app_tools*/ None, &[]);
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
Some(tools_map),
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
let mcp_names: Vec<_> = tools
|
||||
.iter()
|
||||
@@ -1197,7 +1202,7 @@ fn test_build_specs_mcp_tools_sorted_by_name() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
fn search_tool_description_lists_each_mcp_source_once() {
|
||||
let model_info = search_capable_model_info();
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::Apps);
|
||||
@@ -1214,7 +1219,7 @@ fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
|
||||
let (tools, _) = build_specs(
|
||||
let (tools, handlers) = build_specs(
|
||||
&tools_config,
|
||||
Some(HashMap::from([
|
||||
(
|
||||
@@ -1231,29 +1236,32 @@ fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
),
|
||||
])),
|
||||
Some(vec![
|
||||
app_tool(
|
||||
deferred_mcp_tool(
|
||||
"_create_event",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
Some("Plan events and manage your calendar."),
|
||||
),
|
||||
app_tool(
|
||||
deferred_mcp_tool(
|
||||
"_list_events",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
Some("Plan events and manage your calendar."),
|
||||
),
|
||||
app_tool(
|
||||
deferred_mcp_tool(
|
||||
"_search_threads",
|
||||
"mcp__codex_apps__gmail",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Gmail"),
|
||||
Some("Find and summarize email threads."),
|
||||
),
|
||||
app_tool(
|
||||
"echo", "rmcp", "rmcp", /*connector_name*/ None,
|
||||
deferred_mcp_tool(
|
||||
"echo",
|
||||
"mcp__rmcp__",
|
||||
"rmcp",
|
||||
/*connector_name*/ None,
|
||||
/*connector_description*/ None,
|
||||
),
|
||||
]),
|
||||
@@ -1273,14 +1281,24 @@ fn search_tool_description_lists_each_codex_apps_connector_once() {
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert!(description.contains("- rmcp"));
|
||||
assert!(!description.contains("mcp__rmcp__echo"));
|
||||
|
||||
assert!(handlers.contains(&ToolHandlerSpec {
|
||||
name: "mcp__codex_apps__calendar:_create_event".to_string(),
|
||||
kind: ToolHandlerKind::Mcp,
|
||||
}));
|
||||
assert!(handlers.contains(&ToolHandlerSpec {
|
||||
name: "mcp__rmcp__:echo".to_string(),
|
||||
kind: ToolHandlerKind::Mcp,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
let model_info = search_capable_model_info();
|
||||
let app_tools = Some(vec![app_tool(
|
||||
"calendar_create_event",
|
||||
let deferred_mcp_tools = Some(vec![deferred_mcp_tool(
|
||||
"_create_event",
|
||||
"mcp__codex_apps__calendar",
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some("Calendar"),
|
||||
@@ -1305,7 +1323,7 @@ fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
app_tools.clone(),
|
||||
deferred_mcp_tools.clone(),
|
||||
&[],
|
||||
);
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
@@ -1323,7 +1341,7 @@ fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
app_tools.clone(),
|
||||
deferred_mcp_tools.clone(),
|
||||
&[],
|
||||
);
|
||||
assert_lacks_tool_name(&tools, TOOL_SEARCH_TOOL_NAME);
|
||||
@@ -1340,7 +1358,12 @@ fn search_tool_requires_model_capability_and_feature_flag() {
|
||||
sandbox_policy: &SandboxPolicy::DangerFullAccess,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
});
|
||||
let (tools, _) = build_specs(&tools_config, /*mcp_tools*/ None, app_tools, &[]);
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
deferred_mcp_tools,
|
||||
&[],
|
||||
);
|
||||
assert_contains_tool_names(&tools, &[TOOL_SEARCH_TOOL_NAME]);
|
||||
}
|
||||
|
||||
@@ -1366,8 +1389,7 @@ fn tool_suggest_is_not_registered_without_feature_flag() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1407,8 +1429,7 @@ fn tool_suggest_can_be_registered_without_search_tool() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(vec![discoverable_connector(
|
||||
"connector_2128aebfecb84f64a069897515042a44",
|
||||
"Google Calendar",
|
||||
@@ -1476,8 +1497,7 @@ fn tool_suggest_description_lists_discoverable_tools() {
|
||||
let (tools, _) = build_specs_with_discoverable_tools(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*tool_namespaces*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
Some(discoverable_tools),
|
||||
&[],
|
||||
);
|
||||
@@ -1572,7 +1592,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1661,7 +1681,7 @@ fn code_mode_preserves_nullable_and_literal_mcp_input_shapes() {
|
||||
}),
|
||||
),
|
||||
)])),
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -1700,7 +1720,7 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let ToolSpec::Function(ResponsesApiTool { description, .. }) =
|
||||
@@ -1736,7 +1756,7 @@ fn code_mode_only_exec_description_includes_full_nested_tool_details() {
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = &find_tool(&tools, "exec").spec
|
||||
@@ -1773,7 +1793,7 @@ fn code_mode_exec_description_omits_nested_tool_details_when_not_code_mode_only(
|
||||
let (tools, _) = build_specs(
|
||||
&tools_config,
|
||||
/*mcp_tools*/ None,
|
||||
/*app_tools*/ None,
|
||||
/*deferred_mcp_tools*/ None,
|
||||
&[],
|
||||
);
|
||||
let ToolSpec::Freeform(FreeformTool { description, .. }) = &find_tool(&tools, "exec").spec
|
||||
@@ -1833,14 +1853,13 @@ fn search_capable_model_info() -> ModelInfo {
|
||||
fn build_specs<'a>(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, rmcp::model::Tool>>,
|
||||
app_tools: Option<Vec<ToolRegistryPlanAppTool<'a>>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolRegistryPlanDeferredTool<'a>>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
build_specs_with_discoverable_tools(
|
||||
config,
|
||||
mcp_tools,
|
||||
app_tools,
|
||||
/*tool_namespaces*/ None,
|
||||
deferred_mcp_tools,
|
||||
/*discoverable_tools*/ None,
|
||||
dynamic_tools,
|
||||
)
|
||||
@@ -1849,16 +1868,15 @@ fn build_specs<'a>(
|
||||
fn build_specs_with_discoverable_tools<'a>(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, rmcp::model::Tool>>,
|
||||
app_tools: Option<Vec<ToolRegistryPlanAppTool<'a>>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolRegistryPlanDeferredTool<'a>>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
build_specs_with_optional_tool_namespaces(
|
||||
config,
|
||||
mcp_tools,
|
||||
tool_namespaces,
|
||||
app_tools,
|
||||
deferred_mcp_tools,
|
||||
/*tool_namespaces*/ None,
|
||||
discoverable_tools,
|
||||
dynamic_tools,
|
||||
)
|
||||
@@ -1867,8 +1885,8 @@ fn build_specs_with_discoverable_tools<'a>(
|
||||
fn build_specs_with_optional_tool_namespaces<'a>(
|
||||
config: &ToolsConfig,
|
||||
mcp_tools: Option<HashMap<String, rmcp::model::Tool>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolRegistryPlanDeferredTool<'a>>>,
|
||||
tool_namespaces: Option<HashMap<String, ToolNamespace>>,
|
||||
app_tools: Option<Vec<ToolRegistryPlanAppTool<'a>>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
dynamic_tools: &[DynamicToolSpec],
|
||||
) -> (Vec<ConfiguredToolSpec>, Vec<ToolHandlerSpec>) {
|
||||
@@ -1876,13 +1894,12 @@ fn build_specs_with_optional_tool_namespaces<'a>(
|
||||
config,
|
||||
ToolRegistryPlanParams {
|
||||
mcp_tools: mcp_tools.as_ref(),
|
||||
deferred_mcp_tools: deferred_mcp_tools.as_deref(),
|
||||
tool_namespaces: tool_namespaces.as_ref(),
|
||||
app_tools: app_tools.as_deref(),
|
||||
discoverable_tools: discoverable_tools.as_deref(),
|
||||
dynamic_tools,
|
||||
default_agent_type_description: DEFAULT_AGENT_TYPE_DESCRIPTION,
|
||||
wait_agent_timeouts: wait_agent_timeout_options(),
|
||||
codex_apps_mcp_server_name: CODEX_APPS_MCP_SERVER_NAME,
|
||||
},
|
||||
);
|
||||
(plan.specs, plan.handlers)
|
||||
@@ -1921,14 +1938,14 @@ fn discoverable_connector(id: &str, name: &str, description: &str) -> Discoverab
|
||||
}))
|
||||
}
|
||||
|
||||
fn app_tool<'a>(
|
||||
fn deferred_mcp_tool<'a>(
|
||||
tool_name: &'a str,
|
||||
tool_namespace: &'a str,
|
||||
server_name: &'a str,
|
||||
connector_name: Option<&'a str>,
|
||||
connector_description: Option<&'a str>,
|
||||
) -> ToolRegistryPlanAppTool<'a> {
|
||||
ToolRegistryPlanAppTool {
|
||||
) -> ToolRegistryPlanDeferredTool<'a> {
|
||||
ToolRegistryPlanDeferredTool {
|
||||
tool_name,
|
||||
tool_namespace,
|
||||
server_name,
|
||||
|
||||
@@ -58,13 +58,12 @@ pub struct ToolRegistryPlan {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ToolRegistryPlanParams<'a> {
|
||||
pub mcp_tools: Option<&'a HashMap<String, McpTool>>,
|
||||
pub deferred_mcp_tools: Option<&'a [ToolRegistryPlanDeferredTool<'a>]>,
|
||||
pub tool_namespaces: Option<&'a HashMap<String, ToolNamespace>>,
|
||||
pub app_tools: Option<&'a [ToolRegistryPlanAppTool<'a>]>,
|
||||
pub discoverable_tools: Option<&'a [DiscoverableTool]>,
|
||||
pub dynamic_tools: &'a [DynamicToolSpec],
|
||||
pub default_agent_type_description: &'a str,
|
||||
pub wait_agent_timeouts: WaitAgentTimeoutOptions,
|
||||
pub codex_apps_mcp_server_name: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -74,7 +73,7 @@ pub struct ToolNamespace {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ToolRegistryPlanAppTool<'a> {
|
||||
pub struct ToolRegistryPlanDeferredTool<'a> {
|
||||
pub tool_name: &'a str,
|
||||
pub tool_namespace: &'a str,
|
||||
pub server_name: &'a str,
|
||||
|
||||
Reference in New Issue
Block a user