Looking for the MCP Standard? This page covers the ChatGPT Apps SDK protocol specifically. For the official MCP Apps protocol (recommended), see MCP Apps.Cross-Compatibility: mcp-use uniquely allows you to write widgets once using
type: "mcpApps" that work with both ChatGPT (Apps SDK) and MCP Apps clients automatically.Protocol Choice: Apps SDK vs MCP Apps
Before starting, understand your options:| Option | Protocol | Compatibility | Use When |
|---|---|---|---|
type: "mcpApps" | Both MCP Apps + ChatGPT | ✅ ChatGPT ✅ MCP Apps clients | You want maximum compatibility |
type: "appsSdk" | ChatGPT Apps SDK only | ✅ ChatGPT ❌ MCP Apps clients | ChatGPT-only features needed |
Recommended: Use
type: "mcpApps" for new projects to support both ChatGPT and MCP Apps clients. Your widget code remains identical—only the registration differs.See MCP Apps for details on the dual-protocol approach.Quick Start
Start with the Apps SDK template which includes automatic widget registration:Folder Structure
Widgets can be organized in two ways: single-file widgets or folder-based widgets. Choose the organization style that best fits your widget’s complexity. For simple widgets, a single file is sufficient:.tsx file in the resources/ folder becomes a widget. The widget name is derived from the filename (without extension).
For complex widgets with multiple components, hooks, or utilities, organize them in folders:
- The folder name becomes the widget name (e.g.,
product-search-result) - The entry point must be named
widget.tsx(notindex.tsx) - You can organize sub-components, hooks, utilities, and types within the folder
- The
widget.tsxfile should exportwidgetMetadataand the default component
Dual-Protocol Support
mcp-use uniquely supports both ChatGPT Apps SDK and MCP Apps protocols. Choose your registration type based on compatibility needs:Using type: "mcpApps" (Recommended)
Works with both ChatGPT and MCP Apps clients:
Using type: "appsSdk" (ChatGPT only)
For ChatGPT-exclusive features or legacy compatibility:
Widget Metadata
Contains the information that the MCP resource (and the tool that exposes it) will use when are automatically built by mcp-use.The
props field expects a Zod schema that defines the shape of structuredContent returned by the tool. This schema is used to generate the tool’s input validation and to type-check the data in your widget via useWidget<T>().openai/widgetDescription: Generated from your widget’s descriptionopenai/widgetDomain: Defaults to"https://chatgpt.com"(required for app submission)openai/widgetAccessible: Allows component-initiated tool callsopenai/widgetCSP: Content Security Policy with default trusted domainsopenai/toolInvocation/invoking: Loading state text — set viametadata.invoking(default:"Loading MyWidget...")openai/toolInvocation/invoked: Ready state text — set viametadata.invoked(default:"MyWidget ready")
metadata.invoking/metadata.invoked in your widget registration, or use appsSdkMetadata for raw key access:
You can customize any of these in your widget’s appsSdkMetadata export:
-
Via custom tool (recommended): Define a tool that calls
widget()in its handler. This gives you full control over tool naming, description, parameters, and business logic: -
Auto-registered: Widget is auto-registered as a tool using its metadata description and props schema as the tool’s parameters. Simpler, but less control:
*.oaistatic.com, etc.) are also added. For MCP Apps clients, only the domains you declare are used. See Content Security Policy for full CSP documentation.
Components & Hooks
mcp-use provides a comprehensive set of React components and hooks for building OpenAI Apps SDK widgets. These components handle common setup tasks like theme management, error handling, routing, and debugging.Components
| Component | Description | Link |
|---|---|---|
| McpUseProvider | Unified provider that combines all common React setup (StrictMode, ThemeProvider, WidgetControls, ErrorBoundary) | McpUseProvider → |
| WidgetControls | Debug button and view controls (fullscreen/pip) with customizable positioning | WidgetControls → |
| ErrorBoundary | Error boundary component for graceful error handling in widgets | ErrorBoundary → |
| Image | Image component that handles both data URLs and public file paths | Image → |
| ThemeProvider | Theme provider for consistent theme management across widgets | ThemeProvider → |
Hooks
| Hook | Description | Link |
|---|---|---|
| useWidget | Main hook providing type-safe access to all widget capabilities (props, state, theme, actions) | useWidget → |
Static Assets
Widgets can use static assets from apublic/ folder. The framework automatically serves these assets and copies them during build.
Folder Structure
/mcp-use/public/. In production, they’re copied to dist/public/ during build.
Using the Image Component:
window.__mcpPublicUrl: Base URL for public assets (e.g.,http://localhost:3000/mcp-use/public)window.__getFile: Helper function to get file URLs
Patterns
Accessing Widget Data
TheuseWidget hook provides access to both the server-computed props and the original tool arguments:
props— Data computed by the server, sent viastructuredContent(the LLM does not see this)toolInput— The original tool call arguments from the model (e.g.{ query: "mango" })
Widget Lifecycle and Loading States
Widgets render before the tool execution completes. This allows you to show loading states while data is being fetched. TheisPending flag indicates when the tool is still executing:
isPending = true: Widget just mounted,propsis empty{}isPending = false: Tool completed,propscontains actual data
partialToolInput and isStreaming from useWidget for live previews while the LLM generates the tool call. See Streaming tool arguments in the useWidget docs.
Pattern 1: Early return with loading state
Widget State
Widgets can maintain state across interactions. State is persisted by the host, for example in ChatGPT:Calling Other Tools
Widgets can call MCP tools to perform actions and fetch data. Use the dedicateduseCallTool hook for the best developer experience with automatic type inference and state management.
Automatic Type Inference: When you run
mcp-use dev, types are automatically generated from your tool definitions. This gives you autocomplete for tool names, inputs, and outputs.Using useCallTool (Recommended)
TheuseCallTool hook provides TanStack Query-like state management with full TypeScript support:
- ✅ Type-safe: Autocomplete for tool names, inputs, and outputs
- ✅ State management: Built-in loading, success, and error states
- ✅ Callbacks: Optional
onSuccess,onError, andonSettledhandlers - ✅ Async/await: Use
callToolAsyncfor promise-based flows
Type Generation
Types are automatically generated during development:.mcp-use/tool-registry.d.ts with type definitions for all your tools, enabling full IntelliSense in your widgets.
Using useWidget.callTool (Alternative)
You can also call tools directly fromuseWidget():
While
useWidget().callTool works, useCallTool is recommended for better type safety and state management.Display Mode Control
Request different display modes (inline, pip, or fullscreen):'inline'- Default embedded view in conversation'pip'- Picture-in-Picture floating window'fullscreen'- Full browser window (on mobile, PiP coerces to fullscreen)
<WidgetControls /> to automatically add controls to your widget.
Downloads & External Links
Widgets run inside sandboxed iframes, which block two common patterns:Blob+URL.createObjectURL()— object URLs cannot cross the iframe boundaryelement.requestFullscreen()— the browser Fullscreen API is blocked in sandboxed iframes (userequestDisplayMode("fullscreen")instead)
openExternal from useWidget() for all outbound navigation. For file downloads, serve the file from the MCP server and open the URL externally:
Custom Tools with Widgets
You can create custom tools that return widgets instead of relying solely on automatic widget registration. This is useful when you need to:- Fetch data before displaying the widget
- Use different tool parameters than widget props
- Have multiple tools use the same widget
- Add custom logic or validation
Using the widget() Helper
Thewidget() helper returns runtime data for a widget. You must combine it with the widget config on the tool definition to set up all registration-time metadata.
Important: The widget configuration is split between two places:
- Tool definition (
widget: { name, invoking, ... }) - Registration-time metadata - Helper return (
widget({ props, output, ... })) - Runtime data
widget: { name, invoking, invoked, ... }on tool definition - Configures all widget metadata at registration timewidget({ props, output, metadata, message })helper - Returns runtime data only:props- Widget rendering data sent asstructuredContent(LLM does not see). Widget reads viauseWidget().props.output- Optional response helper (text(), object(), etc.) that the model sees incontentmetadata- Optional extra data in_meta. Widget reads viauseWidget().metadata.message- Optional text message override forcontent
- Widget must exist - The widget name must match a
.tsxfile or folder inresources/ exposeAsTooldefaults tofalse- Widgets are resources by default. This is the expected setup when returning widgets from custom tools. To opt into auto-registration instead, setexposeAsTool: truein the widget metadata:
widget config option.
Configuration
Base URL for Production
Set theMCP_URL environment variable or pass baseUrl:
- Widget URLs use the correct domain
- Apps SDK CSP automatically includes your server
baseUrl option to the MCPServer constructor or by setting the MCP_URL environment variable.
Environment Variables
MCP_URL: Base URL for widget assets and public files. Used by Vite’sbaseoption during build. Also used by the server to configure CSP.MCP_SERVER_URL: (Optional) MCP server URL for API calls. When set, URLs are injected at build time for static deployments where widgets are served from storage rather than the MCP server.CSP_URLS: (Optional) Additional domains to whitelist in widget Content Security Policy. Supports comma-separated list. For Supabase, use the base project URL without path (e.g.,https://nnpumlykjksvxivhywwo.supabase.co). Required for static deployments where widget assets are served from different domains. See Content Security Policy for full CSP documentation.
Static Deployments: Set
MCP_URL (for assets), MCP_SERVER_URL (for API calls), and CSP_URLS (for CSP whitelisting) when deploying to platforms like Supabase where widgets are served from static storage.Alternative CSP Configuration: Instead of using the global CSP_URLS environment variable, you can configure CSP per-widget in your widget’s appsSdkMetadata['openai/widgetCSP'] (see Apps SDK Metadata or Content Security Policy).Testing
Using the Inspector
The mcp-use Inspector provides full support for testing widgets during development:- Start your server:
npm run dev - Open Inspector:
http://localhost:3000/inspector - Test widgets: Execute tools to see widgets render
- Verify lifecycle: Widgets render immediately with
isPending=true, then update when tool completes - Debug interactions: Use console logs and inspector features
- Test API methods: Verify
callTool,setState, etc. work correctly
Testing in ChatGPT
You need to enable the Developer Mode in ChatGPT to test widgets.- Enable developer mode: Go to Settings → Connectors → Advanced → Developer mode.
- Import MCPs: In the Connectors tab, add your remote MCP server. It will appear in the composer’s “Developer Mode” tool later during conversations.
-
Use connectors in conversations: Choose Developer mode from the Plus menu and select connectors. You may need to explore different prompting techniques to call the correct tools. For example:
- Be explicit: “Use the “Acme CRM” connector’s “update_record” tool to …”. When needed, include the server label and tool name.
- Disallow alternatives to avoid ambiguity: “Do not use built-in browsing or other tools; only use the Acme CRM connector.”
- Disambiguate similar tools: “Prefer Calendar.create_event for meetings; do not use Reminders.create_task for scheduling.”
- Specify input shape and sequencing: “First call Repo.read_file with { path: ”…” }. Then call Repo.write_file with the modified content. Do not call other tools.”
- If multiple connectors overlap, state preferences up front (e.g., “Use CompanyDB for authoritative data; use other sources only if CompanyDB returns no results”).
- Developer mode does not require search/fetch tools. Any tools your connector exposes (including write actions) are available, subject to confirmation settings.
- See more guidance in Using tools and Prompting.
- Improve tool selection with better tool descriptions: In your MCP server, write action-oriented tool names and descriptions that include “Use this when…” guidance, note disallowed/edge cases, and add parameter descriptions (and enums) to help the model choose the right tool among similar ones and avoid built-in tools when inappropriate.
Next Steps
- MCP Apps - Use the standard MCP protocol with dual-protocol support
- Content Security Policy - CSP configuration for widgets
- Creating MCP Apps Server - Complete server setup guide
- Debugging Widgets - Test widgets with the Inspector
- Widget Components - React components and hooks
- MCP UI Resources - Alternative approach for simpler UIs