---
phase: 16-shared-bridge-library
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - packages/widget-runtime/src/index.ts
  - packages/widget-runtime/src/app.ts
  - packages/widget-runtime/src/transport.ts
  - packages/widget-runtime/src/app-bridge.ts
  - packages/widget-runtime/src/compat.ts
  - packages/widget-runtime/src/types.ts
  - packages/widget-runtime/package.json
  - packages/widget-runtime/tsconfig.json
autonomous: true
requirements: [DEVX-03, DEVX-05]

must_haves:
  truths:
    - "Widget author can `import { App } from './widget-runtime.js'` and call `app.callServerTool()` using the MCP Apps postMessage JSON-RPC protocol"
    - "TypeScript type definitions (.d.ts) ship alongside the compiled ES module with correct types for App, AppBridge, CallToolParams, CallToolResult, and HostContext"
    - "A backward-compatibility shim exposes `window.mcpBridge.callTool()` backed by the new App class internally so existing widgets keep working"
    - "Host-side AppBridge class handles iframe postMessage dispatch and tool call proxying (usable by preview server and WASM standalone mode)"
    - "The compiled ES module has zero runtime dependencies (no React import at module scope)"
  artifacts:
    - path: "packages/widget-runtime/src/app.ts"
      provides: "MCP Apps protocol-aligned App class with callServerTool, connect, lifecycle callbacks"
      contains: "class App"
    - path: "packages/widget-runtime/src/transport.ts"
      provides: "PostMessageTransport for JSON-RPC 2.0 communication between iframe and host"
      contains: "class PostMessageTransport"
    - path: "packages/widget-runtime/src/app-bridge.ts"
      provides: "Host-side AppBridge that creates/manages iframe postMessage bridge and proxies tool calls"
      contains: "class AppBridge"
    - path: "packages/widget-runtime/src/compat.ts"
      provides: "Backward-compat shim mapping window.mcpBridge to App class"
      contains: "window.mcpBridge"
    - path: "packages/widget-runtime/src/types.ts"
      provides: "TypeScript types aligned with @modelcontextprotocol/ext-apps API surface"
      contains: "CallToolParams"
    - path: "packages/widget-runtime/dist/index.mjs"
      provides: "Compiled ES module bundle"
    - path: "packages/widget-runtime/dist/index.d.ts"
      provides: "TypeScript declarations"
  key_links:
    - from: "packages/widget-runtime/src/app.ts"
      to: "packages/widget-runtime/src/transport.ts"
      via: "App creates PostMessageTransport on connect()"
      pattern: "new PostMessageTransport"
    - from: "packages/widget-runtime/src/app-bridge.ts"
      to: "packages/widget-runtime/src/transport.ts"
      via: "AppBridge uses PostMessageTransport host-side to respond to iframe messages"
      pattern: "PostMessageTransport"
    - from: "packages/widget-runtime/src/compat.ts"
      to: "packages/widget-runtime/src/app.ts"
      via: "Shim creates App instance and maps callTool to callServerTool"
      pattern: "new App"
---

<objective>
Build the MCP Apps protocol-aligned TypeScript bridge library with App class, PostMessageTransport, host-side AppBridge, backward-compat shim, and TypeScript type definitions.

Purpose: Establish the canonical widget-runtime source that all bridge modes (preview proxy, WASM standalone, production hosts) will consume. This is the single source of truth for the bridge API, eliminating three separate bridge implementations.

Output: Compiled `dist/index.mjs` ES module + `dist/index.d.ts` type declarations in `packages/widget-runtime/`.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-plan.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/16-shared-bridge-library/16-CONTEXT.md
@.planning/phases/16-shared-bridge-library/16-RESEARCH.md
@.planning/phases/15-wasm-widget-bridge/15-02-SUMMARY.md

# Existing TypeScript source to restructure
@packages/widget-runtime/src/index.ts
@packages/widget-runtime/src/types.ts
@packages/widget-runtime/src/runtime.ts
@packages/widget-runtime/src/utils.ts
@packages/widget-runtime/src/hooks.ts
@packages/widget-runtime/package.json
@packages/widget-runtime/tsconfig.json
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create App, PostMessageTransport, AppBridge, and compat shim TypeScript modules</name>
  <files>
    packages/widget-runtime/src/app.ts
    packages/widget-runtime/src/transport.ts
    packages/widget-runtime/src/app-bridge.ts
    packages/widget-runtime/src/compat.ts
    packages/widget-runtime/src/types.ts
  </files>
  <action>
