tool_search aborts with 'unsupported payload' when using non-native API endpoints (Ollama/Bifrost)
What issue are you seeing?
When using Codex with a large number of MCP tools (exceeding DIRECT_MCP_TOOL_EXPOSURE_THRESHOLD: usize = 100), Codex correctly defers the tools and exposes the tool_search handler and the MCP namespace stubs (e.g. mcp__tilth__). However, when using a non-native compatibility layer like Ollama or LiteLLM to serve models, calling the tool_search tool always results in an immediate turn abort.
The specific error seen in codex-tui.log is:Fatal error: tool_search handler received unsupported payload
Looking at codex-rs/core/src/tools/router.rs, when a tool call comes in via a generic OpenAI-compatible FunctionCall (which is what Ollama/LiteLLM output, rather than the internal ToolSearchCall type), the router wraps the arguments in a ToolPayload::Function { arguments }.
However, the tool_search handler (core/src/tools/handlers/tool_search.rs) explicitly expects ToolPayload::ToolSearch { arguments } and throws a fatal error if it receives ToolPayload::Function, completely breaking the deferred tools workflow for local/compatibility-layer models.
What steps can reproduce the bug?
- Configure an OpenAI-compatible proxy (like Ollama, Bifrost, or LiteLLM) in
config.tomlas the model provider. - Load enough MCP servers/tools so the total exceeds 100, triggering the deferred tool behavior.
- Prompt the model to search for a deferred tool (e.g., "Use
tool_searchto find tilth tools"). - The model generates a standard JSON-RPC tool call for
tool_searchwith valid arguments like{"query": "tilth"}. - The turn immediately aborts with the "unsupported payload" fatal error in the logs.
What is the expected behavior?
The tool_search handler should gracefully parse standard ToolPayload::Function payloads if the arguments map correctly to SearchToolCallParams, rather than strictly requiring ToolPayload::ToolSearch.
Alternatively, router.rs should check if the incoming FunctionCall has the name tool_search and wrap it in a ToolPayload::ToolSearch before dispatching.
Additional information
This makes using extensive MCP toolsets with local or non-GPT models essentially impossible, as they cannot pull deferred tools into context. A simple fallback in the match payload block of ToolSearchHandler::handle to deserialize from the string arguments of ToolPayload::Function would instantly solve this interoperability bug.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