> ## 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.

# Debugging Widgets

> Test and debug MCP Apps and ChatGPT Apps SDK widgets with dual-protocol support

The mcp-use Inspector provides comprehensive support for debugging widgets built with **both** MCP Apps and ChatGPT Apps SDK protocols. Test your tools, render widgets, switch protocols, and verify interactions all within the inspector interface.

<Info>
  **Dual-Protocol Support**: The inspector supports both widget protocols:

  * **MCP Apps** - Standard MCP protocol with JSON-RPC communication
  * **ChatGPT Apps SDK** - OpenAI's protocol with `window.openai` API emulation

  Use the **Protocol Toggle** to switch between protocols and test cross-compatibility.
</Info>

## Overview

### What are Widget Protocols?

MCP servers can provide interactive widgets using two protocols:

| Protocol             | Standard           | Clients                    | Communication             |
| -------------------- | ------------------ | -------------------------- | ------------------------- |
| **MCP Apps**         | MCP standard       | Claude, Goose, MCP clients | JSON-RPC over postMessage |
| **ChatGPT Apps SDK** | OpenAI proprietary | ChatGPT                    | window\.openai API        |

With mcp-use, you can build widgets that support **both protocols simultaneously** using `type: "mcpApps"`.

### How the Inspector Helps

The inspector provides:

* **Dual-Protocol Testing**: Switch between MCP Apps and ChatGPT protocols
* **Protocol Toggle**: Test cross-compatibility with one click
* **Widget Rendering**: Preview widgets in both protocols
* **Debug Controls**: Test different devices, locales, CSP modes
* **Display Modes**: Test inline, picture-in-picture, fullscreen
* **Interactive Debugging**: Test widget interactions and tool calls
* **Dev Mode Support**: Hot reload for widget development

## Protocol Toggle

For widgets that support both protocols (using `type: "mcpApps"`), the inspector shows a **Protocol Toggle** that lets you switch between:

* **MCP Apps** - Tests the MCP Apps protocol with JSON-RPC communication
* **ChatGPT Apps** - Tests the Apps SDK protocol with window\.openai emulation

The toggle only appears for dual-protocol widgets. Single-protocol widgets (`type: "appsSdk"`) only render using ChatGPT protocol.

## MCP Apps Debug Controls

When testing MCP Apps protocol widgets, the inspector provides comprehensive debug controls:

### Device Emulation

Switch between device types to test responsive behavior:

* **Desktop** - Large viewport, hover support
* **Mobile** - Small viewport, touch support
* **Tablet** - Medium viewport, both touch and hover

### Locale & Timezone

Test internationalization:

* **Locale Selector** - Choose from 100+ locales (en-US, es-ES, ja-JP, etc.)
* **Timezone Selector** - Test timezone-specific behavior (MCP Apps only)

### CSP Mode Toggle

Test Content Security Policy enforcement:

* **Permissive** - Relaxed CSP for debugging
* **Widget-Declared** - Enforces widget's declared CSP (production mode)

<Warning>
  CSP violations are logged in the console. Use Widget-Declared mode to catch
  CSP issues before production deployment.
</Warning>

See [Content Security Policy](/typescript/server/content-security-policy) for widget CSP configuration.

### Capabilities Testing

Toggle device capabilities:

* **Touch** - Enable/disable touch input simulation
* **Hover** - Enable/disable hover state detection

### Safe Area Insets

Configure safe area insets for testing mobile layouts:

* **Top** - Notch or status bar area
* **Bottom** - Home indicator area
* **Left/Right** - Display cutouts

### Display Mode Controls

Test different display modes:

* **Inline** - Default embedded view
* **Picture-in-Picture** - Floating window (click PiP button)
* **Fullscreen** - Full browser window (click fullscreen button)

### Props Management

Test widgets with different inputs:

* **Use Tool Input** - Use current tool call parameters
* **Select Preset** - Choose from saved prop configurations
* **Create Preset** - Save current props for reuse
* **Edit Props** - Manually modify prop values

