Widget receives toolOutput: null despite tool returning valid structuredContent
Summary
When my MCP tool returns structuredContent, the ChatGPT model correctly receives and responds to the data, but the widget’s window.openai.toolOutput is null. The data reaches the model but does not get injected into the widget runtime.
Environment
- Platform: ChatGPT web (desktop)
- MCP Server: Cloudflare Workers with
@modelcontextprotocol/sdk - Widget: React 19 + Vite, served as
text/html+skybridge
Steps to Reproduce
- Register an MCP resource with
mimeType: "text/html+skybridge" - Register a tool that returns
structuredContent - Connect the app to ChatGPT
- Invoke the tool via conversation
- Observe that the model responds correctly using the data
- Check
window.openai.toolOutputin the widget — it’snull
Expected Behavior
window.openai.toolOutput should contain the structuredContent returned by the tool.
Actual Behavior
window.openai.toolOutput is null, even though:
- The tool call succeeds (visible in ChatGPT’s tool explorer)
- The model narrates the response using the returned data
Evidence
Tool Response (from ChatGPT tool explorer):
{
"type": "list",
"items": [
{ "id": 1, "title": "Item 1", "description": "..." },
{ "id": 2, "title": "Item 2", "description": "..." }
],
"total": 20
}
Model Response:
The model correctly lists the items with titles and descriptions — proving it received structuredContent.
Widget’s window.openai (logged via console):
{
"displayMode": "inline",
"maxWidth": 768,
"theme": "dark",
"locale": "en-US",
"userAgent": { "device": { "type": "desktop", "platform": "web" }, "capabilities": { "hover": true, "touch": false } },
"view": { "mode": "inline" },
"widget": { "state": null, "props": null },
"toolInput": {},
"toolOutput": null,
"toolResponseMetadata": null,
"widgetState": null,
"subjectId": null,
"safeArea": { "insets": { "top": 0, "bottom": 0, "left": 0, "right": 0 } }
}
Server Code (simplified)
// Resource registration
server.registerResource(
"my-widget",
"ui://widgets/my-widget.html",
{},
async () => ({
contents: [{
uri: "ui://widgets/my-widget.html",
mimeType: "text/html+skybridge",
text: htmlContent,
_meta: {
"openai/widgetPrefersBorder": true,
"openai/widgetCSP": {
connect_domains: ["https://my-domain.com"],
resource_domains: ["https://my-domain.com"],
},
},
}],
})
);
// Tool registration
server.registerTool(
"get-items",
{
title: "Get Items",
description: "Returns a list of items",
inputSchema: {},
_meta: {
"openai/outputTemplate": "ui://widgets/my-widget.html",
},
},
async () => {
const data = await fetchItems();
return {
content: [{ text: `Found ${data.length} items`, type: "text" }],
structuredContent: { type: "list", items: data },
};
}
);
Widget Code (simplified)
function App() {
console.log("window.openai:", JSON.stringify(window.openai, null, 2));
console.log("toolOutput:", window.openai?.toolOutput);
const toolOutput = window.openai?.toolOutput;
if (toolOutput?.type === "list") {
return <ItemList items={toolOutput.items} />;
}
return <div>toolOutput is null</div>;
}
What I’ve Tried
- Verified
mimeTypeis"text/html+skybridge" - Confirmed
outputTemplateURI matches registered resource URI exactly - Used static version strings to avoid cache mismatches
- Disconnected and reconnected the app
- Started fresh conversations
- Tested on multiple browsers
Question
Is there a known issue or additional configuration required for structuredContent to be injected into window.openai.toolOutput? The docs indicate this should happen automatically when the widget loads.