Skip to content

Commit 6df8f40

Browse files
authored
Merge branch 'main' into ochafik/pdf-server2
2 parents 69a5975 + 978d8e6 commit 6df8f40

1 file changed

Lines changed: 197 additions & 43 deletions

File tree

docs/migrate_from_openai_apps.md

Lines changed: 197 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,183 @@
11
# Migrating from OpenAI Apps SDK to MCP Apps SDK
22

3-
This guide helps you migrate from the OpenAI Apps SDK (`window.openai.*`) to the MCP Apps SDK (`@modelcontextprotocol/ext-apps`).
3+
This guide helps you migrate from the OpenAI Apps SDK to the MCP Apps SDK (`@modelcontextprotocol/ext-apps`).
44

5-
## Quick Start Comparison
5+
## Server-Side
6+
7+
### Quick Start Comparison
8+
9+
| OpenAI Apps SDK | MCP Apps SDK |
10+
| ---------------------------------------------------------- | -------------------------------------------------------------- |
11+
| Flat metadata keys (`_meta["openai/..."]`) | Nested metadata structure (`_meta.ui.*`) |
12+
| Direct `server.registerTool()`/`server.registerResource()` | Helper functions: `registerAppTool()`, `registerAppResource()` |
13+
| UI Resource MIME type: `text/html+skybridge` | UI Resource MIME type: `text/html;profile=mcp-app` |
14+
15+
### Tool Metadata
16+
17+
| OpenAI | MCP Apps | Notes |
18+
| ---------------------------------------------- | ---------------------------------- | ----------------------------------------------------------- |
19+
| `_meta["openai/outputTemplate"]` | `_meta.ui.resourceUri` | URI of UI resource |
20+
| `_meta["openai/toolInvocation/invoking"]` || Not yet implemented |
21+
| `_meta["openai/toolInvocation/invoked"]` || Not yet implemented |
22+
| `_meta["openai/widgetAccessible"]` (`boolean`) | `_meta.ui.visibility` (`string[]`) | `true`/`false` → include/exclude `"app"` in array |
23+
| `_meta["openai/visibility"]` (`string`) | `_meta.ui.visibility` (`string[]`) | `"public"`/`"private"` → include/exclude `"model"` in array |
24+
25+
### Resource Metadata
26+
27+
| OpenAI | MCP Apps | Notes |
28+
| ------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------- |
29+
| `_meta["openai/widgetCSP"]` | `_meta.ui.csp` | `connect_domains``connectDomains`, `resource_domains``resourceDomains`, etc. |
30+
|| `_meta.ui.permissions` | MCP adds: permissions for camera, microphone, geolocation, clipboard |
31+
| `_meta["openai/widgetDomain"]` | `_meta.ui.domain` | Dedicated sandbox origin |
32+
| `_meta["openai/widgetPrefersBorder"]` | `_meta.ui.prefersBorder` | Visual boundary preference |
33+
| `_meta["openai/widgetDescription"]` || Not yet implemented; use `app.updateModelContext()` for dynamic context |
34+
35+
### Resource MIME Type
36+
37+
| OpenAI | MCP Apps | Notes |
38+
| --------------------- | --------------------------- | -------------------------------------------------------------------------------- |
39+
| `text/html+skybridge` | `text/html;profile=mcp-app` | Auto-set by `registerAppResource()`; use `RESOURCE_MIME_TYPE` constant if manual |
40+
41+
### Server-Side Migration Example
42+
43+
### Before (OpenAI)
44+
45+
```typescript
46+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
47+
import { z } from "zod";
48+
49+
function createServer() {
50+
const server = new McpServer({ name: "shop", version: "1.0.0" });
51+
52+
// Register tool with OpenAI metadata
53+
server.registerTool(
54+
"shopping-cart",
55+
{
56+
title: "Shopping Cart",
57+
description: "Display the user's shopping cart",
58+
inputSchema: { userId: z.string() },
59+
annotations: { readOnlyHint: true },
60+
_meta: {
61+
"openai/outputTemplate": "ui://widget/cart.html",
62+
"openai/toolInvocation/invoking": "Loading cart...",
63+
"openai/toolInvocation/invoked": "Cart ready",
64+
"openai/widgetAccessible": true,
65+
},
66+
},
67+
async (args) => {
68+
const cart = await getCart(args.userId);
69+
return {
70+
content: [{ type: "text", text: JSON.stringify(cart) }],
71+
structuredContent: { cart },
72+
};
73+
},
74+
);
75+
76+
// Register UI resource
77+
server.registerResource(
78+
"Cart Widget",
79+
"ui://widget/cart.html",
80+
{ mimeType: "text/html+skybridge" },
81+
async () => ({
82+
contents: [
83+
{
84+
uri: "ui://widget/cart.html",
85+
mimeType: "text/html+skybridge",
86+
text: getCartHtml(),
87+
},
88+
],
89+
}),
90+
);
91+
92+
return server;
93+
}
94+
```
95+
96+
### After (MCP Apps)
97+
98+
```typescript
99+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
100+
import {
101+
registerAppTool,
102+
registerAppResource,
103+
RESOURCE_MIME_TYPE,
104+
} from "@modelcontextprotocol/ext-apps/server";
105+
import { z } from "zod";
106+
107+
function createServer() {
108+
const server = new McpServer({ name: "shop", version: "1.0.0" });
109+
110+
// Register tool with MCP Apps metadata
111+
registerAppTool(
112+
server,
113+
"shopping-cart",
114+
{
115+
title: "Shopping Cart",
116+
description: "Display the user's shopping cart",
117+
inputSchema: { userId: z.string() },
118+
annotations: { readOnlyHint: true },
119+
_meta: { ui: { resourceUri: "ui://widget/cart.html" } },
120+
},
121+
async (args) => {
122+
const cart = await getCart(args.userId);
123+
return {
124+
content: [{ type: "text", text: JSON.stringify(cart) }],
125+
structuredContent: { cart },
126+
};
127+
},
128+
);
129+
130+
// Register UI resource
131+
registerAppResource(
132+
server,
133+
"Cart Widget",
134+
"ui://widget/cart.html",
135+
{ description: "Shopping cart UI" },
136+
async () => ({
137+
contents: [
138+
{
139+
uri: "ui://widget/cart.html",
140+
mimeType: RESOURCE_MIME_TYPE,
141+
text: getCartHtml(),
142+
},
143+
],
144+
}),
145+
);
146+
147+
return server;
148+
}
149+
```
150+
151+
### Key Differences Summary
152+
153+
1. **Metadata Structure**: OpenAI uses flat `_meta["openai/..."]` properties; MCP uses nested `_meta.ui.*` structure
154+
2. **Tool Visibility**: OpenAI uses boolean/string (`true`/`"public"`); MCP uses string arrays (`["app", "model"]`)
155+
3. **CSP Property Names**: snake_case → camelCase (`connect_domains``connectDomains`)
156+
4. **App Permissions**: MCP adds `_meta.ui.permissions` for camera, microphone, geolocation, clipboard (not in OpenAI)
157+
5. **Resource MIME Type**: `text/html+skybridge``text/html;profile=mcp-app` (use `RESOURCE_MIME_TYPE` constant)
158+
6. **Helper Functions**: MCP provides `registerAppTool()` and `registerAppResource()` helpers
159+
7. **Not Yet Implemented**: `_meta["openai/toolInvocation/invoking"]`, `_meta["openai/toolInvocation/invoked"]`, and `_meta["openai/widgetDescription"]` don't have MCP equivalents yet
160+
161+
## Client-side
162+
163+
### Quick Start Comparison
6164

