"Runtime error" on all resource widgets in developer mode

Resource-bound widgets (MCP Apps using resources/read with mimeType: text/html;profile=mcp-app) fail to render in ChatGPT’s developer mode. The error is consistent and affects all MCP apps, including Booking[.]com’s official integration.

What happens

When a tool call returns structuredContent tied to a resource widget, ChatGPT shows:

Error loading app
Runtime error
[Retry]

Clicking Retry does not resolve it. The tool call itself succeeds, the server returns valid data, but the widget iframe never connects to the host.

Reproduction

  1. Open ChatGPT in developer mode
  2. Connect any MCP server that exposes a resource with mimeType: text/html;profile=mcp-app
  3. Trigger a tool call that returns structuredContent bound to that resource
  4. The widget shows “Error loading app / Runtime error”

This also reproduces with Booking[.]com — ask it to search for a hotel. The tool executes, results come back, but the Booking[.]com widget fails with the same error. In non-developer (production) ChatGPT, the same Booking[.]com widget renders correctly.

What we’ve verified

  • Not app-specific. Tested across three independent MCP apps (2 custom apps built with sunpeak[.]ai, plus Booking[.]com). All
    produce the identical error.
  • Server-side is correct. Tool calls succeed, structuredContent is returned, resource HTML is served. Server logs show no
    errors.
  • The widget HTML is valid. The same HTML renders correctly in local testing environments and in production ChatGPT
    (non-developer mode).
  • The error comes from ChatGPT’s host-side error boundary. The “Error loading app / Runtime error” text does not exist in any
    of the app code — it’s rendered by the ChatGPT client.

Likely cause

The resource iframe uses postMessage to establish a JSON-RPC bridge with the ChatGPT parent window. In developer mode, the host-side listener for this bridge appears to be missing or misconfigured, so the iframe’s connection handshake times out and throws.

Additional observations from server logs

While investigating, we logged the developer mode’s MCP session behavior per user message:

  • 7-8 MCP sessions opened per turn, when 4 are sufficient (tools/list + resources/list + resources/read + tools/call). Re-discovering tools and resources per turn is expected with streamable HTTP, but the extra 3-4 sessions are not.
  • 3 sessions abandoned every turn — initialize sent, handshake never completed
  • Race condition — at least once, tools/call was sent before notifications/initialized, causing “Error: Bad Request: Server not initialized”

These may be developer-mode-specific behaviors.

Environment

  • ChatGPT: production (chat[.]openai[.]com), developer mode enabled
  • Date observed: April 23, 2026
  • Tested MCP servers: custom apps (Node.js / sunpeak 0.20.7) + Booking[.]com

Ask

Is this a known issue with a timeline for a fix, as we can’t test our widgets at the moment? Are OpenAI developers using the developer mode internally, too?

Hey Daniel, thanks for posting. I can’t seem to reproduce this myself with the Booking app:

Could I ask you for the Request ID so I can debug in our logs?

Here’s how to find it: looking at your network tab when you open a conversation that is failing, can you give me the x-oai-request-id header value of the /ecosystem/widget API call? It should look like a UUID.

Thanks.

@mstoiber

the likely cause posted above was way off, it’s not so easy to reproduce the error, as it requires that Aura iframe timeouts through ngrok, which it seems to be an intermittent problem.

The real problem, I believe it’s how ChatGPT Client caching strategy, as it, on every request:

1. Calls `resources/list` — discovers all resources:

2. Calls `resources/read` for **every** resource — sequentially, one by one

3. Calls `tools/list` — discovers tools

4. Calls `tools/call` — executes a tool, which references a single `resourceUri`

5. Renders **one** widget

Steps 1-2 download all resource HTML upfront even though only one widget is rendered per tool call. This repeats on every turn — nothing is cached across messages.

For N resources of ~150KB each, that’s N × 150KB re-downloaded every turn regardless of which tool is called. As apps grow in complexity (more screens, more widgets), the per-turn cost grows linearly while the value stays constant (1 widget rendered).

**Resource filtering doesn’t work either.** When a server returns a subset from `resources/list`, ChatGPT changes its tool selection behavior — it avoids calling tools whose associated resources are missing from the list, falling back to text responses. This means `resources/list` is implicitly coupled to ChatGPT’s client tool planning, preventing servers from optimizing resource delivery independently.

