Awesome-omni-skill mcpserver
Migrates an MCP server with interactive widgets from the OpenAI Apps SDK (window.openai, text/html+skybridge) to the MCP Apps standard (@modelcontextprotocol/ext-apps), covering server-side and client-side changes.
install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/backend/mcpserver" ~/.claude/skills/diegosouzapw-awesome-omni-skill-mcpserver && rm -rf "$T"
manifest:
skills/backend/mcpserver/SKILL.mdsource content
Skill: Migrate OpenAI Apps SDK → MCP Apps
Migrate an MCP server with interactive widgets from the OpenAI Apps SDK (
window.openai, text/html+skybridge, flat _meta["openai/..."] keys) to the MCP Apps standard (@modelcontextprotocol/ext-apps).
When to Use
Use this skill when:
- An MCP server uses
MIME type for widget resourcestext/html+skybridge - Widget code references
globals (e.g.window.openai
,window.openai.callTool
,window.openai.toolOutput
)window.openai.theme - Server code uses flat
or_meta["openai/outputTemplate"]
keys_meta["openai/widgetAccessible"] - The goal is to make the server compatible with MCP Apps hosts (Claude, ChatGPT, Microsoft 365 Copilot, etc.)
References
- MCP Apps repo: https://github.com/modelcontextprotocol/ext-apps
- API docs: https://modelcontextprotocol.github.io/ext-apps/api/
- Migration guide: https://modelcontextprotocol.github.io/ext-apps/docs/migration/openai-apps
- Patterns: https://modelcontextprotocol.github.io/ext-apps/docs/patterns
Packages
| Package | Where | Purpose |
|---|---|---|
| Server + Widgets | Core MCP Apps SDK |
| Server | MCP protocol SDK (keep existing) |
| Server | Schema definitions for |
Migration Mapping
MIME Type
| Before | After |
|---|---|
| (use constant) |
Server: _meta
Keys
_meta| OpenAI flat key | MCP Apps nested key |
|---|---|
(URI string) | (URI string) |
| |
Server: Class & Helpers
| Before | After |
|---|---|
| |
| |
| or |
| |
| Manual tool/resource list handlers | Automatic via + helpers |
Server: Tool Registration
Widget tools (tools that render UI) use
registerAppTool:
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server"; import { z } from "zod"; const WIDGET_URI = "ui://myapp/widget.html"; registerAppResource(server, "Widget Name", WIDGET_URI, { mimeType: RESOURCE_MIME_TYPE, description: "Description of the widget", }, async (): Promise<ReadResourceResult> => { const html = await fs.readFile(widgetPath, "utf-8"); return { contents: [{ uri: WIDGET_URI, mimeType: RESOURCE_MIME_TYPE, text: html }] }; }); registerAppTool(server, "show-widget", { title: "Show Widget", description: "Displays the widget", inputSchema: { filter: z.string().optional().describe("Optional filter"), }, annotations: { readOnlyHint: true }, _meta: { ui: { resourceUri: WIDGET_URI } }, }, async ({ filter }): Promise<CallToolResult> => { const data = await fetchData(filter); return { content: [{ type: "text", text: `Loaded ${data.length} items.` }], structuredContent: { items: data }, }; });
Data-only tools (no UI) use
server.tool() directly:
server.tool("update-item", "Updates an item.", { id: z.string().describe("Item ID"), status: z.string().describe("New status"), }, async ({ id, status }) => { await db.update(id, { status }); return { content: [{ type: "text" as const, text: `Updated ${id}.` }] }; });
Client (Widget): Global API
OpenAI () | MCP Apps ( from ) |
|---|---|
| |
| |
( / ) | ( / ) |
| |
| |
| |
| N/A | |
Client (Widget): React Hook
| Before | After |
|---|---|
| (custom hook wrapping ) |
| (custom hook returning / ) |
Step-by-Step Migration Process
1. Update Dependencies
Server
— add:package.json
"@modelcontextprotocol/ext-apps": "^1.0.0", "zod": "^3.25.0"
Widgets
— add:package.json
"@modelcontextprotocol/ext-apps": "^1.0.0"
2. Create MCP Apps React Context (Widgets)
Create a shared hook file (e.g.
hooks/useMcpApp.tsx) that wraps the App class:
import React, { createContext, useContext, useEffect, useState, useRef } from "react"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; interface McpAppContextValue { app: ReturnType<typeof useApp>; toolData: unknown; theme: "light" | "dark"; hostContext: { theme?: string; displayMode?: string } | null; } const McpAppContext = createContext<McpAppContextValue | null>(null); export function McpAppProvider({ name, children }: { name: string; children: React.ReactNode }) { const app = useApp({ name }); const [toolData, setToolData] = useState<unknown>(null); const [theme, setTheme] = useState<"light" | "dark">("light"); const [hostContext, setHostContext] = useState<{ theme?: string; displayMode?: string } | null>(null); useEffect(() => { app.ontoolresult = (result: any) => { if (result?.structuredContent) setToolData(result.structuredContent); }; app.onhostcontextchanged = (ctx: any) => { setHostContext(ctx); if (ctx?.theme === "dark" || ctx?.theme === "light") setTheme(ctx.theme); }; const initial = app.getHostContext?.(); if (initial) { setHostContext(initial); if (initial.theme === "dark" || initial.theme === "light") setTheme(initial.theme); } }, [app]); return ( <McpAppContext.Provider value={{ app, toolData, theme, hostContext }}> {children} </McpAppContext.Provider> ); } export function useMcpApp() { const ctx = useContext(McpAppContext); if (!ctx) throw new Error("useMcpApp must be used within McpAppProvider"); return ctx; } export function useMcpToolData<T = unknown>(): T | null { const { toolData } = useMcpApp(); return toolData as T | null; } export function useMcpTheme(): "light" | "dark" { const { toolData, theme } = useMcpApp(); return theme; }
3. Update Widget Entry Points (main.tsx
)
main.tsxWrap the app in
<McpAppProvider> instead of reading window.openai:
import { McpAppProvider, useMcpTheme } from "../hooks/useMcpApp"; function ThemedApp() { const theme = useMcpTheme(); return ( <FluentProvider theme={theme === "dark" ? webDarkTheme : webLightTheme}> <MyWidget /> </FluentProvider> ); } createRoot(document.getElementById("root")!).render( <McpAppProvider name="My Widget"> <ThemedApp /> </McpAppProvider> );
4. Update Widget Components
Replace all
window.openai references:
// BEFORE const toolOutput = useOpenAiGlobal("toolOutput"); window.openai.callTool("update-item", { id: "1", status: "done" }); window.openai.requestDisplayMode( window.openai.displayMode === "expanded" ? "default" : "expanded" ); // AFTER const toolData = useMcpToolData<MyDataType>(); const { app, hostContext } = useMcpApp(); app.callServerTool({ name: "update-item", arguments: { id: "1", status: "done" } }); app.requestDisplayMode( hostContext?.displayMode === "expanded" ? "default" : "expanded" );
5. Rewrite Server (mcp-server.ts
)
mcp-server.ts- Replace
withServerMcpServer - Replace manual
withsetRequestHandler
/registerAppTool
/registerAppResourceserver.tool() - Use
instead ofRESOURCE_MIME_TYPE"text/html+skybridge" - Use
schemas for tool input definitionszod - Return
(object) alongsidestructuredContent
(text array) from widget toolscontent
6. Update Server Entry Point (index.ts
)
index.tsSwitch from
server.connect(transport) with a low-level Server to McpServer:
import { createMcpServer } from "./mcp-server.js"; app.all("/mcp", async (req, res) => { const server = createMcpServer(); // returns McpServer const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, }); await server.connect(transport); await transport.handleRequest(req, res, req.body); });
7. Build & Test
npm run install:all npm run build:widgets npm run dev:server
Verify with the MCP Inspector (
npx @modelcontextprotocol/inspector) or connect from a host like Claude.
Common Pitfalls
| Issue | Fix |
|---|---|
| You missed replacing a reference in a widget component |
| Widget shows but no data | Ensure is returned from the tool handler (not just ) |
| Theme not updating | Wire up and call |
type errors | Import from , use for |
| SSE gateway errors | Set on |
| Resource not found by host | Ensure the in exactly matches the URI in |
Files Typically Changed
| File | Change |
|---|---|
| Add , |
| Add |
| Full rewrite: + + |
| Update imports, now returns |
| New file: MCP Apps React context |
| Update import to use |
| Wrap in , use |
| Replace all calls |
| Can be deleted after migration |