diff --git a/docs/migrate_from_openai_apps.md b/docs/migrate_from_openai_apps.md new file mode 100644 index 00000000..074d367c --- /dev/null +++ b/docs/migrate_from_openai_apps.md @@ -0,0 +1,197 @@ +# Migrating from OpenAI Apps SDK to MCP Apps SDK + +This guide helps you migrate from the OpenAI Apps SDK (`window.openai.*`) to the MCP Apps SDK (`@modelcontextprotocol/ext-apps`). + +## Quick Start Comparison + +| OpenAI Apps SDK | MCP Apps SDK | +| --------------------------------- | ---------------------------------- | +| Implicit global (`window.openai`) | Explicit instance (`new App(...)`) | +| Properties pre-populated on load | Async connection + notifications | +| Sync property access | Getters + event handlers | + +## Setup & Connection + +| OpenAI | MCP Apps | Notes | +| -------------------------------- | -------------------------------------------------- | ------------------------------------------------------ | +| `window.openai` (auto-available) | `const app = new App({name, version}, {})` | MCP requires explicit instantiation | +| (implicit) | `await app.connect()` | MCP requires async connection; auto-detects OpenAI env | +| — | `await app.connect(new OpenAITransport())` | Force OpenAI mode (not yet available, see [PR #172](https://github.com/modelcontextprotocol/ext-apps/pull/172)) | +| — | `await app.connect(new PostMessageTransport(...))` | Force MCP mode explicitly | + +## Host Context Properties + +| OpenAI | MCP Apps | Notes | +| --------------------------- | --------------------------------------------- | --------------------------------------- | +| `window.openai.theme` | `app.getHostContext()?.theme` | `"light"` \| `"dark"` | +| `window.openai.locale` | `app.getHostContext()?.locale` | BCP 47 language tag (e.g., `"en-US"`) | +| `window.openai.displayMode` | `app.getHostContext()?.displayMode` | `"inline"` \| `"pip"` \| `"fullscreen"` | +| `window.openai.maxHeight` | `app.getHostContext()?.viewport?.maxHeight` | Max container height in px | +| `window.openai.safeArea` | `app.getHostContext()?.safeAreaInsets` | `{ top, right, bottom, left }` | +| `window.openai.userAgent` | `app.getHostContext()?.userAgent` | Host user agent string | +| — | `app.getHostContext()?.availableDisplayModes` | MCP adds: which modes host supports | +| — | `app.getHostContext()?.toolInfo` | MCP adds: tool metadata during call | + +## Tool Data (Input/Output) + +| OpenAI | MCP Apps | Notes | +| ------------------------------------ | ---------------------------------------------------- | ----------------------------------- | +| `window.openai.toolInput` | `app.ontoolinput = (params) => { params.arguments }` | Tool arguments; MCP uses callback | +| `window.openai.toolOutput` | `app.ontoolresult = (params) => { params.content }` | Tool result; MCP uses callback | +| `window.openai.toolResponseMetadata` | `app.ontoolresult` → `params._meta` | Widget-only metadata from server | +| — | `app.ontoolinputpartial = (params) => {...}` | MCP adds: streaming partial args | +| — | `app.ontoolcancelled = (params) => {...}` | MCP adds: cancellation notification | + +## Calling Tools + +| OpenAI | MCP Apps | Notes | +| ------------------------------------------ | ----------------------------------------------------- | ---------------------------- | +| `await window.openai.callTool(name, args)` | `await app.callServerTool({ name, arguments: args })` | Call another MCP server tool | + +## Sending Messages + +| OpenAI | MCP Apps | Notes | +| ----------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------- | +| `await window.openai.sendFollowUpMessage({ prompt })` | `await app.sendMessage({ role: "user", content: [{ type: "text", text: prompt }] })` | MCP uses structured content array | + +## External Links + +| OpenAI | MCP Apps | Notes | +| -------------------------------------------- | ----------------------------------- | ------------------------------------ | +| `await window.openai.openExternal({ href })` | `await app.openLink({ url: href })` | Different param name: `href` → `url` | + +## Display Mode + +| OpenAI | MCP Apps | Notes | +| -------------------------------------------------- | --------------------------------------------------------- | ----------------------------------- | +| `await window.openai.requestDisplayMode({ mode })` | `await app.requestDisplayMode({ mode })` | Same API | +| — | Check `app.getHostContext()?.availableDisplayModes` first | MCP lets you check what's available | + +## Size Reporting + +| OpenAI | MCP Apps | Notes | +| --------------------------------------------- | ----------------------------------------- | ----------------------------------- | +| `window.openai.notifyIntrinsicHeight(height)` | `app.sendSizeChanged({ width, height })` | MCP includes width | +| Manual only | Auto via `{ autoResize: true }` (default) | MCP auto-reports via ResizeObserver | + +## State Persistence + +| OpenAI | MCP Apps | Notes | +| ------------------------------------- | -------- | ----------------------------------------------- | +| `window.openai.widgetState` | — | Not directly available in MCP | +| `window.openai.setWidgetState(state)` | — | Use alternative mechanisms (`localStorage`, server-side state, etc.) | + +## File Operations (Not Yet in MCP Apps) + +| OpenAI | MCP Apps | Notes | +| ---------------------------------------------------- | -------- | ------------------- | +| `await window.openai.uploadFile(file)` | — | Not yet implemented | +| `await window.openai.getFileDownloadUrl({ fileId })` | — | Not yet implemented | + +## Other (Not Yet in MCP Apps) + +| OpenAI | MCP Apps | Notes | +| ------------------------------------------- | -------- | ------------------- | +| `await window.openai.requestModal(options)` | — | Not yet implemented | +| `window.openai.requestClose()` | — | Not yet implemented | +| `window.openai.view` | — | Not yet mapped | + +## Event Handling + +| OpenAI | MCP Apps | Notes | +| ------------------------------ | ------------------------------------------- | -------------------------------- | +| Read `window.openai.*` on load | `app.ontoolinput = (params) => {...}` | Register before `connect()` | +| Read `window.openai.*` on load | `app.ontoolresult = (params) => {...}` | Register before `connect()` | +| Poll or re-read properties | `app.onhostcontextchanged = (ctx) => {...}` | MCP pushes context changes | +| — | `app.onteardown = async () => {...}` | MCP adds: cleanup before unmount | + +## Logging + +| OpenAI | MCP Apps | Notes | +| ------------------ | --------------------------------------------- | ------------------------------- | +| `console.log(...)` | `app.sendLog({ level: "info", data: "..." })` | MCP provides structured logging | + +## Host Info + +| OpenAI | MCP Apps | Notes | +| ------ | --------------------------- | ------------------------------------------------- | +| — | `app.getHostVersion()` | Returns `{ name, version }` of host | +| — | `app.getHostCapabilities()` | Check `serverTools`, `openLinks`, `logging`, etc. | + +## Full Migration Example + +### Before (OpenAI) + +```typescript +// OpenAI Apps SDK +const theme = window.openai.theme; +const toolArgs = window.openai.toolInput; +const toolResult = window.openai.toolOutput; + +// Call a tool +const result = await window.openai.callTool("get_weather", { city: "Tokyo" }); + +// Send a message +await window.openai.sendFollowUpMessage({ prompt: "Weather updated!" }); + +// Report height +window.openai.notifyIntrinsicHeight(400); + +// Open link +await window.openai.openExternal({ href: "https://example.com" }); +``` + +### After (MCP Apps) + +```typescript +import { App } from "@modelcontextprotocol/ext-apps"; + +const app = new App( + { name: "MyApp", version: "1.0.0" }, + {}, + { autoResize: true }, // auto height reporting +); + +// Register handlers BEFORE connect +app.ontoolinput = (params) => { + console.log("Tool args:", params.arguments); +}; + +app.ontoolresult = (params) => { + console.log("Tool result:", params.content); +}; + +app.onhostcontextchanged = (ctx) => { + if (ctx.theme) applyTheme(ctx.theme); +}; + +// Connect (auto-detects OpenAI vs MCP) +await app.connect(); + +// Access context +const theme = app.getHostContext()?.theme; + +// Call a tool +const result = await app.callServerTool({ + name: "get_weather", + arguments: { city: "Tokyo" }, +}); + +// Send a message +await app.sendMessage({ + role: "user", + content: [{ type: "text", text: "Weather updated!" }], +}); + +// Open link (note: url not href) +await app.openLink({ url: "https://example.com" }); +``` + +## Key Differences Summary + +1. **Initialization**: OpenAI is implicit; MCP requires `new App()` + `await app.connect()` +2. **Data Flow**: OpenAI pre-populates; MCP uses async notifications (register handlers before `connect()`) +3. **Auto-resize**: MCP has built-in ResizeObserver support via `autoResize` option +4. **Structured Content**: MCP uses `{ type: "text", text: "..." }` arrays for messages +5. **Context Changes**: MCP pushes updates via `onhostcontextchanged`; no polling needed +6. **Capabilities**: MCP lets you check what the host supports before calling methods