Codex wraps MCP tools in type:"namespace" for custom/local providers — backends with strict tools schemas cannot unwrap, MCP unusable

Open 💬 5 comments Opened May 17, 2026 by estrizhok
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #22970

Powered by Codex Action

bharat2808 · 1 month ago

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:

  • flattening type: "namespace" tools into standard function tools;
  • mapping returned flat function names back to Codex’s { namespace, name } format;
  • lifting newer additional_tools input items into the top-level tool list;
  • translating custom tools such as apply_patch;
  • translating ordinary tool_search function calls back into native tool_search_call items.

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

metrofun · 18 days ago

Confirmed against Ollama with the exact shape from the issue. Codex's outbound tools[] carried:

{"type":"namespace","name":"mcp__open_websearch__","tools":[
  {"type":"function","name":"search","parameters":{...}},
  {"type":"function","name":"fetchWebContent","parameters":{...}}]}

What reached the model was just function tools — no namespace type, nothing dropped:

"tools":[
  {"type":"function","function":{"name":"exec_command",...}},
  {"type":"function","function":{"name":"search",...}},
  {"type":"function","function":{"name":"fetchWebContent",...}}]

And when the model called search, the call came back reconstructed to the namespaced identity the client expects:

{"type":"function_call","name":"search","namespace":"mcp__open_websearch__","arguments":"{...}"}

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

lich-wang · 10 days ago

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:

level=DEBUG source=openai.go:1215 msg="parsed Responses tools" count=21 web_search=true

The model then produces a correct call, just with the namespace flattened into the name using .:

name = "mcp__node_repl.js"    arguments = {"code":"1+1"}

codex then rejects it: ERROR codex_core::tools::router: error=unsupported call: mcp__node_repl.js

So 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 + name fields makes it work end to end (mcp: node_repl/js (completed)), while rewriting to mcp__node_repl__js does not (see #20652, #24297).

The proposed fix in this issue (emit MCP tools as flat type:"function" with mcp__<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 report unsupported call.

madmax24-ubuntu · 9 days ago

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 native apply_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 native namespace_tools = false/flatten option in Codex.