Miniapp โ†” host handshake

MCP Apps ui/* JSON-RPC over postMessage ยท the shim is the transport, meta.json is the grant ยท why a missing shim fails silently

Miniapp โ†” host handshake โ€” MCP Apps `ui/*` over postMessage The sealed iframe carries the transport (the shim); meta.json carries the grant (needs). Neither file alone makes the app work. sealed iframe srcDoc ยท null origin ยท sandbox="allow-scripts" index.html + mcpAppClient() shim trusted host console Runner ยท mcpUiHost router attaches iff meta.needs is non-empty shim runs FIRST in <head>, before the game script window.agentgemApp = api ui/initialize bounded retry ร—5 / 800 ms โ€” beats the host listener-attach race result { protocolVersion, tools, hostContext } tools = HOST_TOOLS.filter(t => needs.includes(TOOL_CAP[t.name])) โ€” advertise only declared caps ui/notifications/initialized handshake complete โ€” queued tools/call flush tools/call { name, arguments } name must be a LITERAL string โ€” a dynamic name scans as nothing and gets pruned consent gate AUTO_CAPS bypass ยท gated caps prompt the viewer result | error โˆ’32001 (consent denied) ui/notifications/tool-result { toolName, chunk } streaming caps push each event; one-shot caps answer in the result above When the shim is missing (the silent failure) no shim in <head> window.agentgemApp undefined ui/initialize never sent retries exhaust (~4 s) every callTool rejects "no host" ยท the app degrades to its baked <script id="game-data"> meanwhile meta.json still declared `needs`, so the host advertised tools to a frame that cannot speak โ€” nothing errors, nothing logs Source of truth index.html โ€” what the code USES (transport + literal tool names). The scan is total: gameGate seals every other channel, so the bridge is the only way out. meta.json โ€” what the host GRANTS (needs). Reconciled against the code at save: calling an undeclared tool throws; declaring an uncalled one prunes. Both land in one MCP Apps resource: html โ†’ resource `text`, meta.json โ†’ `_meta["ai.agentgem/game"]`. Spec: modelcontextprotocol/ext-apps (MVP 2026-01-26).

Transport — index.html

  • • mcpAppClient() shim, first in <head>
  • • Defines window.agentgemApp synchronously
  • • ui/initialize, bounded retry ×5 / 800 ms
  • • Baked by every scaffold; injected by the on-read backstop

Grant — meta.json

  • • needs: the capabilities the host advertises
  • • Runner attaches only when it is non-empty
  • • Reconciled against the code at save time
  • • Undeclared call throws; uncalled declaration prunes

Consent

  • • AUTO_CAPS bypass the prompt
  • • Gated caps ask the viewer
  • • Denial answers JSON-RPC error −32001
  • • Streaming caps push ui/notifications/tool-result

The silent failure

  • • No shim → window.agentgemApp undefined
  • • ui/initialize never sent → retries exhaust
  • • callTool rejects "no host"; app shows baked data
  • • Host still advertised tools to a mute frame