---
phase: 44-improving-mcp-preview-to-support-chatgpt-version
plan: 02
type: execute
wave: 2
depends_on: ["44-01"]
files_modified:
  - crates/mcp-preview/assets/index.html
  - crates/mcp-preview/assets/widget-runtime.mjs
autonomous: false
requirements:
  - P44-PROTOCOL-TAB
  - P44-CHATGPT-EMULATION
  - P44-BADGE

must_haves:
  truths:
    - "Protocol tab appears in DevTools alongside State, Console, Network, Events tabs"
    - "Protocol tab validates tools/list, resources/list, tools/call, resources/read responses"
    - "Protocol tab shows pass/fail per check with expandable details and key diffs"
    - "In ChatGPT mode, widget iframe receives postMessage with JSON-RPC ui/notifications/tool-result envelope"
    - "In ChatGPT mode, widget iframe has window.openai stub injected before widget-runtime.mjs"
    - "In Standard mode, existing mcpBridge pattern continues to work unchanged"
    - "Mode badge is visible in the browser UI header area"
    - "Validation is warn-only -- never blocks tool execution or widget rendering"
  artifacts:
    - path: "crates/mcp-preview/assets/index.html"
      provides: "Protocol tab UI, mode badge, ChatGPT postMessage dispatch, validation logic"
      min_lines: 1900
    - path: "crates/mcp-preview/assets/widget-runtime.mjs"
      provides: "ChatGPT mode wrapWidgetHtml variant with window.openai stub injection"
      min_lines: 1524
  key_links:
    - from: "index.html (PreviewRuntime.init)"
      to: "/api/config"
      via: "fetch on startup to read mode, descriptor_keys, invocation_keys"
      pattern: "api/config"
    - from: "index.html (handleToolResponse)"
      to: "widget iframe"
      via: "postMessage with ui/notifications/tool-result in chatgpt mode"
      pattern: "ui/notifications/tool-result"
    - from: "index.html (Protocol tab)"
      to: "validation checks"
      via: "validateProtocol() called after tools/list, resources/list, tools/call, resources/read"
      pattern: "validateProtocol"
---

<objective>
Add Protocol diagnostics tab to DevTools and implement ChatGPT mode postMessage emulation with window.openai stub injection in the mcp-preview browser UI.

Purpose: Gives widget developers instant feedback on ChatGPT protocol compliance (missing/extra _meta keys, wrong MIME type, missing structuredContent) and lets them test their widgets in an environment that emulates ChatGPT's actual data delivery mechanism.

Output: Working Protocol tab with pass/fail checks and expandable diffs. ChatGPT mode delivers data via postMessage + window.openai stub. Standard mode unchanged.
</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/44-improving-mcp-preview-to-support-chatgpt-version/44-CONTEXT.md
@.planning/phases/44-improving-mcp-preview-to-support-chatgpt-version/44-RESEARCH.md
@.planning/phases/44-improving-mcp-preview-to-support-chatgpt-version/44-01-SUMMARY.md

@crates/mcp-preview/assets/index.html
@crates/mcp-preview/assets/widget-runtime.mjs

<interfaces>
<!-- From Plan 44-01: ConfigResponse now includes these fields -->
ConfigResponse JSON from GET /api/config:
```json
{
  "mcp_url": "http://localhost:3000",
  "theme": "light",
  "locale": "en-US",
  "initial_tool": null,
  "mode": "standard",
  "descriptor_keys": [
    "openai/outputTemplate",
    "openai/toolInvocation/invoking",
    "openai/toolInvocation/invoked",
    "openai/widgetAccessible"
  ],
  "invocation_keys": [
    "openai/toolInvocation/invoking",
    "openai/toolInvocation/invoked"
  ]
}
```

