[BUG] ChatGPT mobile app fails to render MCP App widget when the widget-linked tool is called after another tool in the same assistant turn (regression, ~July 10)

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.

Yes we have observed the exact same behaviour on all apps!
We have been using the recommended search then render tool call flows which is now breaking mobile rendering.

I am observing the same behaviour with my app Square Deal. I need to make two successive tool calls to keep them atomic. But this doesn’t work in mobile. Works fine on the desktop. Hope OpenAI provides a fix soon.

We can independently confirm the same regression using our locally installed, unpublished MCP plugin, SkillPilot Coach v1.

Our flow performs two stateless MCP tool calls within a single assistant turn:

  1. get_skillpilot_context — no UI metadata

  2. render_skillpilot_goal_visualization — includes _meta.ui.resourceUri

On August 3, 2026, our server recorded both calls completing successfully:

08:57:59 UTC  get_skillpilot_context                   success
08:58:07 UTC  render_skillpilot_goal_visualization    success

In the native ChatGPT mobile app, the widget then remained indefinitely on ChatGPT’s loading skeleton. No subsequent resources/read request reached our MCP server.

The same local plugin and visualization render correctly in ChatGPT Web, including in a mobile browser. The ordinary textual response also remains usable in the native app.

This is therefore another independent instance of the sequence described in the original report:

non-UI tool → UI-linked render tool within the same assistant turn

One caveat: the absence of an origin-side resources/read request does not establish exactly where resolution stopped. OpenAI-side caching or a failure before MCP dispatch remain possible.

Two related questions:

  • Is there a supported client-capability signal that an MCP plugin can use to avoid this chained UI call on affected native clients?

  • Is there a documented way to ensure that the host removes an unresolved widget shell and cleanly preserves the complete textual fallback?

The robust failure mode should be either to render the widget or to fall back to text without leaving an indefinite host-owned loading skeleton.