Skip to content

MCP App Migration: UI fails to persist/re-render structuredContent on chat revisit #195

@akanellopFH

Description

@akanellopFH

Description

When migrating from the legacy OpenAI App SDK to the Model Context Protocol (MCP) Apps implementation, there is a significant regression in how UI components handle historical data.

In the legacy App SDK, revisiting a chat session allowed the widget to reliably maintain or re-access tool results. In the new MCP implementation, when a user exits and revisits a chat, the App component re-mounts in its initial state, but the ontoolresult event does not fire for the existing data. This leaves the UI in an empty or "loading" state despite the data existing in the chat history.

Steps to Reproduce

Trigger an MCP App tool call that returns structured data (e.g., a carousel of items).

Observe the App rendering correctly via the ontoolresult handler during the active session.

Navigate away from the chat or refresh the browser.

Return to the same chat session.

Expected Behavior

The App should be able to re-hydrate its state from the existing chat context. This could be achieved by:

The host re-emitting the ontoolresult event for the most recent tool output upon the App mounting.

Providing the historical structuredContent within the app instance or useApp hook during initialization.

Actual Behavior

The App component mounts with its initial React state (isLoaded: false). Although the ontoolresult listener is successfully attached in onAppCreated, it is never triggered for historical data. Consequently, the UI (e.g., the trips state) remains empty, and the carousel fails to render.

Technical Details

SDK: MCP Apps

Context: React implementation using useApp

Regression: This behavior was not present when using the legacy openai.toolInput methods.

Tested on developer's mode, and on published mode

Code Snippet

TypeScript
const { app, error } = useApp({
  appInfo: { name: "TripsCarousel", version: "1.0.0" },
  capabilities: {},
  onAppCreated: (app) => {
    // This fires correctly on the initial call, 
    // but is never invoked when revisiting the chat session.
    app.ontoolresult = (result) => {
      handleData(result.structuredContent);
    };

    app.onhostcontextchanged = (ctx) => {
      if (ctx.theme) setTheme(ctx.theme);
    };
  },
});

Old Snippet:

useEffect(() => {
    const applyToolOutput = (data?: ToolOutput | null) => {
      const directItineraries = data?.foundDirectItinerariesForTrip;
      const isArrayReturned = Array.isArray(directItineraries);

      setTrips(isArrayReturned ? directItineraries : []);
      setResponse(isArrayReturned);
      setIsLoaded(true);
    };

    // Check if data already available
    applyToolOutput(window.openai?.toolOutput);

    // OpenAI Apps SDK: globals updated event
    const onSetGlobals = () => {
      const toolOutput = window.openai?.toolOutput;
      if (toolOutput) applyToolOutput(toolOutput);
    };

    window.addEventListener("openai:set_globals", onSetGlobals);
    return () => window.removeEventListener("openai:set_globals", onSetGlobals);
  }, []);

Comparison

Legacy OpenAI SDK: Correctly persisted and rendered data on revisit.

Claude MCP Apps: Successfully persist/re-render UI across sessions.

OpenAI MCP Apps: Fails to trigger data events for historical tool results.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions