|
| 1 | +# Migrating from OpenAI Apps SDK to MCP Apps SDK |
| 2 | + |
| 3 | +This guide helps you migrate from the OpenAI Apps SDK (`window.openai.*`) to the MCP Apps SDK (`@modelcontextprotocol/ext-apps`). |
| 4 | + |
| 5 | +## Quick Start Comparison |
| 6 | + |
| 7 | +| OpenAI Apps SDK | MCP Apps SDK | |
| 8 | +| --------------------------------- | ---------------------------------- | |
| 9 | +| Implicit global (`window.openai`) | Explicit instance (`new App(...)`) | |
| 10 | +| Properties pre-populated on load | Async connection + notifications | |
| 11 | +| Sync property access | Getters + event handlers | |
| 12 | + |
| 13 | +## Setup & Connection |
| 14 | + |
| 15 | +| OpenAI | MCP Apps | Notes | |
| 16 | +| -------------------------------- | -------------------------------------------------- | ------------------------------------------------------ | |
| 17 | +| `window.openai` (auto-available) | `const app = new App({name, version}, {})` | MCP requires explicit instantiation | |
| 18 | +| (implicit) | `await app.connect()` | MCP requires async connection; auto-detects OpenAI env | |
| 19 | +| — | `await app.connect(new OpenAITransport())` | Force OpenAI mode explicitly | |
| 20 | +| — | `await app.connect(new PostMessageTransport(...))` | Force MCP mode explicitly | |
| 21 | + |
| 22 | +## Host Context Properties |
| 23 | + |
| 24 | +| OpenAI | MCP Apps | Notes | |
| 25 | +| --------------------------- | --------------------------------------------- | --------------------------------------- | |
| 26 | +| `window.openai.theme` | `app.getHostContext()?.theme` | `"light"` \| `"dark"` | |
| 27 | +| `window.openai.locale` | `app.getHostContext()?.locale` | BCP 47 language tag (e.g., `"en-US"`) | |
| 28 | +| `window.openai.displayMode` | `app.getHostContext()?.displayMode` | `"inline"` \| `"pip"` \| `"fullscreen"` | |
| 29 | +| `window.openai.maxHeight` | `app.getHostContext()?.viewport?.maxHeight` | Max container height in px | |
| 30 | +| `window.openai.safeArea` | `app.getHostContext()?.safeAreaInsets` | `{ top, right, bottom, left }` | |
| 31 | +| `window.openai.userAgent` | `app.getHostContext()?.userAgent` | Host user agent string | |
| 32 | +| — | `app.getHostContext()?.availableDisplayModes` | MCP adds: which modes host supports | |
| 33 | +| — | `app.getHostContext()?.toolInfo` | MCP adds: tool metadata during call | |
| 34 | + |
| 35 | +## Tool Data (Input/Output) |
| 36 | + |
| 37 | +| OpenAI | MCP Apps | Notes | |
| 38 | +| ------------------------------------ | ---------------------------------------------------- | ----------------------------------- | |
| 39 | +| `window.openai.toolInput` | `app.ontoolinput = (params) => { params.arguments }` | Tool arguments; MCP uses callback | |
| 40 | +| `window.openai.toolOutput` | `app.ontoolresult = (params) => { params.content }` | Tool result; MCP uses callback | |
| 41 | +| `window.openai.toolResponseMetadata` | `app.ontoolresult` → `params._meta` | Widget-only metadata from server | |
| 42 | +| — | `app.ontoolinputpartial = (params) => {...}` | MCP adds: streaming partial args | |
| 43 | +| — | `app.ontoolcancelled = (params) => {...}` | MCP adds: cancellation notification | |
| 44 | + |
| 45 | +## Calling Tools |
| 46 | + |
| 47 | +| OpenAI | MCP Apps | Notes | |
| 48 | +| ---------------------------------------------------- | ----------------------------------------------------- | --------------------------------------- | |
| 49 | +| `await window.openai.callTool(name, args)` | `await app.callServerTool({ name, arguments: args })` | Call another MCP server tool | |
| 50 | +| Returns `{ structuredContent?, content?, isError? }` | Returns `{ content, structuredContent?, isError? }` | Same shape, slightly different ordering | |
| 51 | + |
| 52 | +## Sending Messages |
| 53 | + |
| 54 | +| OpenAI | MCP Apps | Notes | |
| 55 | +| ----------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------- | |
| 56 | +| `await window.openai.sendFollowUpMessage({ prompt })` | `await app.sendMessage({ role: "user", content: [{ type: "text", text: prompt }] })` | MCP uses structured content array | |
| 57 | + |
| 58 | +## External Links |
| 59 | + |
| 60 | +| OpenAI | MCP Apps | Notes | |
| 61 | +| -------------------------------------------- | ----------------------------------- | ------------------------------------ | |
| 62 | +| `await window.openai.openExternal({ href })` | `await app.openLink({ url: href })` | Different param name: `href` → `url` | |
| 63 | + |
| 64 | +## Display Mode |
| 65 | + |
| 66 | +| OpenAI | MCP Apps | Notes | |
| 67 | +| -------------------------------------------------- | --------------------------------------------------------- | ----------------------------------- | |
| 68 | +| `await window.openai.requestDisplayMode({ mode })` | `await app.requestDisplayMode({ mode })` | Same API | |
| 69 | +| — | Check `app.getHostContext()?.availableDisplayModes` first | MCP lets you check what's available | |
| 70 | + |
| 71 | +## Size Reporting |
| 72 | + |
| 73 | +| OpenAI | MCP Apps | Notes | |
| 74 | +| --------------------------------------------- | ----------------------------------------- | ----------------------------------- | |
| 75 | +| `window.openai.notifyIntrinsicHeight(height)` | `app.sendSizeChanged({ width, height })` | MCP includes width | |
| 76 | +| Manual only | Auto via `{ autoResize: true }` (default) | MCP auto-reports via ResizeObserver | |
| 77 | + |
| 78 | +## State Persistence |
| 79 | + |
| 80 | +| OpenAI | MCP Apps | Notes | |
| 81 | +| ------------------------------------- | -------- | ----------------------------------------------- | |
| 82 | +| `window.openai.widgetState` | — | Not directly available in MCP | |
| 83 | +| `window.openai.setWidgetState(state)` | — | Use framework state (React, localStorage, etc.) | |
| 84 | + |
| 85 | +## File Operations (Not Yet in MCP Apps) |
| 86 | + |
| 87 | +| OpenAI | MCP Apps | Notes | |
| 88 | +| ---------------------------------------------------- | -------- | ------------------- | |
| 89 | +| `await window.openai.uploadFile(file)` | — | Not yet implemented | |
| 90 | +| `await window.openai.getFileDownloadUrl({ fileId })` | — | Not yet implemented | |
| 91 | + |
| 92 | +## Other (Not Yet in MCP Apps) |
| 93 | + |
| 94 | +| OpenAI | MCP Apps | Notes | |
| 95 | +| ------------------------------------------- | -------- | ------------------- | |
| 96 | +| `await window.openai.requestModal(options)` | — | Not yet implemented | |
| 97 | +| `window.openai.requestClose()` | — | Not yet implemented | |
| 98 | +| `window.openai.view` | — | Not yet mapped | |
| 99 | + |
| 100 | +## Event Handling |
| 101 | + |
| 102 | +| OpenAI | MCP Apps | Notes | |
| 103 | +| ------------------------------ | ------------------------------------------- | -------------------------------- | |
| 104 | +| Read `window.openai.*` on load | `app.ontoolinput = (params) => {...}` | Register before `connect()` | |
| 105 | +| Read `window.openai.*` on load | `app.ontoolresult = (params) => {...}` | Register before `connect()` | |
| 106 | +| Poll or re-read properties | `app.onhostcontextchanged = (ctx) => {...}` | MCP pushes context changes | |
| 107 | +| — | `app.onteardown = async () => {...}` | MCP adds: cleanup before unmount | |
| 108 | + |
| 109 | +## Logging |
| 110 | + |
| 111 | +| OpenAI | MCP Apps | Notes | |
| 112 | +| ------------------ | --------------------------------------------- | ------------------------------- | |
| 113 | +| `console.log(...)` | `app.sendLog({ level: "info", data: "..." })` | MCP provides structured logging | |
| 114 | + |
| 115 | +## Host Info |
| 116 | + |
| 117 | +| OpenAI | MCP Apps | Notes | |
| 118 | +| ------ | --------------------------- | ------------------------------------------------- | |
| 119 | +| — | `app.getHostVersion()` | Returns `{ name, version }` of host | |
| 120 | +| — | `app.getHostCapabilities()` | Check `serverTools`, `openLinks`, `logging`, etc. | |
| 121 | + |
| 122 | +## Full Migration Example |
| 123 | + |
| 124 | +### Before (OpenAI) |
| 125 | + |
| 126 | +```typescript |
| 127 | +// OpenAI Apps SDK |
| 128 | +const theme = window.openai.theme; |
| 129 | +const toolArgs = window.openai.toolInput; |
| 130 | +const toolResult = window.openai.toolOutput; |
| 131 | + |
| 132 | +// Call a tool |
| 133 | +const result = await window.openai.callTool("get_weather", { city: "Tokyo" }); |
| 134 | + |
| 135 | +// Send a message |
| 136 | +await window.openai.sendFollowUpMessage({ prompt: "Weather updated!" }); |
| 137 | + |
| 138 | +// Report height |
| 139 | +window.openai.notifyIntrinsicHeight(400); |
| 140 | + |
| 141 | +// Open link |
| 142 | +await window.openai.openExternal({ href: "https://example.com" }); |
| 143 | +``` |
| 144 | + |
| 145 | +### After (MCP Apps) |
| 146 | + |
| 147 | +```typescript |
| 148 | +import { App } from "@modelcontextprotocol/ext-apps"; |
| 149 | + |
| 150 | +const app = new App( |
| 151 | + { name: "MyApp", version: "1.0.0" }, |
| 152 | + {}, |
| 153 | + { autoResize: true }, // auto height reporting |
| 154 | +); |
| 155 | + |
| 156 | +// Register handlers BEFORE connect |
| 157 | +app.ontoolinput = (params) => { |
| 158 | + console.log("Tool args:", params.arguments); |
| 159 | +}; |
| 160 | + |
| 161 | +app.ontoolresult = (params) => { |
| 162 | + console.log("Tool result:", params.content); |
| 163 | +}; |
| 164 | + |
| 165 | +app.onhostcontextchanged = (ctx) => { |
| 166 | + if (ctx.theme) applyTheme(ctx.theme); |
| 167 | +}; |
| 168 | + |
| 169 | +// Connect (auto-detects OpenAI vs MCP) |
| 170 | +await app.connect(); |
| 171 | + |
| 172 | +// Access context |
| 173 | +const theme = app.getHostContext()?.theme; |
| 174 | + |
| 175 | +// Call a tool |
| 176 | +const result = await app.callServerTool({ |
| 177 | + name: "get_weather", |
| 178 | + arguments: { city: "Tokyo" }, |
| 179 | +}); |
| 180 | + |
| 181 | +// Send a message |
| 182 | +await app.sendMessage({ |
| 183 | + role: "user", |
| 184 | + content: [{ type: "text", text: "Weather updated!" }], |
| 185 | +}); |
| 186 | + |
| 187 | +// Open link (note: url not href) |
| 188 | +await app.openLink({ url: "https://example.com" }); |
| 189 | +``` |
| 190 | + |
| 191 | +## Key Differences Summary |
| 192 | + |
| 193 | +1. **Initialization**: OpenAI is implicit; MCP requires `new App()` + `await app.connect()` |
| 194 | +2. **Data Flow**: OpenAI pre-populates; MCP uses async notifications (register handlers before `connect()`) |
| 195 | +3. **Auto-resize**: MCP has built-in ResizeObserver support via `autoResize` option |
| 196 | +4. **Structured Content**: MCP uses `{ type: "text", text: "..." }` arrays for messages |
| 197 | +5. **Context Changes**: MCP pushes updates via `onhostcontextchanged`; no polling needed |
| 198 | +6. **Capabilities**: MCP lets you check what the host supports before calling methods |
0 commit comments