<!-- ResourceInfo and ResourceContentItem now include _meta -->
ResourceInfo from GET /api/resources:
```json
{
  "uri": "ui://app/chess",
  "name": "chess",
  "mimeType": "text/html+skybridge",
  "_meta": { "openai/outputTemplate": "...", ... }
}
```

ToolCallResult from POST /api/tools/call:
```json
{
  "success": true,
  "content": [...],
  "structuredContent": { ... },
  "_meta": { "openai/toolInvocation/invoking": "...", ... }
}
```

<!-- Existing DevTools tab pattern in index.html -->
HTML structure:
```html
<div class="devtools-tabs">
  <button class="devtools-tab active" data-tab="state">State</button>
  <button class="devtools-tab" data-tab="console">Console</button>
  <button class="devtools-tab" data-tab="network">Network</button>
  <button class="devtools-tab" data-tab="events">Events</button>
  <!-- ADD: Protocol tab here -->
</div>
<div class="devtools-content">
  <div class="devtools-section active" id="tab-state">...</div>
  <div class="devtools-section" id="tab-console">...</div>
  <div class="devtools-section" id="tab-network">...</div>
  <div class="devtools-section" id="tab-events">...</div>
  <!-- ADD: Protocol section here -->
</div>
```

<!-- Existing handleToolResponse flow -->
In PreviewRuntime class:
- handleToolResponse(result) processes tool call results
- loadResourceWidget(widgetUri) loads HTML into iframe via srcdoc
- wrapWidgetHtml(html, toolName) wraps widget HTML with bridge script injection
- appBridge.sendToolResult({ structuredContent, _meta }) sends data to iframe
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add Protocol tab UI and validation logic to index.html</name>
  <files>crates/mcp-preview/assets/index.html</files>
  <action>
1. **Mode badge in header:** Near the top of the page (in the header/toolbar area), add a mode badge element:
   ```html
   <span id="mode-badge" class="mode-badge"></span>
   ```
   Style it with CSS: pill-shaped badge, green background for Standard, red for ChatGPT. Text: "Standard" or "ChatGPT Strict".

2. **Protocol tab button:** Add a new tab button in the `.devtools-tabs` div after the Events button:
   ```html
   <button class="devtools-tab" data-tab="protocol">Protocol</button>
   ```

3. **Protocol tab content:** Add a new section in `.devtools-content` after `#tab-events`:
   ```html
   <div class="devtools-section" id="tab-protocol">
     <div id="protocol-checks" class="protocol-checks">
       <div class="protocol-empty">Run a tool call to see protocol validation results</div>
     </div>
   </div>
   ```

4. **Protocol tab CSS:** Add styles for protocol checks:
   - `.protocol-checks` container
   - `.protocol-check` item with pass/fail indicator (green check / red X)
   - `.protocol-check-header` clickable row with check name and status
   - `.protocol-check-details` expandable section showing actual keys vs expected keys
   - `.protocol-diff` for key diff display: "Extra keys: ..." in orange, "Missing keys: ..." in red
   - In ChatGPT mode, validation failures get red background highlight. In Standard mode, informational yellow.

5. **Validation logic in PreviewRuntime:** Add a `validateProtocol(checkName, meta, expectedKeys, extras)` method that:
   - Compares actual keys in `_meta` against `expectedKeys` array (from config)
   - Returns `{ pass: boolean, actual: string[], expected: string[], extra: string[], missing: string[] }`
   - Stores results in `this.protocolResults` array

6. **Protocol rendering:** Add `renderProtocolTab()` method that:
   - Clears `#protocol-checks` content
   - For each result in `this.protocolResults`, creates a check item with:
     - Header: check name (e.g., "tools/list _meta") + pass/fail badge
     - Expandable details: actual keys list, expected keys list, diff (extra/missing)
   - In ChatGPT mode (`this.mode === 'chatgpt'`), failures are red warnings. In Standard mode, informational.

