tool_search aborts with 'unsupported payload' when using non-native API endpoints (Ollama/Bifrost)

Open 💬 3 comments Opened May 1, 2026 by rnett

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?

  1. Configure an OpenAI-compatible proxy (like Ollama, Bifrost, or LiteLLM) in config.toml as the model provider.
  2. Load enough MCP servers/tools so the total exceeds 100, triggering the deferred tool behavior.
  3. Prompt the model to search for a deferred tool (e.g., "Use tool_search to find tilth tools").
  4. The model generates a standard JSON-RPC tool call for tool_search with valid arguments like {"query": "tilth"}.
  5. 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