Fix empty tool descriptions (#17946)

## Summary
- Ensure direct namespaced MCP tool groups are emitted with a non-empty
namespace description even when namespace metadata is missing or blank.
- Add regression coverage for missing MCP namespace descriptions.

## Cause
Latest `main` can serialize a direct namespaced MCP tool group with an
empty top-level `description`. The namespace description path used
`unwrap_or_default()` when `tool_namespaces` did not include metadata
for that namespace, so the outbound Responses API payload could contain
a tool like `{"type":"namespace","description":""}`. The Responses API
rejects that because namespace tool descriptions must be a non-empty
string.

## Fix
- Add a fallback namespace description: `Tools in the <namespace>
namespace.`
- Preserve provided namespace descriptions after trimming, but treat
blank descriptions as missing.

### Issue I am seeing
This is what I am seeing on the local build.
<img width="1593" height="488" alt="Screenshot 2026-04-15 at 10 55 55
AM"
src="https://github.com/user-attachments/assets/bab668ba-bf17-4c71-be4e-b102202fce57"
/>

---------

Co-authored-by: Sayan Sisodiya <sayan@openai.com>
This commit is contained in:
Shijie RaoandSayan Sisodiya authored and GitHub committed 2026-04-15 18:14:43 +00:00
1 parent aca781b3a7
commit 78ce61c78e
6 files changed
+67 -14

No files matched your search

+5 -7
View File
@@ -5,6 +5,7 @@ use crate::ResponsesApiTool;
use crate::ToolName;
use crate::ToolSearchOutputTool;
use crate::ToolSpec;
use crate::default_namespace_description;
use crate::mcp_tool_to_deferred_responses_api_tool;
use codex_app_server_protocol::AppInfo;
use serde::Deserialize;
@@ -225,6 +226,8 @@ pub fn collect_tool_search_output_tools<'a>(
let description = first_tool
.connector_description
.map(str::trim)
.filter(|description| !description.is_empty())
.map(str::to_string)
.or_else(|| {
first_tool
@@ -232,12 +235,6 @@ pub fn collect_tool_search_output_tools<'a>(
.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.server_name
))
});
let tools = tools
@@ -251,7 +248,8 @@ pub fn collect_tool_search_output_tools<'a>(
results.push(ToolSearchOutputTool::Namespace(ResponsesApiNamespace {
name: tool_namespace.to_string(),
description: description.unwrap_or_default(),
description: description
.unwrap_or_else(|| default_namespace_description(tool_namespace)),
tools,
}));
}