On the ChatGPT mobile app, a widget from an MCP Apps (Apps SDK) tool is not rendered when the model invokes the widget-linked tool chained after another tool call within the same assistant turn. The exact same tool, template, and metadata render correctly when the tool is (a) the first/only tool call of a turn, or (b) invoked in a separate, later user turn. ChatGPT desktop/web renders the chained case correctly. This breaks the common “compute tool → render tool” two-phase pattern (used to keep large widget payloads out of model context). Our app used this pattern and rendered fine on mobile until approximately 2026-07-10, when mobile rendering stopped while desktop continued to work — so this appears to be a client-side regression around that date.

Environment

  • ChatGPT mobile app: [iOS/Android], app version 1.2026.183 (IOS)
  • Working comparison: ChatGPT web/desktop, same account, same connector
  • MCP server: Spring AI 2.0.0 MCP server (streamable HTTP, stateless), developer-mode connector
  • Widget resource: text/html;profile=mcp-app (MCP Apps spec 2026-01-26), linked via _meta: {“ui”: {“resourceUri”: …}} on the tool

Minimal reproduction setup

An MCP server with two tools and one UI resource:

  1. ToolA (compute) — no widget meta; returns a normal result
  2. ToolB (render) — _meta: {“ui”: {“resourceUri”: “ui://simple-mcp/widget.html”}}; returns a simple text result.
  3. Resource ui://simple-mcp/widget.html — mimeType: text/html;profile=mcp-app, a self-contained HTML page using @modelcontextprotocol/ext-apps (App, ontoolresult, connect()), _meta: {“ui”: {“csp”: {“resourceDomains”: […]}}}.

The tool descriptions instruct the model to call ToolA first and then ToolB (ToolA’s response contains a nextAction field telling the model to call ToolB).

Reproduction steps and results (all on mobile, same connector, same conversation state, fresh chats)

Variables ruled out (each tested in isolation on mobile; none changed the outcome of case #1):

  • Template content: even pointing ToolB’s ui.resourceUri at a widget template that demonstrably renders on mobile (our working single-tool widget’s own template) fails in the chained case.
  • Tool result shape: ToolB returning a plain string (content text only, isError: false, no structuredContent, no _meta) — byte-equivalent in structure to the working single-tool’s result — still fails when chained.
  • Resource metadata: identical _meta/CSP/MIME as the working widget.
  • Resource MIME (text/html;profile=mcp-app vs legacy), CSP resourceDomains variants, fully inlined single-file HTML vs external assets — all previously tested during a one-week investigation; none affect the chained case.

Expected behavior

A tool call whose _meta.ui.resourceUri points to a valid text/html;profile=mcp-app resource should render its widget regardless of whether the call is the first tool call of the assistant turn or follows a preceding tool call, matching desktop/web behavior.

Actual behavior

On mobile, the widget is silently dropped whenever the widget-linked call follows another tool call in the same assistant turn. No error is surfaced; the conversation continues with text only. (The widget area shows only ChatGPT’s own loading skeleton indefinitely.)

Impact

Any app using the two-phase compute→render pattern is unusable on mobile while appearing healthy on desktop — the failure is silent and extremely hard to attribute. We spent a week eliminating MIME types, _meta variants, CSP, and template hosting before isolating the turn-position dependency.