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
Start a new chat in ChatGPT.
Trigger an MCP tool that returns a UI resource (
ui://...).The iframe loads and connects via the SDK.
The app calls
app.updateModelContext({ content: [...], structuredContent: {...} }).The host responds with a successful JSON-RPC result (no error).
The user sends a follow-up message asking the model about the context that was just pushed.
Result: The model has no knowledge of the pushed context. Furthermore, all subsequent calls to
updateModelContextduring 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:
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).
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,
updateModelContextworks perfectly.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-appsentirely and sent rawui/update-model-contextJSON-RPC messages viawindow.parent.postMessage. The behavior is identical.Not a timing/delay issue on our end: We tried delaying the
updateModelContextcalls (usingsetTimeoutandrequestIdleCallbackfor up to 5 seconds after initialization). The host still silently drops the context.Not a capability issue: The host explicitly advertises
updateModelContext: truein its capabilities during theui/initializehandshake.Not a legacy protocol issue: We verified via
postMessageinterception that the host is exclusively using theui/*namespace, not falling back to legacyopenai/*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.
Discussion in the ATmosphere