7. **Hook validation into existing flows:**
   - After `loadTools()` (tools/list): validate each tool's `_meta` has exactly the 4 descriptor keys. Call `validateProtocol('tools/list [toolName]', tool._meta, this.descriptorKeys)`.
   - After `loadResources()` (resources/list): validate each resource's `_meta` has exactly the 4 descriptor keys.
   - After `handleToolResponse()` (tools/call): validate `result._meta` has exactly the 2 invocation keys. Also check `result.structuredContent` is present (separate check).
   - After `read_resource` fetch (resources/read): validate response content's `_meta` has the 4 descriptor keys. Also check `mimeType` is `text/html+skybridge`.
   - Always call `renderProtocolTab()` after adding new results.

8. **Config loading:** In the `init()` method where `/api/config` is fetched, store `this.mode = config.mode`, `this.descriptorKeys = config.descriptor_keys || config.descriptorKeys`, `this.invocationKeys = config.invocation_keys || config.invocationKeys`. Also populate the mode badge text and styling.

9. **Validation is WARN ONLY:** Never block, never prevent rendering. Just log results to Protocol tab. Never throw or return early from tool call / resource read flows based on validation results.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check -p mcp-preview 2>&1 | tail -3</automated>
  </verify>
  <done>Protocol tab appears in DevTools with pass/fail validation for all 4 MCP methods. Mode badge visible in header. Validation results show key diffs with extra/missing keys. All warn-only, never blocks rendering.</done>
</task>

<task type="auto">
  <name>Task 2: ChatGPT mode postMessage emulation and window.openai stub</name>
  <files>crates/mcp-preview/assets/index.html, crates/mcp-preview/assets/widget-runtime.mjs</files>
  <action>
1. **wrapWidgetHtml mode branch:** In the `wrapWidgetHtml(html, toolName)` function (or method), add a mode parameter. When `this.mode === 'chatgpt'`, inject a `window.openai` stub script BEFORE the widget-runtime.mjs import in the srcdoc HTML:
   ```javascript
   const openaiStub = this.mode === 'chatgpt' ? `
     <script>
       window.openai = {
         toolOutput: null,
         toolInput: null,
         theme: '${this.theme || 'light'}',
         callTool: async (name, args) => {
           return window.parent.previewRuntime.handleWidgetToolCall(name, args);
         }
       };
     </script>
   ` : '';
   ```
   Insert this script BEFORE the `<script type="module">` that imports widget-runtime.mjs. This ensures `_detectHost()` in widget-runtime.mjs finds `window.openai` and identifies the host as "chatgpt".

2. **ChatGPT postMessage delivery:** In `handleToolResponse()` (or wherever `appBridge.sendToolResult()` is called), add a ChatGPT mode branch:
   - If `this.mode === 'chatgpt'`:
     - After the widget iframe is loaded, send a postMessage with the ChatGPT JSON-RPC envelope:
       ```javascript
       iframe.contentWindow.postMessage({
         jsonrpc: "2.0",
         method: "ui/notifications/tool-result",
         params: { structuredContent: result.structuredContent }
       }, "*");
       ```
     - Also update `window.openai.toolOutput` in the iframe via a second postMessage or direct property set (if same-origin srcdoc allows):
       ```javascript
       try {
         iframe.contentWindow.openai.toolOutput = result.structuredContent;
         iframe.contentWindow.openai.toolInput = result.toolInput || null;
       } catch(e) { /* srcdoc cross-origin guard */ }
       ```
   - If `this.mode === 'standard'`: continue using existing `appBridge.sendToolResult()` pattern unchanged.

3. **handleWidgetToolCall bridge:** Add a `handleWidgetToolCall(name, args)` method to `PreviewRuntime` that the `window.openai.callTool` stub delegates to via `window.parent.previewRuntime`. This method should:
   - Call the existing `/api/tools/call` endpoint
   - Return the structuredContent from the result
   - This enables widgets that use `window.openai.callTool()` to work in preview

