useWidget hook provides a universal, protocol-agnostic interface for building widgets that work with both MCP Apps (SEP-1865) and ChatGPT Apps SDK protocols. It automatically detects the environment and provides a consistent API regardless of which protocol is being used.
Protocol Detection: The hook automatically detects whether it’s running in:
- MCP Apps environment (JSON-RPC over postMessage)
- ChatGPT Apps SDK environment (window.openai API)
Import
Basic Usage
Cross-Protocol Compatibility: This exact code works without modifications in:
- ChatGPT (via Apps SDK protocol)
- Claude Desktop (via MCP Apps protocol)
- Any MCP Apps-compatible client
Type Parameters
The hook accepts four optional type parameters:Return Values
Props and State
| Property | Type | Description |
|---|---|---|
props | Partial<TProps> | Widget props (mapped from widget-only data). Empty {} when isPending is true |
output | TOutput | null | Tool output from the last execution |
metadata | TMetadata | null | Response metadata from the tool |
state | TState | null | Persisted widget state |
setState | (state: TState | ((prev: TState | null) => TState)) => Promise<void> | Update widget state (persisted and shown to model) |
Layout and Theme
| Property | Type | Description |
|---|---|---|
theme | "light" | "dark" | Current theme (auto-syncs with ChatGPT) |
displayMode | "inline" | "pip" | "fullscreen" | Current display mode |
safeArea | SafeArea | Safe area insets for mobile layout |
maxHeight | number | Maximum height available (pixels) |
userAgent | UserAgent | Device capabilities (device, capabilities) |
locale | string | Current locale (e.g., "en-US") |
mcp_url | string | MCP server base URL for making API requests |
Actions
| Method | Signature | Description |
|---|---|---|
callTool | (name: string, args: Record<string, unknown>) => Promise<CallToolResponse> | Call a tool on the MCP server |
sendFollowUpMessage | (prompt: string) => Promise<void> | Send a follow-up message to the ChatGPT conversation |
openExternal | (href: string) => void | Open an external URL in a new tab |
requestDisplayMode | (mode: DisplayMode) => Promise<{ mode: DisplayMode }> | Request a different display mode |
notifyIntrinsicHeight | (height: number) => Promise<void> | Notify OpenAI about intrinsic height changes for auto-sizing |
Availability
| Property | Type | Description |
|---|---|---|
isAvailable | boolean | Whether the window.openai API is available |
isPending | boolean | Whether the tool is currently executing. When true, props will be empty {} |
Complete Example
Widget Lifecycle
Widgets render before the tool execution completes. This means:-
First render (
isPending = true):- Widget mounts immediately when tool is called
propsis{}(empty object)outputandmetadataarenull- This allows showing loading states
-
After tool completes (
isPending = false):propscontains the actual widget dataoutputandmetadataare available- Widget re-renders with full data
Helper Hooks
For convenience, there are specialized hooks for common use cases:useWidgetProps
Get only the widget props:useWidgetTheme
Get only the theme:useWidgetState
Get state management:Key Features
1. Props Without Props
Components don’t accept props via React props. Instead, props come from the hook:2. Automatic Provider Detection
The hook automatically detects whether it’s running in:- Apps SDK (ChatGPT): Reads from
window.openai - MCP-UI: Reads from URL parameters
- Standalone: Uses default props
3. Reactive Updates
The hook subscribes to allwindow.openai global changes via the openai:set_globals event, ensuring your component re-renders when:
- Theme changes
- Display mode changes
- Widget state updates
- Tool input/output changes
4. Auto-sizing Support
UsenotifyIntrinsicHeight to notify OpenAI about height changes:
McpUseProvider with autoSize={true} for automatic height notifications.
5. State Management
Widget state persists across widget interactions and is shown to the model:6. Tool Calls
Call other MCP tools from your widget:7. Follow-up Messages
Send messages to the ChatGPT conversation:8. Display Mode Control
Request display mode changes:Default Values
The hook provides safe defaults when values are not available:theme:"light"displayMode:"inline"safeArea:{ insets: { top: 0, bottom: 0, left: 0, right: 0 } }maxHeight:600userAgent:{ device: { type: "desktop" }, capabilities: { hover: true, touch: false } }locale:"en"props:{}(ordefaultPropsif provided)output:nullmetadata:nullstate:null
Error Handling
The hook throws errors when methods are called but the API is not available:Related Components
McpUseProvider- Unified provider that includes necessary setupWidgetControls- Debug and view controlsThemeProvider- Theme managementErrorBoundary- Error handling