Bug Report: `ontoolresult` not re-sent to MCP App after page refresh

When using the MCP Apps SDK (@modelcontextprotocol/ext-apps), the ontoolresult notification is only fired once when the tool initially completes. If the user refreshes the page, the iframe reloads and reconnects to the host, but the host never re-sends the last tool-result notification. This leaves the widget stuck on a loading state with no way to recover its previous view.

Steps to Reproduce

  1. Create an MCP App that uses ontoolresult to receive structured content and render UI
    (e.g., a flashcard study widget)
  2. Trigger a tool call (e.g., create-deck) the widget receives ontoolresult and
    renders correctly
  3. Refresh the page (Cmd+R / F5)
  4. The iframe reconnects to the host, but ontoolresult never fires again
  5. The widget is stuck showing a loading spinner indefinitely

Expected Behavior

After a page refresh, the host should re-send the most recent tool-result notification to
the reconnected app so it can restore its UI state.

This is similar to how onhostcontextchanged provides the current host context on
reconnection, ontoolresult should replay the last result so apps don’t need to implement
their own persistence layer just to survive a refresh.

Current Workaround

Persist the last toolOutput and viewUUID to localStorage manually, and restore from
it on mount. This works but shouldn’t be necessary.

Environment

  • @modelcontextprotocol/ext-apps v1.2.0
  • ChatGPT host
  • Tested in Chrome & Safari
1 Like

Is this a bug or is it meant to be this way? @coreyching @OpenAI_Support

Hey everyone,

Thanks for raising this. I reviewed the behavior and this looks like expected SDK/host behavior, not a platform bug.

ontoolresult is emitted as part of the tool execution flow. After a page refresh/iframe reconnect, the host does not guarantee replay of the last prior tool-result event. Instead, widgets are expected to rehydrate from persisted state/tool output.

Official docs that align with this:

State management / rehydration expectations: Managing State

Bridge notifications and compatibility behavior: MCP Apps in ChatGPT

Tool result lifecycle during execution: Build your MCP server

So if the widget is stuck loading after refresh, that usually means the app is depending only on a one-shot ontoolresult instead of restore paths.

What you can do to resolve it:

  • On widget init, read from window.openai.toolOutput (or equivalent hydrated output) before waiting for new events.
  • Persist minimal UI state with setWidgetState and restore from widget state on mount.
  • Keep a backend source of truth for critical data (deck/session/result IDs) and re-fetch on reconnect.
  • Make ontoolresult additive/idempotent: treat it as an update, not initial state bootstrap.
  • Add a refresh-safe fallback flow: If no fresh event within N ms, recover from persisted state/backend. Show “restoring…” and retry controls instead of infinite spinner.
  • Add a regression test for tool -> refresh -> iframe reconnect -> restored UI path.

Hope the above helps. Thank you!

1 Like