Codex wraps MCP tools in type:"namespace" for custom/local providers — backends with strict tools schemas cannot unwrap, MCP unusable
Summary
When Codex uses a custom model_providers.X with wire_api = "responses" (transport responses_http), it emits MCP-server tools wrapped in a proprietary Responses-API extension:
{"type":"namespace","name":"mcp__<server>__","description":"...","tools":[...]}
This wrapper has no equivalent in the Chat-Completions schema that most OpenAI-compatible backends use internally (llama.cpp, LM Studio, Ollama, DeepSeek). When they translate the request, anything whose type is not function is dropped or rejected. The MCP tools never reach the model. The user sees "no MCP tools" with no error from either side.
Related: #19871 — same problem, but bisected as a Codex regression. The cross-backend framing makes it clearer that this is a protocol-shape issue, not just a Codex-internal regression.
Evidence
A codex exec --profile local session against llama-server with one MCP server registered. Codex's outbound /v1/responses tools[] contains:
[
{"type":"function","name":"exec_command", ...},
... 13 native codex tools ...,
{"type":"web_search"},
{"type":"namespace","name":"mcp__open_websearch__",
"tools":[
{"type":"function","name":"fetchWebContent","parameters":{...}},
{"type":"function","name":"search","parameters":{...}}]}
]
The same Codex session against ChatGPT (responses_websocket) sends an identical namespace shape, but the ChatGPT backend understands and unwraps it server-side. Custom backends can't.
Affected backends
- llama.cpp — PR ggml-org/llama.cpp#23041 silently skips
namespace(see ggml-org/llama.cpp#20156). - LM Studio — rejects with
invalid_request_error(lmstudio-ai/lmstudio-bug-tracker#1810). - DeepSeek via CLIProxyAPI — silently returns plain text, no
function_call(router-for-me/CLIProxyAPI#3298). - Ollama Responses API — same, see #19871.
Proposed fix
When the transport is not the ChatGPT websocket (i.e. anytime the wire peer is a generic Responses-compatible server), flatten MCP namespaces into top-level function tools on the codex side:
{"type":"function","function":{"name":"mcp__<server>__<tool>","parameters":{...},"description":"..."}}
Tool-call dispatch then routes back through the same path; no reverse map needed if the namespaced name is preserved end-to-end.
Doing the flattening on the codex side fixes every downstream backend at once. Asking every backend to teach itself the namespace extension is the slower path.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I ran into this same class of problem while using Codex with Ollama and a custom Responses API provider.
I built an experimental user-space workaround called
[codex-ollama-proxy](https://github.com/bharat2808/codex-ollama-proxy):The proxy runs locally between Codex and the configured Responses API upstream. It currently handles the relevant Codex-specific tool shapes by:
type: "namespace"tools into standard function tools;{ namespace, name }format;additional_toolsinput items into the top-level tool list;apply_patch;tool_searchfunction calls back into nativetool_search_callitems.This allowed me to keep Codex app plugins and tools available while using Ollama in my setup.
GitHub: https://github.com/bharat2808/codex-ollama-proxy
npm: https://www.npmjs.com/package/codex-ollama-proxy
Confirmed against Ollama with the exact shape from the issue. Codex's outbound
tools[]carried:What reached the model was just
functiontools — nonamespacetype, nothing dropped:And when the model called
search, the call came back reconstructed to the namespaced identity the client expects:The flatten happens before dispatch and the
{namespace, name}is rebuilt on the way back, so two MCP servers exposing a same-named tool stay distinct. On this step it was also a cross-protocol hop — Responses in, Chat Completions out to Ollama — with the same flattening applied.I ran it through swobuforge/swobu, a local compatibility gateway I maintain, sitting between Codex and Ollama (one workspace, a route to an Ollama target, no code change for this). It covers llama.cpp/LM Studio/DeepSeek the same way, not Ollama alone — the flatten-plus-reconstruct is per-backend rather than a Responses-wire shim.
This is an alternate path, not a fix
Ollama data point: the namespace tool is not dropped — it round-trips with correct arguments but an unusable name
Environment: codex-cli 0.148.0-alpha.9 (ChatGPT desktop 26.810.52044), Ollama 0.32.14, qwen3.8,
wire_api = "responses".The issue lists Ollama as "no tool invocation". In my testing the behaviour is slightly better than that, which may matter for the fix: Ollama accepts
type:"namespace"and does expose the child tools to the model. Server log confirms parsing:The model then produces a correct call, just with the namespace flattened into the name using
.:codex then rejects it:
ERROR codex_core::tools::router: error=unsupported call: mcp__node_repl.jsSo for Ollama specifically this is purely a name-encoding mismatch on the return path, not a dropped-tool problem. I verified with a rewriting proxy that converting the call into separate
namespace+namefields makes it work end to end (mcp: node_repl/js (completed)), while rewriting tomcp__node_repl__jsdoes not (see #20652, #24297).The proposed fix in this issue (emit MCP tools as flat
type:"function"withmcp__<server>__<tool>names) would fix the request path — but per #24297 and my test above, the router also needs to resolve that flat name on the way back, otherwise it will still reportunsupported call.I published a working compatibility bridge for this exact Codex ↔ llama.cpp gap: https://github.com/madmax24-ubuntu/codex-proxy-llama.cpp
It flattens Codex
type:"namespace"MCP tool specs into plain function tools for llama.cpp, then restores the namespace/name mapping on the response path so Codex can dispatch the call correctly. It also handles nativeapply_patch, Responses SSE framing, tool-history replay, reasoning events, and compaction.I built it because direct
wire_api = "responses"worked for built-in tools but MCP/local-model sessions were unreliable. The current version has an installer + doctor command and is tested with llama.cpp/Qwen. Posting it here in case it helps anyone blocked by this issue; it may also be useful as a reference implementation for a nativenamespace_tools = false/flatten option in Codex.