@modelcontextprotocol/ext-apps - v1.0.1
    Preparing search index...

    Migrating from OpenAI Apps SDK to MCP Apps SDK

    This reference maps OpenAI Apps SDK concepts to their MCP Apps SDK (@modelcontextprotocol/ext-apps) equivalents. Use the tables below for quick lookup during migration, and refer to the code examples for complete before/after comparisons.

    This guide covers server-side changes first (metadata, tools, resources), then client-side changes (setup, context, events).

    Note

    Some OpenAI Apps SDK features don't have MCP equivalents yet. These are marked "Not yet implemented" in the tables below.

    The server-side changes involve updating metadata structure and using helper functions.

    OpenAI Apps SDK MCP Apps SDK
    Flat metadata keys (_meta["openai/..."]) Nested metadata structure (_meta.ui.*)
    Direct server.registerTool()/server.registerResource() Helper functions: registerAppTool(), registerAppResource()
    UI Resource MIME type: text/html+skybridge UI Resource MIME type: text/html;profile=mcp-app
    OpenAI MCP Apps Notes
    _meta["openai/outputTemplate"] _meta.ui.resourceUri URI of UI resource
    _meta["openai/toolInvocation/invoking"] Not yet implemented
    _meta["openai/toolInvocation/invoked"] Not yet implemented
    _meta["openai/widgetAccessible"] (boolean) _meta.ui.visibility (string[]) true/false → include/exclude "app" in array
    _meta["openai/visibility"] (string) _meta.ui.visibility (string[]) "public"/"private" → include/exclude "model" in array
    OpenAI MCP Apps Notes
    _meta["openai/widgetCSP"] _meta.ui.csp See CSP field mapping below
    _meta.ui.permissions MCP adds: permissions for camera, microphone, geolocation, clipboard
    _meta["openai/widgetDomain"] _meta.ui.domain Dedicated sandbox origin
    _meta["openai/widgetPrefersBorder"] _meta.ui.prefersBorder Visual boundary preference
    _meta["openai/widgetDescription"] Not yet implemented; use app.updateModelContext() for dynamic context
    OpenAI MCP Apps Notes
    text/html+skybridge text/html;profile=mcp-app Auto-set by registerAppResource(); use RESOURCE_MIME_TYPE constant if manual
    OpenAI MCP Apps Notes
    resource_domains resourceDomains Origins for static assets (images, fonts, styles, scripts)
    connect_domains connectDomains Origins for fetch/XHR/WebSocket requests
    frame_domains frameDomains Origins for nested iframes
    redirect_domains OpenAI-only: origins for openExternal redirects
    baseUriDomains MCP-only: base-uri CSP directive
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { z } from "zod";

    function createServer() {
    const server = new McpServer({ name: "shop", version: "1.0.0" });

    // Register tool with OpenAI metadata
    server.registerTool(
    "shopping-cart",
    {
    title: "Shopping Cart",
    description: "Display the user's shopping cart",
    inputSchema: { userId: z.string() },
    annotations: { readOnlyHint: true },
    _meta: {
    "openai/outputTemplate": "ui://view/cart.html",
    "openai/toolInvocation/invoking": "Loading cart...",
    "openai/toolInvocation/invoked": "Cart ready",
    "openai/widgetAccessible": true,
    },
    },
    async (args) => {
    const cart = await getCart(args.userId);
    return {
    content: [{ type: "text", text: JSON.stringify(cart) }],
    structuredContent: { cart },
    };
    },
    );

    // Register UI resource
    server.registerResource(
    "Cart View",
    "ui://view/cart.html",
    { mimeType: "text/html+skybridge" },
    async () => ({
    contents: [
    {
    uri: "ui://view/cart.html",
    mimeType: "text/html+skybridge",
    text: getCartHtml(),
    _meta: {
    "openai/widgetCSP": {
    resource_domains: ["https://cdn.example.com"],
    connect_domains: ["https://api.example.com"],
    frame_domains: ["https://embed.example.com"],
    },
    },
    },
    ],
    }),
    );

    return server;
    }
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import {
    registerAppTool,
    registerAppResource,
    RESOURCE_MIME_TYPE,
    } from "@modelcontextprotocol/ext-apps/server";
    import { z } from "zod";

    function createServer() {
    const server = new McpServer({ name: "shop", version: "1.0.0" });

    // Register tool with MCP Apps metadata
    registerAppTool(
    server,
    "shopping-cart",
    {
    title: "Shopping Cart",
    description: "Display the user's shopping cart",
    inputSchema: { userId: z.string() },
    annotations: { readOnlyHint: true },
    _meta: { ui: { resourceUri: "ui://view/cart.html" } },
    },
    async (args) => {
    const cart = await getCart(args.userId);
    return {
    content: [{ type: "text", text: JSON.stringify(cart) }],
    structuredContent: { cart },
    };
    },
    );

    // Register UI resource
    registerAppResource(
    server,
    "Cart View",
    "ui://view/cart.html",
    { description: "Shopping cart UI" },
    async () => ({
    contents: [
    {
    uri: "ui://view/cart.html",
    mimeType: RESOURCE_MIME_TYPE,
    text: getCartHtml(),
    _meta: {
    ui: {
    csp: {
    resourceDomains: ["https://cdn.example.com"],
    connectDomains: ["https://api.example.com"],
    frameDomains: ["https://embed.example.com"],
    },
    },
    },
    },
    ],
    }),
    );

    return server;
    }
    1. Metadata Structure: OpenAI uses flat _meta["openai/..."] properties; MCP uses nested _meta.ui.* structure
    2. Tool Visibility: OpenAI uses boolean/string (true/"public"); MCP uses string arrays (["app", "model"])
    3. CSP Field Names: snake_case → camelCase (e.g., connect_domainsconnectDomains)
    4. App Permissions: MCP adds _meta.ui.permissions for camera, microphone, geolocation, clipboard (not in OpenAI)
    5. Resource MIME Type: text/html+skybridgetext/html;profile=mcp-app (use RESOURCE_MIME_TYPE constant)
    6. Helper Functions: MCP provides registerAppTool() and registerAppResource() helpers
    7. Not Yet Implemented: _meta["openai/toolInvocation/invoking"], _meta["openai/toolInvocation/invoked"], and _meta["openai/widgetDescription"] don't have MCP equivalents yet

    Client-side migration involves replacing the implicit window.openai global with an explicit App instance.

    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
    OpenAI MCP Apps Notes
    window.openai (auto-available) const app = new App({name, version}) MCP requires explicit instantiation
    (implicit) Vanilla: await app.connect() / React: useApp() MCP requires async connection; auto-detects OpenAI env
    await app.connect(new OpenAITransport()) Force OpenAI mode (not yet available, see PR #172)
    await app.connect(new PostMessageTransport(...)) Force MCP mode explicitly
    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
    OpenAI MCP Apps Notes
    window.openai.toolInput app.ontoolinput = (params) => { params.arguments } Tool arguments; MCP uses callback
    window.openai.toolOutput app.ontoolresult = (params) => { params.structuredContent } Tool result; MCP uses callback
    window.openai.toolResponseMetadata app.ontoolresultparams._meta Widget-only metadata from server
    app.ontoolinputpartial = (params) => {...} MCP adds: streaming partial args
    app.ontoolcancelled = (params) => {...} MCP adds: cancellation notification
    OpenAI MCP Apps Notes
    await window.openai.callTool(name, args) await app.callServerTool({ name, arguments: args }) Call another MCP server tool
    OpenAI MCP Apps Notes
    await window.openai.sendFollowUpMessage({ prompt }) await app.sendMessage({ role: "user", content: [{ type: "text", text: prompt }] }) MCP uses structured content array
    OpenAI MCP Apps Notes
    await window.openai.openExternal({ href }) await app.openLink({ url: href }) Different param name: hrefurl
    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
    OpenAI MCP Apps Notes
    window.openai.notifyIntrinsicHeight(height) app.sendSizeChanged({ width, height }) MCP includes width
    Manual only new App(appInfo, capabilities, { autoResize: true /* default */ }) MCP auto-reports via ResizeObserver
    OpenAI MCP Apps Notes
    window.openai.widgetState Not directly available in MCP
    window.openai.setWidgetState(state) Use alternative mechanisms (localStorage, server-side state, etc.)
    OpenAI MCP Apps Notes
    await window.openai.uploadFile(file) Not yet implemented
    await window.openai.getFileDownloadUrl({ fileId }) Not yet implemented
    OpenAI MCP Apps Notes
    await window.openai.requestModal(options) Not yet implemented
    window.openai.requestClose() Not yet implemented
    window.openai.setOpenInAppUrl({ href }) Not yet implemented
    window.openai.view Not yet mapped
    OpenAI MCP Apps Notes
    window.openai.toolInput (read on load) app.ontoolinput = (params) => {...} OpenAI: sync property; MCP: callback (register before connect())
    window.openai.toolOutput (read on load) app.ontoolresult = (params) => {...} OpenAI: sync property; MCP: callback (register before connect())
    addEventListener("openai:set_globals", ...) app.onhostcontextchanged = (ctx) => {...} Both push updates; MCP uses callback, OpenAI uses DOM event
    app.onteardown = async () => {...} MCP adds: cleanup before unmount
    OpenAI MCP Apps Notes
    console.log(...) app.sendLog({ level: "info", data: "..." }) MCP provides structured logging
    OpenAI MCP Apps Notes
    app.getHostVersion() Returns { name, version } of host
    app.getHostCapabilities() Check serverTools, openLinks, logging, etc.
    // OpenAI Apps SDK
    applyTheme(window.openai.theme);
    console.log("Tool args:", window.openai.toolInput);
    console.log("Tool result:", 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" });
    import { App } from "@modelcontextprotocol/ext-apps";

    const app = new App({ name: "MyApp", version: "1.0.0" });

    // Register handlers BEFORE connect (events may occur immediately after connect)
    app.ontoolinput = (params) => {
    console.log("Tool args:", params.arguments);
    };

    app.ontoolresult = (params) => {
    console.log("Tool result:", params.structuredContent);
    };

    app.onhostcontextchanged = (ctx) => {
    if (ctx.theme) applyTheme(ctx.theme);
    };

    // Connect (auto-detects OpenAI vs MCP)
    await app.connect();

    // Access context
    applyTheme(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" });
    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