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
132290const result = await window .openai .callTool (" get_weather" , { city: " Tokyo" });
@@ -141,24 +299,20 @@ window.openai.notifyIntrinsicHeight(400);
141299await window .openai .openExternal ({ href: " https://example.com" });
142300```
143301
144- ### After (MCP Apps)
302+ #### After (MCP Apps)
145303
146304``` typescript
147305import { 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)
156310app .ontoolinput = (params ) => {
157311 console .log (" Tool args:" , params .arguments );
158312};
159313
160314app .ontoolresult = (params ) => {
161- console .log (" Tool result:" , params .content );
315+ console .log (" Tool result:" , params .structuredContent );
162316};
163317
164318app .onhostcontextchanged = (ctx ) => {
@@ -169,7 +323,7 @@ app.onhostcontextchanged = (ctx) => {
169323await app .connect ();
170324
171325// Access context
172- const theme = app .getHostContext ()?.theme ;
326+ applyTheme ( app .getHostContext ()?.theme ) ;
173327
174328// Call a tool
175329const result = await app .callServerTool ({
@@ -187,7 +341,7 @@ await app.sendMessage({
187341await app .openLink ({ url: " https://example.com" });
188342```
189343
190- ## Key Differences Summary
344+ ### Key Differences Summary
191345
1923461 . ** Initialization** : OpenAI is implicit; MCP requires ` new App() ` + ` await app.connect() `
1933472 . ** Data Flow** : OpenAI pre-populates; MCP uses async notifications (register handlers before ` connect() ` )
0 commit comments