4. **Standard mode untouched:** Verify that when `this.mode === 'standard'` (default), the existing `mcpBridge`/`AppBridge` flow is used exactly as before. No behavior change for standard mode.

5. **widget-runtime.mjs:** Review `_detectHost()` function. If it already checks `window.openai` to identify ChatGPT host, no changes needed. If it needs adjustment for the stub to be recognized, make minimal changes. The stub must be detected as the "chatgpt" host so widgets get the ChatGPT code path.

IMPORTANT: Keep AppBridge active even in ChatGPT mode for the ui/initialize handshake. The ChatGPT postMessage delivery is ADDITIONAL -- it does not replace AppBridge, it supplements it so widgets that rely on either mechanism work.
  </action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check -p mcp-preview 2>&1 | tail -3</automated>
  </verify>
  <done>In ChatGPT mode, widget iframe has window.openai stub (toolOutput, toolInput, theme, callTool). Data delivered via postMessage with JSON-RPC envelope. In Standard mode, existing mcpBridge pattern works unchanged. AppBridge remains active in both modes.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Verify Protocol tab and ChatGPT mode end-to-end</name>
  <files>crates/mcp-preview/assets/index.html</files>
  <action>Human verification of the complete browser-side implementation. All automation is complete in Tasks 1-2. This checkpoint verifies visual and interactive behavior that cannot be automated.</action>
  <verify>
    <automated>cd /Users/guy/Development/mcp/sdk/rust-mcp-sdk && cargo check -p mcp-preview -p cargo-pmcp 2>&1 | tail -3</automated>
  </verify>
  <done>User has verified: Protocol tab shows validation results, ChatGPT mode emulates postMessage delivery with window.openai stub, Standard mode works unchanged, mode badge displays correctly.</done>
  <what-built>Protocol diagnostics tab and ChatGPT mode emulation in mcp-preview</what-built>
  <how-to-verify>
    1. Start an MCP server with MCP Apps widgets (e.g., chess or map example)
    2. Run: `cargo pmcp preview --url http://localhost:3000 --mode chatgpt --open`
    3. Verify terminal banner shows "ChatGPT Strict" mode
    4. In browser, verify mode badge shows "ChatGPT Strict" (red)
    5. Click the "Protocol" tab in DevTools -- should show validation results
    6. Call a tool -- Protocol tab should show pass/fail for tools/call checks (invocation keys, structuredContent)
    7. Check that widget renders correctly (validation is warn-only)
    8. Open browser console, verify window.openai exists in the widget iframe
    9. Stop and restart with `--mode standard` (or no --mode flag)
    10. Verify mode badge shows "Standard" (green)
    11. Verify Protocol tab still shows informational checks (no red warnings for standard mode)
    12. Verify widget works normally with existing mcpBridge pattern
  </how-to-verify>
  <resume-signal>Type "approved" or describe issues</resume-signal>
</task>

</tasks>

<verification>
- Protocol tab visible in both modes with validation results
- ChatGPT mode: window.openai stub present, postMessage delivery works
- Standard mode: no behavior change from pre-phase-44
- Mode badge accurate in browser header
- Terminal banner shows active mode
- Validation never blocks tool calls or widget rendering
</verification>

<success_criteria>
- Protocol tab shows pass/fail for tools/list (4 descriptor keys), resources/list (4 descriptor keys), tools/call (2 invocation keys + structuredContent), resources/read (4 descriptor keys + mimeType)
- Key diff shows extra/missing keys on failure
- ChatGPT mode delivers data via postMessage with JSON-RPC ui/notifications/tool-result envelope
- ChatGPT mode injects window.openai stub before widget-runtime.mjs
- Standard mode behavior is identical to pre-phase-44
- Mode badge in browser UI header shows current mode
</success_criteria>

<output>
After completion, create `.planning/phases/44-improving-mcp-preview-to-support-chatgpt-version/44-02-SUMMARY.md`
</output>
