MCP Apps in ChatGPT are fundamentally broken - 2 critical bugs

We’ve been building MCP Apps for ChatGPT and have hit two separate bugs that, combined, make the platform essentially unusable for production apps.

We are following the recommended approach from the MCP Apps compatibility guide, which positions MCP Apps as the standard way to build app UIs going forward.

Bug 1: _meta stripped from tool results

ChatGPT doesn’t forward _meta from CallToolResult to the widget via
ui/notifications/tool-result. This breaks the documented viewUUID pattern for state
persistence. params.content and params.structuredContent arrive correctly, but
params._meta is always undefined.

Bug 2: ontoolresult not replayed after page refresh

When the user refreshes the page, the iframe reconnects but the host doesn’t re-send the
last tool-result notification. The widget stays on a loading state with no way to
recover.

Impact

These are standard MCP Apps patterns and not edge cases or ChatGPT-specific extensions.

Since the compatibility guide tells developers to “build with the MCP Apps standard keys and bridge by default,” it’s important that the standard bridge works reliably.

Together these make it hard to build apps that work in ChatGPT:

  • State persistence via viewUUID doesn’t work (Bug 1)
  • Widgets don’t survive a page refresh (Bug 2)

Would love to know

Should we keep investing in MCP Apps and wait for fixes, or should we
move back to window.openai? Some clarity on whether these are being tracked and will be
fixed would really help us decide how to move forward.

Related posts:

Claude just works out of the box. Idk why OpenAI is this way.

because they have launch Apps way earlier… and have a double stack to support with potential collisions

The old way is the inefficient way of putting the HTML in the context and polluting right ?
or is there any other way ?

not sure to get it, there never was any solution including putting html in the context

Thanks for the detailed report. We’re tracking the reports around MCP Apps tool result metadata and refresh behavior.

Because deeper troubleshooting may require app-specific or account-specific details, please don’t post user IDs, org/workspace IDs, API keys, HAR files, private logs, request traces, or account-specific data in this thread.

For now, please email support@openai.com with this forum URL, the related repro links, and any minimal non-sensitive reproduction details you can share privately. After you’ve emailed support, reply here with the Case ID or exact subject line so we can locate it quickly.

As a temporary development workaround, avoid depending only on CallToolResult._meta for widget restore state. If it is safe for your app, use a small non-secret opaque state key in structuredContent, then have the widget re-fetch or rebuild state on startup. Do not put secrets, user data, large private payloads, or account-specific debug artifacts into model-visible fields.

Thanks!

Hi. In order to be able to investigate this issue and provide with a fix, the engineering team is looking for a specific that can be used. If you are still experiencing this issue and can help with the request, please write an email to support@openai.com with this forum URL, the related repro links, and any minimal non-sensitive reproduction details you can share privately. After you’ve emailed support, reply here with the Case ID or exact subject line so we can locate it quickly. Thanks!

Do not put secrets, user data, large private payloads, or account-specific debug artifacts into model-visible fields.

Then please just make sure that the whole tool result message makes it to the widget like it did before. We have an app that worked, and now I’m having to work around this.

I don’t see how setting up an elaborate situation and a whole support interaction will make it any easier. It’s a simple regression.

Hi there. Sorry to read that. The team is still investigating and working on fixing this issue. To help with the investigation, please file a new support case writting an email to support@openai.com with this forum URL, the related repro links, and any minimal non-sensitive reproduction details you can share privately. After you’ve emailed support, reply here with the Case ID or exact subject line so we can locate it quickly. Thanks!

Hey all, sorry this took so long–to be frank, it took me a bit of time personally to understand why this was an issue (I spend more time on the top-level MCP spec rather than the apps extension). A fix to this should be out in the next couple of days, I’d say try again on Tuesday. Thank you for being so persistent about this issue!

This should be deployed now, please let me know if it doesn’t work.

Thanks! Can you clarify what was fixed? The missing _meta or the widgets not surviving page refresh?

My app is still not finding _meta in chatgpt. In Claude _meta is available to the ui

Thanks for following up on that, but for a tool that is definitely returning metadata, we’re not seeing a _meta in the tool result.

