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.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