## ChatGPT Apps SDK: window\.openai API Emulation

<Note>
  This section covers the **ChatGPT Apps SDK** protocol specifically. For MCP Apps protocol widgets, the inspector uses JSON-RPC over postMessage instead.

  Your widget code can work with both protocols by using the `useWidget()` hook from `mcp-use/react`, which abstracts protocol differences.
</Note>

When testing ChatGPT Apps SDK protocol widgets, the inspector provides complete emulation of the `window.openai` API. This API is automatically injected into widget iframes, allowing your components to work identically in the inspector and in ChatGPT.

### API Overview

The `window.openai` object provides:

* **Global Properties**: Theme, display mode, tool data, widget state
* **Methods**: Tool calls, follow-up messages, display mode requests
* **Events**: Global change notifications via `openai:set_globals` events

### Global Properties

These properties are available on `window.openai` and update reactively:

#### `toolInput`

The input parameters passed to the tool that triggered this widget.

```javascript theme={null}
const input = window.openai.toolInput;
// { city: "San Francisco", category: "pizza" }
```

#### `toolOutput`

The structured output from the tool execution. This is the primary data source for your widget.

```javascript theme={null}
const output = window.openai.toolOutput;
// { places: [...], metadata: {...} }
```

#### `toolResponseMetadata`

Additional metadata from the tool response. Contains the `_meta` field from the tool result, which may include custom metadata passed via `widget({ metadata: ... })` on the server.

#### `widgetState`

Persistent state for this widget instance. State is scoped to the specific widget and conversation message.

```javascript theme={null}
// Read current state
const state = window.openai.widgetState;

// Update state (persists across widget interactions)
await window.openai.setWidgetState({ favorites: [...] });
```

<Note>
  Widget state persists in browser localStorage and is rehydrated when the
  widget loads. State is scoped to the widget instance and doesn't travel across
  different widgets or conversation turns.
</Note>

#### `displayMode`

Current display mode: `"inline"`, `"pip"`, or `"fullscreen"`.

```javascript theme={null}
const mode = window.openai.displayMode;
// "inline" | "pip" | "fullscreen"
```

#### `theme`

Current theme: `"light"` or `"dark"`. Automatically syncs with inspector theme.

```javascript theme={null}
const theme = window.openai.theme;
// "light" | "dark"
```

#### `maxHeight`

Maximum height available for the widget container (in pixels).

```javascript theme={null}
const height = window.openai.maxHeight;
// 600 (default)
```

#### `locale`

User's locale setting.

```javascript theme={null}
const locale = window.openai.locale;
// "en-US" (default)
```

#### `safeArea`

Safe area insets for mobile devices.

```javascript theme={null}
const safeArea = window.openai.safeArea;
// { insets: { top: 0, bottom: 0, left: 0, right: 0 } }
```

#### `userAgent`

User agent information including device type and capabilities.

```javascript theme={null}
const userAgent = window.openai.userAgent;
// { device: { type: "desktop" }, capabilities: { hover: true, touch: false } }
```

### API Methods

#### `callTool(name, params)`

Call an MCP tool directly from the widget. Returns a Promise with the tool result.

```javascript theme={null}
const result = await window.openai.callTool("get_restaurants", {
  city: "San Francisco",
  category: "pizza",
});

// Result format matches OpenAI's expected structure:
// { content: [{ type: "text", text: "..." }] }
```

**Implementation:**

* Sends `postMessage` to parent (inspector)
* Inspector executes tool via MCP connection
* Result is formatted to match OpenAI's expected format
* MCP `contents` array is converted to OpenAI `content` format
* 30-second timeout for tool calls

<Warning>
  Tools must be marked as callable by components in your MCP server. The
  inspector forwards all tool calls to the connected MCP server.
</Warning>

#### `sendFollowUpMessage(args)`

Send a follow-up message to ChatGPT as if the user typed it.

