MCP Apps Server
An MCP server template for building ChatGPT-compatible React widgets using the OpenAI Apps SDK. It automatically registers UI components as MCP tools and resources, featuring built-in support for theme detection and ecommerce-focused interactive elements.
README
MCP Apps Server
An MCP server template with OpenAI Apps SDK integration for ChatGPT-compatible widgets.
Features
- š¤ OpenAI Apps SDK: Full compatibility with ChatGPT widgets
- šØ Official UI Components: Integrated OpenAI Apps SDK UI components for consistent, accessible widgets
- š Ecommerce Widgets: Complete ecommerce example with carousel, search, map, and order confirmation
- š Automatic Registration: Widgets auto-register from
resources/folder - š¦ Props Schema: Zod schema validation for widget props
- š Theme Support: Dark/light theme detection via
useWidgethook - š ļø TypeScript: Complete type safety
- š§ Widget Capabilities: Full support for
callTool,sendFollowUpMessage, and persistent state
What's New: Apps SDK Integration
This 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
}
Getting Started
Development
# Install dependencies
npm install
# Start development server
npm run dev
This starts:
- MCP server on port 3000
- Widget serving at
/mcp-use/widgets/* - Inspector UI at
/inspector
Production
# Build the server and widgets
npm run build
# Run the built server
npm start
Project Structure
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
How Automatic Registration Works
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:
- Tool:
display-weather- Accepts parameters via OpenAI - Resource:
ui://widget/display-weather- Static access
Building Widgets with Apps SDK
Using the useWidget Hook
import { 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.
Defining Widget Metadata
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,
};
Theme Support
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';
Official UI Components
This template uses the OpenAI Apps SDK UI component library for building consistent, accessible widgets. The library provides:
- Button: Primary, secondary, and outline button variants
- Card: Container component for content sections
- Carousel: Image and content carousel with transitions
- Input: Form input fields
- Icon: Consistent iconography
- Transition: Smooth animations and transitions
Import components like this:
import {
Button,
Card,
Carousel,
CarouselItem,
Transition,
Icon,
Input,
} from '@openai/apps-sdk-ui';
Ecommerce Widgets
This template includes a complete ecommerce example with four widgets:
1. Ecommerce Carousel (ecommerce-carousel.tsx)
A product carousel widget featuring:
- Title and description
- Carousel of product items with placeholder images
- Info button and Add to Cart button for each item
- Uses official Carousel, Card, Button, Icon, and Transition components
- Integrates with
callToolfor cart operations - Persistent state management
2. Product Search Result (product-search-result.tsx)
A search results widget with:
- Search input with real-time filtering
- Price range filters and stock status filter
- Grid layout of product cards
- Uses
callToolto perform searches - Uses
sendFollowUpMessageto update conversation - Persistent filter state
3. Stores Locations Map (stores-locations-map.tsx)
A store locator widget featuring:
- Interactive map display (placeholder)
- List of store locations with details
- Distance calculation
- Get directions functionality
- Store details on click
- Uses
callToolfor directions and store info
4. Order Confirmation (order-confirmation.tsx)
An order confirmation widget with:
- Order summary and items list
- Shipping information
- Order status tracking
- Track order and view receipt actions
- Uses
callToolfor order tracking
Brand Info Tool
The 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
Example: Weather Widget
The included display-weather.tsx widget demonstrates:
- Schema Definition: Zod schema for validation
- Metadata Export: Widget registration info
- Theme Detection: Dark/light mode support
- Type Safety: Full TypeScript support
// Get props from OpenAI Apps SDK
const { props, theme } = useWidget<WeatherProps>();
// props.city, props.weather, props.temperature are validated
Using Widgets in ChatGPT
Via Tool Call
await client.callTool('display-weather', {
city: 'San Francisco',
weather: 'sunny',
temperature: 22
});
Via Resource Access
await client.readResource('ui://widget/display-weather');
Customization Guide
Adding New Widgets
- Create a React component in
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;
- The widget is automatically registered!
Adding Traditional MCP Tools
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');
},
});
Testing Your Widgets
Via Inspector UI
- Start the server:
npm run dev - Open:
http://localhost:3000/inspector - Test widgets interactively
Direct Browser Access
Visit: http://localhost:3000/mcp-use/widgets/display-weather
Via MCP Client
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
});
Apps SDK vs Other Widget Types
| 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 |
Benefits of Apps SDK
ā 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
Troubleshooting
Widget Not Loading
- Ensure widget has
widgetMetadataexport - Check Zod schema is valid
- Verify widget exists in
dist/resources/mcp-use/widgets/
Props Not Passed
- Ensure schema includes all props
- Check
.describe()for each prop - Verify
useWidgethook is called
Theme Not Applied
- Theme is only available in ChatGPT
- Use
themefromuseWidget()hook - Test in actual ChatGPT interface
Migration from Other Templates
Moving 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;
Using Widget Capabilities
The widgets in this template demonstrate the full capabilities of the Apps SDK:
Calling Tools (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
});
};
Sending Follow-up Messages (sendFollowUpMessage)
Widgets can send messages to the ChatGPT conversation:
const { sendFollowUpMessage } = useWidget();
await sendFollowUpMessage('Product added to cart successfully!');
Persistent State (setState)
Widgets can maintain state across interactions:
const { setState, state } = useWidget();
// Save state
await setState({ cart: [...cart, newItem] });
// Read state
const savedCart = state?.cart || [];
Component Library Note
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.
Learn More
- OpenAI Apps SDK UI Components - Official component library
- MCP Documentation
- OpenAI Apps SDK
- mcp-use Documentation
- React Documentation
- Zod Documentation
Happy building! š
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.