useWidget hook provides a universal, protocol-agnostic interface for building widgets that work with both MCP Apps 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 {} |
partialToolInput | Partial<TProps> | null | Partial/streaming tool arguments, updated in real time as the LLM generates them. null when not streaming. Only set when the host sends tool-input-partial (e.g. MCP Inspector / MCP Apps); in ChatGPT Apps SDK or URL params this remains null. |
isStreaming | boolean | Whether tool arguments are currently being streamed (partial input received but complete input not yet available). false in ChatGPT Apps SDK or when the host does not stream. |
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
tool-input-partial), an optional phase occurs between (1) and (2): the widget receives partial tool input in real time via partialToolInput while isStreaming is true, then transitions to full props when the tool input is complete. See Streaming tool arguments below.
Example:
Streaming tool arguments
When the host streams tool arguments (e.g. MCP Inspector or MCP Apps clients that sendui/notifications/tool-input-partial), the widget can show a live preview of the incoming data. Use partialToolInput and isStreaming from useWidget:
isStreamingistruewhile the LLM is still generating the tool call arguments.partialToolInputcontains the partial arguments parsed so far (may be incomplete or invalid JSON until streaming finishes).- When streaming ends, the host sends the final tool input and
propsis populated;isStreamingbecomesfalseandpartialToolInputis cleared.
partialToolInput stays null and isStreaming stays false.
Example: live preview while streaming
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