```javascript theme={null}
await window.openai.sendFollowUpMessage({
  prompt: "Show me more details about the first restaurant",
});
```

**Implementation:**

* Dispatches custom event `mcp-inspector:widget-followup`
* Message appears in Chat tab
* Can be used to continue conversation from widget interactions

#### `setWidgetState(state)`

Persist widget state across interactions. State is visible to ChatGPT and can influence future tool calls.

```javascript theme={null}
await window.openai.setWidgetState({
  favorites: ["restaurant-1", "restaurant-2"],
  filters: { price: "$$" },
});
```

**Implementation:**

* Stores state in browser localStorage
* Keyed by widget instance ID
* State is rehydrated on widget load
* Sent to parent via `postMessage` for inspector awareness

<Note>
  Keep widget state under 4k tokens for performance. State is sent to ChatGPT
  and can influence model behavior.
</Note>

#### `requestDisplayMode(options)`

Request a different display mode for the widget.

```javascript theme={null}
const result = await window.openai.requestDisplayMode({
  mode: "fullscreen",
});
// Returns: { mode: "fullscreen" }
```

**Supported modes:**

* `"inline"` - Default embedded view
* `"pip"` - Picture-in-Picture floating window
* `"fullscreen"` - Full browser window

**Implementation:**

* Uses native Fullscreen API when available
* Falls back to CSS-based fullscreen
* On mobile, PiP may be coerced to fullscreen
* Updates `displayMode` property and dispatches events

#### `openExternal(payload)`

Open an external link in a new window/tab.

```javascript theme={null}
window.openai.openExternal({
  href: "https://example.com",
});

// Or with string
window.openai.openExternal("https://example.com");
```

**Implementation:**

* Uses `window.open()` with security flags
* Opens in new tab with `noopener,noreferrer`

#### `notifyIntrinsicHeight(height)`

Notify OpenAI about intrinsic height changes for auto-sizing. This allows widgets to dynamically resize based on content.

```javascript theme={null}
await window.openai.notifyIntrinsicHeight(800);
```

**Implementation:**

* Sends `postMessage` with type `openai:notifyIntrinsicHeight`
* Inspector updates iframe height accordingly
* Height is capped based on display mode (fullscreen/pip respect viewport)
* Used by `McpUseProvider` with `autoSize={true}` for automatic height updates

**Usage Example:**

```javascript theme={null}
// Manual height notification
const container = document.getElementById("widget-content");
const height = container.scrollHeight;
await window.openai.notifyIntrinsicHeight(height);

// Or use McpUseProvider with autoSize for automatic updates
<McpUseProvider autoSize>
  <MyWidget />
</McpUseProvider>;
```

## Console Proxy Toggle

The inspector provides a console proxy feature that allows you to forward iframe console logs to the page console for easier debugging.

### Enabling Console Proxy

1. Open the **Console** panel in the inspector (click the terminal icon)
2. Toggle **"Proxy logs to page console"** switch
3. Console logs from the widget iframe will now appear in your browser's developer console

### Features

* **Persistent Preference**: Your preference is saved in `localStorage` and persists across sessions
* **Formatted Output**: Logs are prefixed with `[WIDGET CONSOLE]` for easy identification
* **Log Level Preservation**: Error, warn, info, debug, and trace levels are preserved
* **JSON Formatting**: Objects are automatically stringified for better readability

### Use Cases

* **Debugging Widget Issues**: See all console logs in one place
* **Development Workflow**: Use browser DevTools features (filtering, searching)
* **Error Tracking**: Easier to spot errors and warnings

## Widget State Inspection

The inspector provides built-in widget state inspection capabilities through the `WidgetInspectorControls` component.

### Viewing Widget State

When a widget is rendered in the inspector, you can inspect:

