docs: how to migrate from openai apps (#260)

* Create migrate_from_openai_apps.md

* Update AGENTS.md

* Update docs/migrate_from_openai_apps.md

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

* address PR review comments

- Remove AGENTS.md reference to migration guide
- Add note that OpenAITransport is not yet available (PR #172)
- Remove unnecessary note about return type ordering

---------

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
This commit is contained in:
Olivier Chafik 2026-01-13 15:23:26 +00:00 • committed by GitHub
commit 152b953ae6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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