Migration Checklist
- Replace OpenAI tool metadata keys with
_meta.ui. - Replace
text/html+skybridgewithRESOURCE_MIME_TYPE, which istext/html;profile=mcp-app. - Convert resource CSP keys from snake case to camel case.
- Register UI tools and resources with
registerAppTool()andregisterAppResource(). - Keep model-readable
contentin tool results and put View data instructuredContent. - Replace
window.openaireads and methods with theAppclass or ReactuseApp()hook. - Register App event handlers before
app.connect()when using the plainAppclass. - Test in a host that supports MCP Apps, then verify text fallback behavior for clients without UI support.
Server Metadata Mapping
| OpenAI Apps SDK | MCP Apps SDK | Notes |
|---|---|---|
_meta["openai/outputTemplate"] | _meta.ui.resourceUri | Points the tool at the ui:// resource to render. |
_meta["openai/widgetAccessible"] | _meta.ui.visibility | Include "app" when the View can call the tool. |
_meta["openai/visibility"] | _meta.ui.visibility | Include "model" when the model can call the tool. |
_meta["openai/toolInvocation/invoking"] | No direct MCP Apps field | Use normal tool result text or app state in the View. |
_meta["openai/toolInvocation/invoked"] | No direct MCP Apps field | Use normal tool result text or app state in the View. |
["model", "app"]. A UI helper such as pagination or autosave uses ["app"] so it stays out of the model tool list.
Resource Mapping
| OpenAI Apps SDK | MCP Apps SDK | Notes |
|---|---|---|
text/html+skybridge | RESOURCE_MIME_TYPE | The constant resolves to text/html;profile=mcp-app. |
_meta["openai/widgetCSP"] | _meta.ui.csp | Resource-level CSP for the sandboxed iframe. |
resource_domains | resourceDomains | Static assets such as scripts, CSS, images, fonts, and media. |
connect_domains | connectDomains | Fetch, XHR, EventSource, and WebSocket origins. |
frame_domains | frameDomains | Origins for nested iframes. |
redirect_domains | No direct MCP Apps field | Host-specific OpenAI redirects do not map to MCP Apps CSP. |
_meta["openai/widgetDomain"] | _meta.ui.domain | Stable sandbox origin when a host supports it. |
_meta["openai/widgetPrefersBorder"] | _meta.ui.prefersBorder | Set this explicitly because host defaults can differ. |
_meta["openai/widgetDescription"] | No direct MCP Apps field | Use updateModelContext() for dynamic model-visible context. |
Before and After
Before, an OpenAI Apps SDK tool links its UI with a flat metadata key:Client Runtime Mapping
OpenAI Apps SDK Views usually read a pre-populatedwindow.openai object. MCP Apps Views create an App instance, register handlers, and connect to the host.
| OpenAI Apps SDK | MCP Apps SDK | Notes |
|---|---|---|
window.openai.toolInput | app.ontoolinput = (params) => params.arguments | Register the handler before connect(). |
window.openai.toolOutput | app.ontoolresult = (params) => params.structuredContent | Use structuredContent for UI data. |
window.openai.toolResponseMetadata | app.ontoolresult = (params) => params._meta | Use only for host or component-only metadata. |
window.openai.theme | app.getHostContext()?.theme | Also listen for onhostcontextchanged. |
window.openai.locale | app.getHostContext()?.locale | BCP 47 locale string. |
window.openai.displayMode | app.getHostContext()?.displayMode | Check available modes before requesting a change. |
window.openai.callTool(name, args) | app.callServerTool({ name, arguments: args }) | Used by app-only helper tools. |
window.openai.sendFollowUpMessage({ prompt }) | app.sendMessage({ role, content }) | MCP Apps uses MCP content blocks. |
window.openai.openExternal({ href }) | app.openLink({ url }) | Host-mediated external navigation. |
window.openai.notifyIntrinsicHeight(height) | app.sendSizeChanged({ width, height }) | Auto resize is on by default for the App class. |
useApp(), which manages the connection lifecycle for you.
Features Without Direct Equivalents
Some OpenAI Apps SDK APIs do not have direct MCP Apps equivalents. Keep these migrations explicit so the app fails predictably during review:| OpenAI Apps SDK feature | MCP Apps migration path |
|---|---|
window.openai.widgetState and setWidgetState() | Use server-side state or app-owned browser state. |
window.openai.uploadFile() | Keep file upload in a host-specific branch until MCP Apps adds a standard API. |
window.openai.getFileDownloadUrl() | Use host-specific code or a normal server URL with declared CSP. |
window.openai.requestModal() and requestClose() | Model as an in-View interaction or host-specific branch. |
window.openai.view | Use host context, display modes, and CSS layout hooks. |
Validate the Migration
Run these checks before testing in a real host:- Search for
"openai/in server code. Remaining hits should be deliberate host-specific compatibility code. - Search for
text/html+skybridge. Replace it withRESOURCE_MIME_TYPE. - Search for
resource_domains,connect_domains, andframe_domains. Replace them with MCP Apps camel-case fields. - Search for
window.openaiin View code. Replace common runtime calls withAppor React hooks. - Call
tools/listand confirm the UI tool includes_meta.ui.resourceUri. - Call
resources/readand confirm the resource returnsmimeType: "text/html;profile=mcp-app". - Call the UI tool and confirm the result includes model-readable
contentand View-readablestructuredContent. - Open the app in the sunpeak inspector and verify the iframe can load every declared external origin.