* **Props**: Widget rendering data from `structuredContent` (what the server computed)
* **Tool Input**: Original tool call arguments from the model
* **Output**: Tool output data (`toolOutput` / `structuredContent`)
* **Metadata**: Response metadata from `_meta` (`toolResponseMetadata`)
* **State**: Persistent widget state (`widgetState`)
* **Theme**: Current theme (light/dark)
* **Display Mode**: Current display mode (inline/pip/fullscreen)
* **Safe Area**: Safe area insets for mobile
* **User Agent**: Device capabilities
* **Locale**: User locale

### Debug Information Display

The inspector automatically displays debug information when widgets use `McpUseProvider` with `debugger={true}` or when using `WidgetControls` component. This provides:

* Real-time state updates
* Interactive tool testing
* State modification capabilities
* Full widget context visibility

### State Inspection API

Widgets can respond to state inspection requests:

```javascript theme={null}
// Inspector sends: mcp-inspector:getWidgetState
window.addEventListener("message", (event) => {
  if (event.data?.type === "mcp-inspector:getWidgetState") {
    window.parent.postMessage(
      {
        type: "mcp-inspector:widgetStateResponse",
        toolId: event.data.toolId,
        state: window.openai.widgetState,
      },
      "*",
    );
  }
});
```

This allows the inspector to display current widget state even when the widget is in an iframe.

### Events

#### `openai:set_globals`

Custom event dispatched when any global property changes. React components can listen to this for reactive updates.

```javascript theme={null}
window.addEventListener("openai:set_globals", (event) => {
  const { globals } = event.detail;
  // globals contains all updated properties
  console.log("Theme changed:", globals.theme);
  console.log("Display mode:", globals.displayMode);
});
```

**When dispatched:**

* Initial widget load
* Display mode changes
* Theme changes
* Any global property update from parent

**Event detail structure:**

```typescript theme={null}
{
  globals: {
    toolInput: {...},
    toolOutput: {...},
    toolResponseMetadata: null,
    widgetState: {...},
    displayMode: "inline",
    maxHeight: 600,
    theme: "dark",
    locale: "en-US",
    safeArea: {...},
    userAgent: {...}
  }
}
```

### React Helper Hooks

The inspector's API emulation is compatible with React hooks that use `useSyncExternalStore` to subscribe to global changes. Here's an example pattern:

```javascript theme={null}
function useOpenAiGlobal(key) {
  return useSyncExternalStore(
    (onChange) => {
      const handleSetGlobal = (event) => {
        if (event.detail.globals[key] !== undefined) {
          onChange();
        }
      };
      window.addEventListener("openai:set_globals", handleSetGlobal);
      return () => {
        window.removeEventListener("openai:set_globals", handleSetGlobal);
      };
    },
    () => window.openai[key],
  );
}

// Usage
function MyWidget() {
  const theme = useOpenAiGlobal("theme");
  const toolOutput = useOpenAiGlobal("toolOutput");

  return (
    <div className={theme === "dark" ? "dark" : "light"}>
      {/* Widget content */}
    </div>
  );
}
```

### Implementation Details

#### API Injection

The `window.openai` API is injected into widget iframes via server-side HTML generation:

1. Widget HTML is fetched from MCP server
2. Inspector injects API script before widget content
3. API object is attached to `window.openai` and `window.webplus` (for compatibility)
4. Initial globals event is dispatched
5. Message listeners are set up for parent communication

#### Communication Protocol

Widget-to-inspector communication uses `postMessage`:

**Widget → Inspector:**

* `openai:callTool` - Tool execution requests
* `openai:sendFollowup` - Follow-up messages
* `openai:requestDisplayMode` - Display mode changes
* `openai:setWidgetState` - State updates

**Inspector → Widget:**

* `openai:callTool:response` - Tool call results
* `openai:globalsChanged` - Global property updates
* `openai:displayModeChanged` - Display mode changes (legacy)

#### State Persistence

Widget state is persisted in browser localStorage:

* **Key format**: `mcp-inspector-widget-state-${toolId}`
* **Storage**: Browser localStorage (scoped to inspector domain)
* **Lifetime**: Persists across page reloads
* **Scope**: Per widget instance