Create the core protocol-aligned modules in `packages/widget-runtime/src/`:

**transport.ts** -- PostMessageTransport class:
- JSON-RPC 2.0 message format with correlation IDs (auto-incrementing integer IDs)
- `send(method, params)` returns a `Promise` that resolves when the matching JSON-RPC response arrives
- `PendingRequests` map tracks in-flight requests with timeout (30s default)
- Constructor takes `{ targetWindow, targetOrigin }` -- the window to postMessage to and the expected origin for validation
- `addEventListener('message', ...)` for incoming responses, validates `event.origin`
- `destroy()` cleans up event listeners
- Export `JsonRpcRequest`, `JsonRpcResponse`, `JsonRpcNotification` types

**app.ts** -- App class (widget-side, aligns with `@modelcontextprotocol/ext-apps` API surface):
- Constructor takes `{ name: string, version: string }`
- `connect()` method: creates PostMessageTransport targeting `window.parent`, sends `ui/initialize` JSON-RPC request with `{ name, version }`, awaits `ui/initializeResponse` containing HostContext
- `callServerTool(params: CallToolParams): Promise<CallToolResult>` -- sends `tools/call` JSON-RPC request
- `sendMessage(params)`, `openLink(params)`, `sendLog(params)` -- other MCP Apps methods
- `getHostContext(): HostContext | undefined` -- returns cached context from initialization
- Setter-based lifecycle callbacks: `ontoolinput`, `ontoolresult`, `ontoolcancelled`, `onhostcontextchanged`, `onteardown`
- App listens for incoming JSON-RPC notifications from host for lifecycle events
- Graceful degradation: if `connect()` times out (2s), log a console warning and resolve anyway (allows standalone usage without a host)

**app-bridge.ts** -- AppBridge class (host-side):
- Constructor takes `{ iframe: HTMLIFrameElement, toolCallHandler: (name, args) => Promise<CallToolResult>, origin?: string }`
- `initialize()` method: listens for `ui/initialize` JSON-RPC from iframe, responds with HostContext
- Routes incoming `tools/call` requests to `toolCallHandler`, sends JSON-RPC response back
- `sendToolInput(params)`, `sendToolResult(result)`, `sendHostContextChanged(ctx)` -- push notifications to iframe
- `destroy()` cleans up event listeners
- Uses specific origin (not `'*'`) for postMessage calls -- defaults to `window.location.origin`

**compat.ts** -- Backward-compatibility shim:
- `installCompat(app: App)` function that creates `window.mcpBridge` backed by the App instance
- Maps `callTool(name, args)` to `app.callServerTool({ name, arguments: args })` and normalizes the response to `{ success, content }` shape
- Maps `getState()`, `setState()`, `sendMessage()`, `openExternal()`, `theme`, `locale`, `displayMode` to App equivalents
- Logs a one-time deprecation warning: "window.mcpBridge is deprecated. Use `import { App } from 'widget-runtime.js'` instead."
- Also installs `window.openai` ChatGPT compatibility alias

**types.ts** -- Update the existing types file:
- ADD new MCP Apps protocol types: `CallToolParams`, `CallToolResult`, `HostContext`, `AppOptions`, `AppBridgeOptions`, `TransportOptions`
- KEEP existing types (HostType, HostInfo, McpBridge, etc.) for backward compatibility
- Align `CallToolParams` and `CallToolResult` with `@modelcontextprotocol/ext-apps` type signatures:
  - `CallToolParams: { name: string, arguments?: Record<string, unknown> }`
  - `CallToolResult: { content?: Array<{ type: string, text?: string, mimeType?: string }>, structuredContent?: unknown, isError?: boolean }`
  - `HostContext: { theme?: 'light' | 'dark', locale?: string, timezone?: string, displayMode?: 'inline' | 'pip' | 'fullscreen', containerSize?: { width: number, height: number } }`
- Type only the methods we actually implement per CONTEXT.md -- no phantom types

Important design constraints per CONTEXT.md:
- Focus on the subset needed for interactive views (chess, maps) -- not the full MCP Apps surface
- Graceful degradation with console warnings when host doesn't support a capability
- Do NOT import React at module scope anywhere in these files
  </action>
  <verify>
Run `cd packages/widget-runtime && npx tsc --noEmit` to verify TypeScript compiles without errors. Confirm all 4 new files exist and contain the expected classes.
  </verify>
  <done>
