@modelcontextprotocol/ext-apps).
Overview
When a tool advertises a UI resource URI,@mcp-ts/sdk can render it via useMcpApps and McpAppRenderer. For ui:// and mcp-app:// resources (and any path where HTML is injected), you must provide a sandbox configuration pointing at a sandbox proxy page you serve from your app (see Sandbox proxy). Tool calls from the guest can be forwarded to your SSEClient automatically, or intercepted with onCallTool / onReadResource and related callbacks.
Key features
- Sandbox proxy — Injected HTML loads through a dedicated proxy page; CSP can be passed via query string and applied inside the guest document.
- Resource preloading —
useMcppreloads UI resources when tools are discovered (SSEClient.preloadToolUiResources), so apps open faster. - Host ↔ guest sync — Tool input, final result,
toolInputPartial,toolCancelled, mergedhostContext(includingdisplayModefor inline/fullscreen). - Mediation hooks — Optional
onCallTool,onReadResource,onFallbackRequest,onMessage,onOpenLink, etc., instead of automatic forwarding. - Fullscreen — Guest
requestDisplayModecan drive the browser Fullscreen API (handled insideMcpAppRenderer).
Sandbox proxy
Hosts must ship a static MCP Apps sandbox proxy page (for example copyexamples/agents/public/sandbox.html) that follows @modelcontextprotocol/ext-apps:
- After load, posts a JSON-RPC notification to the parent with method
ui/notifications/sandbox-proxy-ready(same string asSANDBOX_PROXY_READY_METHODfrom@modelcontextprotocol/ext-apps, also re-exported from@mcp-ts/sdk/client). - Listens for
ui/notifications/sandbox-resource-readyfrom the parent (the host sends this viaAppBridge.sendSandboxResourceReadyafter the bridge connects), then writesparams.htmlinto an inner iframe. Optional CSP:?csp=query JSON and/or structuredparams.cspwhen it is a directive map (script-src, etc.).
sandbox.url at that page (no special query string required; csp is appended automatically from sandbox.csp when you pass it in React):
DEFAULT_MCP_APP_CSP (for example narrow or widen connect-src) per deployment.
Quick start
1. MCP connection
Same as the rest of the React client:useMcp, connect to your server, expose mcpClient. See the React guide.
2. Render MCP Apps on tool calls
Passsandbox on every McpAppRenderer that loads server UI resources (HTML injection path):
McpAppRenderer resolves the UI URI from the tool name using mcpClient.connections (see Tool metadata). You can override the URI or pass raw HTML with toolResourceUri / html.
3. Optional: streaming and cancellation
If your agent streams tool arguments or can cancel a run, pass through:4. Optional: host context and mediation
onCallTool / onReadResource are omitted, the host forwards to mcpClient.sseClient using the session inferred from the tool metadata.
Tool metadata & Proxy Unwrapping
getMcpAppMetadata and McpAppRenderer look up UI resources using the first match on the tool name.
Crucially, they both natively support unwrapping ToolRouter proxies (e.g., mcp_execute_tool). If a proxy wrapper is encountered, it seamlessly inspects the input arguments, resolves the true underlying tool name, strips any prefixes like tool_github_..., and returns the underlying UI.
A resource URI may come from:
tool.mcpApp.resourceUritool._meta?.ui?.resourceUritool._meta?.['ui/resourceUri']
Preloading
When the client receives tool discovery events,useMcp calls SSEClient.preloadToolUiResources(sessionId, tools) so ui:// / mcp-app:// HTML is often already cached before the user opens a tool.
For advanced use, AppHost also exposes preload(tools) (see API reference).
API summary
getMcpAppMetadata(mcpClient, toolName, input?)
{ toolName, resourceUri, sessionId } when the tool has a UI URI. Use for conditionally checking if an Interactive UI exists for a tool before rendering. If toolName is a proxy like mcp_execute_tool, supplying input will allow it to unwrap the proxy and find the target app accurately.
McpAppRendererProps
| Prop | Type | Description |
|---|---|---|
client | McpClient? | Your active MCP client object. |
name | string | Tool name (matched against connection tools). Passes natively through proxy unwrapper. |
input | Record<string, unknown>? | Tool arguments; sent with sendToolInput after launch. |
result | unknown? | Final tool result; sent when status === 'complete'. |
status | 'executing' | 'inProgress' | 'complete' | 'idle' | Optional; default 'idle'. |
toolResourceUri | string? | Override UI resource URI from metadata. |
html | string? | Raw HTML instead of fetching toolResourceUri. |
sandbox | SandboxConfig? | Required for injected HTML: { url, csp?, permissions? }. |
hostContext | Record<string, unknown>? | Merged with defaults; displayMode is set by the renderer. |
toolInputPartial | any? | Streamed partial input (sendToolInputPartial). |
toolCancelled | boolean? | When truthy, sends tool cancelled to the guest. |
onCallTool | (params) => Promise<unknown>? | Override automatic callTool forwarding. |
onReadResource | (uri: string) => Promise<{ contents: … }>? | Override resource reads. |
onFallbackRequest | (request: any) => Promise<any>? | AppBridge fallback JSON-RPC. |
onLoggingMessage | (params) => void? | Guest logging notifications. |
onSizeChanged | (params) => void? | Guest size changes (iframe height adjusted automatically). |
onError | (error: Error) => void? | Bridge / launch errors. |
className | string? | Container class names. |
loader | React.ReactNode? | Shown until the app finishes launching. |
Next steps
- React guide — Connection setup and
useMcp. - API reference — Detailed technical documentation.
- MCP App host comparison —
mcp-tsvs@mcp-ui/client.