Skip to main content
The useWidget hook provides a type-safe React adapter for the OpenAI Apps SDK window.openai API. It automatically maps MCP UI props from toolInput, subscribes to all OpenAI global changes, and provides access to theme, display mode, safe areas, and action methods.

Import

import { useWidget } from 'mcp-use/react';

Basic Usage

import { useWidget } from 'mcp-use/react';

interface MyWidgetProps {
  city: string;
  temperature: number;
}

const MyWidget: React.FC = () => {
  const { props, theme, callTool } = useWidget<MyWidgetProps>();
  
  return (
    <div data-theme={theme}>
      <h1>{props.city}</h1>
      <p>{props.temperature}°C</p>
    </div>
  );
};

Type Parameters

The hook accepts four optional type parameters:
useWidget<
  TProps,      // Props type (from toolInput)
  TOutput,     // Output type (from toolOutput/structuredContent)
  TMetadata,   // Metadata type (from toolResponseMetadata)
  TState       // State type (for widgetState)
>()

Return Values

Props and State

PropertyTypeDescription
propsTPropsWidget props (mapped from toolInput for MCP compatibility)
outputTOutput | nullTool output from the last execution
metadataTMetadata | nullResponse metadata from the tool
stateTState | nullPersisted widget state
setState(state: TState | ((prev: TState | null) => TState)) => Promise<void>Update widget state (persisted and shown to model)

Layout and Theme

PropertyTypeDescription
theme"light" | "dark"Current theme (auto-syncs with ChatGPT)
displayMode"inline" | "pip" | "fullscreen"Current display mode
safeAreaSafeAreaSafe area insets for mobile layout
maxHeightnumberMaximum height available (pixels)
userAgentUserAgentDevice capabilities (device, capabilities)
localestringCurrent locale (e.g., "en-US")
mcp_urlstringMCP server base URL for making API requests

Actions

MethodSignatureDescription
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) => voidOpen 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

PropertyTypeDescription
isAvailablebooleanWhether the window.openai API is available

Complete Example

import { useWidget } from 'mcp-use/react';

interface ProductProps {
  productId: string;
  name: string;
  price: number;
}

interface ProductOutput {
  reviews: Array<{ rating: number; comment: string }>;
}

interface ProductState {
  favorites: string[];
}

const ProductWidget: React.FC = () => {
  const {
    // Props and state
    props,
    output,
    state,
    setState,
    
    // Layout & theme
    theme,
    displayMode,
    safeArea,
    
    // Actions
    callTool,
    sendFollowUpMessage,
    openExternal,
    requestDisplayMode,
    notifyIntrinsicHeight,
    
    // Availability
    isAvailable,
  } = useWidget<ProductProps, ProductOutput, {}, ProductState>();
  
  const handleAddToFavorites = async () => {
    const newFavorites = [...(state?.favorites || []), props.productId];
    await setState({ favorites: newFavorites });
  };
  
  const handleGetReviews = async () => {
    const result = await callTool('get-product-reviews', { 
      productId: props.productId 
    });
    // Handle result
  };
  
  return (
    <div data-theme={theme}>
      <h1>{props.name}</h1>
      <p>${props.price}</p>
      <button onClick={handleAddToFavorites}>Add to Favorites</button>
      <button onClick={handleGetReviews}>Get Reviews</button>
    </div>
  );
};

Helper Hooks

For convenience, there are specialized hooks for common use cases:

useWidgetProps

Get only the widget props:
import { useWidgetProps } from 'mcp-use/react';

const props = useWidgetProps<{ city: string; temperature: number }>();
// { city: "Paris", temperature: 22 }

useWidgetTheme

Get only the theme:
import { useWidgetTheme } from 'mcp-use/react';

const theme = useWidgetTheme(); // 'light' | 'dark'

useWidgetState

Get state management:
import { useWidgetState } from 'mcp-use/react';

const [favorites, setFavorites] = useWidgetState<string[]>([]);

// Update state
await setFavorites(['item1', 'item2']);

// Or use functional update
await setFavorites(prev => [...prev, 'newItem']);

Key Features

1. Props Without Props

Components don’t accept props via React props. Instead, props come from the hook:
// ❌ Don't do this
const MyWidget: React.FC<MyProps> = ({ city, temperature }) => { ... }

// ✅ Do this
const MyWidget: React.FC = () => {
  const { props } = useWidget<MyProps>();
  const { city, temperature } = props;
  // ...
}

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 all window.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

Use notifyIntrinsicHeight to notify OpenAI about height changes:
const { notifyIntrinsicHeight } = useWidget();

useEffect(() => {
  const height = containerRef.current?.scrollHeight || 0;
  notifyIntrinsicHeight(height);
}, [content]);
Or use McpUseProvider with autoSize={true} for automatic height notifications.

5. State Management

Widget state persists across widget interactions and is shown to the model:
const { state, setState } = useWidget<{}, {}, {}, { favorites: string[] }>();

// Update state
await setState({ favorites: ['item1', 'item2'] });

// Functional update
await setState(prev => ({
  favorites: [...(prev?.favorites || []), 'newItem']
}));

6. Tool Calls

Call other MCP tools from your widget:
const { callTool } = useWidget();

const handleSearch = async () => {
  const result = await callTool('search-products', { 
    query: 'laptop' 
  });
  // Handle result
};

7. Follow-up Messages

Send messages to the ChatGPT conversation:
const { sendFollowUpMessage } = useWidget();

const handleRequestInfo = async () => {
  await sendFollowUpMessage('Show me more details about this product');
};

8. Display Mode Control

Request display mode changes:
const { requestDisplayMode } = useWidget();

const handleFullscreen = async () => {
  const result = await requestDisplayMode('fullscreen');
  // result.mode is the granted mode (may differ from requested)
};

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: 600
  • userAgent: { device: { type: "desktop" }, capabilities: { hover: true, touch: false } }
  • locale: "en"
  • props: {} (or defaultProps if provided)
  • output: null
  • metadata: null
  • state: null

Error Handling

The hook throws errors when methods are called but the API is not available:
const { callTool, isAvailable } = useWidget();

const handleAction = async () => {
  if (!isAvailable) {
    console.warn('Widget API not available');
    return;
  }
  
  try {
    await callTool('my-tool', {});
  } catch (error) {
    console.error('Tool call failed:', error);
  }
};