It should have been both. I’ll dig in to verify the build, but do you think you could share the MCP you’re using to test this with?

If it’s of any help, here’s what I see when testing:

And my test code:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer({
  name: "meta-probe",
  version: "1.0.0",
});

const TEMPLATE_URI = "ui://widget/meta-probe.html";

const widgetHtml = `<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <style>
      body {
        font-family: system-ui, sans-serif;
        margin: 0;
        padding: 16px;
        color: #111;
        background: #fff;
      }
      h1 { font-size: 18px; margin: 0 0 8px; }
      pre {
        white-space: pre-wrap;
        background: #f5f5f5;
        border: 1px solid #ddd;
        border-radius: 6px;
        padding: 12px;
      }
    </style>
  </head>
  <body>
    <h1>Widget rendered</h1>
    <p>If you can see this, the iframe received template HTML.</p>
    <pre id="debug">Waiting for bridge data...</pre>

    <script>
      const debug = document.getElementById("debug");

      function snapshot(label) {
        const data = {
          label,
          hasOpenAI: Boolean(window.openai),
          toolOutput: window.openai?.toolOutput ?? null,
          toolResponseMetadata: window.openai?.toolResponseMetadata ?? null,
          widgetState: window.openai?.widgetState ?? null
        };

        debug.textContent = JSON.stringify(data, null, 2);
      }

      snapshot("initial");

      window.addEventListener("openai:set_globals", (event) => {
        snapshot("openai:set_globals");
      });

      window.addEventListener("message", (event) => {
        const msg = event.data;
        if (msg?.method === "ui/notifications/tool-result") {
          debug.textContent = JSON.stringify(
            {
              label: "ui/notifications/tool-result",
              params: msg.params
            },
            null,
            2
          );
        }
      });
    </script>
  </body>
</html>`;

server.registerResource("meta-probe-widget", TEMPLATE_URI, {}, async () => ({
  contents: [
    {
      uri: TEMPLATE_URI,
      mimeType: "text/html",
      text: widgetHtml,
      _meta: {
        "openai/widgetDescription": "Validates MCP _meta delivery.",
        "openai/widgetPrefersBorder": true,
        "openai/widgetCSP": {
          connect_domains: [],
          resource_domains: [],
        },
      },
    },
  ],
}));

server.registerTool(
  "show_meta_probe",
  {
    title: "Show meta probe",
    description: "Render a read-only widget that verifies MCP tool result metadata.",
    inputSchema: {},
    annotations: {
      readOnlyHint: true,
      destructiveHint: false,
      openWorldHint: false,
    },
    _meta: {
      "openai/outputTemplate": TEMPLATE_URI,
      "openai/toolInvocation/invoking": "Running meta probe...",
      "openai/toolInvocation/invoked": "Meta probe ready",
    },
  },
  async () => ({
    content: [
      {
        type: "text",
        text: "hello from the MCP server",
      },
    ],
    structuredContent: {
      visibleMessage: "hello from the MCP server",
      structuredOnlyValue: 12345,
    },
    _meta: {
      secretProbeValue: "private meta reached widget",
      largePrivateState: {
        hydratedFromMeta: true,
        timestamp: new Date().toISOString(),
      },
    },
  })
);

export default server;

I’m testing an updated version of my app locally that adds the need for the meta field. However, I’m finding that the widget still is not getting that field.

ChatGPT shows the meta info in the UI:

However it’s not in the ui/notifications/tool-result postMesage. (I can’t include that screenshot in this post since new users are limited 1 image per post)

Browser console showing meta is missing from the postMessage

Thanks for the continued reports here. The team shipped a fix for the tool-result `_meta` delivery issue, so this should now be resolved.

If anyone is still seeing `_meta` missing in ChatGPT on a fresh test, please open a new support case by emailing support@openai.com and include this forum thread, a minimal non-sensitive repro, and the approximate test time. That will let us verify against the current deployed build rather than older behavior.

Nope
_meta is still missing from ontoolresult in chatgpt host. only content and structuredContent are available to the ui