[MCP Apps] Nested MCP calls inside functions.exec do not create renderable widget items

Open 💬 3 comments Opened Aug 3, 2026 by Ethereal49
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

Summary

Codex Desktop successfully calls an MCP App tool and receives valid widget metadata, structured content, and HTML. However, when the MCP call is nested inside functions.exec, the thread records only the outer custom tool call.

The nested MCP result does not become a top-level McpToolCall thread item, so the widget renderer has nothing to mount. The result is serialized as text instead of being displayed as an interactive MCP App card.

This was reproduced with the OpenAI-curated Apple Music connector.

Environment

  • Codex Desktop: 26.727.51351 (build 6119)
  • Bundled Codex CLI: 0.146.0-alpha.9.2
  • Platform: macOS, Apple Silicon
  • Connector: Apple Music

No account identifiers, thread identifiers, local paths, playlist contents, or raw logs are included in this report.

Steps to reproduce

  1. Open a Codex Desktop task.
  2. Invoke the Apple Music App and ask it to generate a playlist.
  3. Codex calls apple_music_get_track_details_batch through functions.exec.
  4. The Apple Music request succeeds and returns matched catalog tracks.
  5. Observe that no Apple Music card is rendered in the conversation.

Actual behavior

The MCP App call succeeds:

  • structuredContent contains body and status.
  • Tool-result _meta contains appleweb/html.
  • The result contains a createPlaylistAction.
  • The widget descriptor contains both _meta.ui.resourceUri and _meta["openai/outputTemplate"].
  • mcpServer/resource/read succeeds for the referenced widget resource.

However:

  • The rollout contains a top-level custom_tool_call for functions.exec.
  • The Apple Music request appears only as a nested mcp_tool_call_end event.
  • No top-level McpToolCall / ThreadItem::McpToolCall is created.
  • No widget renderer or widget-data initialization event follows.
  • The HTML/result envelope is surfaced as ordinary text.
  • No interactive card appears.

Sanitized event shape:

custom_tool_call
  name: exec
  └── nested mcp_tool_call_end
        server: codex_apps
        tool: apple_music_get_track_details_batch
        result: Ok
        structuredContent: [body, status]
        _meta: [appleweb/html]

There is no corresponding top-level event shaped like:

McpToolCall
  appContext.resourceUri: ui://widget/get-track-details-batch.html

Expected behavior

A successful nested MCP App result should either:

  1. Be promoted to a renderable top-level MCP tool-call item carrying the App resource context; or
  2. Have its complete MCP result envelope forwarded through functions.exec to the widget renderer.

The forwarded data should preserve at least:

  • content
  • structuredContent
  • top-level _meta
  • ui.resourceUri
  • openai/outputTemplate
  • App/widget context and action-bridge identifiers
  • tool ID and call ID

The Apple Music widget should then render as an interactive card.

Why this appears to be a Codex transport issue

The connector and resource both appear healthy:

  • The catalog call succeeds.
  • The widget descriptor is present.
  • The widget HTML resource can be read successfully.

The failure occurs after the nested MCP result is returned but before Codex creates the UI/thread item required by the renderer.

Retrying the operation does not change this event shape.

Related issues

  • #33717 — Deferred MCP tool calls drop structuredContent / _meta
  • #21019 — Codex Desktop does not render MCP Apps inline UI resources
  • #29210 — Canva MCP App succeeds but returns no HTML preview

This may share the result-envelope loss described in #33717, but this reproduction differs because no usable card is mounted at all: the nested MCP call never becomes a top-level renderable thread item.

Suggested fix

Preserve nested MCP App results as first-class MCP call records instead of flattening them into the outer functions.exec text result.

Alternatively, add explicit support in the Codex client for forwarding nested MCP App result envelopes into the widget-rendering pipeline.

View original on GitHub ↗

3 Comments

github-actions[bot] contributor · 24 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #36545

Powered by Codex Action

TyceHerrman · 16 days ago

reproduced the same nested-result boundary with the official GitHub connector without relying on widget rendering. The connector call succeeded and its raw result contained:

  • content: only the placeholder "Action completed."
  • structuredContent: the correct GitHub search results

After the nested MCP call returned through functions.exec, the model saw only the placeholder and concluded that no usable results were returned. This suggests the impact is broader than missing MCP App cards: ordinary connector data can also become invisible at the nested-result forwarding boundary.

Related implementation evidence:

  • Cloudgeni-ai/opengeni#168 fixed the exact response shape by forwarding structuredContent into model-visible content.
  • openai/openai-agents-js#1380 added a full-result API preserving content, structuredContent, _meta, and isError.

Preserving the complete nested CallToolResult envelope—or otherwise making its structuredContent model-visible—should address this non-UI reproduction as well.

sepivip · 7 days ago

Reproduced on Windows with a custom remote MCP App, adding a second transport/platform data point.

Environment:

  • Codex Desktop (Microsoft Store): 26.818.3698.0, Windows x64
  • Codex CLI: 0.145.0
  • MCP transport: Streamable HTTP
  • Authentication: OAuth
  • apps = true
  • enable_mcp_apps = true

The MCP server exposes balance_get_stocks with an MCP App resource (_meta.ui.resourceUri) and matching text/html;profile=mcp-app resources. The same deployed endpoint renders its workspaces in the official @modelcontextprotocol/ext-apps basic host.

In Codex Desktop, a successful call routed through functions.exec returned the complete stock result as JSON text, but no interactive widget was mounted. The user-visible result was the flattened outer custom-tool output rather than an MCP App card.

This reproduces the issue with a third-party Streamable HTTP/OAuth server on Windows, rather than a first-party connector on macOS. No credentials, tenant identifiers, or business data are included here.