[MCP Apps] Nested MCP calls inside functions.exec do not create renderable widget items
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(build6119) - 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
- Open a Codex Desktop task.
- Invoke the Apple Music App and ask it to generate a playlist.
- Codex calls
apple_music_get_track_details_batchthroughfunctions.exec. - The Apple Music request succeeds and returns matched catalog tracks.
- Observe that no Apple Music card is rendered in the conversation.
Actual behavior
The MCP App call succeeds:
structuredContentcontainsbodyandstatus.- Tool-result
_metacontainsappleweb/html. - The result contains a
createPlaylistAction. - The widget descriptor contains both
_meta.ui.resourceUriand_meta["openai/outputTemplate"]. mcpServer/resource/readsucceeds for the referenced widget resource.
However:
- The rollout contains a top-level
custom_tool_callforfunctions.exec. - The Apple Music request appears only as a nested
mcp_tool_call_endevent. - No top-level
McpToolCall/ThreadItem::McpToolCallis 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:
- Be promoted to a renderable top-level MCP tool-call item carrying the App resource context; or
- Have its complete MCP result envelope forwarded through
functions.execto the widget renderer.
The forwarded data should preserve at least:
contentstructuredContent- top-level
_meta ui.resourceUriopenai/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.
3 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
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:
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:
Preserving the complete nested CallToolResult envelope—or otherwise making its structuredContent model-visible—should address this non-UI reproduction as well.
Reproduced on Windows with a custom remote MCP App, adding a second transport/platform data point.
Environment:
26.818.3698.0, Windows x640.145.0apps = trueenable_mcp_apps = trueThe MCP server exposes
balance_get_stockswith an MCP App resource (_meta.ui.resourceUri) and matchingtext/html;profile=mcp-appresources. The same deployed endpoint renders its workspaces in the official@modelcontextprotocol/ext-appsbasic host.In Codex Desktop, a successful call routed through
functions.execreturned 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.