#### Tool Result Formatting

MCP tool results are automatically converted to OpenAI's expected format:

**MCP Format:**

```json theme={null}
{
  "contents": [{ "type": "text", "text": "Result" }]
}
```

**OpenAI Format (returned to widget):**

```json theme={null}
{
  "content": [{ "type": "text", "text": "Result" }]
}
```

#### Compatibility

The inspector maintains compatibility with:

* **OpenAI Apps SDK**: Full API compatibility
* **Legacy APIs**: `sendFollowupTurn` (aliased to `sendFollowUpMessage`)
* **React Router**: URL normalization for routing
* **Multiple display modes**: Inline, PiP, Fullscreen

### Differences from ChatGPT

While the inspector provides full API compatibility, there are some differences:

1. **User Agent**: Inspector provides mock user agent data
2. **Safe Area**: Defaults to zero insets (not mobile-specific)
3. **Locale**: Defaults to "en-US" (not user-specific)
4. **Tool Results**: Converted from MCP format to OpenAI format
5. **Follow-ups**: Appear in inspector Chat tab instead of ChatGPT

### Testing Your Widget

To test widget compatibility:

1. **Develop locally** with inspector
2. **Test all API methods** in your widget
3. **Verify state persistence** across interactions
4. **Test display mode transitions**
5. **Verify tool calls** work correctly
6. **Check theme adaptation**
7. **Test in ChatGPT** for final verification

### Official Documentation

For the complete OpenAI Apps SDK API reference, see:

* [OpenAI Apps SDK - Build a custom UX](https://developers.openai.com/apps-sdk/build/custom-ux) - Official API documentation
* [OpenAI Apps SDK Reference](https://developers.openai.com/apps-sdk/reference) - Complete API reference

## Connecting Your ChatGPT App

### Setting Up Your MCP Server

Your ChatGPT App needs an MCP server that exposes tools and optionally widgets:

```typescript theme={null}
import { MCPServer } from "mcp-use/server";
import { z } from "zod";

const server = new MCPServer({
  name: "my-chatgpt-app",
  version: "1.0.0",
});

// Add a tool that returns a widget
server.tool({
  name: "create_dashboard",
  description: "Create an interactive dashboard",
  schema: z.object({
    title: z.string(),
    data: z.object({}).passthrough(),
  }),
}, async (args) => {
    // Return widget URI in metadata
    return {
      contents: [
        {
          type: "text",
          text: "Dashboard created",
        },
      ],
      _meta: {
        "openai/outputTemplate": "dashboard-widget-123",
      },
    };
  },
});
```

### Connecting via Inspector

1. Start your MCP server
2. Open the inspector (local or hosted)
3. Connect to your server URL:
   * Local: `http://localhost:3000/mcp`
   * Remote: `https://your-server.com/mcp`
4. Server appears in Connected Servers list

### Authentication Setup

If your ChatGPT App requires authentication:

1. Configure OAuth in connection settings
2. Or add custom headers with API keys
3. Complete authentication flow
4. Inspector stores credentials securely

## Testing Tools

### Executing Tools

Test your ChatGPT App tools directly:

1. Navigate to **Tools** tab
2. Find your tool in the list
3. Click to select it
4. Enter test parameters
5. Click **Execute**
6. View results in real-time

### Viewing Tool Results

Tool results show:

* **Text Output**: Plain text responses
* **Structured Data**: JSON responses
* **Widget References**: Links to OpenAI Apps SDK widgets
* **Metadata**: Tool execution metadata

### Testing with Different Parameters

Test edge cases and variations:

1. Execute tool with different parameters
2. Save successful requests for replay
3. Test error handling
4. Verify parameter validation

<Note>
  Use saved requests to quickly test the same tool with different parameters.
</Note>

## Widget/Component Testing

### OpenAI Apps SDK Widget Support

The inspector fully supports OpenAI Apps SDK widgets:

* **Widget Rendering**: Interactive widget display
* **Dev Mode**: Hot reload during development
* **Display Modes**: Inline, Picture-in-Picture, Fullscreen
* **CSP Handling**: Content Security Policy support

### Rendering Widgets

When a tool returns a widget reference:

1. Tool executes successfully
2. Inspector detects widget URI in metadata
3. Widget automatically loads and renders
4. Interactive components become available

**Widget detection:**

* Looks for `openai/outputTemplate` in tool metadata
* Fetches widget resource from MCP server
* Renders in dedicated widget container

### Widget Lifecycle Testing

Widgets render **before** tool execution completes, allowing them to show loading states. The mcp-use Inspector properly emulates this lifecycle:

1. **Tool Called** → Widget iframe created immediately
2. **Widget Renders** → `isPending=true`, shows loading state
3. **Tool Completes** → Widget receives result via `window.openai` updates
4. **Widget Updates** → `isPending=false`, shows data

**Testing isPending Transitions:**

To verify your widget handles the pending state correctly:

1. Use a tool that takes >2 seconds to complete
2. Widget should show loading state immediately (not after tool completes)
3. Open browser DevTools console
4. Look for logs showing `isPending: true` then `isPending: false`
5. Verify widget transitions from loading UI to data display

**Example Console Output:**

```
[MyWidget] isPending: true, props: {}           // Immediate render
[MyWidget] isPending: false, props: {city: ...} // After tool completes
```

<Tip>
  Use the **Tools tab** to test widget lifecycle - execute a slow tool and watch the widget render in pending state before the tool completes.
</Tip>

See [Widget Lifecycle](/typescript/server/widget-components/usewidget#widget-lifecycle) for complete details on `isPending` behavior and implementation patterns.

### Dev Mode for Widgets

Dev mode is enabled automatically when running `mcp-use dev`. The framework detects dev mode from the resource metadata and uses Vite's dev server for hot module replacement:

1. Run `mcp-use dev` (or `npm run dev`)
2. Inspector detects dev mode automatically
3. Widget changes reload instantly via Vite HMR
4. Console logs visible in inspector

### Widget Display Modes

Widgets support three display modes:

**Inline:**

* Default mode
* Embedded in result panel
* Scrollable content

**Picture-in-Picture (PiP):**

* Floating window
* Stays visible while scrolling
* Resizable and draggable

**Fullscreen:**

* Full browser window
* Maximum visibility
* Exit with ESC or close button

**Switching modes:**

* Widget can request mode changes
* Inspector handles transitions
* State persists during session

### Widget CSP Handling

Content Security Policy is automatically handled:

* CSP metadata from tool results
* Applied to widget iframe
* Secure sandbox environment
* Script execution allowed

## Interactive Widget Features

### Tool Calls from Widgets

Widgets can call MCP tools:

1. Widget uses `window.openai.callTool()`
2. Inspector intercepts the call
3. Executes tool via MCP connection
4. Returns result to widget
5. Widget updates with response

**Example widget code:**

```javascript theme={null}
// In your widget
const result = await window.openai.callTool("get_data", {
  userId: "123",
});
```

### Follow-up Messages

Widgets can send follow-up messages to ChatGPT:

1. Widget calls `window.openai.sendFollowup()`
2. Inspector captures the message
3. Message appears in Chat tab
4. ChatGPT processes the follow-up
5. Conversation continues

**Use cases:**

* User interactions in widget
* Dynamic conversation flow
* Context-aware responses

### Widget State Management

Widget state is managed automatically:

* **Tool Input**: Parameters passed to tool
* **Tool Output**: Results from tool execution
* **Widget Data**: Resource content for widget
* **Display State**: Current display mode

### Console Logging from Widgets

View widget console output:

1. Widget console logs appear in inspector
2. Access via console panel in widget container
3. Filter by log level
4. Debug widget issues

**Console features:**

* Real-time log streaming
* Log level filtering
* Error highlighting
* Stack trace display

## Debugging Workflow

### Step-by-Step Process

1. **Connect to Server**
   * Add your MCP server in inspector
   * Verify connection status

2. **Test Tools**
   * Execute each tool independently
   * Verify parameters and responses
   * Check for errors

3. **Test Widgets**
   * Execute tools that return widgets
   * Verify widget rendering
   * Use Protocol Toggle to test both protocols (if dual-protocol)
   * Test widget interactions and tool calls
   * Test different display modes (inline, PiP, fullscreen)

4. **Test Device Compatibility**
   * Use Device Emulation (Desktop/Mobile/Tablet)
   * Test different locales and timezones
   * Verify CSP compliance (Widget-Declared mode)
   * Test touch and hover capabilities

5. **Test Integration**
   * Use Chat tab with LLM
   * Verify tool calls from AI
   * Check widget rendering in context

6. **Debug Issues**
   * Check console logs
   * Review tool results
   * Verify widget metadata
   * Test error scenarios

### Common Issues and Solutions

**Widget Not Rendering:**

* **MCP Apps**: Check `_meta.ui.resourceUri` in tool metadata (the `ui/resourceUri` flat format is deprecated)
* **ChatGPT**: Check `_meta["openai/outputTemplate"]` in tool metadata
* Verify widget resource exists at the specified path
* Check CSP settings match widget's network requirements
* Use Protocol Toggle to test if it works in the other protocol
* Review console for errors

**Tool Calls Failing:**

* Verify tool name matches
* Check parameter schema
* Review authentication
* Check server logs

**Widget Interactions Not Working:**

* **ChatGPT**: Verify `window.openai` API availability in widget
* **MCP Apps**: Check JSON-RPC postMessage communication
* Check widget iframe sandbox permissions
* Review console for errors
* Test in different display modes
* Try switching protocols with Protocol Toggle

### Testing Widget Interactions

1. **Render Widget**: Execute tool to load widget
2. **Interact**: Click buttons, fill forms in widget
3. **Monitor**: Watch console for tool calls
4. **Verify**: Check tool results and widget updates
5. **Iterate**: Fix issues and retest

### Verifying Tool Outputs

* **Preview Mode**: See formatted widget output
* **JSON Mode**: View raw tool response
* **Metadata**: Check widget references
* **Structure**: Verify data format

## Best Practices

### Development vs Production Widgets

**Development:**

* Use dev mode for hot reload
* Enable console logging and verbose debugging
* Use Permissive CSP mode for easier debugging
* Test in all display modes and device types
* Test both protocols (MCP Apps and ChatGPT)
* Verify error handling and fallbacks

**Production:**

* Disable dev mode
* Use Widget-Declared CSP mode to catch violations
* Minimize console output
* Test with production CSP restrictions
* Verify performance across device types
* Test internationalization with different locales

### Testing Widget Responsiveness

* Test in different display modes (inline, PiP, fullscreen)
* Use Device Emulation to test Desktop/Mobile/Tablet
* Verify mobile layouts with safe area insets
* Check resize behavior and auto-resize (MCP Apps)
* Test PiP mode transitions
* Test with different locales and timezones

### Handling Widget Errors

* Implement error boundaries
* Show user-friendly messages
* Log errors to console
* Provide fallback UI

### Performance Considerations

* Optimize widget loading
* Minimize initial bundle size
* Lazy load components
* Cache widget resources

## Next Steps

* [MCP Apps](/typescript/server/mcp-apps) - Build widgets with the standard MCP protocol
* [ChatGPT Apps SDK](/typescript/server/mcp-apps) - Build ChatGPT-specific widgets
* [Widget Components](/typescript/server/widget-components/usewidget) - React hooks and components
* [Inspector Overview](/inspector/index) - Inspector features and usage
* [Connection Settings](/inspector/connection-settings) - Advanced inspector configuration
