loading…
Search for a command to run...
loading…
An MCP server template integrated with the OpenAI Apps SDK for building ChatGPT-compatible widgets with automatic tool registration. It provides a suite of inte
An MCP server template integrated with the OpenAI Apps SDK for building ChatGPT-compatible widgets with automatic tool registration. It provides a suite of interactive UI components and ecommerce examples for creating type-safe, theme-aware widgets.
An MCP server template with OpenAI Apps SDK integration for ChatGPT-compatible widgets.
resources/ folderuseWidget hookcallTool, sendFollowUpMessage, and persistent stateThis template demonstrates how to build ChatGPT-compatible widgets using OpenAI's Apps SDK:
import { useWidget } from 'mcp-use/react';
const MyWidget: React.FC = () => {
const { props, theme } = useWidget<MyProps>();
// props contains validated inputs from OpenAI
// theme is 'dark' or 'light' based on ChatGPT setting
}
# Install dependencies
npm install
# Start development server
npm run dev
This starts:
/mcp-use/widgets/*/inspector# Build the server and widgets
npm run build
# Run the built server
npm start
mcp-apps/
├── resources/ # React widget components
│ ├── display-weather.tsx # Weather widget example
│ ├── ecommerce-carousel.tsx # Ecommerce product carousel
│ ├── product-search-result.tsx # Product search with filters
│ ├── stores-locations-map.tsx # Store locations map
│ └── order-confirmation.tsx # Order confirmation widget
├── index.ts # Server entry point (includes brand info tool)
├── package.json
├── tsconfig.json
└── README.md
All React components in the resources/ folder are automatically registered as MCP tools and resources when they export widgetMetadata:
import { z } from 'zod';
import type { WidgetMetadata } from 'mcp-use/react';
const propSchema = z.object({
city: z.string().describe('The city name'),
temperature: z.number().describe('Temperature in Celsius'),
});
export const widgetMetadata: WidgetMetadata = {
description: 'My widget description',
props: propSchema,
};
const MyWidget: React.FC = () => {
const { props } = useWidget<z.infer<typeof propSchema>>();
// Your widget implementation
};
export default MyWidget;
This automatically creates:
display-weather - Accepts parameters via OpenAIui://widget/display-weather - Static accessuseWidget Hookimport { useWidget } from 'mcp-use/react';
interface MyProps {
title: string;
count: number;
}
const MyWidget: React.FC = () => {
const { props, theme, isPending } = useWidget<MyProps>();
// IMPORTANT: Widgets render before tool execution completes
// Always check isPending to handle the loading state
if (isPending) {
return <div>Loading...</div>;
}
// Now props are safely available
// props are validated and typed based on your schema
// theme is automatically set by ChatGPT
return (
<div className={theme === 'dark' ? 'dark-theme' : 'light-theme'}>
<h1>{props.title}</h1>
<p>Count: {props.count}</p>
</div>
);
};
Note: Widgets render before the tool execution completes. On first render,
propswill be empty{}andisPendingwill betrue. Always checkisPendingbefore accessing props. See the Widget Lifecycle documentation for more details.
Use Zod schemas to define widget inputs:
import { z } from 'zod';
import type { WidgetMetadata } from 'mcp-use/react';
const propSchema = z.object({
name: z.string().describe('Person name'),
age: z.number().min(0).max(120).describe('Age in years'),
email: z.string().email().describe('Email address'),
});
export const widgetMetadata: WidgetMetadata = {
description: 'Display user information',
props: propSchema,
};
Automatically adapt to ChatGPT's theme:
const { theme } = useWidget();
const bgColor = theme === 'dark' ? 'bg-gray-900' : 'bg-white';
const textColor = theme === 'dark' ? 'text-gray-100' : 'text-gray-800';
This template uses the OpenAI Apps SDK UI component library for building consistent, accessible widgets. The library provides:
Import components like this:
import {
Button,
Card,
Carousel,
CarouselItem,
Transition,
Icon,
Input,
} from '@openai/apps-sdk-ui';
This template includes a complete ecommerce example with four widgets:
ecommerce-carousel.tsx)A product carousel widget featuring:
callTool for cart operationsproduct-search-result.tsx)A search results widget with:
callTool to perform searchessendFollowUpMessage to update conversationstores-locations-map.tsx)A store locator widget featuring:
callTool for directions and store infoorder-confirmation.tsx)An order confirmation widget with:
callTool for order trackingThe template includes a get-brand-info tool (normal MCP tool, not a widget) that returns brand information:
// Call the tool
await client.callTool('get-brand-info', {});
// Returns brand details including:
// - Company name, tagline, description
// - Mission and values
// - Contact information
// - Social media links
The included display-weather.tsx widget demonstrates:
// Get props from OpenAI Apps SDK
const { props, theme } = useWidget<WeatherProps>();
// props.city, props.weather, props.temperature are validated
await client.callTool('display-weather', {
city: 'San Francisco',
weather: 'sunny',
temperature: 22
});
await client.readResource('ui://widget/display-weather');
resources/my-widget.tsx:import React from 'react';
import { z } from 'zod';
import { useWidget, type WidgetMetadata } from 'mcp-use/react';
const propSchema = z.object({
message: z.string().describe('Message to display'),
});
export const widgetMetadata: WidgetMetadata = {
description: 'Display a message',
props: propSchema,
};
type Props = z.infer<typeof propSchema>;
const MyWidget: React.FC = () => {
const { props, theme } = useWidget<Props>();
return (
<div>
<h1>{props.message}</h1>
</div>
);
};
export default MyWidget;
You can mix Apps SDK widgets with regular MCP tools:
import { text } from 'mcp-use/server';
server.tool({
name: 'get-data',
description: 'Fetch data from API',
cb: async () => {
return text('Data');
},
});
npm run devhttp://localhost:3000/inspectorVisit: http://localhost:3000/mcp-use/widgets/display-weather
import { createMCPClient } from 'mcp-use/client';
const client = createMCPClient({
serverUrl: 'http://localhost:3000/mcp',
});
await client.connect();
// Call widget as tool
const result = await client.callTool('display-weather', {
city: 'London',
weather: 'rain',
temperature: 15
});
| Feature | Apps SDK | External URL | Remote DOM |
|---|---|---|---|
| ChatGPT Compatible | ✅ Yes | ❌ No | ❌ No |
| Theme Detection | ✅ Automatic | ❌ Manual | ❌ Manual |
| Props Validation | ✅ Zod Schema | ❌ Manual | ❌ Manual |
| React Support | ✅ Full | ✅ Full | ❌ Limited |
| OpenAI Metadata | ✅ Yes | ❌ No | ❌ No |
✅ ChatGPT Native - Works seamlessly in ChatGPT ✅ Theme Aware - Automatic dark/light mode ✅ Type Safe - Full TypeScript with Zod validation ✅ Simple API - One hook for all props ✅ Auto Registration - Export metadata and done
widgetMetadata exportdist/resources/mcp-use/widgets/.describe() for each propuseWidget hook is calledtheme from useWidget() hookMoving from starter to mcp-apps:
// Before: Manual props handling
const params = new URLSearchParams(window.location.search);
const city = params.get('city');
// After: Apps SDK hook
const { props } = useWidget();
const city = props.city;
The widgets in this template demonstrate the full capabilities of the Apps SDK:
callTool)Widgets can call other MCP tools:
const { callTool } = useWidget();
const handleAction = async () => {
const result = await callTool('add-to-cart', {
productId: '123',
productName: 'Product Name',
price: 29.99
});
};
sendFollowUpMessage)Widgets can send messages to the ChatGPT conversation:
const { sendFollowUpMessage } = useWidget();
await sendFollowUpMessage('Product added to cart successfully!');
setState)Widgets can maintain state across interactions:
const { setState, state } = useWidget();
// Save state
await setState({ cart: [...cart, newItem] });
// Read state
const savedCart = state?.cart || [];
This template uses the OpenAI Apps SDK UI component library. The exact component API may vary based on the library version. If you encounter import errors, check the official documentation for the correct component names and props.
If the official library is not available, you can replace the imports with custom React components or other UI libraries while maintaining the same widget structure.
Happy building! 🚀
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"mcp-apps-server": {
"command": "npx",
"args": []
}
}
}