Resolution failure for flattened MCP tool names from OpenAI-compatible proxies
What issue are you seeing?
When using Codex with non-GPT models via OpenAI-compatible proxies (e.g., Ollama, Bifrost, LiteLLM), MCP tools belonging to a namespace (e.g., mcp__tilth__) cannot be called. The proxy layer flattens the namespaced tool call into a single string (e.g., mcp__tilth__search) and provides it in the name field of the tool call.
Codex's McpConnectionManager and resolve_mcp_tool_info perform strict equality checks on ToolName structs. A call with the plain string mcp__tilth__search fails resolution even if the tool is registered under the mcp__tilth__ namespace. This leads to an "unsupported call" error or an "unsupported payload" error (if routed to the MCP handler without proper metadata).
What steps can reproduce the bug?
- Configure Codex to use a local Ollama or Bifrost instance as a model provider.
- Enable an MCP server (e.g.,
tilth). - Attempt to use an MCP tool (e.g.,
mcp__tilth__search). - Observe that the model generates a flat tool call
{"name": "mcp__tilth__search", "arguments": ...}. - Codex fails to resolve this call to the namespaced MCP tool.
What is the expected behavior?
Codex should attempt to "un-flatten" tool names that follow the standard mcp__<server>__<tool> pattern if a direct match fails.
Additional information
A suggested fix is to update McpConnectionManager::resolve_tool_info in codex-mcp/src/connection_manager.rs to attempt a fuzzy match. If a tool name starts with mcp__, the resolver should attempt to split it by __ and match it against registered namespaces.
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I’m seeing an MCP invocation issue with Codex v0.131.0 using Ollama/local provider:
BrowserOS MCP is discovered correctly. /mcp shows the server, URL, and full tool list, including browseros_info, list_pages, and get_active_page.
But calling those concrete tools fails:
So discovery works, but execution fails.
This seems like a Codex + Ollama/local-provider MCP dispatch issue rather than a BrowserOS server issue.
+1 also facing this issue
Additional data point: the flattened name uses
.(not__), and splitting into separatenamespace+namefields is what actually resolvesReproduced on codex-cli 0.148.0-alpha.9 (bundled in ChatGPT desktop 26.810.52044) against Ollama 0.32.14 with
wire_api = "responses", model qwen3.8.Two details that differ from the current description:
1. Ollama flattens with a single
., not__. Sending codex's{"type":"namespace","name":"mcp__node_repl","tools":[{"name":"js",...}]}to Ollama's/v1/responses, the model emits:The arguments are fully correct — the model understands the tool perfectly. Only the name encoding differs.
2. Splitting by
__is not sufficient. I put a rewriting proxy in front of Ollama and tried both fixes:| Rewrite | codex result |
|---|---|
|
mcp__node_repl.js→mcp__node_repl__js| stillERROR codex_core::tools::router: error=unsupported call: mcp__node_repl__js|| split into
namespace="mcp__node_repl",name="js"| works —mcp: node_repl/js (completed)|So the resolver isn't looking for a particular separator; it needs the two structured fields. This matches #24297, where
mcp__node_repl__jsis also rejected. A fix that only un-flattens by__would likely still fail.Control: same machine, same plugins, only switching to
model_provider = "openai"with gpt-5.6-luna — the identical task succeeds (mcp: node_repl/js (completed)), confirming the MCP server and plugin side are healthy.Third failure mode: the namespace is dropped entirely rather than flattened — on the built-in
amazon-bedrock-runtimeproviderAdding a data point that I think narrows the fix. @lich-wang's finding above (Ollama emits
mcp__node_repl.js, and only splitting into the two structured fields resolves it) matches my root cause, but the model I hit does not flatten at all — it drops the namespace, leaving nothing to un-flatten. That makes themcp__-prefix-splitting fix proposed in the issue body insufficient in a second, distinct way.Environment
amazon-bedrock-runtime— Codex's built-in Bedrock provider, not an OSS/local proxy or OpenAI-compatible gatewayglobal.xai.grok-4.6global.openai.gpt-5.6-solmy-mcp-server)Caveat stated up front: Grok 4.6 is not in Codex's shipped
models_cache.json; I added it viamodel_catalog_json. But the failure is not in the catalog layer — the capture below shows Codex emitting a well-formed namespace container, and the model simply not echoing the field back. Any model that does not implement the proprietarynamespacecontainer will behave this way regardless of how it is reached.Control: same provider, same config, only the model changes
Unlike previous reports in this cluster, the control here does not switch provider or transport. Both runs go through the identical
amazon-bedrock-runtimeprovider, sameconfig.toml, same MCP server, same prompt. The only variable is the model, which isolates the cause to the model's tool-call schema support rather than to any proxy or wire adapter.Rollout records (
~/.codex/sessions/.../rollout-*.jsonl), same tool, same arguments:The request never leaves Codex, so the MCP server, its transport, and credentials are all provably uninvolved.
What Codex puts on the wire
Captured by pointing
base_urlat a loopback listener that dumps the request body (wire_api = "responses"):Leaf names inside a
namespacecontainer are bare; the server identity exists only in the enclosing object.{"type":"namespace"}is not part of the public Responses API tool schema, so a model that has not been trained on it has no way to know the container name is semantically required on the way back.Three distinct degradation modes are now documented
| Model / provider | Emitted
name|namespace| Prefix survives? ||---|---|---|---|
| Ollama, Bifrost, LiteLLM (#20652 body) |
mcp__tilth__search| absent | yes,__|| Ollama 0.32.14 + qwen3.8 (@lich-wang) |
mcp__node_repl.js| absent | yes,.|| Grok 4.6 via Bedrock (this report) |
memory_info| absent | no — dropped entirely |Modes 1 and 2 differ only in separator, so a tolerant un-flattener could recover both. Mode 3 cannot be recovered by any string-splitting rule: the emitted name carries zero information about which namespace it came from.
Blast radius is wider than MCP
multi_agent_v1is shipped as a namespace container too (see the capture above), so on an affected model the multi-agent tools —spawn_agent,wait_agent,send_input,resume_agent,close_agent— are equally unreachable. Only the flatfunction/customtools work. This matches the observable symptom precisely:exec_command,apply_patch,list_mcp_resourcesand friends all work fine, while every namespaced tool fails.Config-level escape hatches I tested, none of which help
All still produce
unsupported call: memory_info:features.non_prefixed_mcp_tool_names = true{enabled = true, server_names = ["my-mcp-server"]}features.code_mode = trueplustool_mode = "code_mode"on the catalog entry (this does silence the "does not advertise Code Mode support" warning, so the field is accepted — the model still emits a direct namespaced call)multi_agent_version = "v2"to the catalog entry (Grok otherwise falls back to v1 while gpt-5.6-sol runs v2)So as of 0.148.0 there is no user-reachable workaround short of not using MCP with the model.
Why prefix-splitting is not enough, and what would cover all three modes
ToolRegistrykeys onToolName { namespace, name }and looks up by strict equality, then dead-ends inunsupported_tool_call_message():A fallback that covers all three modes, applied only after strict lookup misses and only when
is_default_namespace()holds:<namespace><sep><leaf>for any registered namespace, withsepin__/.//, resolve it. Matching against registered namespace strings rather than splitting on a fixed separator handles servers whose own names contain__, and covers modes 1 and 2 including #24297.unsupported call: <name>gives it nothing to correct.Step 2 is the part that needs an owner's decision, since silently picking a unique leaf name is a routing-behaviour change and the ambiguous case needs a deliberate policy. Happy to supply the full captured request body or the raw rollout files if that would help.
One framing note: because
amazon-bedrock-runtimeis a first-party built-in provider, this is not only a custom-proxy concern — it currently means no non-OpenAI model on Bedrock can use MCP or multi-agent tools at all. Given thatUse Responses compaction for Amazon Bedrocklanded on 2026-08-21, the Bedrock path looks actively maintained, so this may be cheaper to fix now than thecustom-modellabel suggests.