I found a reproducible issue in ChatGPT web where an MCP App widget works on the first tool call but
does not receive the tool result when the same widget-linked tool is called again in the next
conversation turn.
The second turn causes ChatGPT to remove the first iframe and create a new one. However, the newly
created iframe never receives the second result through window.openai.toolOutput. The user
therefore sees an incomplete or blank widget even though the MCP tool implementation returns valid,
distinct data for each invocation.
Environment
| Component | Version or configuration |
|---|---|
| Host | ChatGPT web, Developer mode, custom MCP connector |
| Date observed | July 31, 2026 |
| Operating system | Windows 11 Pro 25H2 |
| Browser | Chrome 150.0.7871.127 |
| Runtime | Node.js 20 or later |
| MCP transport | Stateless Streamable HTTP, exposed through ngrok |
@modelcontextprotocol/ext-apps |
1.7.5 |
@modelcontextprotocol/sdk |
1.30.0 |
zod |
4.3.6 |
| Observed MCP protocol version | 2025-11-25 |
Minimal reproduction
The reproduction server contains only:
- One input-free tool named
show-demo-map - One static UI resource at
ui://renderer-turn-repro/map-v4.html - One inline HTML widget
- No external API, database, authentication, frontend framework, or build step
- No tool call initiated from inside the widget
The complete result-generation logic is:
let renderCallNumber = 0;
function renderStaticResult() {
renderCallNumber += 1;
return {
content: [{ type: "text", text: "Displaying the static demo result." }],
structuredContent: {
itemCount: 3,
callNumber: renderCallNumber,
},
};
}
The server returns a monotonically increasing callNumber:
{
"itemCount": 3,
"callNumber": 1
}
The next invocation returns:
{
"itemCount": 3,
"callNumber": 2
}
An integration test calls the same tool twice against the same server instance and confirms that
the two results contain callNumber: 1 and callNumber: 2.
Steps to reproduce
- Start a server with the minimal configuration and result-generation logic shown above, then
expose its/mcpendpoint over HTTPS. - Register the endpoint as a custom connector in ChatGPT Developer mode.
- Start a new conversation.
- Enter
@<connector-name> Display the map. - Wait for the first widget to render.
- Enter
@<connector-name> Display the map again.
Expected behavior
The conversation should contain two rendered widgets:
- The first widget displays
callNumber: 1. - The second widget displays
callNumber: 2.
Alternatively, if ChatGPT intentionally reuses or replaces the existing widget, the active iframe
should still receive the most recent tool result.
Actual behavior
The first call renders correctly and the widget receives itemCount and callNumber.
On the second turn:
- ChatGPT invokes the same widget-linked tool again.
- The first iframe receives a
pagehideevent. - ChatGPT creates a new iframe.
- The new iframe starts with empty
toolInputandtoolOutputobjects. - The new iframe never receives the second tool result.
- The visible widget displays
callNumber: missinginstead ofcallNumber: 2.
Browser console evidence
The first rendered iframe receives the expected result:
[renderer-turn-repro] app-created {
"toolInputKeys": [],
"toolOutputKeys": ["itemCount", "callNumber"],
"itemCount": 3,
"callNumber": 1
}
When the same tool is requested in the next turn, the first iframe is removed:
[renderer-turn-repro] pagehide
The replacement iframe is then created without any tool result:
[renderer-turn-repro] app-created {
"toolInputKeys": [],
"toolOutputKeys": [],
"itemCount": null,
"callNumber": null
}
No later global update delivers callNumber: 2 to that iframe.
Why this appears to be a ChatGPT host issue
- The resource is valid because the first invocation renders successfully.
- The tool has no input, so the failure is not caused by argument changes between turns.
- The result is generated locally and synchronously, with no external dependency.
- The same server instance returns distinct, schema-valid results for two consecutive calls in the
integration test. - The browser log shows that ChatGPT explicitly tears down the working iframe and creates another
iframe, but the replacement is not populated with the new result. - The failure occurs at the boundary between the tool result and the ChatGPT-provided
window.openai.toolOutputstate.
This suggests that the result of the second invocation is not being associated with, or delivered
to, the replacement iframe.
Impact
Any MCP App that uses repeated tool calls across conversation turns can become blank, stale, or
incomplete. This affects common interactions such as refreshing a search, changing filters,
requesting another result, or reopening the same type of visualization.
From the user’s perspective, the tool appears to stop working after the first successful response,
and the widget cannot show a useful error because the host does not provide the second result.
Related report
This may be related to an existing community topic titled:
ChatGPT Apps SDK widget becomes blank after the second question
The reproduction described here additionally isolates the iframe replacement and shows that the
new iframe is created with an empty toolOutput object.
Request
Could the ChatGPT Apps team investigate how a second tools/call result is associated with the
newly mounted iframe when the same widget-linked tool is called in consecutive conversation turns?
Please also let us know whether there is a supported workaround that allows the replacement iframe
to receive the latest tool result reliably.