outputSchema.
The examples below use the v2 TypeScript API.
Check host support
The OpenAI platform support table describes the following expected support. Its web column refers to the ChatGPT Work browser and excludes classic ChatGPT. Availability depends on the host release.
ChatGPT supports
inline and fullscreen display modes.
Other MCP Apps hosts can support pip.
Test each extension in the host that your users use.
Attach evidence to the next model turn
UseuseModelContext() when the user selects evidence to send with the next model turn.
For example, add a book summary when the user selects Add to chat.
Keep cart actions separate from attachment actions.
Import the hook from mcp-use/react and render it in your view.
The hook activates native context delivery. It needs no viewConfig option.
The SDK checks host capabilities during initialization and checks each block before delivery.
Calls made during initialization wait for the host connection.
Add, remove, and clear selected evidence
This component adds a summary, lists selected attachments, and lets the user remove or clear them. It catches action errors and shows the shared delivery error.
Each method returns
Promise<{ status: "synced" | "superseded" }>.
"synced" confirms context delivery, not permanent selection or a visible composer chip.
"superseded" means a newer action replaced the operation before acknowledgment.
Catch rejected promises as shown above.
Use distinct, stable application keys, such as book:summary and book:cover.
Keys are shared across all hook instances in one view runtime.
Exact duplicate blocks under different keys reject.
Unmounting a hook consumer does not clear its selections.
Choose a content block
The hook supports four block types:text, image, resource_link, and resource.
Each type accepts a friendly top-level title.
The title is also available in attachments[i].block.title after host restoration.
These blocks can replace the text block in the example above.
For the image and thumbnail, put a cover at public/covers/book.png.
thumbnail with an MCP Icon shape.
The thumbnail path resolves like the Image component’s public path and stays a URL.
The SDK does not fetch thumbnail bytes. ChatGPT iOS currently ignores thumbnails.
Image src uses the same public path resolver as Image, including proxy and asset-host configuration.
Absolute URLs pass through the resolver.
The SDK fetches the image and converts it to native base64 content before changing the selection.
Source responses must contain PNG, JPEG, GIF, or WebP bytes with the matching Content-Type.
Browser CORS rules apply to remote URLs.
You can also supply base64 bytes directly:
src or data plus mimeType in an image block.
Do not combine these forms.
Returned image attachments contain data and mimeType, even when you supply src.
During image preparation, pending is true and the previous selection stays visible.
Other ready keys can sync independently.
A fetch failure, unsupported or missing Content-Type, or empty response rejects the action and preserves the previous attachment.
A later add or remove for the same key, or clearAttachments, cancels preparation and returns "superseded" for that action.
A single image is limited to 10 MiB of decoded bytes, whether supplied through src or base64 data. Source downloads stop when they exceed that limit, including when the response omits its size. An oversized image rejects without replacing the current selection. This is an SDK memory budget, not a host upload-size guarantee.
A late fetch response cannot restore the canceled attachment.
Named types include ModelContextText, ModelContextImage, ModelContextResourceLink, and ModelContextResource.
All are exported from mcp-use/react.
A resource link uses its native title field, which takes precedence over unrelated title metadata.
Text and image titles use OpenAI metadata.
Embedded-resource titles use SDK metadata; host display of these titles can vary.
Raw metadata and annotations are preserved. Conflicting shorthand and wire fields reject.
Audio blocks are not supported.
Respect host removals
OpenAI host context can restore surviving attachments when the view initializes. Restored keys start with@restored: and belong to that runtime.
Use these returned keys to remove restored entries.
Do not use the reserved prefix for new keys or infer original keys from titles, resource URIs, or list positions.
Confirmed host removals update attachments where the SDK can identify the removed blocks.
Do not add the same evidence again from a render effect or a cart update.
Keep removable evidence out of duplicate useViewState values or ModelContext descriptions.
The optional audience field accepts "user" and "assistant" and maps to MCP annotations.
Audience controls presentation. All blocks still reach the model.
Assistant-only content can be restored as background instead of a visible attachment.
Removal affects current context; it does not erase previous conversation turns or backend data.
Combine context hooks
RenderuseModelContext in the initial view tree when you also use useViewState or ModelContext. Native delivery activates when the hook commits, before state and description effects; discarded renders do not change delivery.
The runtime sends attachments, structured view state, and descriptions through one native writer.
State and descriptions keep their model-visible JSON projection.
clearAttachments() preserves those separate contributions.
See Model context to choose a channel for each value.
On OpenAI hosts, native host context restores model-visible state after the hook activates.
Private widget state keeps its separate persistence channel.
Views that do not render useModelContext keep their existing widget-state transport.
Activation rejects if model-visible widget modelContent or imageIds already exists, or a widget-state write was dispatched.
The SDK does not clear that persisted context for you.
Use a view without the conflicting state; reopening alone can retain the conflict.
A prior native write must finish, and OpenAI host readback must match the acknowledged context before activation.
Handle delivery failures
A definite host rejection retains the requested selection and sets the sharederror.
The next valid add, remove, or clearAttachments makes one fresh delivery attempt with the current selection.
Invalid input and background updates do not trigger another attempt.
The public hook has no retry method or automatic retry loop.
A successful model-context response does not require an OpenAI updateId in its metadata.
When present and valid, the SDK uses that ID to correlate a whole context revision.
The ID is not an attachment key or a compare-and-swap token.
Uncertain transport outcomes or ambiguous host ordering stop further publication and reject subsequent mutations.
A request that was already sent cannot be recalled.
Wait for outstanding remote writes to finish before you open a fresh view runtime.
Another notification or a hook remount does not establish that writes have finished.
Verify attachment behavior
- Open the view in the host that your users use.
- Add a summary and a cover under different keys. Check that both remain selected.
- Remove one attachment in the view. Check that the other remains.
- Clear the attachments, then add one again.
- Where the host supports composer removal, remove an attachment there and check the view’s list.
- Change the cart or filter state. Check that removed evidence does not return.
Add global and thread entrypoints
Declareglobal to add an app launcher in navigation.
Declare thread to add a content tab in a conversation.
Both launchers send {} to the tool.
Each conversation has its own thread app instance.
views/library/view.tsx for this tool.
Render the initial tool result in the view.
The initial result already contains the launch data.
Global and thread tools must accept empty arguments.
Use optional fields or defaults for additional inputs.
Keep tools with required query inputs separate from launchers.
The MCP SDK validates tool calls against the input schema, including custom refinements.
Entrypoints open fullscreen in supporting hosts.
Entrypoint calls ignore the tool visibility hint.
Use a clear tool title to name the launcher.
Set entrypoint tool icons
Seticons on the tool definition, alongside name and title, as shown above.
Put library.svg in public/; the SDK emits its absolute asset URL on tools/list.
Absolute HTTP(S) and image data URLs also work.
Follow the OpenAI icon guidelines.
Use a monochrome SVG with a transparent background, currentColor, and a 20×20 viewport.
Use 1.33px strokes for stroke icons.
For example, save this book icon as public/library.svg:
view.entrypoints declares launch locations in _meta["openai/ui"].entrypoints.
The separate top-level icons field supplies the tool’s artwork.
Server constructor icons supplies server branding and remains the fallback when a tool has no icon.
Hosts prefer tool icons, then the local server icon or hosted app logo, then a generic icon.
Call tools/list to check the tool’s icons and fetch each emitted src.
Test the SVG in light and dark host themes.
Add a file entrypoint
Declare the file extensions that your view supports. Each extension must start with a dot. The host sendsfile.name and file.resourceUri to the tool.
views/csv/view.tsx for this tool.
The host also sends the file arguments to the view through ui/notifications/tool-input.
Treat file.resourceUri as an opaque host handle.
Use the host resource APIs to read the file.
The HTML view resource URI identifies the view, not the opened file.
Use a non-empty extension list.
Require both string fields when file is present.
Additional input fields must be optional or have defaults.
Combine entrypoint types
One tool can declare all three entrypoint types. For a combined tool, make the outerfile field optional.
Keep file.name and file.resourceUri required when file is present.
Replace the CSV tool registration above with this registration:
Check entrypoint metadata
Calltools/list to inspect the tool descriptor.
The CSV tool advertises these metadata fields:
_meta object.
Typed view.entrypoints takes precedence over raw _meta["openai/ui"].entrypoints.
If you omit the typed field, existing raw entrypoint declarations pass through.
Read incoming deep links
UseuseDeepLink() to read a route that the host sends to a global entrypoint.
The hook uses the view’s existing MCP Apps connection.
It reads the initial host context and updates when another deep link arrives.
{ url: string | undefined }.
An undefined URL means the host has supplied no link.
Hosts without deep-link support also return an undefined URL.
The hook reads hostContext["openai/deepLink"].url.
It converts older path and query payloads to the same URL form.
It removes its subscription when the component unmounts.
Local route changes do not update the host URL.
Validate incoming routes before you load data.
Authorize data access on the server.
Construct a deep link
Encode the plugin ID, tool name, and complete app-relative URL. The app-relative URL must start with/ and must not contain a fragment.
codex://plugins/<plugin-id>/app/<tool-name>?path=<encoded-url>.
iOS links use the chatgpt scheme.
Custom marketplace links include @<marketplace> after the plugin ID.
See the deep-link specification for the native formats.
Add structured settings and actions
Register one settings pair before you start the server.server.settings() creates read and update tools.
The host discovers these tools through the openai/settings capability.
This example keeps one shared settings object in memory.
The object resets when the process restarts.
RequestContext argument.
Scope stored values to the correct account.
Persist partial updates atomically before you return all effective values.
The framework does not provide storage, account scoping, or concurrency control.
The read callback receives ctx.
The update callback receives set and ctx.
The context includes ctx.request, ctx.signal, and ctx.client.
OAuth servers also expose ctx.auth.
Define fields and layout
Register at least one field. Fields support boolean, string, string enum, number, and integer schemas. Use a Standard Schema library with JSON Schema conversion, such as Zod. Give each field a non-blanktitle.
You can also supply a description.
Primitive schema constraints still apply.
Use primitive schemas without value transforms.
Return an effective value for every field from both callbacks.
This rule also applies to optional fields and fields with schema defaults.
The wire schema omits default annotations.
Apply application defaults in the read callback.
Use kind: "property" for a field control.
Use kind: "tool" for a same-server action button.
Action tools must accept {} and exist before the server starts.
The MCP SDK validates action arguments when the host calls the tool.
The host places fields absent from layout in an Other settings group.
A regular tool action returns text feedback in settings.
A view-bound tool action opens an MCP App modal.
See the structured settings contract for host behavior.
Check settings tools
The default tool names aresettings.read and settings.update.
Set readTool and updateTool to use distinct, unused names.
The registration advertises the pair during modern discovery and legacy initialization.
Updates preserve fields absent from
set through your callback.
Empty patches, unknown keys, invalid values, and incomplete callback results produce tool errors.
Set resource display modes
ExportviewConfig directly from views/<name>/view.tsx to declare supported modes and an initial preference.
Build and development tools read the declaration before the view starts.
displayModes must include inline and contain no duplicates.
The default list is ["inline", "fullscreen", "pip"].
autoResize defaults to true.
With autoResize: false, use useSendSizeChanged() to report the view size.
preferredDisplayMode accepts inline or fullscreen.
The preference must appear in displayModes.
The preference has no default, and the host can ignore it.
Entrypoint launch behavior remains separate from this preference for ordinary tool calls.
The HTML resource content advertises the ChatGPT subset in _meta["openai/ui"]:
pip for other hosts.
Keep the declaration static
Use object literals, array literals, localconst references, and spreads.
TypeScript as and satisfies wrappers also work.
Imported values and runtime expressions cause an extraction error when tooling detects the named configuration.
Keep the configuration and its shared object or array references immutable.
Tooling rejects writes to those references and calls that could mutate them during module initialization, including calls through local helpers.
Components can read the configuration through calls such as useState and useMemo after initialization.
Request a mode at runtime
UseuseDisplayMode() for mode changes after initialization.
Render from the reported displayMode.
A completed request does not guarantee that the host changed the mode.
availableDisplayModes contains the intersection of the view modes and host modes.
If the host supplies no mode list, only inline is available.
Requests for other modes reject before the runtime sends them.
Verify the integration
- Call
tools/listto check entrypoint metadata and settings tool names. - Call an entrypoint tool with its expected empty or file arguments.
- Call
settings.read, then update one field and check the complete result. - Run
npx mcp-use buildto check staticviewConfigextraction. - Read the HTML view resource to check its display metadata.
- Open the app in a supporting host to check launchers, routes, settings actions, and presentation.