Replace {@includeCode} with synced code fences

Migrate JSDoc and markdown documentation from `{@includeCode ./file#region}`
tags to inline code fences with `source="./file#region"` attributes.
Code examples are now visible directly in source files while remaining
type-checked via their `.examples.ts` source files.

Add `scripts/sync-snippets.ts` to extract code from `//#region` blocks
and sync it into labeled fences. Run with `npm run sync:snippets`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-22 17:30:43 -06:00
commit 58019a48ce
No known key found for this signature in database
GPG key ID: 0DA40AFD21043691
10 changed files with 1776 additions and 96 deletions

View file

@ -215,7 +215,36 @@ type RequestHandlerExtra = Parameters<
* 5. **Teardown**: Call {@link teardownResource `teardownResource`} before unmounting iframe
*
* @example Basic usage
* {@includeCode ./app-bridge.examples.ts#AppBridge_basicUsage}
* ```ts source="./app-bridge.examples.ts#AppBridge_basicUsage"
* // Create MCP client for the server
* const client = new Client({
* name: "MyHost",
* version: "1.0.0",
* });
* await client.connect(serverTransport);
*
* // Create bridge for the View
* const bridge = new AppBridge(
* client,
* { name: "MyHost", version: "1.0.0" },
* { openLinks: {}, serverTools: {}, logging: {} },
* );
*
* // Set up iframe and connect
* const iframe = document.getElementById("app") as HTMLIFrameElement;
* const transport = new PostMessageTransport(
* iframe.contentWindow!,
* iframe.contentWindow!,
* );
*
* bridge.oninitialized = () => {
* console.log("View initialized");
* // Now safe to send tool input
* bridge.sendToolInput({ arguments: { location: "NYC" } });
* };
*
* await bridge.connect(transport);
* ```
*/
export class AppBridge extends Protocol<
AppRequest,
@ -238,10 +267,26 @@ export class AppBridge extends Protocol<
* @param options - Configuration options (inherited from Protocol)
*
* @example With MCP client (automatic forwarding)
* {@includeCode ./app-bridge.examples.ts#AppBridge_constructor_withMcpClient}
* ```ts source="./app-bridge.examples.ts#AppBridge_constructor_withMcpClient"
* const bridge = new AppBridge(
* mcpClient,
* { name: "MyHost", version: "1.0.0" },
* { openLinks: {}, serverTools: {}, logging: {} },
* );
* ```
*
* @example Without MCP client (manual handlers)
* {@includeCode ./app-bridge.examples.ts#AppBridge_constructor_withoutMcpClient}
* ```ts source="./app-bridge.examples.ts#AppBridge_constructor_withoutMcpClient"
* const bridge = new AppBridge(
* null,
* { name: "MyHost", version: "1.0.0" },
* { openLinks: {}, serverTools: {}, logging: {} },
* );
* bridge.oncalltool = async (params, extra) => {
* // Handle tool calls manually
* return { content: [] };
* };
* ```
*/
constructor(
private _client: Client | null,
@ -280,7 +325,14 @@ export class AppBridge extends Protocol<
* @returns view capabilities, or `undefined` if not yet initialized
*
* @example Check view capabilities after initialization
* {@includeCode ./app-bridge.examples.ts#AppBridge_getAppCapabilities_checkAfterInit}
* ```ts source="./app-bridge.examples.ts#AppBridge_getAppCapabilities_checkAfterInit"
* bridge.oninitialized = () => {
* const caps = bridge.getAppCapabilities();
* if (caps?.tools) {
* console.log("View provides tools");
* }
* };
* ```
*
* @see {@link McpUiAppCapabilities `McpUiAppCapabilities`} for the capabilities structure
*/
@ -297,7 +349,14 @@ export class AppBridge extends Protocol<
* @returns view implementation info, or `undefined` if not yet initialized
*
* @example Log view information after initialization
* {@includeCode ./app-bridge.examples.ts#AppBridge_getAppVersion_logAfterInit}
* ```ts source="./app-bridge.examples.ts#AppBridge_getAppVersion_logAfterInit"
* bridge.oninitialized = () => {
* const appInfo = bridge.getAppVersion();
* if (appInfo) {
* console.log(`View: ${appInfo.name} v${appInfo.version}`);
* }
* };
* ```
*/
getAppVersion(): Implementation | undefined {
return this._appInfo;
@ -317,7 +376,11 @@ export class AppBridge extends Protocol<
* @param extra - Request metadata (abort signal, session info)
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onping_handleRequest}
* ```ts source="./app-bridge.examples.ts#AppBridge_onping_handleRequest"
* bridge.onping = (params, extra) => {
* console.log("Received ping from view");
* };
* ```
*/
onping?: (params: PingRequest["params"], extra: RequestHandlerExtra) => void;
@ -332,7 +395,16 @@ export class AppBridge extends Protocol<
* host container dimension changes, use {@link setHostContext `setHostContext`}.
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onsizechange_handleResize}
* ```ts source="./app-bridge.examples.ts#AppBridge_onsizechange_handleResize"
* bridge.onsizechange = ({ width, height }) => {
* if (width != null) {
* iframe.style.width = `${width}px`;
* }
* if (height != null) {
* iframe.style.height = `${height}px`;
* }
* };
* ```
*
* @see {@link McpUiSizeChangedNotification `McpUiSizeChangedNotification`} for the notification type
* @see {@link app!App.sendSizeChanged `App.sendSizeChanged`} - the View method that sends these notifications
@ -390,7 +462,12 @@ export class AppBridge extends Protocol<
* initialization handshake and is ready to receive tool input and other data.
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_oninitialized_sendToolInput}
* ```ts source="./app-bridge.examples.ts#AppBridge_oninitialized_sendToolInput"
* bridge.oninitialized = () => {
* console.log("View ready");
* bridge.sendToolInput({ arguments: toolArgs });
* };
* ```
*
* @see {@link McpUiInitializedNotification `McpUiInitializedNotification`} for the notification type
* @see {@link sendToolInput `sendToolInput`} for sending tool arguments to the View
@ -422,7 +499,17 @@ export class AppBridge extends Protocol<
* - Returns: `Promise<McpUiMessageResult>` with optional `isError` flag
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onmessage_logMessage}
* ```ts source="./app-bridge.examples.ts#AppBridge_onmessage_logMessage"
* bridge.onmessage = async ({ role, content }, extra) => {
* try {
* await chatManager.addMessage({ role, content, source: "app" });
* return {}; // Success
* } catch (error) {
* console.error("Failed to add message:", error);
* return { isError: true };
* }
* };
* ```
*
* @see {@link McpUiMessageRequest `McpUiMessageRequest`} for the request type
* @see {@link McpUiMessageResult `McpUiMessageResult`} for the result type
@ -460,7 +547,26 @@ export class AppBridge extends Protocol<
* - Returns: `Promise<McpUiOpenLinkResult>` with optional `isError` flag
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onopenlink_handleRequest}
* ```ts source="./app-bridge.examples.ts#AppBridge_onopenlink_handleRequest"
* bridge.onopenlink = async ({ url }, extra) => {
* if (!isAllowedDomain(url)) {
* console.warn("Blocked external link:", url);
* return { isError: true };
* }
*
* const confirmed = await showDialog({
* message: `Open external link?\n${url}`,
* buttons: ["Open", "Cancel"],
* });
*
* if (confirmed) {
* window.open(url, "_blank", "noopener,noreferrer");
* return {};
* }
*
* return { isError: true };
* };
* ```
*
* @see {@link McpUiOpenLinkRequest `McpUiOpenLinkRequest`} for the request type
* @see {@link McpUiOpenLinkResult `McpUiOpenLinkResult`} for the result type
@ -499,7 +605,14 @@ export class AppBridge extends Protocol<
* - Returns: `Promise<McpUiRequestDisplayModeResult>` with the actual mode set
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onrequestdisplaymode_handleRequest}
* ```ts source="./app-bridge.examples.ts#AppBridge_onrequestdisplaymode_handleRequest"
* bridge.onrequestdisplaymode = async ({ mode }, extra) => {
* if (availableDisplayModes.includes(mode)) {
* currentDisplayMode = mode;
* }
* return { mode: currentDisplayMode };
* };
* ```
*
* @see {@link McpUiRequestDisplayModeRequest `McpUiRequestDisplayModeRequest`} for the request type
* @see {@link McpUiRequestDisplayModeResult `McpUiRequestDisplayModeResult`} for the result type
@ -535,7 +648,14 @@ export class AppBridge extends Protocol<
* - `params.data` - Log message and optional structured data
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onloggingmessage_handleLog}
* ```ts source="./app-bridge.examples.ts#AppBridge_onloggingmessage_handleLog"
* bridge.onloggingmessage = ({ level, logger, data }) => {
* console[level === "error" ? "error" : "log"](
* `[${logger ?? "View"}] ${level.toUpperCase()}:`,
* data,
* );
* };
* ```
*/
set onloggingmessage(
callback: (params: LoggingMessageNotification["params"]) => void,
@ -561,7 +681,16 @@ export class AppBridge extends Protocol<
* update received.
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onupdatemodelcontext_storeContext}
* ```ts source="./app-bridge.examples.ts#AppBridge_onupdatemodelcontext_storeContext"
* bridge.onupdatemodelcontext = async (
* { content, structuredContent },
* extra,
* ) => {
* // Store the context snapshot for inclusion in the next model request
* modelContextManager.update({ content, structuredContent });
* return {};
* };
* ```
*
* @see {@link McpUiUpdateModelContextRequest `McpUiUpdateModelContextRequest`} for the request type
*/
@ -592,7 +721,15 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_oncalltool_forwardToServer}
* ```ts source="./app-bridge.examples.ts#AppBridge_oncalltool_forwardToServer"
* bridge.oncalltool = async (params, extra) => {
* return mcpClient.request(
* { method: "tools/call", params },
* CallToolResultSchema,
* { signal: extra.signal },
* );
* };
* ```
*
* @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type
* @see `CallToolResult` from @modelcontextprotocol/sdk for the result type
@ -647,7 +784,15 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onlistresources_returnResources}
* ```ts source="./app-bridge.examples.ts#AppBridge_onlistresources_returnResources"
* bridge.onlistresources = async (params, extra) => {
* return mcpClient.request(
* { method: "resources/list", params },
* ListResourcesResultSchema,
* { signal: extra.signal },
* );
* };
* ```
*
* @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type
@ -719,7 +864,15 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onreadresource_returnResource}
* ```ts source="./app-bridge.examples.ts#AppBridge_onreadresource_returnResource"
* bridge.onreadresource = async (params, extra) => {
* return mcpClient.request(
* { method: "resources/read", params },
* ReadResourceResultSchema,
* { signal: extra.signal },
* );
* };
* ```
*
* @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type
* @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type
@ -779,7 +932,15 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_onlistprompts_returnPrompts}
* ```ts source="./app-bridge.examples.ts#AppBridge_onlistprompts_returnPrompts"
* bridge.onlistprompts = async (params, extra) => {
* return mcpClient.request(
* { method: "prompts/list", params },
* ListPromptsResultSchema,
* { signal: extra.signal },
* );
* };
* ```
*
* @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type
@ -915,10 +1076,17 @@ export class AppBridge extends Protocol<
* @param hostContext - The complete new host context state
*
* @example Update theme when user toggles dark mode
* {@includeCode ./app-bridge.examples.ts#AppBridge_setHostContext_updateTheme}
* ```ts source="./app-bridge.examples.ts#AppBridge_setHostContext_updateTheme"
* bridge.setHostContext({ theme: "dark" });
* ```
*
* @example Update multiple context fields
* {@includeCode ./app-bridge.examples.ts#AppBridge_setHostContext_updateMultiple}
* ```ts source="./app-bridge.examples.ts#AppBridge_setHostContext_updateMultiple"
* bridge.setHostContext({
* theme: "dark",
* containerDimensions: { maxHeight: 600, width: 800 },
* });
* ```
*
* @see {@link McpUiHostContext `McpUiHostContext`} for the context structure
* @see {@link McpUiHostContextChangedNotification `McpUiHostContextChangedNotification`} for the notification type
@ -971,7 +1139,13 @@ export class AppBridge extends Protocol<
* @param params - Complete tool call arguments
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolInput_afterInit}
* ```ts source="./app-bridge.examples.ts#AppBridge_sendToolInput_afterInit"
* bridge.oninitialized = () => {
* bridge.sendToolInput({
* arguments: { location: "New York", units: "metric" },
* });
* };
* ```
*
* @see {@link McpUiToolInputNotification `McpUiToolInputNotification`} for the notification type
* @see {@link oninitialized `oninitialized`} for the initialization callback
@ -998,7 +1172,17 @@ export class AppBridge extends Protocol<
* @param params - Partial tool call arguments (may be incomplete)
*
* @example Stream partial arguments as they arrive
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolInputPartial_streaming}
* ```ts source="./app-bridge.examples.ts#AppBridge_sendToolInputPartial_streaming"
* // As streaming progresses...
* bridge.sendToolInputPartial({ arguments: { loc: "N" } });
* bridge.sendToolInputPartial({ arguments: { location: "New" } });
* bridge.sendToolInputPartial({ arguments: { location: "New York" } });
*
* // When complete, send final input
* bridge.sendToolInput({
* arguments: { location: "New York", units: "metric" },
* });
* ```
*
* @see {@link McpUiToolInputPartialNotification `McpUiToolInputPartialNotification`} for the notification type
* @see {@link sendToolInput `sendToolInput`} for sending complete arguments
@ -1021,7 +1205,13 @@ export class AppBridge extends Protocol<
* @param params - Standard MCP tool execution result
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution}
* ```ts source="./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution"
* const result = await mcpClient.request(
* { method: "tools/call", params: { name: "get_weather", arguments: args } },
* CallToolResultSchema,
* );
* bridge.sendToolResult(result);
* ```
*
* @see {@link McpUiToolResultNotification `McpUiToolResultNotification`} for the notification type
* @see {@link sendToolInput `sendToolInput`} for sending tool arguments before results
@ -1045,10 +1235,19 @@ export class AppBridge extends Protocol<
* - `reason`: Human-readable explanation for why the tool was cancelled
*
* @example User-initiated cancellation
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolCancelled_userInitiated}
* ```ts source="./app-bridge.examples.ts#AppBridge_sendToolCancelled_userInitiated"
* // User clicked "Cancel" button
* bridge.sendToolCancelled({ reason: "User cancelled the operation" });
* ```
*
* @example System-level cancellation
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolCancelled_systemLevel}
* ```ts source="./app-bridge.examples.ts#AppBridge_sendToolCancelled_systemLevel"
* // Sampling error or timeout
* bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
*
* // Classifier intervention
* bridge.sendToolCancelled({ reason: "Content policy violation detected" });
* ```
*
* @see {@link McpUiToolCancelledNotification `McpUiToolCancelledNotification`} for the notification type
* @see {@link sendToolResult `sendToolResult`} for sending successful results
@ -1099,7 +1298,15 @@ export class AppBridge extends Protocol<
* @returns Promise resolving when view confirms readiness for teardown
*
* @example
* {@includeCode ./app-bridge.examples.ts#AppBridge_teardownResource_gracefulShutdown}
* ```ts source="./app-bridge.examples.ts#AppBridge_teardownResource_gracefulShutdown"
* try {
* await bridge.teardownResource({});
* // View is ready, safe to unmount iframe
* iframe.remove();
* } catch (error) {
* console.error("Teardown failed:", error);
* }
* ```
*/
teardownResource(
params: McpUiResourceTeardownRequest["params"],
@ -1144,10 +1351,33 @@ export class AppBridge extends Protocol<
* before calling `bridge.connect()`.
*
* @example With MCP client (automatic forwarding)
* {@includeCode ./app-bridge.examples.ts#AppBridge_connect_withMcpClient}
* ```ts source="./app-bridge.examples.ts#AppBridge_connect_withMcpClient"
* const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
* const transport = new PostMessageTransport(
* iframe.contentWindow!,
* iframe.contentWindow!,
* );
*
* bridge.oninitialized = () => {
* console.log("View ready");
* bridge.sendToolInput({ arguments: toolArgs });
* };
*
* await bridge.connect(transport);
* ```
*
* @example Without MCP client (manual handlers)
* {@includeCode ./app-bridge.examples.ts#AppBridge_connect_withoutMcpClient}
* ```ts source="./app-bridge.examples.ts#AppBridge_connect_withoutMcpClient"
* const bridge = new AppBridge(null, hostInfo, capabilities);
*
* // Register handlers manually
* bridge.oncalltool = async (params, extra) => {
* // Custom tool call handling
* return { content: [] };
* };
*
* await bridge.connect(transport);
* ```
*/
async connect(transport: Transport) {
if (this._client) {

View file

@ -72,10 +72,27 @@ export {
* with the `_meta.ui.resourceUri` format instead.
*
* @example How MCP servers use this key (server-side, not in Apps)
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_serverSide}
* ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_serverSide"
* server.registerTool(
* "weather",
* {
* description: "Get weather forecast",
* _meta: {
* [RESOURCE_URI_META_KEY]: "ui://weather/forecast",
* },
* },
* handler,
* );
* ```
*
* @example How hosts check for this metadata (host-side)
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_hostSide}
* ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_hostSide"
* // Check tool definition metadata (from tools/list response):
* const uiUri = tool._meta?.[RESOURCE_URI_META_KEY];
* if (typeof uiUri === "string" && uiUri.startsWith("ui://")) {
* // Fetch the resource and display the UI
* }
* ```
*/
export const RESOURCE_URI_META_KEY = "ui/resourceUri";
@ -155,7 +172,19 @@ type RequestHandlerExtra = Parameters<
* Both patterns work; use whichever fits your coding style better.
*
* @example Basic usage with PostMessageTransport
* {@includeCode ./app.examples.ts#App_basicUsage}
* ```ts source="./app.examples.ts#App_basicUsage"
* const app = new App(
* { name: "WeatherApp", version: "1.0.0" },
* {}, // capabilities
* );
*
* // Register handlers before connecting to ensure no notifications are missed
* app.ontoolinput = (params) => {
* console.log("Tool arguments:", params.arguments);
* };
*
* await app.connect();
* ```
*/
export class App extends Protocol<AppRequest, AppNotification, AppResult> {
private _hostCapabilities?: McpUiHostCapabilities;
@ -170,7 +199,13 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param options - Configuration options including `autoResize` behavior
*
* @example
* {@includeCode ./app.examples.ts#App_constructor_basic}
* ```ts source="./app.examples.ts#App_constructor_basic"
* const app = new App(
* { name: "MyApp", version: "1.0.0" },
* { tools: { listChanged: true } }, // capabilities
* { autoResize: true }, // options
* );
* ```
*/
constructor(
private _appInfo: Implementation,
@ -199,7 +234,12 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host capabilities, or `undefined` if not yet connected
*
* @example Check host capabilities after connection
* {@includeCode ./app.examples.ts#App_getHostCapabilities_checkAfterConnection}
* ```ts source="./app.examples.ts#App_getHostCapabilities_checkAfterConnection"
* await app.connect();
* if (app.getHostCapabilities()?.serverTools) {
* console.log("Host supports server tool calls");
* }
* ```
*
* @see {@link connect `connect`} for the initialization handshake
* @see {@link McpUiHostCapabilities `McpUiHostCapabilities`} for the capabilities structure
@ -218,7 +258,11 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host implementation info, or `undefined` if not yet connected
*
* @example Log host information after connection
* {@includeCode ./app.examples.ts#App_getHostVersion_logAfterConnection}
* ```ts source="./app.examples.ts#App_getHostVersion_logAfterConnection"
* await app.connect(transport);
* const { name, version } = app.getHostVersion() ?? {};
* console.log(`Connected to ${name} v${version}`);
* ```
*
* @see {@link connect `connect`} for the initialization handshake
*/
@ -239,7 +283,16 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host context, or `undefined` if not yet connected
*
* @example Access host context after connection
* {@includeCode ./app.examples.ts#App_getHostContext_accessAfterConnection}
* ```ts source="./app.examples.ts#App_getHostContext_accessAfterConnection"
* await app.connect(transport);
* const context = app.getHostContext();
* if (context?.theme === "dark") {
* document.body.classList.add("dark-theme");
* }
* if (context?.toolInfo) {
* console.log("Tool:", context.toolInfo.tool.name);
* }
* ```
*
* @see {@link connect `connect`} for the initialization handshake
* @see {@link onhostcontextchanged `onhostcontextchanged`} for context change notifications
@ -264,7 +317,14 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with the tool input params ({@link McpUiToolInputNotification.params `McpUiToolInputNotification.params`})
*
* @example
* {@includeCode ./app.examples.ts#App_ontoolinput_setter}
* ```ts source="./app.examples.ts#App_ontoolinput_setter"
* // Register before connecting to ensure no notifications are missed
* app.ontoolinput = (params) => {
* console.log("Tool:", params.arguments);
* // Update your UI with the tool arguments
* };
* await app.connect();
* ```
*
* @see {@link setNotificationHandler `setNotificationHandler`} for the underlying method
* @see {@link McpUiToolInputNotification `McpUiToolInputNotification`} for the notification structure
@ -297,7 +357,29 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with each partial tool input update ({@link McpUiToolInputPartialNotification.params `McpUiToolInputPartialNotification.params`})
*
* @example Progressive rendering of tool arguments
* {@includeCode ./app.examples.ts#App_ontoolinputpartial_progressiveRendering}
* ```ts source="./app.examples.ts#App_ontoolinputpartial_progressiveRendering"
* let toolInputs: Record<string, unknown> | null = null;
* let toolInputsPartial: Record<string, unknown> | null = null;
*
* app.ontoolinputpartial = (params) => {
* toolInputsPartial = params.arguments as Record<string, unknown>;
* render();
* };
*
* app.ontoolinput = (params) => {
* toolInputs = params.arguments as Record<string, unknown>;
* toolInputsPartial = null;
* render();
* };
*
* function render() {
* if (toolInputs) {
* renderFinalUI(toolInputs);
* } else {
* renderLoadingUI(toolInputsPartial); // e.g., shimmer with partial preview
* }
* }
* ```
*
* @see {@link setNotificationHandler `setNotificationHandler`} for the underlying method
* @see {@link McpUiToolInputPartialNotification `McpUiToolInputPartialNotification`} for the notification structure
@ -326,7 +408,15 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with the tool result ({@link McpUiToolResultNotification.params `McpUiToolResultNotification.params`})
*
* @example Display tool execution results
* {@includeCode ./app.examples.ts#App_ontoolresult_displayResults}
* ```ts source="./app.examples.ts#App_ontoolresult_displayResults"
* app.ontoolresult = (params) => {
* if (params.isError) {
* console.error("Tool execution failed:", params.content);
* } else if (params.content) {
* console.log("Tool output:", params.content);
* }
* };
* ```
*
* @see {@link setNotificationHandler `setNotificationHandler`} for the underlying method
* @see {@link McpUiToolResultNotification `McpUiToolResultNotification`} for the notification structure
@ -356,7 +446,12 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called when tool execution is cancelled. Receives optional cancellation reason — see {@link McpUiToolCancelledNotification.params `McpUiToolCancelledNotification.params`}.
*
* @example Handle tool cancellation
* {@includeCode ./app.examples.ts#App_ontoolcancelled_handleCancellation}
* ```ts source="./app.examples.ts#App_ontoolcancelled_handleCancellation"
* app.ontoolcancelled = (params) => {
* console.log("Tool cancelled:", params.reason);
* // Update your UI to show cancellation state
* };
* ```
*
* @see {@link setNotificationHandler `setNotificationHandler`} for the underlying method
* @see {@link McpUiToolCancelledNotification `McpUiToolCancelledNotification`} for the notification structure
@ -390,7 +485,15 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with the updated host context
*
* @example Respond to theme changes
* {@includeCode ./app.examples.ts#App_onhostcontextchanged_respondToTheme}
* ```ts source="./app.examples.ts#App_onhostcontextchanged_respondToTheme"
* app.onhostcontextchanged = (params) => {
* if (params.theme === "dark") {
* document.body.classList.add("dark-theme");
* } else {
* document.body.classList.remove("dark-theme");
* }
* };
* ```
*
* @see {@link setNotificationHandler `setNotificationHandler`} for the underlying method
* @see {@link McpUiHostContextChangedNotification `McpUiHostContextChangedNotification`} for the notification structure
@ -428,7 +531,14 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* Must return `McpUiResourceTeardownResult` (can be an empty object `{}`) or a Promise resolving to it.
*
* @example Perform cleanup before teardown
* {@includeCode ./app.examples.ts#App_onteardown_performCleanup}
* ```ts source="./app.examples.ts#App_onteardown_performCleanup"
* app.onteardown = async () => {
* await saveState();
* closeConnections();
* console.log("App ready for teardown");
* return {};
* };
* ```
*
* @see {@link setRequestHandler `setRequestHandler`} for the underlying method
* @see {@link McpUiResourceTeardownRequest `McpUiResourceTeardownRequest`} for the request structure
@ -464,7 +574,15 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* in the constructor.
*
* @example Handle tool calls from the host
* {@includeCode ./app.examples.ts#App_oncalltool_handleFromHost}
* ```ts source="./app.examples.ts#App_oncalltool_handleFromHost"
* app.oncalltool = async (params, extra) => {
* if (params.name === "greet") {
* const name = params.arguments?.name ?? "World";
* return { content: [{ type: "text", text: `Hello, ${name}!` }] };
* }
* throw new Error(`Unknown tool: ${params.name}`);
* };
* ```
*
* @see {@link setRequestHandler `setRequestHandler`} for the underlying method
*/
@ -498,7 +616,13 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* allowed; capability validation occurs when handlers are invoked.
*
* @example Return available tools
* {@includeCode ./app.examples.ts#App_onlisttools_returnTools}
* ```ts source="./app.examples.ts#App_onlisttools_returnTools"
* app.onlisttools = async (params, extra) => {
* return {
* tools: ["greet", "calculate", "format"],
* };
* };
* ```
*
* @see {@link setRequestHandler `setRequestHandler`} for the underlying method
* @see {@link oncalltool `oncalltool`} for handling tool execution
@ -587,7 +711,21 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* between transport failures (thrown) and tool execution failures (returned).
*
* @example Fetch updated weather data
* {@includeCode ./app.examples.ts#App_callServerTool_fetchWeather}
* ```ts source="./app.examples.ts#App_callServerTool_fetchWeather"
* try {
* const result = await app.callServerTool({
* name: "get_weather",
* arguments: { location: "Tokyo" },
* });
* if (result.isError) {
* console.error("Tool returned error:", result.content);
* } else {
* console.log(result.content);
* }
* } catch (error) {
* console.error("Tool call failed:", error);
* }
* ```
*/
async callServerTool(
params: CallToolRequest["params"],
@ -613,10 +751,40 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If the request times out or the connection is lost
*
* @example Send a text message from user interaction
* {@includeCode ./app.examples.ts#App_sendMessage_textFromInteraction}
* ```ts source="./app.examples.ts#App_sendMessage_textFromInteraction"
* try {
* const result = await app.sendMessage({
* role: "user",
* content: [{ type: "text", text: "Show me details for item #42" }],
* });
* if (result.isError) {
* console.error("Host rejected the message");
* // Handle rejection appropriately for your app
* }
* } catch (error) {
* console.error("Failed to send message:", error);
* // Handle transport/protocol error
* }
* ```
*
* @example Send follow-up message after offloading large data to model context
* {@includeCode ./app.examples.ts#App_sendMessage_withLargeContext}
* ```ts source="./app.examples.ts#App_sendMessage_withLargeContext"
* const markdown = `---
* word-count: ${fullTranscript.split(/\s+/).length}
* speaker-names: ${speakerNames.join(", ")}
* ---
*
* ${fullTranscript}`;
*
* // Offload long transcript to model context
* await app.updateModelContext({ content: [{ type: "text", text: markdown }] });
*
* // Send brief trigger message
* await app.sendMessage({
* role: "user",
* content: [{ type: "text", text: "Summarize the key points" }],
* });
* ```
*
* @see {@link McpUiMessageRequest `McpUiMessageRequest`} for request structure
*/
@ -640,7 +808,13 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param params - Log level and message
*
* @example Log app state for debugging
* {@includeCode ./app.examples.ts#App_sendLog_debugState}
* ```ts source="./app.examples.ts#App_sendLog_debugState"
* app.sendLog({
* level: "info",
* data: "Weather data refreshed",
* logger: "WeatherApp",
* });
* ```
*
* @returns Promise that resolves when the log notification is sent
*/
@ -668,10 +842,39 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If the request times out or the connection is lost
*
* @example Update model context with current app state
* {@includeCode ./app.examples.ts#App_updateModelContext_appState}
* ```ts source="./app.examples.ts#App_updateModelContext_appState"
* const markdown = `---
* item-count: ${itemList.length}
* total-cost: ${totalCost}
* currency: ${currency}
* ---
*
* User is viewing their shopping cart with ${itemList.length} items selected:
*
* ${itemList.map((item) => `- ${item}`).join("\n")}`;
*
* await app.updateModelContext({
* content: [{ type: "text", text: markdown }],
* });
* ```
*
* @example Report runtime error to model
* {@includeCode ./app.examples.ts#App_updateModelContext_reportError}
* ```ts source="./app.examples.ts#App_updateModelContext_reportError"
* try {
* const _stream = await navigator.mediaDevices.getUserMedia({ audio: true });
* // ... use _stream for transcription
* } catch (err) {
* // Inform the model that the app is in a degraded state
* await app.updateModelContext({
* content: [
* {
* type: "text",
* text: "Error: transcription unavailable",
* },
* ],
* });
* }
* ```
*
* @returns Promise that resolves when the context update is acknowledged
*/
@ -702,7 +905,14 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If the request times out or the connection is lost
*
* @example Open documentation link
* {@includeCode ./app.examples.ts#App_openLink_documentation}
* ```ts source="./app.examples.ts#App_openLink_documentation"
* const { isError } = await app.openLink({ url: "https://docs.example.com" });
* if (isError) {
* // Host denied the request (e.g., blocked domain, user cancelled)
* // Optionally show fallback: display URL for manual copy
* console.warn("Link request denied");
* }
* ```
*
* @see {@link McpUiOpenLinkRequest `McpUiOpenLinkRequest`} for request structure
* @see {@link McpUiOpenLinkResult `McpUiOpenLinkResult`} for result structure
@ -734,7 +944,14 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Result containing the actual display mode that was set
*
* @example Toggle display mode
* {@includeCode ./app.examples.ts#App_requestDisplayMode_toggle}
* ```ts source="./app.examples.ts#App_requestDisplayMode_toggle"
* const ctx = app.getHostContext();
* if (ctx?.availableDisplayModes?.includes("fullscreen")) {
* const target = ctx.displayMode === "fullscreen" ? "inline" : "fullscreen";
* const result = await app.requestDisplayMode({ mode: target });
* console.log("Now in:", result.mode);
* }
* ```
*
* @see {@link McpUiRequestDisplayModeRequest `McpUiRequestDisplayModeRequest`} for request structure
* @see {@link McpUiHostContext `McpUiHostContext`} for checking availableDisplayModes
@ -762,7 +979,12 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param params - New width and height in pixels
*
* @example Manually notify host of size change
* {@includeCode ./app.examples.ts#App_sendSizeChanged_manual}
* ```ts source="./app.examples.ts#App_sendSizeChanged_manual"
* app.sendSizeChanged({
* width: 400,
* height: 600,
* });
* ```
*
* @returns Promise that resolves when the notification is sent
*
@ -789,7 +1011,20 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Cleanup function to disconnect the observer
*
* @example Manual setup for custom scenarios
* {@includeCode ./app.examples.ts#App_setupAutoResize_manual}
* ```ts source="./app.examples.ts#App_setupAutoResize_manual"
* const app = new App(
* { name: "MyApp", version: "1.0.0" },
* {},
* { autoResize: false },
* );
* await app.connect(transport);
*
* // Later, enable auto-resize manually
* const cleanup = app.setupSizeChangedNotifications();
*
* // Clean up when done
* cleanup();
* ```
*/
setupSizeChangedNotifications() {
let scheduled = false;
@ -861,7 +1096,16 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If initialization fails or connection is lost
*
* @example Connect with PostMessageTransport
* {@includeCode ./app.examples.ts#App_connect_withPostMessageTransport}
* ```ts source="./app.examples.ts#App_connect_withPostMessageTransport"
* const app = new App({ name: "MyApp", version: "1.0.0" }, {});
*
* try {
* await app.connect(new PostMessageTransport(window.parent, window.parent));
* console.log("Connected successfully!");
* } catch (error) {
* console.error("Failed to connect:", error);
* }
* ```
*
* @see {@link McpUiInitializeRequest `McpUiInitializeRequest`} for the initialization request structure
* @see {@link McpUiInitializedNotification `McpUiInitializedNotification`} for the initialized notification

View file

@ -24,10 +24,20 @@ import {
* ## Usage
*
* **View**:
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_view}
* ```ts source="./message-transport.examples.ts#PostMessageTransport_view"
* const transport = new PostMessageTransport(window.parent, window.parent);
* await app.connect(transport);
* ```
*
* **Host**:
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_host}
* ```ts source="./message-transport.examples.ts#PostMessageTransport_host"
* const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
* const transport = new PostMessageTransport(
* iframe.contentWindow!,
* iframe.contentWindow!,
* );
* await bridge.connect(transport);
* ```
*
* @see {@link app!App.connect `App.connect`} for View usage
* @see {@link app-bridge!AppBridge.connect `AppBridge.connect`} for Host usage
@ -46,10 +56,18 @@ export class PostMessageTransport implements Transport {
* `window.parent`. For hosts, pass `iframe.contentWindow`.
*
* @example View connecting to parent
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_constructor_view}
* ```ts source="./message-transport.examples.ts#PostMessageTransport_constructor_view"
* const transport = new PostMessageTransport(window.parent, window.parent);
* ```
*
* @example Host connecting to iframe
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_constructor_host}
* ```ts source="./message-transport.examples.ts#PostMessageTransport_constructor_host"
* const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
* const transport = new PostMessageTransport(
* iframe.contentWindow!,
* iframe.contentWindow!,
* );
* ```
*/
constructor(
private eventTarget: Window = window.parent,

View file

@ -17,7 +17,19 @@
* @module @modelcontextprotocol/ext-apps/react
*
* @example Basic React App
* {@includeCode ./index.examples.tsx#index_basicReactApp}
* ```tsx source="./index.examples.tsx#index_basicReactApp"
* function MyApp() {
* const { app, isConnected, error } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* });
*
* if (error) return <div>Error: {error.message}</div>;
* if (!isConnected) return <div>Connecting...</div>;
*
* return <div>Connected!</div>;
* }
* ```
*/
export * from "./useApp";
export * from "./useAutoResize";

View file

@ -30,7 +30,17 @@ export interface UseAppOptions {
* @param app - The newly created `App` instance
*
* @example Register an event handler
* {@includeCode ./useApp.examples.tsx#useApp_registerHandler}
* ```tsx source="./useApp.examples.tsx#useApp_registerHandler"
* useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* onAppCreated: (app) => {
* app.ontoolresult = (result) => {
* console.log("Tool result:", result);
* };
* },
* });
* ```
*/
onAppCreated?: (app: App) => void;
}
@ -70,7 +80,39 @@ export interface AppState {
* timeouts, initialization handshake failures, or transport errors).
*
* @example Basic usage of useApp hook with common event handlers
* {@includeCode ./useApp.examples.tsx#useApp_basicUsage}
* ```tsx source="./useApp.examples.tsx#useApp_basicUsage"
* function MyApp() {
* const [hostContext, setHostContext] = useState<
* McpUiHostContext | undefined
* >(undefined);
*
* const { app, isConnected, error } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* onAppCreated: (app) => {
* app.ontoolinput = (input) => {
* console.log("Tool input:", input);
* };
* app.ontoolresult = (result) => {
* console.log("Tool result:", result);
* };
* app.ontoolcancelled = (params) => {
* console.log("Tool cancelled:", params.reason);
* };
* app.onerror = (error) => {
* console.log("Error:", error);
* };
* app.onhostcontextchanged = (params) => {
* setHostContext((prev) => ({ ...prev, ...params }));
* };
* },
* });
*
* if (error) return <div>Error: {error.message}</div>;
* if (!isConnected) return <div>Connecting...</div>;
* return <div>Theme: {hostContext?.theme}</div>;
* }
* ```
*
* @see {@link App.connect `App.connect`} for the underlying connection method
* @see {@link useAutoResize `useAutoResize`} for manual auto-resize control when using custom App options

View file

@ -20,7 +20,33 @@ import { App } from "../app";
* cause unnecessary effect re-runs; omit this parameter.
*
* @example Manual App creation with custom auto-resize control
* {@includeCode ./useAutoResize.examples.tsx#useAutoResize_manualApp}
* ```tsx source="./useAutoResize.examples.tsx#useAutoResize_manualApp"
* function MyComponent() {
* // For custom App options, create App manually instead of using useApp
* const [app, setApp] = useState<App | null>(null);
* const [error, setError] = useState<Error | null>(null);
*
* useEffect(() => {
* const myApp = new App(
* { name: "MyApp", version: "1.0.0" },
* {}, // capabilities
* { autoResize: false }, // Disable default auto-resize
* );
*
* const transport = new PostMessageTransport(window.parent, window.parent);
* myApp
* .connect(transport)
* .then(() => setApp(myApp))
* .catch((err) => setError(err));
* }, []);
*
* // Add manual auto-resize control
* useAutoResize(app);
*
* if (error) return <div>Connection failed: {error.message}</div>;
* return <div>My content</div>;
* }
* ```
*
* @see {@link App.setupSizeChangedNotifications `App.setupSizeChangedNotifications`} for the underlying implementation
* @see {@link useApp `useApp`} which enables auto-resize by default

View file

@ -15,10 +15,31 @@ import { McpUiTheme } from "../types";
* @returns The current theme ("light" or "dark")
*
* @example Conditionally render based on theme
* {@includeCode ./useDocumentTheme.examples.tsx#useDocumentTheme_conditionalRender}
* ```tsx source="./useDocumentTheme.examples.tsx#useDocumentTheme_conditionalRender"
* function MyApp() {
* const theme = useDocumentTheme();
*
* return <div>{theme === "dark" ? <DarkIcon /> : <LightIcon />}</div>;
* }
* ```
*
* @example Use with theme-aware styling
* {@includeCode ./useDocumentTheme.examples.tsx#useDocumentTheme_themedButton}
* ```tsx source="./useDocumentTheme.examples.tsx#useDocumentTheme_themedButton"
* function ThemedButton() {
* const theme = useDocumentTheme();
*
* return (
* <button
* style={{
* background: theme === "dark" ? "#333" : "#fff",
* color: theme === "dark" ? "#fff" : "#333",
* }}
* >
* Click me
* </button>
* );
* }
* ```
*
* @see {@link getDocumentTheme `getDocumentTheme`} for the underlying function
* @see {@link applyDocumentTheme `applyDocumentTheme`} to set the theme

View file

@ -26,7 +26,23 @@ import { McpUiHostContext } from "../types";
* If provided, styles and theme will be applied immediately on mount.
*
* @example
* {@includeCode ./useHostStyles.examples.tsx#useHostStyleVariables_basicUsage}
* ```tsx source="./useHostStyles.examples.tsx#useHostStyleVariables_basicUsage"
* function MyApp() {
* const { app } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* });
*
* // Apply host styles - pass initial context to apply styles from connect() immediately
* useHostStyleVariables(app, app?.getHostContext());
*
* return (
* <div style={{ background: "var(--color-background-primary)" }}>
* Hello!
* </div>
* );
* }
* ```
*
* @see {@link applyHostStyleVariables `applyHostStyleVariables`} for the underlying styles function
* @see {@link applyDocumentTheme `applyDocumentTheme`} for the underlying theme function
@ -89,7 +105,19 @@ export function useHostStyleVariables(
* If provided, fonts will be applied immediately on mount.
*
* @example Basic usage with useApp
* {@includeCode ./useHostStyles.examples.tsx#useHostFonts_basicUsage}
* ```tsx source="./useHostStyles.examples.tsx#useHostFonts_basicUsage"
* function MyApp() {
* const { app } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* });
*
* // Apply host fonts - pass initial context to apply fonts from connect() immediately
* useHostFonts(app, app?.getHostContext());
*
* return <div style={{ fontFamily: "var(--font-sans)" }}>Hello!</div>;
* }
* ```
*
* @see {@link applyHostFonts `applyHostFonts`} for the underlying fonts function
* @see {@link useHostStyleVariables `useHostStyleVariables`} for applying style variables and theme
@ -136,7 +164,23 @@ export function useHostFonts(
* Pass `app?.getHostContext()` to apply styles immediately on mount.
*
* @example
* {@includeCode ./useHostStyles.examples.tsx#useHostStyles_basicUsage}
* ```tsx source="./useHostStyles.examples.tsx#useHostStyles_basicUsage"
* function MyApp() {
* const { app } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* });
*
* // Apply all host styles - pass initial context to apply styles from connect() immediately
* useHostStyles(app, app?.getHostContext());
*
* return (
* <div style={{ background: "var(--color-background-primary)" }}>
* Hello!
* </div>
* );
* }
* ```
*
* @see {@link useHostStyleVariables `useHostStyleVariables`} for style variables and theme only
* @see {@link useHostFonts `useHostFonts`} for fonts only

View file

@ -8,7 +8,27 @@
* @module server-helpers
*
* @example
* {@includeCode ./index.examples.ts#index_overview}
* ```ts source="./index.examples.ts#index_overview"
* // Register a tool that displays a view
* registerAppTool(
* server,
* "weather",
* {
* description: "Get weather forecast",
* _meta: { ui: { resourceUri: "ui://weather/view.html" } },
* },
* toolCallback,
* );
*
* // Register the HTML resource the tool references
* registerAppResource(
* server,
* "Weather View",
* "ui://weather/view.html",
* {},
* readCallback,
* );
* ```
*/
import {
@ -115,13 +135,67 @@ export interface McpUiAppResourceConfig extends ResourceMetadata {
* @param cb - Tool handler function
*
* @example Basic usage
* {@includeCode ./index.examples.ts#registerAppTool_basicUsage}
* ```ts source="./index.examples.ts#registerAppTool_basicUsage"
* registerAppTool(
* server,
* "get-weather",
* {
* title: "Get Weather",
* description: "Get current weather for a location",
* inputSchema: { location: z.string() },
* _meta: {
* ui: { resourceUri: "ui://weather/view.html" },
* },
* },
* async (args) => {
* const weather = await fetchWeather(args.location);
* return { content: [{ type: "text", text: JSON.stringify(weather) }] };
* },
* );
* ```
*
* @example Tool visible to model but not callable by UI
* {@includeCode ./index.examples.ts#registerAppTool_modelOnlyVisibility}
* ```ts source="./index.examples.ts#registerAppTool_modelOnlyVisibility"
* registerAppTool(
* server,
* "show-cart",
* {
* description: "Display the user's shopping cart",
* _meta: {
* ui: {
* resourceUri: "ui://shop/cart.html",
* visibility: ["model"],
* },
* },
* },
* async () => {
* const cart = await getCart();
* return { content: [{ type: "text", text: JSON.stringify(cart) }] };
* },
* );
* ```
*
* @example Tool hidden from model, only callable by UI
* {@includeCode ./index.examples.ts#registerAppTool_appOnlyVisibility}
* ```ts source="./index.examples.ts#registerAppTool_appOnlyVisibility"
* registerAppTool(
* server,
* "update-quantity",
* {
* description: "Update item quantity in cart",
* inputSchema: { itemId: z.string(), quantity: z.number() },
* _meta: {
* ui: {
* resourceUri: "ui://shop/cart.html",
* visibility: ["app"],
* },
* },
* },
* async ({ itemId, quantity }) => {
* const cart = await updateCartItem(itemId, quantity);
* return { content: [{ type: "text", text: JSON.stringify(cart) }] };
* },
* );
* ```
*
* @see {@link registerAppResource `registerAppResource`} to register the HTML resource referenced by the tool
*/
@ -170,10 +244,54 @@ export function registerAppTool<
* @param readCallback - Callback that returns the resource contents
*
* @example Basic usage
* {@includeCode ./index.examples.ts#registerAppResource_basicUsage}
* ```ts source="./index.examples.ts#registerAppResource_basicUsage"
* registerAppResource(
* server,
* "Weather View",
* "ui://weather/view.html",
* {
* description: "Interactive weather display",
* },
* async () => ({
* contents: [
* {
* uri: "ui://weather/view.html",
* mimeType: RESOURCE_MIME_TYPE,
* text: await fs.readFile("dist/view.html", "utf-8"),
* },
* ],
* }),
* );
* ```
*
* @example With CSP configuration for external domains
* {@includeCode ./index.examples.ts#registerAppResource_withCsp}
* ```ts source="./index.examples.ts#registerAppResource_withCsp"
* registerAppResource(
* server,
* "Music Player",
* "ui://music/player.html",
* {
* description: "Audio player with external soundfonts",
* },
* async () => ({
* contents: [
* {
* uri: "ui://music/player.html",
* mimeType: RESOURCE_MIME_TYPE,
* text: musicPlayerHtml,
* _meta: {
* ui: {
* csp: {
* resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
* connectDomains: ["https://api.example.com"], // For fetch/WebSocket
* },
* },
* },
* },
* ],
* }),
* );
* ```
*
* @see {@link registerAppTool `registerAppTool`} to register tools that reference this resource
*/

View file

@ -10,7 +10,10 @@ import { McpUiStyles, McpUiTheme } from "./types";
* @returns The current theme ("light" or "dark")
*
* @example Check current theme
* {@includeCode ./styles.examples.ts#getDocumentTheme_checkCurrent}
* ```ts source="./styles.examples.ts#getDocumentTheme_checkCurrent"
* const theme = getDocumentTheme();
* document.body.classList.toggle("dark", theme === "dark");
* ```
*
* @see {@link applyDocumentTheme `applyDocumentTheme`} to set the theme
* @see {@link McpUiTheme `McpUiTheme`} for the theme type
@ -39,7 +42,22 @@ export function getDocumentTheme(): McpUiTheme {
* @param theme - The theme to apply ("light" or "dark")
*
* @example Apply theme from host context
* {@includeCode ./styles.examples.ts#applyDocumentTheme_fromHostContext}
* ```ts source="./styles.examples.ts#applyDocumentTheme_fromHostContext"
* // Apply when host context changes
* app.onhostcontextchanged = (params) => {
* if (params.theme) {
* applyDocumentTheme(params.theme);
* }
* };
*
* // Apply initial theme after connecting
* app.connect().then(() => {
* const ctx = app.getHostContext();
* if (ctx?.theme) {
* applyDocumentTheme(ctx.theme);
* }
* });
* ```
*
* @example Use with CSS selectors
* ```css
@ -72,10 +90,35 @@ export function applyDocumentTheme(theme: McpUiTheme): void {
* @param root - The element to apply styles to (defaults to `document.documentElement`)
*
* @example Apply style variables from host context
* {@includeCode ./styles.examples.ts#applyHostStyleVariables_fromHostContext}
* ```ts source="./styles.examples.ts#applyHostStyleVariables_fromHostContext"
* // Use CSS variables in your styles
* document.body.style.background = "var(--color-background-primary)";
*
* // Apply when host context changes
* app.onhostcontextchanged = (params) => {
* if (params.styles?.variables) {
* applyHostStyleVariables(params.styles.variables);
* }
* };
*
* // Apply initial styles after connecting
* app.connect().then(() => {
* const ctx = app.getHostContext();
* if (ctx?.styles?.variables) {
* applyHostStyleVariables(ctx.styles.variables);
* }
* });
* ```
*
* @example Apply to a specific element
* {@includeCode ./styles.examples.ts#applyHostStyleVariables_toElement}
* ```ts source="./styles.examples.ts#applyHostStyleVariables_toElement"
* app.onhostcontextchanged = (params) => {
* const container = document.getElementById("app-root");
* if (container && params.styles?.variables) {
* applyHostStyleVariables(params.styles.variables, container);
* }
* };
* ```
*
* @example Use host style variables in CSS
* ```css
@ -118,13 +161,44 @@ export function applyHostStyleVariables(
* @param fontCss - CSS string containing `@font-face` rules and/or `@import` statements
*
* @example Apply fonts from host context
* {@includeCode ./styles.examples.ts#applyHostFonts_fromHostContext}
* ```ts source="./styles.examples.ts#applyHostFonts_fromHostContext"
* // Apply when host context changes
* app.onhostcontextchanged = (params) => {
* if (params.styles?.css?.fonts) {
* applyHostFonts(params.styles.css.fonts);
* }
* };
*
* // Apply initial fonts after connecting
* app.connect().then(() => {
* const ctx = app.getHostContext();
* if (ctx?.styles?.css?.fonts) {
* applyHostFonts(ctx.styles.css.fonts);
* }
* });
* ```
*
* @example Host providing self-hosted fonts
* {@includeCode ./styles.examples.ts#applyHostFonts_selfHosted}
* ```ts source="./styles.examples.ts#applyHostFonts_selfHosted"
* // Example of what a host might provide:
* const fontCss = `
* @font-face {
* font-family: "Anthropic Sans";
* src: url("https://assets.anthropic.com/.../Regular.otf") format("opentype");
* font-weight: 400;
* }
* `;
* applyHostFonts(fontCss);
* ```
*
* @example Host providing Google Fonts
* {@includeCode ./styles.examples.ts#applyHostFonts_googleFonts}
* ```ts source="./styles.examples.ts#applyHostFonts_googleFonts"
* // Example of what a host might provide:
* const fontCss = `
* @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
* `;
* applyHostFonts(fontCss);
* ```
*
* @example Use host fonts in CSS
* ```css