### Requested

1. **Lazy loading** — fetch `resources/read` after a tool returns `resourceUri`, not before. The tool result already specifies which widget to render — there’s no need to prefetch all of them speculatively.

2. **Caching** — resource URIs already include cache-busting identifiers (e.g. `ui://widget-abc123`). When the URI hasn’t changed between turns, skip the re-fetch.

3. **Decouple resources from tool selection** — tool selection should be driven by `tools/list` and tool descriptions, not by which resources are in `resources/list`. Servers should be able to filter resources without it affecting which tools ChatGPT decides to call.

### Why this matters

The prefetch-all model caps app complexity. Every resource added increases the per-turn bandwidth cost linearly, but only one widget is shown at a time. Multi-step apps (guided flows, wizards, checkout processes) naturally need many widgets — one per step — but the current behavior makes them progressively slower with each widget added.

With lazy loading or decoupled resource/tool selection, servers could dynamically serve only the widget needed for the current step. This removes the scaling ceiling and enables richer MCP apps without hitting timeout or bandwidth limits.

### Environment

ChatGPT developer mode, MCP Apps with Streamable HTTP, April 2026. Reproducible with any MCP app that has 3+ resource-bound tools.

Hey Daniel_Guimaraes, Can you please share a request id (x-oai-request-id) similar to what Max is asking in the previous response. Without that we won't be able to identify the root cause as we are not able to reproduce the issue on our end. Thank you!

Hey @mstoiber,

I enabled full header logging on my MCP server and captured the requests, but ChatGPT doesn’t appear to send an x-oai-request-id header on MCP requests. The headers I see are:

  • user-agent: openai-mcp/1.0.0

  • x-openai-session

  • x-openai-subject

  • traceparent / x-datadog-trace-id / x-datadog-parent-id

Here are the Datadog trace IDs from a session where the widget failed to load:

  • 2952400983837445269

  • 5305024332406104435

What the server logs show

The issue isn’t a server-side failure — all responses are sent successfully. The problem is the prefetch pattern itself. On every single user turn, ChatGPT:

1. Opens 8 parallel sessions (each with its own initialize handshake)

2. Calls resources/list — discovers all resources

3. Calls resources/read for every resource — sequentially, downloading the full widget HTML (~727KB) each time

4. Calls tools/list — discovers tools

5. Calls tools/call — executes a tool, which references a single resourceUri

6. Renders one widget

Steps 1–3 are pure overhead. The tool result in step 5 already specifies which widget to render via resourceUri — there’s no need to prefetch all resource HTML before the tool is even called.

This entire sequence repeats on every turn — nothing is cached across messages. For a single 727KB resource, that’s already ~6MB of redundant prefetch traffic per turn (8 sessions × resources/read).

For apps with N resources, it scales to N × 8 × resource_size per turn.

Current workaround: single universal resource

To avoid the N × prefetch scaling problem, I’ve merged all my widgets (car grid, brand selection, model selection, configuration, checkout, etc.) into a single resource that renders different views based on the action field in the tool’s structured content. This means resources/read only downloads one 727KB payload instead of N separate ones.

Do you think this single-resource pattern would be blocked during app submission review? Or is it an accepted approach?

Let me know if you need a different identifier or if I can capture anything else.

Hey Daniel,

Thanks for checking and for sharing those details. Huge apologies but we are still not able to identify the root cause. Since this likely needs request-level troubleshooting, the best next step is to capture a HAR file while reproducing the issue in ChatGPT developer mode.

Please don’t share the HAR file publicly here, since it can contain sensitive request/session details. Instead, open a support request by emailing support@openai.com, include the HAR file there, and add a short summary of the issue: resource-bound MCP widgets in developer mode fail or become slow because ChatGPT appears to fetch/read widget resources repeatedly before rendering.

Once you’ve opened the support request, please reply here with the case ID and case subject only. We can use that to track the case internally and continue troubleshooting with the private details from the HAR. Thanks!

Hi @mstoiber, I submitted the HAR file, Case Number: 09249914

Hey Daniel, Thank you for sharing the case id. We will continue the correspondence in your support request.