Custom stdio MCP server discovered by /mcp but tools not exposed to Desktop threads
Summary
Codex Desktop sees a custom stdio MCP server and successfully discovers its tools via tools/list, but the tools are not exposed to Desktop threads or tool_search.
This appears to be a regression or exposure-layer bug in Desktop/App Server 0.124.0-alpha.2.
Environment
- Codex Desktop client:
26.422.21637 - Codex/App Server:
0.124.0-alpha.2 - Platform: macOS
- Server type: custom stdio MCP server
- Config location: project-scoped
.codex/config.toml
MCP config
[mcp_servers.interdomestik_qa]
command = "/bin/bash"
args = ["scripts/start-repo-qa.sh"]
cwd = "."
The wrapper script resolves the repo root, changes into it, and starts a TypeScript MCP server over stdio.
Expected behavior
The following custom MCP tools should be exposed to Desktop threads and callable via names like mcp__interdomestik_qa__project_map:
project_mapread_filescode_searchcheck_healthpr_verifysecurity_guarde2e_gate
Actual behavior
- Desktop
/mcpshows the server enabled. codex mcp list --jsonshows the server enabled.- Repo preflight passes, including live MCP
tools/listdiscovery. - Desktop logs show the server starts and returns a
ListToolsResultcontaining the expected custom QA tools. tool_searchreturns 0 callableinterdomestik_qatools.- Thread request payloads do not include any
mcp__interdomestik_qa__*tools. - The current thread's
thread_dynamic_toolsonly contains generic Desktop tools such asautomation_update,read_thread_terminal,load_workspace_dependencies, andinstall_workspace_dependencies.
Evidence collected locally
Manual/repo preflight confirms live MCP discovery works:
pnpm mcp:preflight
Desktop logs show the custom server is started and queried successfully:
- custom QA server starts on stdio
- client initializes against the server
ListToolsRequestis sentListToolsResultis received with the expected custom tools
But those discovered tools are not present in the thread/deferred tool surface.
Historical comparison from local Codex state/logs:
- An Apr 20 thread on Codex
0.91.0successfully called tools such as: mcp__interdomestik_qa__read_filesmcp__interdomestik_qa__code_searchmcp__interdomestik_qa__project_map- Apr 24 threads on Codex
0.124.0-alpha.2do not expose those tools, despite successful/mcpdiscovery.
I also checked whether stale Desktop workspace state was the cause. The active workspace root and thread cwd both point to the correct repo, but the custom MCP tools are still not exposed.
Suspected failure point
The failure appears to be after successful MCP tools/list discovery and before Desktop registers/indexes those tools into the per-thread/deferred tool surface.
It does not appear to be caused by the MCP server implementation, stdio transport, project config, or CLI discovery, because live tools/list discovery succeeds outside the Desktop thread tool surface.
35 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Update after full Codex Desktop restart:
Outcome is still: Discovery works, exposure still fails.
Fresh thread details:
/Users/arbenlila/development/interdomestik-crystal-home019dc0ed-8043-78b1-badb-e1a5aa5e6f02tool_searchfor expected QA tools returned 0 tools, both initially and after rechecking.codex mcp list --jsonshowsinterdomestik_qaenabled.pnpm mcp:preflightpassed, including livetools/listdiscovery.thread_dynamic_toolsfor the current thread contains only:automation_updateread_thread_terminalload_workspace_dependenciesinstall_workspace_dependenciesthread_dynamic_toolscontains 0interdomestik_qa,project_map,read_files, orcode_searchentries.interdomestik-qaversion1.0.0.ListToolsResultfor this same thread containing the expected QA tool names.tools/listfailure.Versions after restart:
codex --version:codex-cli 0.124.0cli_version:0.125.0-alpha.3Codex.app26.422.30944, build2080Most precise failure boundary:
Desktop/RMCP can start
interdomestik_qaand receiveListToolsResult, but those tools are not persisted/indexed into the current thread's callable dynamic tools, sotool_searchexposes none of them.Reviewed the potential duplicates suggested by the bot. I do not think this is an exact duplicate, though it is likely related to the same MCP exposure/indexing area.
Comparison:
ListToolsResultfor the same current thread containing the expected tool names, whilethread_dynamic_toolsstill contains no custom MCP entries.codex execsession schema. This issue is Desktop thread/tool_search exposure.tools/list, so OAuth is not involved.So the narrower failure here remains: Desktop/RMCP successfully starts the stdio server and receives
ListToolsResult, but the discovered tools are not persisted/indexed into the thread's callable dynamic tools.Additional cross-client comparison from the same local repo/machine:
Gemini/Antigravity is able to mount and use MCP tools successfully in this repo context. Verified working there:
mcp-context-server/project_mapmcp-context-server/read_filescontext7/resolve-library-idplaywright/browser_tabse2e-test-generator/generate_basic_testThis further suggests the MCP server/environment is not the root problem. Other MCP clients can index and call repo/context/playwright-style MCP tools on the same machine, while Codex Desktop still discovers
interdomestik_qaviatools/listbut does not expose the tools to the thread/tool_search surface.One separate note: Gemini's
repo-qawrapper hit a JSON parsing/encoding issue (invalid character 'â') for some QA commands, which looks like command-output formatting inside that wrapper. That is distinct from this Codex issue, where the tools are not mounted at all despite successfulListToolsResultdiscovery.Additional related issue with a useful version boundary: #19649
That report describes the same core failure class: MCP servers are registered and visible via
codex mcp list/codex mcp get, but their tools are not injected into Codex threads.Important detail from #19649:
codex-cli 0.120.0codex-cli 0.122.0-alpha.13and later0.120.0That lines up with this Desktop report:
0.91.0had usablemcp__interdomestik_qa__...calls0.124.x/0.125.xdiscovertools/listbut do not expose the tools totool_search/thread_dynamic_toolsSo this issue may be the Desktop/app-server manifestation of the same regression family as #19649. This issue still adds Desktop-specific evidence: the logs show
ListToolsResultfor the same current thread, whilethread_dynamic_toolsremains missing the discovered MCP tools.This may be https://github.com/openai/codex/issues/20574, are you using a custom or older model?
Thanks. I do not think this is #20574.
In this repro I am not using a custom provider like Ollama/LiteLLM/Bifrost. The failing Desktop thread is using the normal Codex Desktop / ChatGPT path with
gpt-5.5.The failure signature is also different:
tool_searchis present but aborts withunsupported payloadwhen called through a non-native provider.tool_searchexecutes successfully, but returns an empty result forinterdomestik_qa.codex mcp list --jsonshowsinterdomestik_qaenabled.pnpm mcp:preflightpasses, including livetools/listdiscovery from the stdio server.ListToolsResultcontaining the expectedinterdomestik_qatool names.mcp__interdomestik_qa__...namespace becomes callable.So this still looks like a Desktop/app-server MCP exposure/indexing issue, not the custom-provider
tool_searchpayload bug from #20574.Additional update: I also tested a repo-side hardening for a possible stale-workspace-root factor.
Originally the project-scoped MCP entry used a relative cwd:
I changed it to pin the server cwd to the absolute repository root while keeping the launcher argument unchanged:
I also added a local preflight guard so
pnpm mcp:preflightfails ifinterdomestik_qaresolves with a relative cwd or a cwd outside this repo.Verification after that hardening:
codex mcp list --jsonresolvesinterdomestik_qawith the absolute repo cwd.pnpm mcp:preflightpasses.tools/listdiscovery still passes and returns the expected QA tools.However, this did not fix Desktop thread exposure. In the running Desktop session,
tool_searchstill returns 0 results forinterdomestik_qa/project_map/read_files/code_search.So the absolute-cwd change is a useful repo-side mitigation that removes stale root ambiguity, but it is not a complete fix for this issue. The remaining failure is still in the Desktop/app-server exposure/indexing step after successful MCP discovery.
Additional update from the repo side: I hardened the
interdomestik_qaMCP configuration to remove possible local blockers, but the Desktop exposure failure remains in the current thread.Repo-side changes made and verified:
interdomestik_qais pinned to the absolute repository cwd instead ofcwd = ".".enabled_toolsallowlist for the core agent tools:project_mapread_filesread_file_rangecode_searchgit_status_compactgit_branch_infochanged_filesscope_auditcheck_healthpr_verifysecurity_guarde2e_gateadditionalProperties: false.pnpm mcp:preflightnow verifiescodex mcp get interdomestik_qa --json, including the parsedenabled_toolsallowlist.Verification results:
codex mcp get interdomestik_qa --jsonshows the server enabled with the absolute cwd and the parsedenabled_toolsallowlist.pnpm mcp:preflightpasses.tools/listdiscovery passes.tools/listreturns 35 tools, all with strict object schemas, and the core allowlisted tools are present.However, this existing Codex Desktop thread still returns 0 results from
tool_searchforinterdomestik_qa project_map read_files code_search ....So the repo/server side is now exposure-ready: no relative cwd, no unbounded Codex tool surface, and no permissive input schemas. If a fresh Desktop thread still does not surface
mcp__interdomestik_qa__..., the remaining failure is still in Desktop/app-server MCP indexing/injection after successful discovery.Additional update from the current Desktop session after restart and after testing the local-plugin/CLI distinction.
Current state:
Interdomestik QAplugin as available/enabled in the session metadata.tool_searchstill returns 0 results forinterdomestik_qa,project_map,read_files, andcode_search.mcp__interdomestik_qa__...namespace in the Desktop session.interdomestik-qa, while the MCP server id remainsinterdomestik_qa; Desktop can know about the plugin/server, but the tools still are not admitted into the callable tool index.CLI / local stdio contrast from the same repo and machine:
tools/list, and can execute tool calls.pnpm mcp:tool listreturns the expected QA tools.pnpm mcp:tool call read_file_range '{"file":"package.json","startLine":1,"endLine":8}'succeeds.interdomestik_qaas enabled with the expected command/cwd/env configuration.So the current narrowed boundary is:
initializeworks.tools/listworks.tool_search/ the callablemcp__...namespace.I also checked public issues/docs for evidence that Codex Desktop has started admitting local custom MCPs into
tool_search; I did not find a confirmed fix or documented supported path for that yet. This still looks like a Desktop/app-server MCP admission/indexing bug, not a repo MCP implementation problem.Hi OpenAI Codex team,
I’m asking for help with a Codex Desktop MCP exposure issue.
I have a repo-local MCP server named
interdomestik_qa. The server is configured, startable, and discoverable, but Codex Desktop does not expose its tools as callablemcp__interdomestik_qa__...tools throughtool_search.What works:
codex mcp get interdomestik_qa --jsonshows the server enabled.initializeworks.tools/listworks.pnpm mcp:preflightpasses.additionalProperties: false.What fails:
tool_searchreturns 0 tools forinterdomestik_qa,project_map,read_files,read_file_range, andcode_search.mcp__interdomestik_qa__project_mapappears, even after restarting Codex Desktop from the repo root.Current suspected boundary:
Codex Desktop can see/start/discover the MCP server, but the discovered tools are not admitted into the Desktop/app-server callable tool index.
Could you please clarify whether Codex Desktop currently supports exposing local custom stdio MCP servers into
tool_search? If yes, what exact config/plugin shape is required? If not, is this a known limitation or regression?This is blocking my preferred workflow because the repo MCP tools are much faster and deeper for codebase inspection than shell fallbacks.
Thank you.
I'm running into same issue, this is a huge one, I think it should have been fixed by now...
Codex CLI recognize my MCP and model can make call, but Codex app does not. /cli on codex app shown the MCP server, but model not being able to call it.
it's absurd this bug is still open after all this time. MCP is such a key feature, why is the team not fixing something like this?
I'm experiencing this issue as well.
I am seeing the same Desktop MCP discovery-to-exposure failure on a newer Codex build, with two independent custom Streamable HTTP MCP servers.
Environment:
26.707.722210.144.20.144.5~/.codex/config.tomlWhat works:
codex mcp listshows both custom servers enabled.initializesuccessfully.toolscapability.tools/listreturns their complete and valid tool definitions.What fails:
mcp__<server>__...tools are present in the thread tool registry.This appears to fail after successful server initialization and tool discovery, but before the discovered tools are admitted into the Desktop thread callable-tool index. That matches the boundary described in this issue.
The servers are primarily tool-oriented and do not advertise MCP resources, so this may overlap with:
The older #3441 appears different because its example used the obsolete
[mcp.servers]configuration shape. My configuration uses the current[mcp_servers.<name>]format and is parsed correctly by both CLI versions.So this is still reproducible on Desktop/app-server
0.144.2, well beyond the0.124.xversions originally reported here.Adding a stricter reproduction on a newer Desktop build. This server exposes both MCP resources and tools, so the failure is not limited to tool-only servers or an empty
resources/listresponse.Environment:
26.707.91948(build 5440)0.144.5~/.codex/config.tomlVerified working:
listed MCP server tools while building tool list server_name=RepoPromptCE tool_count=20Observed failure:
mcp__RepoPromptCE__*tools.tool_searchor equivalent deferred-tool retrieval bridge.ToolSearchAlwaysDeferMcpToolsenabled.Expected behavior:
When Codex defers discovered MCP tools, the task must receive
tool_search; otherwise it should inject the direct MCP tools. Successfully discovered tools must not become unreachable through both paths.This places the failure after successful
tools/listand before model-visible direct/deferred tool registration, matching the boundary described in this issue. The server configuration, process, transport, resource path, and tool discovery all remain healthy.this is the discovery-vs-registration split, and it shows up wherever tool exposure is a separate layer from tool discovery. /mcp, codex mcp list, and your preflight are all reading the same fact: a tools/list round trip succeeded. none of them read the thread's tool surface, which is the thing that actually gates a call. so every green light you have is green about the wrong layer. the useful diff is the 0.91.0 thread against the alpha one, same ListToolsResult, different thread_dynamic_tools. whatever indexes discovered tools into that surface stopped consuming the result, and nothing downstream of it is built to notice the gap.
Additional reproduction: Windows / Streamable HTTP / Codex Desktop 0.144.5
I can reproduce this with a custom Streamable HTTP MCP server for a game
I am developing. The server exposes resources and three tools for observing
and controlling the running application.
The failure does not appear to be game-specific: Codex successfully discovers
the tools but does not expose them to the thread.
Environment:
Observed behavior:
resources/list,resources/templates/list, andresources/readsucceed.tools/list.tools/callrequests to the server succeed.server_name=<custom_server> tool_count=3thread tools.
tool_searchis also unavailable in the affected thread, so the deferredtools cannot be discovered.
The discovered tools consist of:
The server exposes resources as well as tools, so this is not caused by an
empty or unsupported
resources/listimplementation.I reproduced the result in an independent fresh Codex task. It also reported
that none of the custom MCP tools was directly available, ruling out stale
tool state in an existing thread.
Additional diagnostic result:
codex features listreports:tool_search_always_defer_mcp_tools removed trueAdding the following user configuration had no effect and was removed
afterward:
tool_search_always_defer_mcp_tools = falseExpected behavior:
Either:
tool_searchmust be supplied whenever those tools are deferred.The failure appears to occur after successful
tools/listdiscovery butbefore the tools are exposed to the thread or tool-search surface.
I traced this to a concrete code-mode exposure bug and have a tested patch.
Root cause: in
code_mode_only, the planner retains only the code executor tools and tools markedDirectModelOnly. MCP tools are intentionally deferred totool_search, but the cachedtool_searchhandler itself retained normal exposure and was filtered out. The MCP server could therefore initialize and returnListToolsResult, while both access paths were unavailable: the MCP tools were deferred and the search bridge was hidden.The patch marks
tool_searchasDirectModelOnly. Deferred MCP tools remain out of the initial prompt, but the model keeps the top-level search bridge, can receive the deferred namespace, and can then call the MCP tool. I added planner coverage and an end-to-end Responses Lite test for search -> deferred MCP namespace -> tool call, and also verified the patched app-server against a real configured Streamable HTTP Jira MCP server.Review PR: https://github.com/yunusemregul/codex/pull/1
Commit: https://github.com/yunusemregul/codex/commit/dd5b8e84ca190efcd4222f2eb0460e38959a8301
OpenAI documents external Codex contributions as invitation-only, and GitHub rejected creating the PR directly against
openai/codexthrough both GraphQL and REST. The fork PR is therefore the reviewable patch; if a maintainer invites it, the same branch can be submitted upstream directly.Related reports: #14242, #10499, and #19649.
Thanks for the detailed report. I independently reproduced a closely matching Windows case and narrowed it to a specific Sol/Responses Lite tool-exposure path. I believe it belongs in this thread because MCP discovery succeeds, but the Desktop task does not receive a usable deferred tool surface.
Environment
0.144.5gpt-5.6-soluse_responses_lite: truesupports_search_tool: truetool_mode: directmulti_agent_version: v1Controlled live results
| Route | Only catalog change |
tools/list| Successful MCP call ||---|---|---:|---:|
| GPT-5.5, normal Responses | none | 1/1 | 1/1 |
| Sol, Responses Lite + deferred
tool_search| none | 3/3 | 0/3 || Sol, normal Responses + deferred
tool_search|use_responses_lite: true → false| 3/3 | 3/3 || Sol, Responses Lite + direct MCP namespace |
supports_search_tool: true → false| 2/2 | 2/2 |Every failing Sol/Lite run initialized the MCP server, received a valid one-tool
tools/listresult, and logged that tool in Codex’s inventory. None madetools/call; each returned the prompt’s exactTOOL_SEARCH_UNAVAILABLEfallback. All five matched Sol controls called the canary with the expected token.This rules out the MCP process, stdio transport, tool schema, Windows helper, Codex’s MCP invocation router, a generic Sol tool-calling limitation, and random tool choice.
Sanitized final outbound request trace
I captured the final request with the real
0.144.5executable pointed only at a loopback endpoint. No request was forwarded externally, and no raw body, header values, credentials, prompts, instructions, or tool schemas were persisted.For the affected Sol/Lite route, the request:
POST /v1/responses;x-openai-internal-codex-responses-lite;toolsfield;additional_toolsitem; andThe initial body does not contain
mcp__node_repl, but that is expected for a deferred namespace. Its metadata remains in Codex’s client-side search index and should be returned after a successfultool_searchcall.The matched wire controls further narrow the boundary:
use_responses_lite: false, the same client-executedtool_searchappears in top-leveltools, and Sol succeeds 3/3.supports_search_tool: false,additional_toolscontains the directmcp__node_replnamespace and its three child tools. Sol successfully invokes the equivalent synthetic MCP canary 2/2.Therefore, Responses Lite can expose and invoke direct MCP namespaces. The failing sub-path is specifically the client-executed
tool_searchsurface under Responses Lite.Public-source check
I checked both the installed source-equivalent commit
8c68d4cand upstreammainat9ff4786. In both versions, the Lite request builder intentionally serializes tools into a leadingAdditionalToolsitem.The existing
stdio_server_round_triptests:However, it covers only normal Responses. I added a local Lite variant that asserts the wire placement and reuses the complete search/load/invoke round trip:
Formatting also passes. This confirms that the public client serializer, client-side search index, response routing, and MCP invocation path can complete a mocked Lite exchange. It does not cover the internal service transformation that determines which tools become model-visible.
Most likely remaining boundary
Could a maintainer inspect a sanitized post-ingestion trace for one failing Sol request and confirm:
additional_toolsitem still contains{"type":"tool_search","execution":"client"}.tool_searchis registered in the model-visible tool list.AdditionalToolsconversion, or a Sol-specific allowlist.I have sanitized per-trial summaries, three matched request traces, and the focused regression-test patch available to attach.
For users, a fresh GPT-5.5 task is a reliable workaround. A fresh Sol task alone is not sufficient in the affected
direct + Responses Litecatalog cohort. Forcing non-Lite was useful as a diagnostic, not a supported configuration recommendation.Additional reproduction: official Linear app/plugin regressed immediately after Desktop update
I can reproduce the same discovery-to-exposure failure with the official curated Linear integration, rather than a custom MCP server.
Environment:
26.707.91948(build5440)0.144.5gpt-5.6-sollinear@openai-curatedpluginBefore/after boundary:
codex_appsLinearget_issuecall at2026-07-16T02:16Z, before the Desktop update.26.707.91948.Verified healthy after the update:
https://mcp.linear.app/mcpendpoint.codex mcp listandcodex mcp get linearshow the Linear MCP server enabled with OAuth.codex mcp login linearcompletes successfully.Observed failure:
codex_appsLinear tools.tool_searchbridge.codex execprobe cannot callget_issueand reports the Linear tool unavailable.unknown MCP server lineareven thoughcodex mcp list/getreport it enabled.This appears to be the same post-discovery exposure failure described above, but it also affects a first-party curated plugin whose app connection and OAuth state are healthy. The update did not merely break a custom server configuration; it made a previously working official connector unreachable from new tasks.
The current release notes say existing app connections should remain unaffected by the App Directory to Plugin Directory migration, so this behavior appears to be an unintended regression.
All identifiers, account details, local paths, OAuth data, issue content, and workspace-specific values have been intentionally omitted.
it's really crazy that a bug of this magnitude goes ignored for so long considering the development is done primarily by agents. What is going on? Don't OpenAI have auth tests for MCP in the testing harness? I have that in my own hand rolled framework!
please contribute to the discussion https://github.com/openai/codex/discussions/33682 to raise visibility with the team. The MCP bug reports are very clearly being ignored right now.
Additional reproduction: Solo orchestration MCP / resources visible, action tools unreachable
I can reproduce this with Solo's local stdio MCP server. This is especially confusing because Solo correctly reports Codex as connected, and Codex can use Solo's resource APIs, while Solo's orchestration/action tools remain unreachable.
Environment:
0.144.5gpt-5.6-sol, medium reasoning0.9.3Verified working:
codex mcp get soloshows the server enabled.server="solo".resources/listreturns Solo scratchpads and todos.tools/listround trip returns Solo's complete action-tool catalog, including:whoamilist_agent_toolsspawn_agentsend_inputget_process_outputObserved failure:
mcp__solo__...action tools.tool_searchbridge.list_agent_tools,spawn_agent,send_input, or timers even though Solo is connected and advertises them successfully.Restart/update test:
0.144.4.0.144.5.codex features listreports:tool_search_always_defer_mcp_tools = falsetoconfig.tomlhas no effect on the reported effective state.Expected behavior:
Either Solo's discovered action tools should be injected directly, or the thread must receive
tool_searchso the deferred tools can be loaded. A healthy connection plus resource access should not leave every action tool inaccessible.This matches the
code_mode_only/DirectModelOnlyroot cause and proposed patch described in the comment above.Analysis (community)
Residual analysis for #19425 (custom stdio MCP discovered by
/mcpbut tools not exposed to Desktop threads) under docs/contributing.md — invitation-only. Complements our earlier residual note on #32590 (OAuth refresh path); this is a different layer. No unsolicited PR.Root-cause hypothesis
/mcpand the thread's model-visible tool surface use different connection sets and different failure modes:/mcpcan fall back to a tool catalog cache, whilecapture_binding_with_metadatasilently skips servers whose client is not ready and never falls back. Whensearch_tool_enabledis true, MCP tools are also marked Deferred, so a skipped server is missing from both initial specs andtool_search.Tracing the exposure path at HEAD (
4c43465133) to add a code-anchored complement to the OAuth-refresh thread (#32590). I believe there are two distinct bugs here, and only one is token-refresh.Discovery->exposure gap, separate from auth. The
/mcpstatus path and the Desktop thread's model-visible tool list read from different connection sets./mcp(codex-rs/codex-mcp/src/mcp/mod.rs:406-429) builds a freshMcpConnectionSetand callslist_all_tools(connection_manager/tool_catalog.rs:60-125), which falls back to the 30-minMcpToolCatalogCache(rmcp_client.rs:526-539) and skipstool_is_model_visible. The thread's binding (connection_manager/tool_catalog.rs:131-217,capture_binding_with_metadata) requiresclient.client().awaitto beOk, silentlycontinues onErrwith no cache fallback (:149-151), and drops non-model-visible tools (:185-186). So a server that/mcplists can be silently absent from the thread - exactly the symptom arbenl, xy6859, dionisokan report, with healthy OAuth (and, for several reporters, no OAuth at all - stdio).Deferral compounds it.
mcp_tool_exposure.rs:35-39marks every MCP toolDeferredwhensearch_tool_enabledis true (spec_plan.rs:333-335), so even tools in the binding are omitted from the initial specs (spec_plan.rs:258-268) and reachable only viatool_search- whose index is built from that same per-thread slice (spec_plan.rs:971-976). If the server was silently skipped,tool_searchis empty too, and the tool is unreachable by either path. Woudart's A/B here (supports_search_tool: true->false, 0/3 -> 2/2) is direct causal evidence that deferral is the proximate cause.c53b1dae09(#29486) is the version-boundary commit; the--enable/--disableflags areStage::Removedno-ops (features/src/lib.rs:491-493), which matches dionisokan's finding.Suggested minimal fix: (1) give
capture_binding_with_metadatathe samelisted_toolscache fallbacklist_all_toolsalready has, for stdio servers; (2) whensearch_tool_enabledis true but the deferred-tool search index is empty, fall back toDirectexposure for those namespaces (or emit atools_changedevent) instead of silently swallowing them; (3) emit an exposure-failure event on every silent skip so/mcpand the UI can reconcile - this would also address #26196 (tools disappear after reconnect) and give #16607's resume path an invalidation hook.Fail-first tests I'd add: custom stdio tool appears in
/mcpbut is absent from the thread's model-visible specs; long-running thread drops a tool after server reconnect with no event; deferred tool unreachable when search index is empty.Separately - the frustration voiced in discussion #33682 is fair. The MCP path is under active refactoring (25+ commits May-Jul 2026, including the
McpRuntimecentralization #34930 andMcpConnectionSetsplit #34708), so this is a good moment to land the parity fix before the divergent code paths get baked in further. If the outline aligns, would an invited PR with the fail-first tests above be welcome? I will not open an unsolicited PR.Cross-links (related residual, not duplicates of this path): #30922, #30343, #26196, #16607, #33593; OAuth-refresh residual remains #32590.
Happy to refine. No unsolicited PR from me.
Additional reproduction: Windows / Streamable HTTP / PAT-authenticated remote MCP
I can reproduce the same discovery-to-exposure failure on Windows with the official remote GitHub MCP server.
Environment and configuration:
26.727.51351..codex/config.tomlinitially contained two MCP servers:bearer_token_env_var = "GH_TOKEN".Control comparison:
GitHub MCP verification outside the Desktop tool manifest:
codex mcp listandcodex mcp get githubreport the server enabled withtransport = streamable_httpandbearer_token_env_var = GH_TOKEN.initializewith HTTP 200.tools/listsucceeds.get_mesucceeds and returns the expected GitHub account.search_issuescall succeeds and returns results.The GitHub entry was then moved from the project config to the global
~/.codex/config.toml, followed by a full Codex Desktop restart and a fresh thread. The result did not change:codex mcp liststill reports GitHub enabled and authenticated by bearer-token binding.search_issues,issue_read,get_me, or repository tools are callable from the Desktop thread.This separates the failure from:
tools/listdiscovery;The observed boundary remains:
The Gramps control is important: an unauthenticated project MCP is exposed normally, while the authenticated remote HTTP GitHub MCP is not. This looks like a Desktop/app-server exposure or admission problem for this remote/authenticated MCP path, rather than a generic project-config problem.
No credentials, token values, or private repository data are included here.
Human context and attribution
This report was prepared collaboratively by Semyon Poklad and Lad, an AI engineering agent, based on a user-requested reproduction and verification.
The practical impact is larger than this single missing tool namespace. We have repeatedly had to fall back to wrappers, shell probes, browser routes, and manual transport checks because the native Codex GitHub integration and the Codex Desktop MCP tool surface do not provide a reliable path to ordinary development operations.
GitHub MCP itself works when called through a direct MCP client, and another AI client in the same environment is able to use comparable GitHub/MCP functionality natively. The failure is specifically in the Codex Desktop integration: the server is configured, authenticated, discovered, and listed by
codex mcp, but its tools are still unavailable to the active Desktop thread.This creates disproportionate user friction around a basic development capability: reading and updating issues and repositories. After enough cycles of configuration checks, wrappers, restarts, and fallback routes, users can lose track of the actual engineering task entirely.
We are asking for a reliable end-to-end contract:
Attribution: prepared collaboratively by Semyon Poklad and Lad, an AI engineering agent. The initial report was published before the final manual review step; this addendum is being submitted to correct the attribution and explain the user impact.
Open Design is listed as enabled by
codex mcp list, but@open-designdoes not appear in Codex Desktop's mention picker after a full app restart.
Open Design 0.17.0; Codex Desktop 26.727.51351; codex-cli 0.145.0.
@TeaShaman-cyber's control comparison is the most useful new datum here: two Streamable HTTP servers in the same trusted project, one visible in the Desktop thread (Gramps), one not (remote GitHub). Transport and auth are therefore not the discriminator, which fits the binding-layer skip traced above rather than a GitHub/PAT problem.
Recap of that split:
capture_binding_with_metadata(codex-rs/codex-mcp/src/connection_manager/tool_catalog.rs:131-217) requiresclient.client().awaitto beOkand silentlycontinues onErr(:149-151) with no cache fallback, while/mcpandcodex mcp listgo throughlist_all_tools, which does fall back to the 30-minMcpToolCatalogCache(rmcp_client.rs:526-539). A server that is enabled, authenticated and listed can therefore be absent from the thread — which is exactly what both of you are seeing.@rkcrawf-lang's mention-picker symptom is the same slice: the picker is built from the per-thread binding, so a server skipped there never reaches it.
To separate the skip from the deferral half, both of you can A/B
supports_search_tool: true → false. Woudart's earlier A/B (0/3 → 2/2) isolates deferral (mcp_tool_exposure.rs:35-39,spec_plan.rs:333-335). If the tools appear with search off, it is deferral; if they still do not, it is the silent skip.Still no unsolicited PR — the parity fix outlined above (cache fallback in the binding, plus an exposure-failure event on every silent skip) is available if a maintainer wants it.
Current macOS reproduction on a newer Desktop/App Server build (2026-08-07).
Environment
26.803.41515codex-cli 0.147.0-alpha.6.5Observed
initializeandtools/listcomplete successfully, returning 47, 30, 1, 1, and 2 tools respectively.mcp__<server>__*namespaces are present in the effective tool registry.node_replserver are exposed in the same thread. The missing set is specifically the user-configured external STDIO servers.features.code_mode.excluded_tool_namespacesandfeatures.code_mode.direct_only_tool_namespacesare both empty.This also reproduced on the previous Desktop/App Server combination:
26.730.616390.147.0-alpha.1.2Control comparison on that build:
codex execusing the same bundled binary and shared MCP configuration successfully called a read-only tool from one of the STDIO servers with bothgpt-5.6-solandgpt-5.5.gpt-5.5still returned zero matching tools for the same namespace, including an exact namespace selection.This rules out MCP protocol health, downstream authentication, the selected model, stale thread state, and an explicit namespace exclusion. The remaining failure boundary is still between successful MCP catalog discovery and registration of those tools in the Desktop thread's effective/direct-or-deferred tool surface.
Expected
After an enabled STDIO MCP server completes
tools/list, its tools should either be callable directly, discoverable through the deferred tool mechanism, or accompanied by a visible exposure error explaining why they were rejected.No credentials, command arguments, environment values, internal service names, or private payloads are included in this report.
Follow-up: Windows AppX / CLI control test
I ran a bounded reproduction on Windows with the current Codex Desktop Store/AppX build
26.730.8199.0and the current global Codex configuration.Observed:
.envcontains a workingGH_TOKEN; the token value was loaded only into a child process and was not printed or persisted;gh repo view TeaShaman-cyber/theseus-researchsucceeds as a read-only control;initialize, establishes a session, and completestools/list;codex mcp get githubreports the server as enabled withtransport = streamable_httpandbearer_token_env_var = GH_TOKEN;codex exec --ignore-user-configrun with only the GitHub MCP server and an explicitly injectedGH_TOKENsuccessfully performs one read-onlysearch_repositoriescall;codex execrun without explicit environment injection reportsGH_TOKEN not set, showing that the project-root.envis not automatically loaded into the MCP child process;mcp__github__*tools in its native tool manifest;This separates the working parts from the remaining failure boundary:
The remaining problem is therefore not GitHub authorization, the GitHub MCP endpoint, MCP initialization,
tools/list, or stdio transport. It is the Desktop/AppX registration or exposure path, with launch-time environment inheritance as a separate possible factor.A plain restart with unchanged configuration and unchanged launch conditions is not a meaningful control for this reproduction. We are currently using the working CLI route as the operational fallback.
Follow-up: Current AppX build still reproduces the exposure gap
After Codex Desktop updated, I fully restarted the application and repeated the bounded Windows reproduction.
Current environment:
26.803.5235.0;ChatGPT.exeand bundledcodex.exeare loaded from the new AppX package path;bearer_token_env_var = "GH_TOKEN";.env, WSL environment, or wrapper was changed for this comparison.Observed after the update and restart:
.envstill provides a workingGH_TOKENwhen explicitly loaded into a child process;initializeandtools/list;GH_TOKENis explicitly present in the child environment;codex mcp get githubstill reports the server as enabled;OK, with no unavailable-server events;mcp__github__*tools in the native thread tool manifest.This update/restart therefore does not change the observed boundary:
configured → authenticated → initialized → tools/listsucceeds, butDesktop thread tool exposurestill fails.The reproduction now covers a newer AppX build and a fresh process, so the missing GitHub namespace is not explained by a stale pre-update Desktop process. The working CLI route remains available as the operational fallback.
@TeaShaman-cyber thanks — the AppX
26.803.5235.0restart control is the right pin.Your split still matches the earlier binding-layer read:
configured → authenticated → initialize → tools/list(and CLI tool call) PASS, but Desktop thread tool exposure ofmcp__github__*still FAIL on a fresh process.So this is not a stale Desktop session and not GitHub auth/transport. Launch-time env inheritance for
bearer_token_env_varremains a secondary factor for auto-spawned MCP children; the primary gap is still the Desktop thread registration/exposure path. CLI fallback remains the workable route until that lands.Control comparison after restart: Gramps exposed, GitHub unavailable
After a full restart of the current Codex Desktop build (
26.803.5235.0), the live Gramps MCP control became available in the same Desktop context:mcp__family_gramps__*tools appeared in the native manifest;mcp__family_gramps__get_metadatacall succeeded;githubinunavailable_servers.This control comparison rules out a generic inability to refresh the Desktop manifest after restart. The refreshed Desktop can expose and call one external MCP server, while the configured GitHub MCP server remains unavailable. The remaining boundary is therefore GitHub-specific admission, authentication, transport handling, or exposure rather than a universal stale-manifest problem.
No credentials or private payloads are included here.
@TeaShaman-cyber the Gramps vs GitHub control after the same restart is the decisive pin:
mcp__family_gramps__*tools in the native manifest + live call OKunavailable_serversThat rules out a universal stale-manifest / “Desktop never re-binds external MCPs” story. Remaining failure is GitHub-specific admission (auth/env inheritance for
bearer_token_env_var, transport handling, or exposure), not Desktop MCP plumbing in general. CLI path with explicitGH_TOKENremains the working fallback.