TypeScript source compiles. `app.ts` exports `App` class with `connect()` and `callServerTool()`. `transport.ts` exports `PostMessageTransport` with JSON-RPC 2.0 support. `app-bridge.ts` exports `AppBridge` for host-side bridge management. `compat.ts` exports `installCompat()` function. `types.ts` includes `CallToolParams`, `CallToolResult`, `HostContext` aligned with MCP Apps spec.
  </done>
</task>

<task type="auto">
  <name>Task 2: Update package.json, index.ts exports, and build ESM + DTS output</name>
  <files>
    packages/widget-runtime/src/index.ts
    packages/widget-runtime/package.json
  </files>
  <action>
**index.ts** -- Update public exports:
- ADD exports for new modules: `App`, `AppBridge`, `PostMessageTransport`, `installCompat`
- ADD type exports: `CallToolParams`, `CallToolResult`, `HostContext`, `AppOptions`, `AppBridgeOptions`, `TransportOptions`, `JsonRpcRequest`, `JsonRpcResponse`
- KEEP all existing exports (WidgetRuntime, types, utils) for backward compatibility
- React hooks remain exported from the same file -- tsup tree-shaking handles dead code elimination for vanilla JS consumers (React hooks import `react` but are never instantiated unless called)

**package.json** -- Update build configuration:
- Update `scripts.build` to: `"tsup src/index.ts --format esm,cjs --dts --clean"` (add `--clean` to remove stale artifacts)
- Add `scripts.build:copy` as: `"cp dist/index.mjs ../../crates/mcp-preview/assets/widget-runtime.mjs"` (copy ESM output to preview assets)
- Update the `module` field to `"dist/index.mjs"` (ensure ESM is the primary module)
- Add `"sideEffects": false` for tree-shaking optimization

**Build and verify:**
- Run `cd packages/widget-runtime && npm install && npm run build`
- Confirm `dist/index.mjs` exists (ES module)
- Confirm `dist/index.d.ts` exists (type declarations)
- Confirm `dist/index.d.mts` exists (ESM type declarations)
- Verify the compiled output does NOT contain a top-level `import ... from 'react'` that would break vanilla JS usage -- the React hooks should be tree-shakeable dead code when not imported
  </action>
  <verify>
Run `cd packages/widget-runtime && npm run build` and confirm exit code 0. Check `ls packages/widget-runtime/dist/` contains `index.mjs`, `index.js`, `index.d.ts`. Run `grep -c "from 'react'" packages/widget-runtime/dist/index.mjs` -- React imports are acceptable in the bundle since tree-shaking happens at the consumer side; what matters is that the module loads without error when React is not present (dynamic imports or optional chaining guard the hooks).
  </verify>
  <done>
`npm run build` succeeds. `dist/index.mjs` is a valid ES module exporting `App`, `AppBridge`, `PostMessageTransport`, `installCompat`, and all existing exports. `dist/index.d.ts` contains TypeScript declarations for all public APIs. The compiled bundle is the single source of truth for widget-runtime.
  </done>
</task>

</tasks>

<verification>
1. `cd packages/widget-runtime && npx tsc --noEmit` -- zero errors
2. `cd packages/widget-runtime && npm run build` -- exit code 0
3. `ls packages/widget-runtime/dist/index.mjs packages/widget-runtime/dist/index.d.ts` -- both exist
4. `grep 'class App' packages/widget-runtime/dist/index.mjs` -- App class in output
5. `grep 'class AppBridge' packages/widget-runtime/dist/index.mjs` -- AppBridge in output
6. `grep 'class PostMessageTransport' packages/widget-runtime/dist/index.mjs` -- Transport in output
7. `grep 'CallToolParams' packages/widget-runtime/dist/index.d.ts` -- type definition present
8. `grep 'CallToolResult' packages/widget-runtime/dist/index.d.ts` -- type definition present
9. `grep 'HostContext' packages/widget-runtime/dist/index.d.ts` -- type definition present
</verification>

<success_criteria>
- TypeScript source compiles with zero errors
- tsup build produces `dist/index.mjs` (ES module) and `dist/index.d.ts` (declarations)
- App class implements `connect()`, `callServerTool()`, and lifecycle callbacks using postMessage JSON-RPC
- AppBridge class handles host-side message routing and tool call proxying
- PostMessageTransport implements JSON-RPC 2.0 with correlation IDs and origin validation
- Backward-compat shim maps `window.mcpBridge` to App class with deprecation warning
- All existing exports (WidgetRuntime, hooks, utils, types) remain available
</success_criteria>

<output>
After completion, create `.planning/phases/16-shared-bridge-library/16-01-SUMMARY.md`
</output>
