UI Widgets
UI widgets allow you to create rich, interactive user interfaces that MCP clients can display. The framework supports multiple widget formats including MCP-UI and OpenAI Apps SDK components.
Automatic Widget Registration
Themcp-use framework automatically registers all React components in your resources/ folder as MCP tools and resources, eliminating the need for manual configuration and boilerplate code. This powerful feature allows you to focus on building UI components while the framework handles the MCP integration.
Overview
When you create a React component in theresources/ folder with proper metadata, mcp-use automatically:
- Registers an MCP Tool - LLMs can invoke it with parameters
- Registers an MCP Resource - Clients can discover and access it
- Extracts Input Schema - Tool parameters are derived from component props
- Serves with HMR - Hot Module Replacement for rapid development
- Builds for Production - Optimized Apps SDK widgets
- Handles Props Mapping - From LLM → Tool arguments → Apps SDK → React component
Quick Start
1. Create a Project
Start with the Apps SDK template which includes automatic widget registration:2. Create a Widget
Add a new file inresources/, for example resources/user-card.tsx:
3. That’s It!
The widget is automatically:- Registered as tool: user-card
- Available as resource: ui://widget/user-card.html
- Ready in ChatGPT: LLM can call it with name, email, avatar, role
server.tool() or server.uiResource() calls needed!
How It Works
The Flow
1. Widget Discovery
When you callserver.listen(), the framework automatically calls mountWidgets() which:
- Scans the resources/directory for.tsxand.tsfiles
- Creates a Vite dev server for each widget with HMR support
- Loads widget metadata using Vite SSR
2. Metadata Extraction
The framework looks for thewidgetMetadata export in each widget file:
inputs Zod schema defines:
- What props the component needs
- What parameters the tool accepts
- Type validation and descriptions for the LLM
3. Tool Registration
The framework automatically registers a tool with:- Name: Derived from filename (e.g., display-weather.tsx→display-weather)
- Description: From widgetMetadata.description
- Inputs: Converted from Zod schema to MCP input definitions
- Callback: Returns the widget resource with a unique URI
4. Resource Registration
The framework registers both:- Static Resource: ui://widget/{name}.html- Base resource
- Dynamic Template: ui://widget/{name}-{id}.html- Unique per invocation
- HTML Template: With proper script tags and styles
- Apps SDK Metadata: CSP configuration, widget description, etc.
- MIME Type: text/html+skybridgefor Apps SDK compatibility
5. Props Flow
When an LLM invokes the tool:- LLM fills tool parameters based on context and inputs schema
- MCP Server returns widget resource URI + structuredContentwith params
- Apps SDK (ChatGPT) loads the widget HTML
- Apps SDK injects params as window.openai.toolInput
- useWidget Hook reads from window.openaiand provides props to React component
- React Component renders with the props
Widget Metadata
widgetMetadata Object
Contains the information that the MCP resource (and the tool that exposes it) will use when are automatically built by mcp-use.Props Schema with Zod
Theinputs field uses Zod for:
- Type Safety: TypeScript inference for props
- Runtime Validation: Ensure correct data types
- LLM Context: .describe()helps LLM understand what to fill
The useWidget Hook
TheuseWidget hook is the bridge between Apps SDK and React:
Key Features
1. Props Without Props Components don’t accept props via React props. Instead, props come from the hook:- Apps SDK (ChatGPT): Reads from window.openai
- MCP-UI: Reads from URL parameters
- Standalone: Uses default props
Complete Example: Weather Widget
Here’s the completedisplay-weather.tsx from the template:
Testing in ChatGPT
Once your server is running, you can test in ChatGPT: User: “Show me the weather in Paris. It’s 22 degrees and sunny.” ChatGPT (behind the scenes):- Recognizes it should use display-weathertool
- Fills parameters: { city: "Paris", weather: "sunny", temperature: 22 }
- Calls the tool
- Receives widget resource URI
- Loads and displays the widget
Advanced Features
Accessing Tool Output
Widgets can access the output of their own tool execution:Persistent Widget State
Widgets can maintain state across interactions:Calling Other Tools
Widgets can call other MCP tools:Display Mode Control
Request fullscreen or popup display:Development Workflow
1. Start Dev Server
- MCP server with auto-widget registration
- Vite dev server with HMR for each widget
- Inspector UI for testing
2. Create Widget
Add a new file inresources/:
3. Test in Inspector
- Open http://localhost:3000/inspector
- Navigate to Tools
- Find my-new-widgettool (automatically registered!)
- Test with parameters: { "title": "Hello" }
- See the widget render
4. Edit with Hot Reload
Edit your widget file and save. Changes appear instantly thanks to HMR!5. Build for Production
Under the Hood
Server Initialization
In yourindex.ts:
mountWidgets() Implementation
ThemountWidgets() method (called automatically by listen()):
- Scans the resources/directory for.tsxand.tsfiles
- Creates Vite dev server with React and Tailwind plugins
- Generates entry files for each widget in .mcp-use/temp directory
- Serves widgets at /mcp-use/widgets/{name}with HMR
- Extracts metadata using Vite SSR module loading
- Registers both tool and resource for each widget
Widget URL Structure
Development:- Widget: http://localhost:3000/mcp-use/widgets/display-weather
- Assets: http://localhost:3000/mcp-use/widgets/display-weather/assets/...
- Widget: https://myserver.com/mcp-use/widgets/display-weather
- Assets: https://myserver.com/mcp-use/widgets/display-weather/assets/...
Apps SDK CSP Configuration
The framework automatically configures Content Security Policy for Apps SDK:Configuration
Custom Resources Directory
Base URL for Production
Set theMCP_URL environment variable or pass baseUrl:
- Widget URLs use the correct domain
- Apps SDK CSP automatically includes your server
- Works behind proxies and custom domains
Environment Variables
Best Practices
1. Use Descriptive Schemas
Help the LLM understand your inputs:2. Provide Defaults
Make optional fields with sensible defaults:3. Keep Widgets Focused
Each widget should do one thing well:4. Theme Support
Always support both themes:5. Error Handling
Handle missing or invalid props gracefully:6. Performance
Keep widgets lightweight:- Minimize dependencies
- Lazy load heavy components
- Use React.memo for expensive renders
Troubleshooting
Widget Not Registered
Problem: Widget file exists but tool doesn’t appear Solutions:- Ensure file has .tsxor.tsextension
- Export widgetMetadataobject
- Export default React component
- Check server logs for errors during widget loading
Props Not Passed
Problem: Component receives empty props Solutions:- Use useWidget()hook instead of React props
- Ensure widgetMetadata.inputsis a valid Zod schema
- Check Apps SDK is injecting window.openai.toolInput
- Verify tool parameters match schema
HMR Not Working
Problem: Changes don’t reflect without restart Solutions:- Check Vite dev server is running
- Ensure you’re editing the source file in resources/
- Clear browser cache
- Check console for Vite errors
CSP Errors
Problem: Widget loads but assets fail with CSP errors Solutions:- Set baseUrlin server config
- Add external domains to appsSdkMetadata['openai/widgetCSP']
- Check browser console for blocked domains
Comparison: Auto vs Manual
Auto Registration (Recommended)
- Zero boilerplate
- Type-safe props
- Automatic tool/resource registration
- Hot module replacement
- Standardized structure
- ⚠️ Less control over registration details
- ⚠️ Must follow naming conventions
Manual Registration
- Full control over configuration
- Custom tool behavior
- Can use raw HTML strings
- ❌ More boilerplate
- ❌ Manual schema duplication
- ❌ Prop mapping is manual
- ❌ No HMR out of the box
Next Steps
- UI Widgets Guide - General widget development
- Project Templates - Explore available templates
- Examples - Real-world widget examples