diff --git a/codex-rs/codex-mcp/src/connection_manager_tests.rs b/codex-rs/codex-mcp/src/connection_manager_tests.rs index 01b1161b730a..69ff10e02389 100644 --- a/codex-rs/codex-mcp/src/connection_manager_tests.rs +++ b/codex-rs/codex-mcp/src/connection_manager_tests.rs @@ -40,7 +40,7 @@ fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo { server_name: server_name.to_string(), callable_name: tool_name.to_string(), callable_namespace: tool_namespace, - server_instructions: None, + namespace_description: None, tool: Tool { name: tool_name.to_string().into(), title: None, @@ -55,7 +55,6 @@ fn create_test_tool(server_name: &str, tool_name: &str) -> ToolInfo { connector_id: None, connector_name: None, plugin_display_names: Vec::new(), - connector_description: None, } } diff --git a/codex-rs/codex-mcp/src/rmcp_client.rs b/codex-rs/codex-mcp/src/rmcp_client.rs index 38ef96919fc1..c6a781bb849a 100644 --- a/codex-rs/codex-mcp/src/rmcp_client.rs +++ b/codex-rs/codex-mcp/src/rmcp_client.rs @@ -362,16 +362,23 @@ pub(crate) async fn list_tools_for_client_uncached( tool_def.title = Some(normalized_title); } } + let has_connector_metadata = connector_id.is_some() + || connector_name.is_some() + || connector_description.is_some(); + let namespace_description = if has_connector_metadata { + connector_description + } else { + server_instructions.map(str::to_string) + }; ToolInfo { server_name: server_name.to_owned(), callable_name, callable_namespace, - server_instructions: server_instructions.map(str::to_string), + namespace_description, tool: tool_def, connector_id, connector_name, plugin_display_names: Vec::new(), - connector_description, } }) .collect(); diff --git a/codex-rs/codex-mcp/src/tools.rs b/codex-rs/codex-mcp/src/tools.rs index 9b677e8a07c7..1a3e1a6bc230 100644 --- a/codex-rs/codex-mcp/src/tools.rs +++ b/codex-rs/codex-mcp/src/tools.rs @@ -35,16 +35,16 @@ pub struct ToolInfo { /// Model-visible namespace used for deferred tool loading. #[serde(rename = "tool_namespace", alias = "callable_namespace")] pub callable_namespace: String, - /// Instructions from the MCP server initialize result. - #[serde(default)] - pub server_instructions: Option, + /// Model-visible namespace description. + // Keep the old serialized field name readable for cached ToolInfo values. + #[serde(default, alias = "connector_description")] + pub namespace_description: Option, /// Raw MCP tool definition; `tool.name` is sent back to the MCP server. pub tool: Tool, pub connector_id: Option, pub connector_name: Option, #[serde(default)] pub plugin_display_names: Vec, - pub connector_description: Option, } impl ToolInfo { diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index b83be7dc8ae4..9f0381f53a92 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -525,7 +525,7 @@ pub(crate) fn accessible_connectors_from_mcp_tools( Some(codex_connectors::accessible::AccessibleConnectorTool { connector_id: connector_id.to_string(), connector_name: tool.connector_name.clone(), - connector_description: tool.connector_description.clone(), + connector_description: tool.namespace_description.clone(), plugin_display_names: tool.plugin_display_names.clone(), }) }); diff --git a/codex-rs/core/src/connectors_tests.rs b/codex-rs/core/src/connectors_tests.rs index b3538d1ff062..513f677e9b92 100644 --- a/codex-rs/core/src/connectors_tests.rs +++ b/codex-rs/core/src/connectors_tests.rs @@ -119,11 +119,10 @@ fn codex_app_tool( server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), callable_name: tool_name.to_string(), callable_namespace: tool_namespace, - server_instructions: None, + namespace_description: None, tool: test_tool_definition(tool_name), connector_id: Some(connector_id.to_string()), connector_name: connector_name.map(ToOwned::to_owned), - connector_description: None, plugin_display_names: plugin_names(plugin_display_names), } } @@ -198,11 +197,10 @@ fn accessible_connectors_from_mcp_tools_carries_plugin_display_names() { server_name: "sample".to_string(), callable_name: "echo".to_string(), callable_namespace: "sample".to_string(), - server_instructions: None, + namespace_description: None, tool: test_tool_definition("echo"), connector_id: None, connector_name: None, - connector_description: None, plugin_display_names: plugin_names(&["ignored"]), }, ), @@ -323,7 +321,7 @@ fn accessible_connectors_from_mcp_tools_preserves_description() { server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), callable_name: "calendar_create_event".to_string(), callable_namespace: "mcp__codex_apps__calendar".to_string(), - server_instructions: None, + namespace_description: Some("Plan events".to_string()), tool: Tool { name: "calendar_create_event".to_string().into(), title: None, @@ -337,7 +335,6 @@ fn accessible_connectors_from_mcp_tools_preserves_description() { }, connector_id: Some("calendar".to_string()), connector_name: Some("Calendar".to_string()), - connector_description: Some("Plan events".to_string()), plugin_display_names: Vec::new(), }, )]); diff --git a/codex-rs/core/src/mcp_tool_exposure_test.rs b/codex-rs/core/src/mcp_tool_exposure_test.rs index cbd4d3b29c76..32707e4f8ba3 100644 --- a/codex-rs/core/src/mcp_tool_exposure_test.rs +++ b/codex-rs/core/src/mcp_tool_exposure_test.rs @@ -58,7 +58,7 @@ fn make_mcp_tool( server_name: server_name.to_string(), callable_name: tool_name.to_string(), callable_namespace: tool_namespace, - server_instructions: None, + namespace_description: None, tool: Tool { name: tool_name.to_string().into(), title: None, @@ -73,7 +73,6 @@ fn make_mcp_tool( connector_id: connector_id.map(str::to_string), connector_name: connector_name.map(str::to_string), plugin_display_names: Vec::new(), - connector_description: None, } } diff --git a/codex-rs/core/src/tools/handlers/tool_search.rs b/codex-rs/core/src/tools/handlers/tool_search.rs index f38b4ee88321..67bc7b7f27c7 100644 --- a/codex-rs/core/src/tools/handlers/tool_search.rs +++ b/codex-rs/core/src/tools/handlers/tool_search.rs @@ -392,7 +392,7 @@ mod tests { server_name: server_name.to_string(), callable_name: tool_name.to_string(), callable_namespace: format!("mcp__{server_name}__"), - server_instructions: None, + namespace_description: None, tool: Tool { name: tool_name.to_string().into(), title: None, @@ -411,7 +411,6 @@ mod tests { connector_id: None, connector_name: None, plugin_display_names: Vec::new(), - connector_description: None, } } diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 479dcc382fbf..53cb34f7a487 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -57,10 +57,7 @@ fn map_mcp_tools_for_plan(mcp_tools: &HashMap) -> McpToolPlanI tool.callable_namespace.clone(), ToolNamespace { name: tool.callable_namespace.clone(), - description: tool - .connector_description - .clone() - .or_else(|| tool.server_instructions.clone()), + description: tool.namespace_description.clone(), }, ) }) @@ -118,7 +115,7 @@ pub(crate) fn build_specs_with_discoverable_tools( name: tool.canonical_tool_name(), server_name: tool.server_name.as_str(), connector_name: tool.connector_name.as_deref(), - connector_description: tool.connector_description.as_deref(), + description: tool.namespace_description.as_deref(), }) .collect::>() }); diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index 33cbb2718d15..6bd796508dec 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -62,12 +62,11 @@ fn mcp_tool_info(tool: rmcp::model::Tool) -> ToolInfo { server_name: "test_server".to_string(), callable_name: tool.name.to_string(), callable_namespace: "mcp__test_server__".to_string(), - server_instructions: None, + namespace_description: None, tool, connector_id: None, connector_name: None, plugin_display_names: Vec::new(), - connector_description: None, } } @@ -81,12 +80,11 @@ fn mcp_tool_info_with_display_name(display_name: &str, tool: rmcp::model::Tool) server_name: "test_server".to_string(), callable_name, callable_namespace, - server_instructions: None, + namespace_description: None, tool, connector_id: None, connector_name: None, plugin_display_names: Vec::new(), - connector_description: None, } } @@ -898,7 +896,7 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), callable_name: "_create_event".to_string(), callable_namespace: "mcp__codex_apps__calendar".to_string(), - server_instructions: None, + namespace_description: None, tool: mcp_tool( "calendar_create_event", "Create calendar event", @@ -907,7 +905,6 @@ async fn search_tool_description_falls_back_to_connector_name_without_descriptio connector_id: Some("calendar".to_string()), connector_name: Some("Calendar".to_string()), plugin_display_names: Vec::new(), - connector_description: None, }, )])), &[], @@ -950,7 +947,7 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() { server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), callable_name: "_create_event".to_string(), callable_namespace: "mcp__codex_apps__calendar".to_string(), - server_instructions: None, + namespace_description: None, tool: mcp_tool( "calendar-create-event", "Create calendar event", @@ -958,7 +955,6 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() { ), connector_id: Some("calendar".to_string()), connector_name: Some("Calendar".to_string()), - connector_description: None, plugin_display_names: Vec::new(), }, ), @@ -968,7 +964,7 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() { server_name: CODEX_APPS_MCP_SERVER_NAME.to_string(), callable_name: "_list_events".to_string(), callable_namespace: "mcp__codex_apps__calendar".to_string(), - server_instructions: None, + namespace_description: None, tool: mcp_tool( "calendar-list-events", "List calendar events", @@ -976,7 +972,6 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() { ), connector_id: Some("calendar".to_string()), connector_name: Some("Calendar".to_string()), - connector_description: None, plugin_display_names: Vec::new(), }, ), @@ -986,11 +981,10 @@ async fn search_tool_registers_namespaced_mcp_tool_aliases() { server_name: "rmcp".to_string(), callable_name: "echo".to_string(), callable_namespace: "mcp__rmcp__".to_string(), - server_instructions: None, + namespace_description: None, tool: mcp_tool("echo", "Echo", serde_json::json!({"type": "object"})), connector_id: None, connector_name: None, - connector_description: None, plugin_display_names: Vec::new(), }, ), diff --git a/codex-rs/core/src/tools/tool_search_entry.rs b/codex-rs/core/src/tools/tool_search_entry.rs index 5d65d814613a..06b7efcd0603 100644 --- a/codex-rs/core/src/tools/tool_search_entry.rs +++ b/codex-rs/core/src/tools/tool_search_entry.rs @@ -80,7 +80,7 @@ fn mcp_tool_search_entry(info: &ToolInfo) -> Result String { parts.push(connector_name.to_string()); } - if let Some(connector_description) = info.connector_description.as_deref() - && !connector_description.trim().is_empty() + if let Some(description) = info.namespace_description.as_deref() + && !description.trim().is_empty() { - parts.push(connector_description.to_string()); + parts.push(description.to_string()); } parts.extend( diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index b4edf24668ef..9573e1b586be 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -1029,3 +1029,92 @@ async fn tool_search_indexes_only_enabled_non_app_mcp_tools() -> Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn tool_search_uses_non_app_mcp_server_instructions_as_namespace_description() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let apps_server = AppsTestServer::mount_searchable(&server).await?; + let search_call_id = "tool-search-echo"; + let mock = mount_sse_sequence( + &server, + vec![ + sse(vec![ + ev_response_created("resp-1"), + ev_tool_search_call( + search_call_id, + &json!({ + "query": "Echo back the provided message and include environment data.", + "limit": 8, + }), + ), + ev_completed("resp-1"), + ]), + sse(vec![ + ev_response_created("resp-2"), + ev_assistant_message("msg-1", "done"), + ev_completed("resp-2"), + ]), + ], + ) + .await; + + let rmcp_test_server_bin = stdio_server_bin()?; + let mut builder = + configured_builder(apps_server.chatgpt_base_url.clone()).with_config(move |config| { + let mut servers = config.mcp_servers.get().clone(); + servers.insert( + "rmcp".to_string(), + McpServerConfig { + transport: McpServerTransportConfig::Stdio { + command: rmcp_test_server_bin, + args: Vec::new(), + env: None, + env_vars: Vec::new(), + cwd: None, + }, + experimental_environment: None, + enabled: true, + required: false, + disabled_reason: None, + startup_timeout_sec: Some(Duration::from_secs(10)), + tool_timeout_sec: None, + default_tools_approval_mode: None, + enabled_tools: Some(vec!["echo".to_string()]), + disabled_tools: None, + scopes: None, + oauth_resource: None, + supports_parallel_tool_calls: false, + tools: HashMap::new(), + }, + ); + config + .mcp_servers + .set(servers) + .expect("test mcp servers should accept any configuration"); + }); + let test = builder.build(&server).await?; + + test.submit_turn_with_approval_and_permission_profile( + "Find the rmcp echo tool.", + AskForApproval::Never, + PermissionProfile::Disabled, + ) + .await?; + + let requests = mock.requests(); + assert_eq!(requests.len(), 2); + + let tools = tool_search_output_tools(&requests[1], search_call_id); + let rmcp_namespace = tools + .iter() + .find(|tool| tool.get("name").and_then(Value::as_str) == Some("mcp__rmcp__")) + .expect("tool_search should return the rmcp namespace"); + assert_eq!( + rmcp_namespace.get("description").and_then(Value::as_str), + Some("Use these tools to exercise the rmcp test server.") + ); + + Ok(()) +} diff --git a/codex-rs/rmcp-client/src/bin/test_stdio_server.rs b/codex-rs/rmcp-client/src/bin/test_stdio_server.rs index cb83da5e6101..7add4d05f5af 100644 --- a/codex-rs/rmcp-client/src/bin/test_stdio_server.rs +++ b/codex-rs/rmcp-client/src/bin/test_stdio_server.rs @@ -401,6 +401,7 @@ impl ServerHandler for TestToolServer { )])); ServerInfo { + instructions: Some("Use these tools to exercise the rmcp test server.".to_string()), capabilities, ..ServerInfo::default() } diff --git a/codex-rs/tools/src/tool_discovery.rs b/codex-rs/tools/src/tool_discovery.rs index 9b3dd5df9ff8..623118bbc1c7 100644 --- a/codex-rs/tools/src/tool_discovery.rs +++ b/codex-rs/tools/src/tool_discovery.rs @@ -27,7 +27,7 @@ pub struct ToolSearchSourceInfo { pub struct ToolSearchSource<'a> { pub server_name: &'a str, pub connector_name: Option<&'a str>, - pub connector_description: Option<&'a str>, + pub description: Option<&'a str>, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -37,7 +37,7 @@ pub struct ToolSearchResultSource<'a> { pub tool_name: &'a str, pub tool: &'a rmcp::model::Tool, pub connector_name: Option<&'a str>, - pub connector_description: Option<&'a str>, + pub description: Option<&'a str>, } #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] @@ -215,7 +215,7 @@ pub fn tool_search_result_source_to_loadable_tool_spec( fn tool_search_result_source_namespace_description(source: ToolSearchResultSource<'_>) -> String { source - .connector_description + .description .map(str::trim) .filter(|description| !description.is_empty()) .map(str::to_string) @@ -251,7 +251,7 @@ pub fn collect_tool_search_source_infos<'a>( return Some(ToolSearchSourceInfo { name: name.to_string(), description: tool - .connector_description + .description .map(str::trim) .filter(|description| !description.is_empty()) .map(str::to_string), @@ -265,7 +265,11 @@ pub fn collect_tool_search_source_infos<'a>( Some(ToolSearchSourceInfo { name: name.to_string(), - description: None, + description: tool + .description + .map(str::trim) + .filter(|description| !description.is_empty()) + .map(str::to_string), }) }) .collect() diff --git a/codex-rs/tools/src/tool_registry_plan.rs b/codex-rs/tools/src/tool_registry_plan.rs index c00c7c8b13ed..34a3f228d713 100644 --- a/codex-rs/tools/src/tool_registry_plan.rs +++ b/codex-rs/tools/src/tool_registry_plan.rs @@ -280,7 +280,7 @@ pub fn build_tool_registry_plan( ToolSearchSource { server_name: tool.server_name, connector_name: tool.connector_name, - connector_description: tool.connector_description, + description: tool.description, } })) }) diff --git a/codex-rs/tools/src/tool_registry_plan_tests.rs b/codex-rs/tools/src/tool_registry_plan_tests.rs index 71aef8b1a77b..ce575bcd1440 100644 --- a/codex-rs/tools/src/tool_registry_plan_tests.rs +++ b/codex-rs/tools/src/tool_registry_plan_tests.rs @@ -1413,7 +1413,7 @@ fn search_tool_description_lists_each_mcp_source_once() { "mcp__rmcp__", "rmcp", /*connector_name*/ None, - /*connector_description*/ None, + Some("Remote memory tools."), ), ]), &[], @@ -1432,7 +1432,7 @@ fn search_tool_description_lists_each_mcp_source_once() { .count(), 1 ); - assert!(description.contains("- rmcp")); + assert!(description.contains("- rmcp: Remote memory tools.")); assert!(!description.contains("mcp__rmcp__echo")); assert!(handlers.contains(&ToolHandlerSpec { @@ -1453,7 +1453,7 @@ fn search_tool_requires_model_capability_and_enabled_feature() { "mcp__codex_apps__calendar", CODEX_APPS_MCP_SERVER_NAME, Some("Calendar"), - /*connector_description*/ None, + /*description*/ None, )]); let features = Features::with_defaults(); @@ -2354,13 +2354,13 @@ fn deferred_mcp_tool<'a>( tool_namespace: &'a str, server_name: &'a str, connector_name: Option<&'a str>, - connector_description: Option<&'a str>, + description: Option<&'a str>, ) -> ToolRegistryPlanDeferredTool<'a> { ToolRegistryPlanDeferredTool { name: ToolName::namespaced(tool_namespace, tool_name), server_name, connector_name, - connector_description, + description, } } diff --git a/codex-rs/tools/src/tool_registry_plan_types.rs b/codex-rs/tools/src/tool_registry_plan_types.rs index f6ea215ac1cd..7c3772783d6b 100644 --- a/codex-rs/tools/src/tool_registry_plan_types.rs +++ b/codex-rs/tools/src/tool_registry_plan_types.rs @@ -85,7 +85,7 @@ pub struct ToolRegistryPlanDeferredTool<'a> { pub name: ToolName, pub server_name: &'a str, pub connector_name: Option<&'a str>, - pub connector_description: Option<&'a str>, + pub description: Option<&'a str>, } impl ToolRegistryPlan {