> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
> Use this file to discover all available pages before exploring further.

# useWidget()

> React hooks and types for building MCP Apps widgets API Documentation

**Source Code**

View the source code for this module on GitHub: [[https://github.com/mcp-use/mcp-use/blob/main/libraries/typescript/packages/mcp-use/src/react/useWidget.ts](https://github.com/mcp-use/mcp-use/blob/main/libraries/typescript/packages/mcp-use/src/react/useWidget.ts)](https://github.com/mcp-use/mcp-use/blob/main/libraries/typescript/packages/mcp-use/src/react/useWidget.ts)

React hook for building MCP Apps widgets. `useWidget` abstracts over two data providers, selected automatically:

1. **MCP Apps bridge** (SEP-1865 `postMessage`), the primary runtime for hosted widget iframes including ChatGPT. The hook connects via `ui/initialize` and listens for `ui/notifications/tool-input`, `ui/notifications/tool-input-partial`, `ui/notifications/tool-result`, and `ui/notifications/host-context-changed`.
2. **URL params fallback** (`mcpUseParams`), used during local development (the `mcp-use dev` inspector) where `toolInput` and `toolOutput` are injected via the query string. There is no live streaming in this mode.

The server controls what the LLM sees (the `content` text array) separately from what the widget sees (`structuredContent` mapped to `props`). This lets a tool return rich structured data for rendering without polluting the model's context.

All exports come from `mcp-use/react`:

```ts theme={null}
import {
  useWidget,
  useWidgetProps,
  useViewState,
  useWidgetTheme,
} from "mcp-use/react";
import type {
  UseWidgetResult,
  API,
  DisplayMode,
  SafeArea,
  SafeAreaInsets,
  Theme,
  DeviceType,
  UserAgent,
  OpenAiGlobals,
} from "mcp-use/react";
```

## Hooks

### useWidget

The primary hook for building MCP Apps widgets. Returns props, persisted state, host context (theme, display mode, locale, layout), host actions (call a tool, send a message, open a link, request a display mode), and streaming and availability flags.

```tsx theme={null}
import { useWidget } from "mcp-use/react";
```

Build an MCP Apps widget. Components do not receive props through React props; props come from this hook. The hook automatically selects the MCP Apps bridge inside a hosted iframe and the URL params fallback during local development.

The hook accepts five optional type parameters in this exact order: `TProps`, `TState`, `TOutput`, `TMetadata`, `TToolInput`. The single runtime argument, `defaultProps`, seeds `props` before any tool data arrives.

`props` is merged from `toolInput` (base) and `structuredContent` (overlay). When the widget is exposed as a tool, `props` equals `toolInput` while pending and `structuredContent` once done. When the widget is returned by another tool, `props` equals `structuredContent` and `toolInput` holds the parent tool's arguments.

**Type Parameters**

> **TProps** `object` (default: UnknownObject)
>
> Shape of `props`, mapped from `structuredContent` in the tool result (or from `toolInput` while pending).
>
> **TState** `object` (default: UnknownObject)
>
> Shape of the persisted widget `state` read and written via `setState`.
>
> **TOutput** `object` (default: UnknownObject)
>
> Shape of `output`, the raw `structuredContent` field from the tool result.
>
> **TMetadata** `object` (default: UnknownObject)
>
> Shape of `metadata`, the `_meta` object from the tool result.
>
> **TToolInput** `object` (default: UnknownObject)
>
> Shape of `toolInput`, the arguments the model passed to the tool.

**Parameters**

> **defaultProps** `TProps` (default: undefined)
>
> Optional initial props used as the merge base before `toolInput` or `structuredContent` arrives. When omitted, the base is an empty object.

**Returns**

`UseWidgetResult<TProps, TState, TOutput, TMetadata, TToolInput>`, a discriminated union on `isPending`. While `isPending` is `true`, `props` is `Partial<TProps>`; once `isPending` is `false`, `props` is the full `TProps`.

> **props** `Partial\<TProps\> | TProps`
>
> Merged widget props. `Partial<TProps>` while `isPending` is `true`, then the full `TProps` once the tool result arrives. Defaults to `defaultProps` (or `{}`).
>
> **toolInput** `TToolInput`
>
> The arguments the model passed when calling the tool, delivered via `ui/notifications/tool-input`. Defaults to `{}` when unavailable.
>
> **output** `TOutput | null`
>
> The `structuredContent` field from the tool result. Widget-only data the LLM never sees. `null` while the tool is still executing.
>
> **metadata** `TMetadata | null`
>
> The `_meta` object from the tool result (timestamps, cache headers, API version, etc.). Not in the model's context. `null` while pending, and always `null` on the URL params fallback.
>
> **state** `TState | null`
>
> Persisted widget state the model can read on future turns. `null` until `setState` is first called. The internal model-context key is stripped from the returned value.
>
> **setState** `(state: TState | ((prevState: TState | null) =\> TState)) =\> Promise\<void\>`
>
> Update the persisted widget state. Accepts a new value or a functional updater `(prev) => next`. Sends `ui/update-model-context` so the LLM sees the new state next turn. Rapid updates before the next user message are deduplicated by the host.
>
> **theme** `Theme`
>
> The host's color-scheme preference, `"light"` or `"dark"`. Defaults to `"light"`. Changes arrive via `ui/notifications/host-context-changed`.
>
> **displayMode** `DisplayMode`
>
> Current rendering context: `"inline"`, `"fullscreen"`, or `"pip"`. Defaults to `"inline"`. On mobile, `"pip"` is coerced to `"fullscreen"` by the host.
>
> **safeArea** `SafeArea`
>
> Safe-area inset boundaries (in pixels) for OS chrome (notch, home indicator, system bars). Defaults to `{ insets: { top: 0, bottom: 0, left: 0, right: 0 } }`. From `HostContext.safeAreaInsets`.
>
> **maxHeight** `number`
>
> Maximum height the widget container can grow to, in pixels. Defaults to `600`. From `HostContext.containerDimensions.maxHeight`.
>
> **maxWidth** `number | undefined`
>
> Maximum width the widget container can grow to, in pixels. From `HostContext.containerDimensions.maxWidth`. `undefined` when unbounded or unavailable (no default is applied).
>
> **userAgent** `UserAgent`
>
> Device type and input-capability flags. Defaults to `{ device: { type: "desktop" }, capabilities: { hover: true, touch: false } }`. Derived from `HostContext.platform` and `HostContext.deviceCapabilities`.
>
> **locale** `string`
>
> The user's BCP 47 language and region tag (for example `"en-US"`). Defaults to `WIDGET_DEFAULTS.LOCALE`. From `HostContext.locale`.
>
> **timeZone** `string`
>
> The user's IANA timezone (for example `"America/New_York"`). From `HostContext.timeZone`. Falls back to the browser timezone (`Intl.DateTimeFormat().resolvedOptions().timeZone`), or `"UTC"` when no `window` is present.
>
> **mcp\_url** `string`
>
> Base URL of the MCP server running inside the sandbox, derived from `window.__mcpPublicUrl` with the `/mcp-use/public` suffix removed. `""` when unavailable.
>
> **callTool** `(name: string, args: Record\<string, unknown\>) =\> Promise\<CallToolResponse\>`
>
> Call any tool registered on the MCP server (including app-only tools). Sends `tools/call` via the host and returns a normalized `CallToolResponse`.
>
> **sendFollowUpMessage** `(content: string | MessageContentBlock[]) =\> Promise\<void\>`
>
> Add a user-role message to the conversation and trigger a new model turn. A plain string is wrapped as a single `text` block; an array of `MessageContentBlock` is sent as-is. Sends `ui/message`.
>
> **openExternal** `(href: string) =\> void`
>
> Ask the host to open a URL in the user's browser or a new tab via `ui/open-link`. The host may deny the request; errors are logged, not thrown.
>
> **requestDisplayMode** `(mode: DisplayMode) =\> Promise\<\{ mode: DisplayMode \}\>`
>
> Request a display-mode change via `ui/request-display-mode`. The host returns the actual granted mode, which may differ from the requested one.
>
> **isAvailable** `boolean`
>
> `true` when the MCP Apps `postMessage` bridge has connected. `false` while connecting or on the URL params fallback (where it is always `false`).
>
> **isPending** `boolean`
>
> `true` while the tool is still executing; becomes `false` once `ui/notifications/tool-result` is received. While `true`, `props` is `Partial<TProps>`. On the URL params fallback it is `true` until `toolOutput` is present.
>
> **partialToolInput** `Partial\<TToolInput\> | null`
>
> Partial tool arguments streamed in real time while the LLM is still generating them, via `ui/notifications/tool-input-partial`. A best-effort parse of incomplete JSON (auto-closed brackets), so fields may be missing or change. `null` on the URL params fallback.
>
> **isStreaming** `boolean`
>
> `true` while `partialToolInput` is non-null and the complete `toolInput` has not yet arrived. Always `false` on the URL params fallback.
>
> **hostInfo** `\{ name: string; version: string \} | undefined`
>
> Name and version of the MCP Apps host from the `ui/initialize` handshake. `undefined` outside a supported MCP Apps host.
>
> **hostCapabilities** `Record\<string, unknown\> | undefined`
>
> Capabilities advertised by the host during `ui/initialize` (SEP-1865 `HostCapabilities`). `undefined` outside a supported host or when none were sent.
>
> **hostContext** `HostContext | undefined`
>
> Raw host context from the MCP Apps bridge. Includes standardized host style variables at `hostContext.styles.variables` when provided. `undefined` outside a supported host.

**Signature**

```ts wrap theme={null}
function useWidget<TProps = UnknownObject, TState = UnknownObject, TOutput = UnknownObject, TMetadata = UnknownObject, TToolInput = UnknownObject>(defaultProps?: TProps): UseWidgetResult<TProps, TState, TOutput, TMetadata, TToolInput>
```

**Example**

```tsx wrap theme={null}
import { useWidget } from "mcp-use/react";

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

const WeatherWidget: React.FC = () => {
  const { props, isPending, toolInput, theme } = useWidget<WeatherProps>();

  if (isPending) return <p>Loading...</p>;

  return (
    <div data-theme={theme}>
      <h1>{props.city}</h1>
      <p>{props.temperature}C</p>
      <p>Requested: {toolInput.city}</p>
    </div>
  );
};
```

The matching server tool, using `new MCPServer({ ... })`:

```ts wrap theme={null}
import { MCPServer, widget } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "mcp-apps-example",
  version: "1.0.0",
  description: "Dual-protocol widget support (ChatGPT and MCP Apps clients)",
});

server.tool(
  {
    name: "get-weather",
    description: "Get current weather for a city",
    schema: z.object({ city: z.string().describe("City name") }),
    widget: {
      name: "weather-display",
      invoking: "Fetching weather data...",
      invoked: "Weather data loaded",
    },
  },
  async ({ city }) =>
    widget({
      props: { city, temperature: 22 },
      message: `Current weather in ${city}: 22C`,
    }),
);

await server.listen();
```

### useWidgetProps

Convenience wrapper around `useWidget` that returns only the widget props. Equivalent to calling `useWidget(defaultProps)` and reading the `props` field.

**Type Parameters**

> **TProps** `object` (default: UnknownObject)
>
> Shape of the returned props.

**Parameters**

> **defaultProps** `TProps` (default: undefined)
>
> Optional initial props used as the merge base, forwarded to `useWidget`.

**Returns**

> **returns** `Partial\<TProps\>`
>
> The merged widget props. Fields may be `undefined` while the tool is still executing.

**Signature**

```ts wrap theme={null}
function useWidgetProps<TProps = UnknownObject>(defaultProps?: TProps): Partial<TProps>
```

**Example**

```tsx wrap theme={null}
import { useWidgetProps } from "mcp-use/react";

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

### useViewState

`useViewState` replaces the legacy `useWidgetState` helper. It requires a JSON-serializable object default, shares one state object across the mounted view, and returns a synchronous `useState`-style setter.

```tsx wrap theme={null}
import { useViewState } from "mcp-use/react";

const [state, setState] = useViewState({ favorites: [] as string[] });
setState((previous) => ({
  favorites: [...previous.favorites, "new-item"],
}));
```

See the [`useViewState` API reference](https://docs.mcp-use.com/v1/typescript/api-reference/react/useviewstate) for host restoration and `ModelContext` integration.

### useWidgetTheme

Convenience wrapper around `useWidget` that returns only the current `theme` value.

**Returns**

> **returns** `Theme`
>
> The host's color-scheme preference, `"light"` or `"dark"`. Defaults to `"light"`.

**Signature**

```ts wrap theme={null}
function useWidgetTheme(): Theme
```

**Example**

```tsx wrap theme={null}
import { useWidgetTheme } from "mcp-use/react";

const theme = useWidgetTheme(); // "light" | "dark"
```

## Types

### UseWidgetResult

The return type of `useWidget`. A discriminated union on `isPending` over a shared base of fields.

When `isPending` is `true`, `props` is typed as `Partial<TProps>` (fields may be `undefined`). When `isPending` is `false`, `props` is typed as the full `TProps` (all required fields present). Guarding on `isPending` narrows the type, so after an `if (isPending) return ...` guard, `props` field access is safe without optional chaining.

Every field is described in the `useWidget` Returns section above.

**Type Parameters**

> **TProps** `object` (default: UnknownObject)
>
> Shape of `props`.
>
> **TState** `object` (default: UnknownObject)
>
> Shape of `state`.
>
> **TOutput** `object` (default: UnknownObject)
>
> Shape of `output`.
>
> **TMetadata** `object` (default: UnknownObject)
>
> Shape of `metadata`.
>
> **TToolInput** `object` (default: UnknownObject)
>
> Shape of `toolInput`.

**Signature**

```ts wrap theme={null}
type UseWidgetResult<TProps = UnknownObject, TState = UnknownObject, TOutput = UnknownObject, TMetadata = UnknownObject, TToolInput = UnknownObject> = UseWidgetResultBase<TState, TOutput, TMetadata, TToolInput> & ({ isPending: true; props: Partial<TProps> } | { isPending: false; props: TProps })
```

### API

Type definition for the `window.openai` extension API exposed by the ChatGPT Apps SDK host. `useWidget` wraps these methods (for example `callTool`, `sendFollowUpMessage`, `openExternal`, `requestDisplayMode`, `setState`) so widget code does not call `window.openai` directly. The interface is parameterized by `WidgetState`.

**Type Parameters**

> **WidgetState** `UnknownObject` (default: UnknownObject)
>
> Shape of the persisted widget state passed to `setWidgetState`.

**Members**

> **callTool** `(name: string, args: Record\<string, unknown\>) =\> Promise\<CallToolResponse\>`
>
> Call a tool on your MCP server and return the full response.
>
> **sendFollowUpMessage** `(args: \{ prompt: string \}) =\> Promise\<void\>`
>
> Trigger a follow-up turn in the conversation.
>
> **openExternal** `(payload: \{ href: string \}) =\> void`
>
> Open an external link, redirect the web page, or open a mobile app.
>
> **requestDisplayMode** `(args: \{ mode: DisplayMode \}) =\> Promise\<\{ mode: DisplayMode \}\>`
>
> Transition the app between inline, fullscreen, and pip. The host may reject the request, and on mobile pip is always coerced to fullscreen.
>
> **setWidgetState** `(state: WidgetState) =\> Promise\<void\>`
>
> Persist widget state that will be shown to the model.
>
> **notifyIntrinsicHeight** `(height: number) =\> Promise\<void\>`
>
> Notify OpenAI about intrinsic height changes for auto-sizing.
>
> **uploadFile** `((file: File) =\> Promise\<FileMetadata\>) | undefined`
>
> Upload a file to the host. Optional: only present when the OpenAI extension exposes file APIs. Detect availability with `useFiles().isSupported`.
>
> **getFileDownloadUrl** `((file: FileMetadata) =\> Promise\<\{ downloadUrl: string \}\>) | undefined`
>
> Get a temporary download URL for a previously uploaded file. Optional: only present when the OpenAI extension exposes file APIs.

**Signature**

```ts wrap theme={null}
interface API<WidgetState extends UnknownObject = UnknownObject> {
  callTool: (name: string, args: Record<string, unknown>) => Promise<CallToolResponse>;
  sendFollowUpMessage: (args: { prompt: string }) => Promise<void>;
  openExternal(payload: { href: string }): void;
  requestDisplayMode: (args: { mode: DisplayMode }) => Promise<{ mode: DisplayMode }>;
  setWidgetState: (state: WidgetState) => Promise<void>;
  notifyIntrinsicHeight: (height: number) => Promise<void>;
  uploadFile?: (file: File) => Promise<FileMetadata>;
  getFileDownloadUrl?: (file: FileMetadata) => Promise<{ downloadUrl: string }>;
}
```

### DisplayMode

The rendering context of a widget. `"inline"` embeds the widget in the host's content flow, `"fullscreen"` occupies the full screen or window, and `"pip"` is a floating picture-in-picture overlay. On mobile, `"pip"` requests are coerced to `"fullscreen"` by the host.

**Signature**

```ts wrap theme={null}
type DisplayMode = "pip" | "inline" | "fullscreen"
```

### SafeArea

Host-provided safe-area boundaries for a widget. Wraps a `SafeAreaInsets` value under an `insets` key. Apply the insets as padding or margin so interactive elements are not hidden behind OS chrome.

**Members**

> **insets** `SafeAreaInsets`
>
> The four-sided inset measurements, in pixels.

**Signature**

```ts wrap theme={null}
type SafeArea = {
  insets: SafeAreaInsets;
}
```

### SafeAreaInsets

Pixel inset measurements for each side of the viewport, describing the space consumed by OS chrome (notch, home indicator, rounded corners, system bars).

**Members**

> **top** `number`
>
> Inset from the top edge, in pixels.
>
> **bottom** `number`
>
> Inset from the bottom edge, in pixels.
>
> **left** `number`
>
> Inset from the left edge, in pixels.
>
> **right** `number`
>
> Inset from the right edge, in pixels.

**Signature**

```ts wrap theme={null}
type SafeAreaInsets = {
  top: number;
  bottom: number;
  left: number;
  right: number;
}
```

### Theme

The host's color-scheme preference. `useWidget` defaults this to `"light"` when no host context is available.

**Signature**

```ts wrap theme={null}
type Theme = "light" | "dark"
```

### DeviceType

The device class reported by the host, used as `UserAgent.device.type`. The MCP Apps bridge maps host platforms to `"mobile"` or `"desktop"`; `"tablet"` and `"unknown"` are valid members of the type.

**Signature**

```ts wrap theme={null}
type DeviceType = "mobile" | "tablet" | "desktop" | "unknown"
```

### UserAgent

Device type and input-capability flags reported by the host. Use `device.type` to adapt layout density and `capabilities.hover` / `capabilities.touch` to decide whether to show hover-only UI or touch-friendly hit targets.

**Members**

> **device** `\{ type: DeviceType \}`
>
> The reported device class.
>
> **capabilities** `\{ hover: boolean; touch: boolean \}`
>
> Input capabilities: whether the device supports hover and whether it supports touch.

**Signature**

```ts wrap theme={null}
type UserAgent = {
  device: { type: DeviceType };
  capabilities: {
    hover: boolean;
    touch: boolean;
  };
}
```

### OpenAiGlobals

Type definition for the global state values exposed on `window.openai` by the ChatGPT Apps SDK host (theme, layout, locale, and tool state). `useWidget` reads equivalent values from the MCP Apps host context, so widget code does not access these globals directly. The interface is parameterized by the tool input, output, response-metadata, and widget-state shapes.

**Type Parameters**

> **ToolInput** `UnknownObject` (default: UnknownObject)
>
> Shape of the tool input arguments.
>
> **ToolOutput** `UnknownObject` (default: UnknownObject)
>
> Shape of the tool output.
>
> **ToolResponseMetadata** `UnknownObject` (default: UnknownObject)
>
> Shape of the tool response metadata.
>
> **WidgetState** `UnknownObject` (default: UnknownObject)
>
> Shape of the persisted widget state.

**Members**

> **theme** `Theme`
>
> Current color-scheme preference.
>
> **userAgent** `UserAgent`
>
> Device type and input-capability flags.
>
> **locale** `string`
>
> The user's BCP 47 locale tag.
>
> **maxHeight** `number`
>
> Maximum container height, in pixels.
>
> **displayMode** `DisplayMode`
>
> Current display mode.
>
> **safeArea** `SafeArea`
>
> Safe-area insets for the current layout.
>
> **toolInput** `ToolInput`
>
> The arguments the model passed to the tool.
>
> **toolOutput** `ToolOutput | null`
>
> The tool output, or `null` while pending.
>
> **toolResponseMetadata** `ToolResponseMetadata | null`
>
> The tool response metadata, or `null` while pending.
>
> **widgetState** `WidgetState | null`
>
> The persisted widget state, or `null` when unset.

**Signature**

```ts wrap theme={null}
interface OpenAiGlobals<ToolInput extends UnknownObject = UnknownObject, ToolOutput extends UnknownObject = UnknownObject, ToolResponseMetadata extends UnknownObject = UnknownObject, WidgetState extends UnknownObject = UnknownObject> {
  theme: Theme;
  userAgent: UserAgent;
  locale: string;
  maxHeight: number;
  displayMode: DisplayMode;
  safeArea: SafeArea;
  toolInput: ToolInput;
  toolOutput: ToolOutput | null;
  toolResponseMetadata: ToolResponseMetadata | null;
  widgetState: WidgetState | null;
}
```

## Widget lifecycle

Widgets render before the tool finishes executing:

1. **First render** (`isPending === true`): the widget mounts immediately, `props` is `Partial<TProps>`, and `output` and `metadata` are `null`. Render a loading state here.
2. **After the tool completes** (`isPending === false`): `props` is the full `TProps`, and `output` and `metadata` are populated. The widget re-renders with full data.

When the host streams tool arguments (the `mcp-use dev` inspector or MCP Apps clients that send `ui/notifications/tool-input-partial`), an optional phase occurs between (1) and (2): the widget receives `partialToolInput` in real time while `isStreaming` is `true`, then transitions to full `props`. On the URL params fallback, `partialToolInput` stays `null` and `isStreaming` stays `false`.

```tsx wrap theme={null}
const MyWidget: React.FC = () => {
  const { props, isPending } = useWidget<{ city: string; temperature: number }>();

  if (isPending) return <LoadingSpinner />;

  // props.city and props.temperature are non-optional here
  return (
    <div>
      {props.city} - {props.temperature}C
    </div>
  );
};
```

## Default values

When host values are unavailable, `useWidget` returns these safe defaults:

* `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`: `WIDGET_DEFAULTS.LOCALE`
* `timeZone`: browser timezone, or `"UTC"` when no `window`
* `props`: `defaultProps` when provided, otherwise `{}`
* `output`: `null`
* `metadata`: `null`
* `state`: `null`
* `maxWidth`: no default (stays `undefined` when unavailable)

## Related

* [`McpUseProvider`](https://docs.mcp-use.com/v1/typescript/api-reference/react/mcpuseprovider) for unified widget setup including auto-sizing.
* [`WidgetControls`](https://docs.mcp-use.com/v1/typescript/api-reference/react/widgetcontrols) for debug and view controls.
* [`ThemeProvider`](https://docs.mcp-use.com/v1/typescript/api-reference/react/themeprovider) for theme management.
* [`ErrorBoundary`](https://docs.mcp-use.com/v1/typescript/api-reference/react/errorboundary) for widget error handling.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.