MCP tools silently unavailable with DeepSeek official setup (custom models.json: supports_search_tool=true + tool_mode=null)
Summary
Following DeepSeek's official one-command setup (irm https://cdn.deepseek.com/api-docs/codex-deepseek-setup-en.ps1 | iex), the generated ~/.codex/models.json registers deepseek-v4-flash / deepseek-v4-pro with "supports_search_tool": true and "tool_mode": null. On Codex 0.145.0 this combination makes all configured MCP tools silently unavailable: the session exposes web search and MCP resources, but no mcp__* callable tools and no tool_search.
Native OpenAI catalog models (e.g. gpt-5.6-sol, tool_mode: code_mode) can call the same MCP servers fine, so this only shows up with custom models registered through model_catalog_json.
Environment
- Codex CLI
0.145.0on Windows - Custom provider in
config.toml:[model_providers.deepseek]withbase_url = "https://api.deepseek.com/"andwire_api = "responses" model = "deepseek-v4-flash",model_catalog_json = "C:/Users/<user>/.codex/models.json"(written by the DeepSeek setup script)- MCP servers:
codegraph(stdio),idalib-mcp(stdio),universal-memory(stdio) — allenabled;codex mcp listshowsAuth: Unsupported(expected for stdio) - The MCP server handshake succeeds:
idalib-mcp --stdioinitialize +tools/listworks, and in-sessionlist_mcp_resourcesreturns the server's resources
Reproduction
- Run the DeepSeek setup script and choose model 1.
- Add any stdio server under
[mcp_servers.*]inconfig.toml. - Start a new session with
deepseek-v4-flash. - Ask the model to call any MCP tool (e.g.
mcp__idalib__server_health).
Actual: the model reports the tool is not in its available tool set. The session's tool surface contains web search and the MCP resource tools but neither mcp__* tools nor tool_search.
Expected: MCP tools registered in [mcp_servers.*] should be directly callable (or at least discoverable via tool_search) for custom models, matching catalog models.
Root cause (client-side)
codex-rs/core/src/tools/spec_plan.rs:search_tool_enabled = model_info.supports_search_tool && provider.capabilities().namespace_toolscodex-rs/core/src/mcp_tool_exposure.rs: whensearch_tool_enabledis true, every MCP tool is registered withToolExposure::Deferredbuild_model_visible_specs*only emits tools whose exposureis_direct(); Deferred tools are excluded from the model-visible listeffective_tool_mode:tool_mode: nullplus no code-mode feature flags resolves toToolMode::Direct, so there is no code-mode exec tool into which the deferred tools could be nested- The DeepSeek setup script hardcodes
"supports_search_tool": true(and"tool_mode": null) in themodels.jsonit writes
Net effect: official DeepSeek setup + any MCP server = MCP tools silently invisible, with the tool_search escape hatch also absent from the session.
Workaround
Set "supports_search_tool": false in both entries of ~/.codex/models.json. MCP tools are then registered with ToolExposure::Direct and appear in a new session. Hosted web search is unaffected (it is driven by provider capabilities + web_search_tool_type, not supports_search_tool).
Related
- #33609 (closed): gpt-5.6-sol hides MCP tools without exposing
tool_search - #31750: custom
model_provider, notool_search/ dynamic tool discovery - #32101: Code Mode omits
tool_searchfrom exec, degrading deferred MCP discovery - #34018: Windows Desktop custom stdio MCP tools pass discovery but are not exposed
5 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Confirmed on Windows 11 + Codex 0.146.0 with the same DeepSeek official one-command setup. Same fingerprint: models.json registers deepseek-v4-flash/-pro/-free with supports_search_tool=true + tool_mode=null, and MCP tools are silently hidden in exec mode (and intermittently in other modes).
Local workaround that fully fixes it (verified live): set
supports_search_tool: falsefor the DeepSeek models in ~/.codex/models.json. After that all configured MCP tools (10 hermes-team tools in our case) are exposed and calls succeed in exec mode. No codex upgrade needed.This is worth fixing in the official setup script (deepseek setup ps1) and/or docs — every DeepSeek-onboarding user hits it.
Additional data point for this family, also from DeepSeek's official catalog (
supports_search_tool: true,tool_mode: null):With
multi_agent_version: "v1", the V1 multi-agent tools are registered as Deferred behindtool_search. Thetool_searchtool is present in the request's tools array, but deepseek-v4-flash does not recognize or call it, so the V1 tools are unreachable. Tool discovery is silently broken for this model in general, not just for MCP tools, which also explains why the documented V1 workaround for subagent delivery fails with DeepSeek.More detail in the write-up: https://github.com/CCanxue/codex-deepseek-subagent-fix
Confirmed on Linux + Codex 0.146.0 + custom router (LiteLLM → sglang), with additional findings:
Same symptom: MCP tools silently invisible for
opencode-zen/deepseek-v4-flash-freevia a custommodel_providersentry, while native GPT models call the same MCP servers fine.Additional data points from debugging the full chain (codex → router → LiteLLM → sglang):
tool_searchis unusable for custom providers, not just absent. Whensupports_search_tool: true, Codex emits the tool withtype: "tool_search"(nofunctionfield). LiteLLM's Responses→chat translation rejects it (400: Field required ... tools[13].function), and if you rewrite it to a plain function, Codex's own handler fails withtool_search handler received unsupported payload(core/src/tools/handlers/tool_search.rs). So the "escape hatch" is doubly broken on the custom-provider path.tool_call_mcp_elicitation, naming the tool in the prompt makes native models (gpt-5.6-terra/luna) callmcp__*directly. DeepSeek instead callslist_mcp_resources, sees onlycodex_apps, concludes nothing exists, and gives up — it never attempts the deferred call, so elicitation never fires.supports_search_tool: falseworkaround did not expose tools in our setup (Linux, custom provider via LiteLLM). Tools stayed invisible; the model's tool surface contained only codex host functions +codex_appsresources. Possibly the exposure decision also depends onprovider.capabilities().namespace_tools/tool_modeas noted inspec_plan.rs.reasoning_contenton follow-up requests, causing400: The reasoning_content in the thinking mode must be passed back to the APIfrom sglang whenever a tool call happened. (Fixed locally by preserving the field.)Net: MCP tools for custom/non-OpenAI models need a real fix in
mcp_tool_exposure.rs/spec_plan.rs— either expose deferred tools directly whentool_searchcan't work on the provider path, or maketool_searcha plain function tool that custom providers can pass through. Upvoting; happy to provide logs/config.Confirmed on Linux (x86_64), codex-cli 0.147.0, running the official DeepSeek
deepseek-v4-flashsetup (custommodels.jsonwithsupports_search_tool: true,tool_mode: null, provider[model_providers.deepseek]->https://api.deepseek.com/,wire_api = \"responses\").Same symptom:
codex mcp listshows the stdio server enabled and the handshake works (MCP stdiotools/listreturns all tools), butcodex execnever injectsmcp__*tools into the model-visible schema — only the built-ins plus thelist_mcp_resources/resource functions.Fixing
~/.codex/models.jsonby setting"supports_search_tool": falsein thedeepseek-v4-flash(anddeepseek-v4-pro) entries makes every MCP tool registered with direct exposure and the tools immediately appear in a fresh session. Verified end-to-end: the model then successfully called an stdio MCP*_healthtool and returned its real payload.Hosted web search still works (driven by provider capabilities +
web_search_tool_type, notsupports_search_tool), so the workaround is minimal. Thanks for the clean root-cause writeup.