MCP App updateModelContext silently dropped for live-rendered iframes (works after page refresh)

Bug Report: ui/update-model-context silently dropped for live-rendered iframes in ChatGPT MCP Apps

Summary

When an MCP App calls ui/update-model-context (via the @modelcontextprotocol/ext-apps SDK or raw postMessage), ChatGPT acknowledges the JSON-RPC request with a success result ({}), but the context is silently dropped and never surfaced to the model on the next user turn.

This failure occurs whenever the iframe is rendered live as part of the active chat (i.e., in direct response to a tool call). It is not limited to the first iframe in a session — every iframe instantiated through normal chat surfacing is affected, including subsequent calls to the same tool. The only iframes that do work are those rendered from rehydrated chat history (see Workarounds below).

Steps to Reproduce

  1. Start a new chat in ChatGPT.

  2. Trigger an MCP tool that returns a UI resource (ui://...).

  3. The iframe loads and connects via the SDK.

  4. The app calls app.updateModelContext({ content: [...], structuredContent: {...} }).

  5. The host responds with a successful JSON-RPC result (no error).

  6. The user sends a follow-up message asking the model about the context that was just pushed.

  7. Result: The model has no knowledge of the pushed context. Furthermore, all subsequent calls to updateModelContext during this session are also silently ignored by the model.

Workarounds that “fix” the issue (Confirming it’s a host-side state bug)

The context is successfully surfaced to the model if any of the following conditions are met:

  1. Other MCP Hosts: The exact same MCP server and UI code works flawlessly on first load in other compliant MCP App hosts (e.g., MCPJam Inspector).

  2. Rehydrated Chat History: If the app is used in an older chat, or if the user simply refreshes the ChatGPT browser tab (rehydrating the chat history) and then interacts with the app, updateModelContext works perfectly.

  3. DevTools Open During Render: If Chrome DevTools is open while the iframe is initially rendering, the context updates work. (Opening DevTools after the initial render does not fix it).

What we’ve ruled out (Client-side troubleshooting)

We spent significant time debugging the iframe client to rule out implementation errors:

  • Not an SDK bug: We bypassed @modelcontextprotocol/ext-apps entirely and sent raw ui/update-model-context JSON-RPC messages via window.parent.postMessage. The behavior is identical.

  • Not a timing/delay issue on our end: We tried delaying the updateModelContext calls (using setTimeout and requestIdleCallback for up to 5 seconds after initialization). The host still silently drops the context.

  • Not a capability issue: The host explicitly advertises updateModelContext: true in its capabilities during the ui/initialize handshake.

  • Not a legacy protocol issue: We verified via postMessage interception that the host is exclusively using the ui/* namespace, not falling back to legacy openai/* methods.

Hypothesis

There appears to be a race condition or state-binding failure in ChatGPT’s host implementation during the live render of any MCP App iframe inside an active chat turn.

When the iframe sends its ui/initialize or ui/notifications/initialized handshake during a live tool-call render, the host’s internal plumbing that binds the iframe’s updateModelContext channel to the active model context is not yet ready (or is being torn down/replaced as the assistant turn streams). Because the host still returns a success result to the RPC call, the app has no way of knowing the data was dropped.

The DevTools workaround suggests that slowing down the iframe’s initial execution allows the host’s binding logic to complete before the handshake occurs. The rehydration workaround (page refresh, older chats) suggests that the host follows a different, working code path when restoring iframes from saved chat history versus instantiating them live.

Request

Could the team investigate the lifecycle binding of the updateModelContext channel for iframes that are instantiated live during an active chat turn (as opposed to those restored from chat history)? A reliable way for the app to know the channel is actually bound — or deferring the JSON-RPC success response until it is — would prevent this silent failure.

Adding another data point because this is the closest match I have seen to the failure we are debugging.

In our app, the iframe sends a tiny selection-first context payload through `ui/update-model-context`. The browser console shows the outbound JSON-RPC message, the payload contains the current selected phrase/reference, and ChatGPT ACKs it with `{}`. Network activity follows the update. From the app side, the update was sent and accepted.

The failure pattern is slightly different from “every live iframe fails immediately”, but it still points at the same lifecycle/binding class of bug:

  • after a full ChatGPT page reload, the first context-dependent question usually works;
  • sometimes the second context-dependent question works too;
  • after repeated selection/context updates, the model starts behaving as if the latest context was not attached;
  • it may say it cannot see the selection, answer from stale context, or answer using an older page/selection.

The cross-surface split is important:

  • Claude web works with the same app interaction model;
  • ChatGPT Android works much more reliably in my testing;
  • ChatGPT web, macOS desktop, and Windows desktop are unreliable.

This also appears to be a recent regression for our app. The selection-to-follow-up-question flow worked reliably before; the regression has been observed over roughly the last two weeks.

We tried to remove likely app-side confounders. Passive viewport context does not overwrite explicit selection, adjacent rendered pages publish their actual page data, the model context is small and selection-first, and app-side instrumentation records the latest published context. We can prove outbound update plus `{}` ACK; we cannot inspect what context ChatGPT actually stored or attached to the next model turn.

Your DevTools/rehydration observations fit what we are seeing: the ACK seems to mean the host accepted the RPC message, not that the iframe’s context channel is actually bound to the next model request. A useful fix would be to delay the success ACK until the context is actually attachable, expose a way for the app to inspect the currently stored/attached model context, or emit a real warning/error when the update cannot be attached.