7165
| OpenAI Apps SDK | MCP Apps SDK |
8166
| --------------------------------- | ---------------------------------- |
9167
| Implicit global (`window.openai`) | Explicit instance (`new App(...)`) |
10168
| Properties pre-populated on load | Async connection + notifications |
11169
| Sync property access | Getters + event handlers |
12170

13-
## Setup & Connection
171+
### Setup & Connection
14172

15173
| OpenAI | MCP Apps | Notes |
16174
| -------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
17175
| `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 |
176+
| (implicit) | Vanilla: `await app.connect()` / React: `useApp()` | MCP requires async connection; auto-detects OpenAI env |
19177
|| `await app.connect(new OpenAITransport())` | Force OpenAI mode (not yet available, see [PR #172](https://github.com/modelcontextprotocol/ext-apps/pull/172)) |
20178
|| `await app.connect(new PostMessageTransport(...))` | Force MCP mode explicitly |
21179

22-
## Host Context Properties
180+
### Host Context Properties
23181

24182
| OpenAI | MCP Apps | Notes |
25183
| --------------------------- | --------------------------------------------- | --------------------------------------- |
@@ -32,71 +190,71 @@ This guide helps you migrate from the OpenAI Apps SDK (`window.openai.*`) to the
32190
|| `app.getHostContext()?.availableDisplayModes` | MCP adds: which modes host supports |
33191
|| `app.getHostContext()?.toolInfo` | MCP adds: tool metadata during call |
34192

35-
## Tool Data (Input/Output)
193+
### Tool Data (Input/Output)
36194

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 |
195+
| OpenAI | MCP Apps | Notes |
196+
| ------------------------------------ | ------------------------------------------------------------- | ----------------------------------- |
197+
| `window.openai.toolInput` | `app.ontoolinput = (params) => { params.arguments }` | Tool arguments; MCP uses callback |
198+
| `window.openai.toolOutput` | `app.ontoolresult = (params) => { params.structuredContent }` | Tool result; MCP uses callback |
199+
| `window.openai.toolResponseMetadata` | `app.ontoolresult``params._meta` | Widget-only metadata from server |
200+
|| `app.ontoolinputpartial = (params) => {...}` | MCP adds: streaming partial args |
201+
|| `app.ontoolcancelled = (params) => {...}` | MCP adds: cancellation notification |
44202

45-
## Calling Tools
203+
### Calling Tools
46204

47205
| OpenAI | MCP Apps | Notes |
48206
| ------------------------------------------ | ----------------------------------------------------- | ---------------------------- |
49207
| `await window.openai.callTool(name, args)` | `await app.callServerTool({ name, arguments: args })` | Call another MCP server tool |
50208

51-
## Sending Messages
209+
### Sending Messages
52210

53211
| OpenAI | MCP Apps | Notes |
54212
| ----------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------- |
55213
| `await window.openai.sendFollowUpMessage({ prompt })` | `await app.sendMessage({ role: "user", content: [{ type: "text", text: prompt }] })` | MCP uses structured content array |
56214

57-
## External Links
215+
### External Links
58216

59217
| OpenAI | MCP Apps | Notes |
60218
| -------------------------------------------- | ----------------------------------- | ------------------------------------ |
61219
| `await window.openai.openExternal({ href })` | `await app.openLink({ url: href })` | Different param name: `href``url` |
62220

63-
## Display Mode
221+
### Display Mode
64222

65223
| OpenAI | MCP Apps | Notes |
66224
| -------------------------------------------------- | --------------------------------------------------------- | ----------------------------------- |
67225
| `await window.openai.requestDisplayMode({ mode })` | `await app.requestDisplayMode({ mode })` | Same API |
68226
|| Check `app.getHostContext()?.availableDisplayModes` first | MCP lets you check what's available |
69227

70-
## Size Reporting
228+
### Size Reporting
71229

72-
| OpenAI | MCP Apps | Notes |
73-
| --------------------------------------------- | ----------------------------------------- | ----------------------------------- |
74-
| `window.openai.notifyIntrinsicHeight(height)` | `app.sendSizeChanged({ width, height })` | MCP includes width |
75-
| Manual only | Auto via `{ autoResize: true }` (default) | MCP auto-reports via ResizeObserver |
230+
| OpenAI | MCP Apps | Notes |
231+
| --------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------- |
232+
| `window.openai.notifyIntrinsicHeight(height)` | `app.sendSizeChanged({ width, height })` | MCP includes width |
233+
| Manual only | `new App(appInfo, capabilities, { autoResize: true /* default */ })` | MCP auto-reports via `ResizeObserver` |
76234

77-
## State Persistence
235+
### State Persistence
78236

79237
| OpenAI | MCP Apps | Notes |
80238
| ------------------------------------- | -------- | -------------------------------------------------------------------- |
81239
| `window.openai.widgetState` || Not directly available in MCP |
82240
| `window.openai.setWidgetState(state)` || Use alternative mechanisms (`localStorage`, server-side state, etc.) |
83241

84-
## File Operations (Not Yet in MCP Apps)
242+
### File Operations (Not Yet in MCP Apps)
85243

86244
| OpenAI | MCP Apps | Notes |
87245
| ---------------------------------------------------- | -------- | ------------------- |
88246
| `await window.openai.uploadFile(file)` || Not yet implemented |
89247
| `await window.openai.getFileDownloadUrl({ fileId })` || Not yet implemented |
90248

91-
## Other (Not Yet in MCP Apps)
249+
### Other (Not Yet in MCP Apps)
92250

93251
| OpenAI | MCP Apps | Notes |
94252
| ------------------------------------------- | -------- | ------------------- |
95253
| `await window.openai.requestModal(options)` || Not yet implemented |
96254
| `window.openai.requestClose()` || Not yet implemented |
97255
| `window.openai.view` || Not yet mapped |
98256

99-
## Event Handling
257+
### Event Handling
100258

101259
| OpenAI | MCP Apps | Notes |
102260
| ------------------------------ | ------------------------------------------- | -------------------------------- |
@@ -105,28 +263,28 @@ This guide helps you migrate from the OpenAI Apps SDK (`window.openai.*`) to the
105263
| Poll or re-read properties | `app.onhostcontextchanged = (ctx) => {...}` | MCP pushes context changes |
106264
|| `app.onteardown = async () => {...}` | MCP adds: cleanup before unmount |
107265

108-
## Logging
266+
### Logging
109267

110268
| OpenAI | MCP Apps | Notes |
111269
| ------------------ | --------------------------------------------- | ------------------------------- |
112270
| `console.log(...)` | `app.sendLog({ level: "info", data: "..." })` | MCP provides structured logging |
113271

114-
## Host Info
272+
### Host Info
115273

116274
| OpenAI | MCP Apps | Notes |
117275
| ------ | --------------------------- | ------------------------------------------------- |
118276
|| `app.getHostVersion()` | Returns `{ name, version }` of host |
119277
|| `app.getHostCapabilities()` | Check `serverTools`, `openLinks`, `logging`, etc. |
120278

121-
## Full Migration Example
279+
### Full Migration Example
122280

123-
### Before (OpenAI)
281+
#### Before (OpenAI)
124282

125283
```typescript
126284
// OpenAI Apps SDK
127-
const theme = window.openai.theme;
128-
const toolArgs = window.openai.toolInput;
129-
const toolResult = window.openai.toolOutput;
285+
applyTheme(window.openai.theme);
286+
console.log("Tool args:", window.openai.toolInput);
287+
console.log("Tool result:", window.openai.toolOutput);
130288

