Summary
My MCP server returns valid structuredContent but the widget never receives the data. window.openai.toolOutput is always
null, and no openai:set_globals event fires.
Environment
- Browser: Safari (also tested Chrome)
- MCP Server: Python/FastAPI deployed on AWS Lambda
- Widget: Vanilla HTML/JS (no React)
- MIME type: text/html;profile=mcp-app
- Testing in: Developer Mode (Settings → Connectors → Developer mode)
What Works
- MCP server responds correctly to all JSON-RPC methods (initialize, tools/list, tools/call, resources/read)
- tools/call returns proper structuredContent with book data (verified via curl)
- Widget HTML loads in ChatGPT iframe
- Widget JavaScript executes
- window.openai exists with 48 properties/methods
What Doesn’t Work
- window.openai.toolOutput is always null
- window.openai.widgetState is always null
- No openai:set_globals event ever fires
- No postMessage with ui/notifications/tool-result received
- Widget shows “Runtime error” after loading state
Console Logs from Widget
[WOEB] Script starting…
[WOEB] document.readyState: “loading”
[WOEB] typeof window.openai: “object”
[WOEB] window.openai keys: [“openExternal”, “setOpenInAppUrl”, …] (48)
[WOEB] toolOutput: null
[WOEB] widgetState: null
[WOEB] displayMode: “inline”
[WOEB] maxHeight: undefined
[WOEB] render() called
[WOEB] No data yet, showing loading state
MCP Server Response (tools/call)
json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{"type": "text", "text": "Found 8 wonderful books!"}],
"structuredContent": {
"view": "grid",
"books": [{"id": "animal-factopia", "title": "Animal FACTopia!", "price_gbp": 10.99}],
"total_count": 8
},
"_meta": {
"openai/outputTemplate": "ui://widget/book-shop.html",
"openai/widgetAccessible": true,
"openai/toolInvocation/invoking": "Loading books...",
"openai/toolInvocation/invoked": "Found books!"
}
}
}
MCP Server Response (tools/list)
json
{
"tools": [{
"name": "search_books",
"title": "Browse & Recommend Books",
"inputSchema": {},
"_meta": {
"openai/outputTemplate": "ui://widget/book-shop.html",
"openai/widgetAccessible": true,
"openai/widgetCSP": {"connect_domains": [], "resource_domains": []},
"ui": {"resourceUri": "ui://widget/book-shop.html"}
}
}]
}
}
Widget Code (simplified)
What I’ve Tried
- Verified MCP responses with curl - all correct
- Added openai/outputTemplate to both tools/list and tools/call
- Added/removed openai/widgetCSP configuration (with empty arrays, with domains, without)
- Called notifyIntrinsicHeight() and notifyIntrinsicWidth()
- Listened for openai:set_globals and postMessage events
- Polled window.openai.toolOutput every 100ms
- Tried calling window.openai.callTool() from widget
- Cleared browser cache, reconnected MCP server multiple times
Questions
- Is there a required handshake or initialization the widget must perform before receiving data?
- Is there documentation on exactly how window.openai.toolOutput gets populated?
- Are vanilla HTML widgets supported, or is the React SDK required?
- Is a specific openai/widgetCSP configuration required for toolOutput to be populated, even when the widget doesn’t load external resources? I’m testing in Developer Mode where CSP checks appear disabled - could this affect how tool results are delivered?