{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidjtatp5xpdp35r4tvpf77asz2guakngb5sdqrfqzxesyxxqnvmnu",
    "uri": "at://did:plc:lk3jfj3zq4k4wxnk474axylu/app.bsky.feed.post/3mprnd25ykde2"
  },
  "path": "/t/bug-apps-sdk-fullscreen-opens-blank-on-android-when-widget-app-is-invoked-in-the-first-message-of-a-new-conversation/1385679#post_1",
  "publishedAt": "2026-07-03T21:54:47.000Z",
  "site": "https://community.openai.com",
  "tags": [
    "Video when fullscreen not works",
    "Video when fullscreen works",
    "@modelcontextprotocol"
  ],
  "textContent": "Hi everyone,\n\nI’m trying to isolate what looks like a ChatGPT Android app issue with Apps SDK / MCP widget fullscreen behavior.\n\nThe issue seems to happen specifically when the widget/app is invoked in the **first message of a new conversation**.\n\nI created a minimal MCP widget to reproduce it. The widget only renders one inline button. When the user taps the button, it calls:\n\n\n    await window.openai?.requestDisplayMode?.({ mode: \"fullscreen\" });\n\n\nThen it replaces the body with a simple `hello world` screen.\n\nThe behavior on the ChatGPT Android app is:\n\nScenario | Result\n---|---\nNew conversation → first message asks to open the widget → tap fullscreen button | Fullscreen opens blank\nNew conversation → send any normal message first, such as `hi` → then ask to open the same widget → tap fullscreen button | Fullscreen works\n\nSo this does not seem to be a general fullscreen issue, because fullscreen works on Android after the conversation already has at least one previous message.\n\nI also noticed the same pattern in another Apps SDK app I’m testing on Android:\n\nScenario | Result\n---|---\nNew conversation → first message asks ChatGPT to generate an image and then open the app/widget | Fullscreen fails / opens blank\nNew conversation → send `hi` first → then ask ChatGPT to generate the image and open the app/widget | Fullscreen works\n\nThis makes me suspect a cold-start / first-turn initialization issue in the ChatGPT Android host. The inline widget/app can render, but the fullscreen surface seems not to render correctly when the app/widget is first invoked on the first turn of a brand-new conversation.\n\nI will attach an Android screen recording showing both cases:\n\n  1. Failing case: new conversation, first message invokes the widget/app, fullscreen opens blank.\n  2. Working case: new conversation, send `hi` first, then invoke the same widget/app, fullscreen works.\n\n\n\nHere is the minimal repro code I used for the simple `hello world` widget:\n\n\n    import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from \"@modelcontextprotocol/ext-apps/server\";\n    import { createMcpExpressApp } from \"@modelcontextprotocol/sdk/server/express.js\";\n    import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n    import { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\n\n    const PORT = Number(process.env.PORT ?? 3000);\n\n    function toOrigin(value) {\n      if (!value) return undefined;\n\n      try {\n        return new URL(value).origin;\n      } catch {\n        return undefined;\n      }\n    }\n\n    const ORIGIN = toOrigin(process.env.BASE);\n    const TEMPLATE_URI = \"ui://widget/widget.html\";\n\n    const widgetHtml = `\n    <!doctype html>\n    <html>\n      <head>\n        <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,viewport-fit=cover\">\n        <meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\">\n        <meta http-equiv=\"Pragma\" content=\"no-cache\">\n        <meta http-equiv=\"Expires\" content=\"0\">\n      </head>\n\n      <body>\n        <button id=\"open\">Abrir fullscreen</button>\n\n        <script>\n          document.getElementById(\"open\").onclick = async () => {\n            try {\n              await window.openai?.requestDisplayMode?.({ mode: \"fullscreen\" });\n            } catch (_) {}\n\n            document.body.innerHTML =\n              '<main style=\"height:100vh;display:grid;place-items:center;font:32px system-ui;\">hello world</main>';\n          };\n        </script>\n      </body>\n    </html>\n    `.trim();\n\n    function createServer() {\n      const server = new McpServer({\n        name: \"hello-world-widget\",\n        version: \"1.0.0\",\n      });\n\n      registerAppResource(\n        server,\n        \"hello-world-widget\",\n        TEMPLATE_URI,\n        {},\n        async () => ({\n          contents: [\n            {\n              uri: TEMPLATE_URI,\n              mimeType: RESOURCE_MIME_TYPE,\n              text: widgetHtml,\n              _meta: {\n                ui: {\n                  ...(ORIGIN ? { domain: ORIGIN } : {}),\n                  csp: {\n                    connectDomains: [],\n                    resourceDomains: [],\n                  },\n                },\n              },\n            },\n          ],\n        }),\n      );\n\n      registerAppTool(\n        server,\n        \"hello_world_widget\",\n        {\n          title: \"Hello World Widget\",\n          description: \"Abre um widget simples com um botão que mostra hello world em fullscreen.\",\n          inputSchema: {},\n          outputSchema: {},\n          _meta: {\n            ui: {\n              resourceUri: TEMPLATE_URI,\n            },\n            \"openai/outputTemplate\": TEMPLATE_URI,\n          },\n          annotations: {\n            readOnlyHint: true,\n            destructiveHint: false,\n            openWorldHint: false,\n          },\n        },\n        async () => ({\n          structuredContent: {},\n          content: [\n            {\n              type: \"text\",\n              text: \"Widget aberto.\",\n            },\n          ],\n        }),\n      );\n\n      return server;\n    }\n\n    const app = createMcpExpressApp({ host: \"0.0.0.0\" });\n\n    app.all(\"/mcp\", async (req, res) => {\n      const server = createServer();\n\n      const transport = new StreamableHTTPServerTransport({\n        sessionIdGenerator: undefined,\n      });\n\n      res.on(\"close\", () => {\n        transport.close().catch(() => {});\n        server.close().catch(() => {});\n      });\n\n      try {\n        await server.connect(transport);\n        await transport.handleRequest(req, res, req.body);\n      } catch (error) {\n        console.error(\"MCP error:\", error);\n\n        if (!res.headersSent) {\n          res.status(500).json({\n            jsonrpc: \"2.0\",\n            error: {\n              code: -32603,\n              message: \"Internal server error\",\n            },\n            id: null,\n          });\n        }\n      }\n    });\n\n    app.listen(PORT, () => {\n      console.log(`MCP server running - PORT: ${PORT} - ORIGIN: ${ORIGIN}`);\n    });\n\n\n\nTo run this code:\n\n\n    BASE=https://<ngrok-hash>.ngrok-free.app node index.mjs\n\n\nThings I already tried:\n\n  * Adding `_meta.ui.domain` with an HTTPS origin.\n  * Testing different CSP configurations.\n  * Adding legacy metadata aliases such as `openai/widgetDomain` and `openai/widgetCSP`.\n  * Cache-busting the template URI, for example changing `ui://widget/widget.html` to versioned names.\n  * Returning the same HTML from a real HTTP route and calling `window.openai.setOpenInAppUrl(...)`.\n  * Persisting state with `window.openai.setWidgetState(...)` before requesting fullscreen.\n  * Rendering the fullscreen content before and after `requestDisplayMode`.\n  * Testing `requestModal()` only as a diagnostic step. Modal is not a valid solution for my use case because I specifically need fullscreen.\n\n\n\nThe important finding is that the same widget/app can work on Android if the conversation already has a previous message. The failure appears when the app/widget is invoked on the first turn of a new conversation.\n\nMy questions:\n\n  * Is fullscreen expected to work on Android when an Apps SDK widget/app is invoked in the first message of a brand-new conversation?\n  * Is there any additional initialization step required before calling `requestDisplayMode({ mode: \"fullscreen\" })` on Android?\n  * Could this be a known Android host cold-start / first-turn issue?\n  * Is there any difference in fullscreen behavior between widgets invoked after a normal assistant turn and widgets invoked immediately in the first assistant response?\n\n\n\nEnvironment:\n\n  * ChatGPT Android app version: `1.2026.181 (1)`\n  * Android device: `Galaxy S23 Ultra - SM-S918B`\n  * Android OS version: `16`\n  * MCP package versions:\n    * `@modelcontextprotocol/sdk`: `^1.29.0`\n    * `@modelcontextprotocol/ext-apps`: `^1.7.4`\n  * Node.js version: `v22.23.1`\n  * Hosting/tunnel provider: `ngrok`\n  * App mode:\n    * Developer mode connector: `yes`\n    * Published app: `no`\n\n\n\nAny guidance would be appreciated. I’m trying to keep this repro intentionally minimal so the issue is easier to isolate.\n\n  * Video when fullscreen not works\n  * Video when fullscreen works\n\n",
  "title": "[bug] Apps SDK fullscreen opens blank on Android when widget/app is invoked in the first message of a new conversation"
}