MCP tools silently unavailable with llama-server backends — add option to flatten namespace tool specs to functions
What variant of Codex are you using?
CLI
What feature would you like to see?
A per-provider compatibility option to expose MCP tools as plain type: "function" entries
(with prefixed names) instead of a type: "namespace" entry, for Responses API backends that
do not implement the namespace tool type.
Why: since ~0.144, codex sends each configured MCP server's tools as ONE type: "namespace"
entry in the Responses tools array (create_tool_spec in codex-rs/core/src/tools/handlers/mcp.rs,
unconditional). Backends that implement /v1/responses by translating to chat completions — e.g.
llama.cpp's llama-server — skip every non-function tool type (tools/server/server-chat.cpp:
"unsupported Responses tool type ... skipped"). The result is fully silent: built-in tools work,codex mcp list shows the server enabled, the server's stdio handshake and tools/list succeed,
yet the model never sees any MCP tool, in the TUI and in ACP clients alike. There is no config
escape today: ProviderCapabilities::namespace_tools is hard-true for custom TOML providers,
and no flatten path exists.
Ask (either would work):
- honor a
namespace_tools = falsekey on[model_providers.<id>], falling back to the
pre-namespace behavior (flat prefixed function specs); or
- a
[features]/provider-levelmcp_tool_compat = "flatten"option with the same effect.
Feasibility evidence: we run a proxy in front of llama-server that does exactly this
translation and codex works end to end with local models: expand each namespace entry to flat
functions named <namespace>__<tool> on the request, and rewrite the model's function_call
items back to the split {"namespace": ..., "name": ...} form on the response (codex's tool
router resolves calls by exact (namespace, name) lookup, so both directions are needed).
Having codex emit the flat form natively would remove the need for that middleware for every
local-model user.
Additional information
- Reproduction: any custom
model_providerwithwire_api = "responses"pointed at
llama-server (current master), one stdio MCP server under [mcp_servers.*], new session →
ask the model to list its callable tools: built-ins only, no mcp__* tools, no error anywhere
(llama-server logs the skip at WARN).
- Verified against codex 0.146.0 (npm) and main d1fb77d (2026-08-04); llama.cpp master as of the
same date still skips non-function Responses tool types, so the gap is real on both sides today.
- Possibly the same underlying mechanism as #36382 ("MCP tools silently unavailable with DeepSeek
official setup" — custom provider, wire_api = "responses", resources visible but no callable
MCP tools): if DeepSeek's Responses implementation also drops unknown tool types, that report's
models.json theory would be a red herring and this option would fix it too.
- Happy to test a build; the middleware workaround above is in production for us, so this is a
compatibility improvement for the local/OSS-backend ecosystem rather than an urgent blocker.
4 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I built and published a compatibility bridge that implements the exact workaround described here for Codex + llama.cpp: https://github.com/madmax24-ubuntu/codex-proxy-llama.cpp
It expands each Codex MCP
namespacetool into flat functions before forwarding to llama.cpp, then reconstructs the namespace/name mapping on the response path so Codex can route the call correctly. The bridge also handles nativeapply_patch, Responses SSE/event normalization, replayable tool history, reasoning handling, and compaction.It is currently working end-to-end with local Qwen + llama.cpp, and the installer/doctor are included. Sharing it here both as a workaround for users who need MCP now and as a concrete reference implementation for a native Codex flatten option.
I have a working middleware implementation of the exact two-way flatten/restore path described here: https://github.com/madmax24-ubuntu/codex-proxy-llama.cpp
It expands Codex
type:"namespace"MCP specs into flat function tools for llama.cpp, then rewrites returned function calls back into the namespace/name form Codex expects. I’m using it end-to-end with local Qwen throughwire_api = "responses".The bridge also normalizes Codex-specific Responses behavior that local backends commonly miss (
apply_patch, tool-history replay, SSE framing, reasoning events, compaction), but the namespace translation itself is isolated and may be useful as a reference implementation for a nativenamespace_tools = falseoption.There is an installer and
doctor.pyin the repo if anyone wants to test the workaround without patching Codex/llama.cpp.Confirming this against a second backend — Ollama (the report cites llama.cpp/llama-server), and adding the failure mode, which I think matters for prioritisation.
Setup:
codex-cli 0.149.0, Ollama 0.32.15, macOS 27,qwen3-coder:30b(Q4, ~3B active MoE), via--oss --local-provider ollama(wire_api = "responses").Captured request (proxy between Codex and Ollama, 100,655 bytes):
So none of the MCP tools reach the model as callable functions — matching this report exactly.
How it fails is the problem. Ollama doesn't reject the namespace entry; it surfaces it to the model flattened into prompt text, so the model sees
mcp__atlassian.getVisibleJiraProjectsand tries to call it with that dotted name — which Codex won't accept. In its own words mid-session: "the previous attempt failed". After that it degrades into emitting raw Qwen XML as assistant text:Note
mcp__jira_mcp— an invented server name; the real one isatlassian. Asked to check Jira, it then explored the repo with shell tools and produced a confident, wrong diagnosis (that a credential was missing, with remediation steps naming a real script). Verified outside the sandbox, that credential was fine. So the user-visible symptom isn't "tool unavailable" — it's a plausible answer to a problem that doesn't exist.Positive control — this is not model capability. Same model, same endpoint, tools sent as flat
type:"function":| tools offered | result |
|---|---|
| 1 | correct structured
tool_calls, 0.4s || 30 (with realistic decoys) | correct, 1.3s |
| aggregate operation-selector shape | correct —
mcp__atlassian({"operation":"getVisibleJiraProjects"})|And end-to-end through a harness that sends flat function tools, the same model chained
atlassianUserInfo→getAccessibleAtlassianResources→getVisibleJiraProjectsagainst live Jira and returned the right projects. The encoding is the only difference.One related note:
supports_search_tool = truein a custom model catalog makes this worse, not better. Ollama silently drops{"type":"tool_search"}too, so the deferral hides every MCP tool behind a tool the model can never see. Confirmed with a control — offered atool_searchentry alongside a normal function namedmarker_tool_alpha, the model listed the function and never mentionedtool_search.+1 for the per-provider flatten option proposed here. Thanks @madmax24-ubuntu for the bridge — useful to know there's a working path in the meantime.