A comprehensive toolkit for embedding interactive UIs in chat applications. This monorepo provides everything you need to create rich, interactive widget applications using the Model Context Protocol (MCP) and cross-iframe messaging.
Head to our OpenAI Apps SDK quickstart if you would like to skip straight to building!
Want to understand how everything works first? Check out How It Works.
This SDK provides tools for embedding UIs in chat applications including:
- OpenAI Apps SDK - Build interactive widgets for ChatGPT
- MCP-UI Protocol - Build embeddable UIs with cross-iframe messaging
- Custom embedding solutions - Use our messaging layer for any iframe-based UI embedding
The SDK makes it easier to:
- Build MCP servers with custom widget UIs
- Create widget UI components using React hooks
- Embed UIs using cross-iframe messaging protocols
- Bundle and deploy your widgets for production
- Preview widgets before deployment
This toolkit provides production-ready packages to start building today.
- @fractal-mcp/oai-hooks - React hooks for building widget UIs (OpenAI Apps SDK)
- @fractal-mcp/oai-server - Server toolkit for building MCP servers with widgets
- @fractal-mcp/oai-preview - Development tool for previewing widgets
- @fractal-mcp/shared-ui - RPC layer for iframe communication
- @fractal-mcp/mcp-ui-messenger - MCP-UI compatible iframe messenger
- @fractal-mcp/mcp-ui-hooks - React hooks for MCP-UI messaging
- @fractal-mcp/bundle - Bundling library for React components, JS/TS, and HTML files
- @fractal-mcp/cli - Command-line tools for bundling widgets
- @fractal-mcp/mcp-express - Express utilities for serving MCP servers
React hooks for building widget UIs that communicate with ChatGPT.
npm install @fractal-mcp/oai-hooksKey features:
- Access widget props from server
- Manage persistent widget state
- Respond to layout changes (display mode, max height)
- Access ChatGPT's global context (theme, safe area)
Example:
import { useWidgetProps, useWidgetState } from "@fractal-mcp/oai-hooks";
function WeatherWidget() {
const props = useWidgetProps<{ location: string; temp: number }>();
const [state, setState] = useWidgetState({ unit: "fahrenheit" });
return (
<div>
<h2>Weather in {props.location}</h2>
<p>{props.temp}°{state.unit === "celsius" ? "C" : "F"}</p>
</div>
);
}Server-side toolkit for building MCP servers with custom widget UIs.
npm install @fractal-mcp/oai-serverKey features:
- Register tools with custom UI widgets
- Serve widget HTML and assets via MCP resources
- Handle SSE transport for real-time communication
- Type-safe input validation with Zod
Example:
import { McpServer, registerOpenAIWidget, startOpenAIWidgetHttpServer } from "@fractal-mcp/oai-server";
const server = new McpServer({ name: "my-app", version: "1.0.0" });
registerOpenAIWidget(
server,
{
id: "weather",
title: "Get Weather",
templateUri: "ui://widget/weather.html",
html: `<div id="root"></div>`,
// ... more config
},
async (args) => ({
content: [{ type: "text", text: "Weather data" }],
structuredContent: { temp: 72, location: args.location }
})
);
startOpenAIWidgetHttpServer({ port: 8000, serverFactory: () => server });Development/Testing Tool - React component for previewing and testing widgets.
npm install --save-dev @fractal-mcp/oai-previewExample:
import { WidgetDisplayComponent } from "@fractal-mcp/oai-preview";
<WidgetDisplayComponent
htmlSnippet={widgetHtml}
toolInput={{ location: "San Francisco" }}
toolOutput={{ temp: 72, condition: "Sunny" }}
onToolCall={async (name, params) => {/* handle tool calls */}}
/>Credits: Built by studying MCPJam Inspector and ChatGPT's widget source code.
Low-level RPC communication layer for iframe messaging.
npm install @fractal-mcp/shared-uiKey features:
- RPC-style request/response messaging
- Event emission and listening
- Message correlation tracking
- Works with any iframe messaging protocol
MCP-UI compatible cross-iframe messenger for embeddable UIs.
npm install @fractal-mcp/mcp-ui-messengerKey features:
- Implements MCP-UI Embeddable UI Protocol
- Automatic iframe lifecycle management
- Render data handling
- Intent, notification, prompt, and tool messaging
- Auto-resize observer
Example:
import { initUIMessenger } from '@fractal-mcp/mcp-ui-messenger';
const messenger = await initUIMessenger({ rootElId: 'root' });
const renderData = messenger.getRenderData();
messenger.emitIntent({
intent: 'button-clicked',
params: { timestamp: Date.now() }
});React hooks for MCP-UI cross-iframe messaging.
npm install @fractal-mcp/mcp-ui-hooksKey features:
- Dead simple React integration
- Automatic initialization and cleanup
- All MCP-UI message types supported
- TypeScript support
Example:
import { useUIMessenger } from '@fractal-mcp/mcp-ui-hooks';
function App() {
const { ready, renderData, emitIntent } = useUIMessenger();
if (!ready) return <div>Loading...</div>;
return (
<div>
<h1>Theme: {renderData?.theme}</h1>
<button onClick={() => emitIntent({ intent: 'action' })}>
Click Me
</button>
</div>
);
}Note: For OpenAI Apps SDK, use
@fractal-mcp/oai-hooksinstead.
Bundling utilities for React components, JS/TS entry points, and HTML files.
npm install @fractal-mcp/bundleKey features:
- Bundle React components into standalone HTML files
- Multiple output formats (full HTML, snippets, separate assets)
- Framework-agnostic (React, Vue, Svelte)
- Powered by Vite for fast builds
- Built-in testing utilities with Playwright
Example:
import { bundleReactComponent } from '@fractal-mcp/bundle';
await bundleReactComponent({
entrypoint: './src/MyWidget.tsx',
out: './dist'
});
// Outputs: dist/index.html (single file with everything inlined)Command-line tools for bundling widgets.
npm install -g @fractal-mcp/cli
# or use with npxUsage:
# Bundle a React component
npx @fractal-mcp/cli bundle --entrypoint=./src/Widget.tsx --out=./dist
# Bundle an HTML application
npx @fractal-mcp/cli bundle --entrypoint=./index.html --out=./distUtilities for serving MCP servers with Express, including connection to Fractal's registry.
npm install @fractal-mcp/mcp-express// ui/WeatherWidget.tsx
import { useWidgetProps } from "@fractal-mcp/oai-hooks";
export default function WeatherWidget() {
const props = useWidgetProps<{ location: string; temp: number }>();
return (
<div>
<h2>{props.location}</h2>
<p>{props.temp}°F</p>
</div>
);
}npx @fractal-mcp/cli bundle --entrypoint=./ui/WeatherWidget.tsx --out=./distThis creates dist/index.html with your widget bundled as a single HTML file.
// server/index.ts
import { McpServer, registerOpenAIWidget, startOpenAIWidgetHttpServer } from "@fractal-mcp/oai-server";
import { z } from "zod";
import fs from "fs";
function createServer() {
const server = new McpServer({ name: "weather-server", version: "1.0.0" });
// Read bundled widget HTML
const widgetHtml = fs.readFileSync("./dist/index.html", "utf-8");
registerOpenAIWidget(
server,
{
id: "get-weather",
title: "Get Weather",
description: "Show weather for a location",
templateUri: "ui://widget/weather.html",
invoking: "Fetching weather...",
invoked: "Weather loaded!",
html: widgetHtml,
responseText: "Here's the weather",
inputSchema: z.object({
location: z.string().describe("City name")
})
},
async (args) => ({
content: [{ type: "text", text: `Weather in ${args.location}` }],
structuredContent: {
location: args.location,
temp: 72
}
})
);
return server;
}
startOpenAIWidgetHttpServer({
port: 8000,
serverFactory: createServer
});npm run build
node dist/server/index.jsYour MCP server is now running at http://localhost:8000 and ready to be connected to ChatGPT!
Check out the examples directory for complete working examples:
- oai-apps - Full example showing server and UI integration
- User invokes tool in ChatGPT
- Server handler processes request and returns:
content: Text/resources for the chatstructuredContent: Props for the widget
- ChatGPT renders widget using the bundled HTML
- Widget UI receives props via
useWidgetProps()and renders - User interacts with widget, state persists via
useWidgetState()
┌──────────────────┐
│ oai-hooks │ React hooks for widget UI
└────────┬─────────┘
│ used by
↓
┌──────────────────┐
│ Your Widget │ Your React components
└────────┬─────────┘
│ bundled by
↓
┌──────────────────┐
│ bundle/cli │ Bundling tools
└────────┬─────────┘
│ produces HTML for
↓
┌──────────────────┐
│ oai-server │ MCP server with widgets
└──────────────────┘
# Install dependencies
npm install
# Build all packages
npm run build
# Run tests
npm testsdk/
├── packages/
│ ├── OpenAI Apps SDK:
│ │ ├── oai-hooks/ # UI hooks for ChatGPT widgets
│ │ ├── oai-server/ # Server toolkit for MCP with widgets
│ │ └── oai-preview/ # 🧪 Dev/testing tool
│ ├── MCP-UI:
│ │ ├── shared-ui/ # RPC layer for iframe communication
│ │ ├── mcp-ui-messenger/ # MCP-UI compatible messenger
│ │ └── mcp-ui-hooks/ # React hooks for MCP-UI
│ ├── Bundling:
│ │ ├── bundle/ # Bundling library
│ │ └── cli/ # CLI tools
│ └── Server Utilities:
│ └── mcp-express/ # Express utilities
└── apps/
└── examples/ # Example applications
Each package can be published independently to npm:
cd packages/[package-name]
npm publish- Node.js 18+
- React 18+ (for widget UIs)
- TypeScript 5+ (recommended)
Contributions are welcome! Please feel free to submit issues or pull requests.
Apache License, Version 2.0
This SDK provides tools for embedding UIs in chat applications, supporting multiple protocols and frameworks. Special thanks to:
- OpenAI for pioneering the Apps SDK and widget approach to building interactive AI applications
- MCP-UI for creating the embeddable UI protocol specification
- MCPJam Inspector for their excellent open-source MCP testing platform, which helped inform our development tooling
- The MCP community for building an ecosystem of tools and servers