131289
// Call a tool
132290
const result = await window.openai.callTool("get_weather", { city: "Tokyo" });
@@ -141,24 +299,20 @@ window.openai.notifyIntrinsicHeight(400);
141299
await window.openai.openExternal({ href: "https://example.com" });
142300
```
143301

144-
### After (MCP Apps)
302+
#### After (MCP Apps)
145303

146304
```typescript
147305
import { App } from "@modelcontextprotocol/ext-apps";
148306

149-
const app = new App(
150-
{ name: "MyApp", version: "1.0.0" },
151-
{},
152-
{ autoResize: true }, // auto height reporting
153-
);
307+
const app = new App({ name: "MyApp", version: "1.0.0" });
154308

155-
// Register handlers BEFORE connect
309+
// Register handlers BEFORE connect (events may occur immediately after connect)
156310
app.ontoolinput = (params) => {
157311
console.log("Tool args:", params.arguments);
158312
};
159313

160314
app.ontoolresult = (params) => {
161-
console.log("Tool result:", params.content);
315+
console.log("Tool result:", params.structuredContent);
162316
};
163317

164318
app.onhostcontextchanged = (ctx) => {
@@ -169,7 +323,7 @@ app.onhostcontextchanged = (ctx) => {
169323
await app.connect();
170324

171325
// Access context
172-
const theme = app.getHostContext()?.theme;
326+
applyTheme(app.getHostContext()?.theme);
173327

174328
// Call a tool
175329
const result = await app.callServerTool({
@@ -187,7 +341,7 @@ await app.sendMessage({
187341
await app.openLink({ url: "https://example.com" });
188342
```
189343

190-
## Key Differences Summary
344+
### Key Differences Summary
191345

192346
1. **Initialization**: OpenAI is implicit; MCP requires `new App()` + `await app.connect()`
193347
2. **Data Flow**: OpenAI pre-populates; MCP uses async notifications (register handlers before `connect()`)

0 commit comments

Comments
 (0)