Move JSDoc examples to type-checked .examples.ts files

Extract inline code examples from JSDoc comments into companion
`.examples.ts`/`.examples.tsx` files. Examples are now:

- Type-checked by wrapping them in functions
- Referenced via `{@includeCode ./file.examples.ts#regionName}` tags
- Organized using `//#region` and `//#endregion` markers

This keeps the main source files cleaner while ensuring examples
compile correctly. Updated TypeDoc config with `exampleTag: false`
to support title text on `@example` tags.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-18 15:11:00 -06:00
commit ad04e89745
22 changed files with 1661 additions and 838 deletions

View file

@ -78,9 +78,13 @@ Guest UI (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP
6. Host sends `sendToolResult()` when tool execution completes
7. Host calls `teardownResource()` before unmounting iframe
## Examples
## Documentation
Uses npm workspaces. Examples in `examples/` are separate packages:
JSDoc `@example` tags use `{@includeCode ./file.examples.ts#regionName}` to pull in type-checked code from companion `.examples.ts`/`.examples.tsx` files. Regions are marked with `//#region name` and `//#endregion name`, wrapped in functions (whose parameters provide types for external values). Region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `useApp_basicUsage`, `App_hostCapabilities_checkAfterConnection`).
## Full Examples
Uses npm workspaces. Full examples in `examples/` are separate packages:
- `basic-server-*` - Starter templates (vanillajs, react, vue, svelte, preact, solid). Use these as the basis for new examples.
- `basic-host` - Reference host implementation

509
src/app-bridge.examples.ts Normal file
View file

@ -0,0 +1,509 @@
/**
* Type-checked examples for {@link AppBridge}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import {
CallToolResult,
CallToolResultSchema,
ListResourcesResultSchema,
ReadResourceResultSchema,
ListPromptsResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { AppBridge, PostMessageTransport } from "./app-bridge.js";
/**
* Example: Basic usage of the AppBridge class with PostMessageTransport.
*/
async function AppBridge_basicUsage(serverTransport: Transport) {
//#region 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 Guest UI
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("Guest UI initialized");
// Now safe to send tool input
bridge.sendToolInput({ arguments: { location: "NYC" } });
};
await bridge.connect(transport);
//#endregion AppBridge_basicUsage
}
/**
* Example: Creating an AppBridge with an MCP client for automatic forwarding.
*/
function AppBridge_constructor_withMcpClient(mcpClient: Client) {
//#region AppBridge_constructor_withMcpClient
const bridge = new AppBridge(
mcpClient,
{ name: "MyHost", version: "1.0.0" },
{ openLinks: {}, serverTools: {}, logging: {} },
);
//#endregion AppBridge_constructor_withMcpClient
return bridge;
}
/**
* Example: Creating an AppBridge without an MCP client, using manual handlers.
*/
function AppBridge_constructor_withoutMcpClient() {
//#region 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: [] };
};
//#endregion AppBridge_constructor_withoutMcpClient
return bridge;
}
/**
* Example: Check Guest UI capabilities after initialization.
*/
function AppBridge_guestCapabilities_checkAfterInit(bridge: AppBridge) {
//#region AppBridge_guestCapabilities_checkAfterInit
bridge.oninitialized = () => {
const caps = bridge.getAppCapabilities();
if (caps?.tools) {
console.log("Guest UI provides tools");
}
};
//#endregion AppBridge_guestCapabilities_checkAfterInit
}
/**
* Example: Log Guest UI information after initialization.
*/
function AppBridge_guestInfo_logAfterInit(bridge: AppBridge) {
//#region AppBridge_guestInfo_logAfterInit
bridge.oninitialized = () => {
const appInfo = bridge.getAppVersion();
if (appInfo) {
console.log(`Guest UI: ${appInfo.name} v${appInfo.version}`);
}
};
//#endregion AppBridge_guestInfo_logAfterInit
}
/**
* Example: Handle Guest UI initialization and send tool input.
*/
function AppBridge_oninitialized_sendToolInput(bridge: AppBridge) {
const toolArgs = { location: "NYC" };
//#region AppBridge_oninitialized_sendToolInput
bridge.oninitialized = () => {
console.log("Guest UI ready");
bridge.sendToolInput({ arguments: toolArgs });
};
//#endregion AppBridge_oninitialized_sendToolInput
}
/**
* Example: Handle message requests from the Guest UI.
*/
function AppBridge_onmessage_logMessage(bridge: AppBridge) {
//#region 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 };
}
};
//#endregion AppBridge_onmessage_logMessage
}
// Stub for example code - represents a hypothetical chat manager
declare const chatManager: {
addMessage(message: {
role: string;
content: unknown;
source: string;
}): Promise<void>;
};
// Stub for example code - represents a hypothetical URL validator
declare function isAllowedDomain(url: string): boolean;
// Stub for example code - represents a hypothetical dialog API
declare function showDialog(options: {
message: string;
buttons: string[];
}): Promise<boolean>;
// Stub for example code - represents a hypothetical model context storage
declare let modelContext: {
type: string;
content: unknown;
structuredContent: unknown;
timestamp: number;
};
// Stub for example code - represents a hypothetical MCP client that can be passed to AppBridge
// Using Client type directly since AppBridge expects Client | null
declare const mcpClient: Client;
/**
* Example: Handle external link requests from the Guest UI.
*/
function AppBridge_onopenlink_handleRequest(bridge: AppBridge) {
//#region 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 };
};
//#endregion AppBridge_onopenlink_handleRequest
}
/**
* Example: Store model context updates from the Guest UI.
*/
function AppBridge_onupdatemodelcontext_storeContext(bridge: AppBridge) {
//#region AppBridge_onupdatemodelcontext_storeContext
bridge.onupdatemodelcontext = async (
{ content, structuredContent },
extra,
) => {
// Update the model context with the new snapshot
modelContext = {
type: "app_context",
content,
structuredContent,
timestamp: Date.now(),
};
return {};
};
//#endregion AppBridge_onupdatemodelcontext_storeContext
}
/**
* Example: Forward tool calls to the MCP server.
*/
function AppBridge_oncalltool_forwardToServer(bridge: AppBridge) {
//#region AppBridge_oncalltool_forwardToServer
bridge.oncalltool = async ({ name, arguments: args }, extra) => {
return mcpClient.request(
{ method: "tools/call", params: { name, arguments: args } },
CallToolResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_oncalltool_forwardToServer
}
/**
* Example: Forward list resources requests to the MCP server.
*/
function AppBridge_onlistresources_returnResources(bridge: AppBridge) {
//#region AppBridge_onlistresources_returnResources
bridge.onlistresources = async (params, extra) => {
return mcpClient.request(
{ method: "resources/list", params },
ListResourcesResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onlistresources_returnResources
}
/**
* Example: Forward read resource requests to the MCP server.
*/
function AppBridge_onreadresource_returnResource(bridge: AppBridge) {
//#region AppBridge_onreadresource_returnResource
bridge.onreadresource = async ({ uri }, extra) => {
return mcpClient.request(
{ method: "resources/read", params: { uri } },
ReadResourceResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onreadresource_returnResource
}
/**
* Example: Forward list prompts requests to the MCP server.
*/
function AppBridge_onlistprompts_returnPrompts(bridge: AppBridge) {
//#region AppBridge_onlistprompts_returnPrompts
bridge.onlistprompts = async (params, extra) => {
return mcpClient.request(
{ method: "prompts/list", params },
ListPromptsResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onlistprompts_returnPrompts
}
// Stub for example code - represents a hypothetical iframe element
declare const iframe: HTMLIFrameElement;
// Stub for example code - represents a hypothetical host context
declare const hostContext: {
availableDisplayModes?: Array<"inline" | "fullscreen" | "pip">;
};
/**
* Example: Handle ping requests from the Guest UI.
*/
function AppBridge_onping_handleRequest(bridge: AppBridge) {
//#region AppBridge_onping_handleRequest
bridge.onping = (params, extra) => {
console.log("Received ping from Guest UI");
};
//#endregion AppBridge_onping_handleRequest
}
/**
* Example: Handle size change notifications from the Guest UI.
*/
function AppBridge_onsizechange_handleResize(bridge: AppBridge) {
//#region AppBridge_onsizechange_handleResize
bridge.onsizechange = ({ width, height }) => {
if (width != null) {
iframe.style.width = `${width}px`;
}
if (height != null) {
iframe.style.height = `${height}px`;
}
};
//#endregion AppBridge_onsizechange_handleResize
}
/**
* Example: Handle display mode requests from the Guest UI.
*/
function AppBridge_onrequestdisplaymode_handleRequest(bridge: AppBridge) {
//#region AppBridge_onrequestdisplaymode_handleRequest
type McpUiDisplayMode = "inline" | "fullscreen" | "pip";
let currentDisplayMode: McpUiDisplayMode = "inline";
bridge.onrequestdisplaymode = async ({ mode }, extra) => {
const availableModes = hostContext.availableDisplayModes ?? ["inline"];
if (availableModes.includes(mode)) {
currentDisplayMode = mode;
return { mode };
}
// Return current mode if requested mode not available
return { mode: currentDisplayMode };
};
//#endregion AppBridge_onrequestdisplaymode_handleRequest
}
/**
* Example: Handle logging messages from the Guest UI.
*/
function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) {
//#region AppBridge_onloggingmessage_handleLog
bridge.onloggingmessage = ({ level, logger, data }) => {
const prefix = logger ? `[${logger}]` : "[Guest UI]";
console[level === "error" ? "error" : "log"](
`${prefix} ${level.toUpperCase()}:`,
data,
);
};
//#endregion AppBridge_onloggingmessage_handleLog
}
/**
* Example: Gracefully tear down the Guest UI before unmounting.
*/
async function AppBridge_teardownResource_gracefulShutdown(bridge: AppBridge) {
//#region AppBridge_teardownResource_gracefulShutdown
try {
await bridge.teardownResource({});
// Guest UI is ready, safe to unmount iframe
iframe.remove();
} catch (error) {
console.error("Teardown failed:", error);
}
//#endregion AppBridge_teardownResource_gracefulShutdown
}
/**
* Example: Update theme when user toggles dark mode.
*/
function AppBridge_setHostContext_updateTheme(bridge: AppBridge) {
//#region AppBridge_setHostContext_updateTheme
bridge.setHostContext({ theme: "dark" });
//#endregion AppBridge_setHostContext_updateTheme
}
/**
* Example: Update multiple context fields at once.
*/
function AppBridge_setHostContext_updateMultiple(bridge: AppBridge) {
//#region AppBridge_setHostContext_updateMultiple
bridge.setHostContext({
theme: "dark",
containerDimensions: { maxHeight: 600, width: 800 },
});
//#endregion AppBridge_setHostContext_updateMultiple
}
/**
* Example: Send tool input after initialization.
*/
function AppBridge_sendToolInput_afterInit(bridge: AppBridge) {
//#region AppBridge_sendToolInput_afterInit
bridge.oninitialized = () => {
bridge.sendToolInput({
arguments: { location: "New York", units: "metric" },
});
};
//#endregion AppBridge_sendToolInput_afterInit
}
/**
* Example: Stream partial arguments as they arrive.
*/
function AppBridge_sendToolInputPartial_streaming(bridge: AppBridge) {
//#region 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" },
});
//#endregion AppBridge_sendToolInputPartial_streaming
}
/**
* Example: Send tool result after execution.
*/
async function AppBridge_sendToolResult_afterExecution(
bridge: AppBridge,
mcpClientParam: Client,
args: Record<string, unknown>,
) {
//#region AppBridge_sendToolResult_afterExecution
// import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
const result = await mcpClient.request(
{ method: "tools/call", params: { name: "get_weather", arguments: args } },
CallToolResultSchema,
);
bridge.sendToolResult(result);
//#endregion AppBridge_sendToolResult_afterExecution
// Make the parameter used to satisfy linting
void mcpClientParam;
}
/**
* Example: User-initiated cancellation.
*/
function AppBridge_sendToolCancelled_userInitiated(bridge: AppBridge) {
//#region AppBridge_sendToolCancelled_userInitiated
// User clicked "Cancel" button
bridge.sendToolCancelled({ reason: "User cancelled the operation" });
//#endregion AppBridge_sendToolCancelled_userInitiated
}
/**
* Example: System-level cancellation.
*/
function AppBridge_sendToolCancelled_systemLevel(bridge: AppBridge) {
//#region AppBridge_sendToolCancelled_systemLevel
// Sampling error or timeout
bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
// Classifier intervention
bridge.sendToolCancelled({ reason: "Content policy violation detected" });
//#endregion AppBridge_sendToolCancelled_systemLevel
}
/**
* Example: Connect with MCP client for automatic forwarding.
*/
async function AppBridge_connect_withMcpClient(
mcpClientParam: Client,
hostInfo: { name: string; version: string },
capabilities: { openLinks: {}; serverTools: {}; logging: {} },
toolArgs: Record<string, unknown>,
) {
//#region AppBridge_connect_withMcpClient
const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
const transport = new PostMessageTransport(
iframe.contentWindow!,
iframe.contentWindow!,
);
bridge.oninitialized = () => {
console.log("Guest UI ready");
bridge.sendToolInput({ arguments: toolArgs });
};
await bridge.connect(transport);
//#endregion AppBridge_connect_withMcpClient
}
/**
* Example: Connect without MCP client using manual handlers.
*/
async function AppBridge_connect_withoutMcpClient(
hostInfo: { name: string; version: string },
capabilities: { openLinks: {}; serverTools: {}; logging: {} },
transport: Transport,
) {
//#region 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);
//#endregion AppBridge_connect_withoutMcpClient
}

View file

@ -215,39 +215,7 @@ type RequestHandlerExtra = Parameters<
* 5. **Teardown**: Call {@link teardownResource} before unmounting iframe
*
* @example Basic usage
* ```typescript
* import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
* import { Client } from '@modelcontextprotocol/sdk/client/index.js';
*
* // Create MCP client for the server
* const client = new Client({
* name: "MyHost",
* version: "1.0.0",
* });
* await client.connect(serverTransport);
*
* // Create bridge for the Guest UI
* 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("Guest UI initialized");
* // Now safe to send tool input
* bridge.sendToolInput({ arguments: { location: "NYC" } });
* };
*
* await bridge.connect(transport);
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_basicUsage}
*/
export class AppBridge extends Protocol<
AppRequest,
@ -270,23 +238,10 @@ export class AppBridge extends Protocol<
* @param options - Configuration options (inherited from Protocol)
*
* @example With MCP client (automatic forwarding)
* ```typescript
* const bridge = new AppBridge(
* mcpClient,
* { name: "MyHost", version: "1.0.0" },
* { openLinks: {}, serverTools: {}, logging: {} }
* );
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_constructor_withMcpClient}
*
* @example Without MCP client (manual handlers)
* ```typescript
* const bridge = new AppBridge(
* null,
* { name: "MyHost", version: "1.0.0" },
* { openLinks: {}, serverTools: {}, logging: {} }
* );
* bridge.oncalltool = async (params, extra) => { ... };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_constructor_withoutMcpClient}
*/
constructor(
private _client: Client | null,
@ -325,14 +280,7 @@ export class AppBridge extends Protocol<
* @returns Guest UI capabilities, or `undefined` if not yet initialized
*
* @example Check Guest UI capabilities after initialization
* ```typescript
* bridge.oninitialized = () => {
* const caps = bridge.getAppCapabilities();
* if (caps?.tools) {
* console.log("Guest UI provides tools");
* }
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_guestCapabilities_checkAfterInit}
*
* @see {@link McpUiAppCapabilities} for the capabilities structure
*/
@ -349,14 +297,7 @@ export class AppBridge extends Protocol<
* @returns Guest UI implementation info, or `undefined` if not yet initialized
*
* @example Log Guest UI information after initialization
* ```typescript
* bridge.oninitialized = () => {
* const appInfo = bridge.getAppVersion();
* if (appInfo) {
* console.log(`Guest UI: ${appInfo.name} v${appInfo.version}`);
* }
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_guestInfo_logAfterInit}
*/
getAppVersion(): Implementation | undefined {
return this._appInfo;
@ -376,11 +317,7 @@ export class AppBridge extends Protocol<
* @param extra - Request metadata (abort signal, session info)
*
* @example
* ```typescript
* bridge.onping = (params, extra) => {
* console.log("Received ping from Guest UI");
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onping_handleRequest}
*/
onping?: (params: PingRequest["params"], extra: RequestHandlerExtra) => void;
@ -395,16 +332,7 @@ export class AppBridge extends Protocol<
* host container dimension changes, use {@link setHostContext}.
*
* @example
* ```typescript
* bridge.onsizechange = ({ width, height }) => {
* if (width != null) {
* iframe.style.width = `${width}px`;
* }
* if (height != null) {
* iframe.style.height = `${height}px`;
* }
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onsizechange_handleResize}
*
* @see {@link McpUiSizeChangedNotification} for the notification type
* @see {@link app!App.sendSizeChanged} - the Guest UI method that sends these notifications
@ -462,12 +390,7 @@ export class AppBridge extends Protocol<
* initialization handshake and is ready to receive tool input and other data.
*
* @example
* ```typescript
* bridge.oninitialized = () => {
* console.log("Guest UI ready");
* bridge.sendToolInput({ arguments: toolArgs });
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_oninitialized_sendToolInput}
*
* @see {@link McpUiInitializedNotification} for the notification type
* @see {@link sendToolInput} for sending tool arguments to the Guest UI
@ -499,17 +422,7 @@ export class AppBridge extends Protocol<
* - Returns: `Promise<McpUiMessageResult>` with optional `isError` flag
*
* @example
* ```typescript
* 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 };
* }
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onmessage_logMessage}
*
* @see {@link McpUiMessageRequest} for the request type
* @see {@link McpUiMessageResult} for the result type
@ -547,26 +460,7 @@ export class AppBridge extends Protocol<
* - Returns: `Promise<McpUiOpenLinkResult>` with optional `isError` flag
*
* @example
* ```typescript
* 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 };
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onopenlink_handleRequest}
*
* @see {@link McpUiOpenLinkRequest} for the request type
* @see {@link McpUiOpenLinkResult} for the result type
@ -605,19 +499,7 @@ export class AppBridge extends Protocol<
* - Returns: `Promise<McpUiRequestDisplayModeResult>` with the actual mode set
*
* @example
* ```typescript
* let currentDisplayMode: McpUiDisplayMode = "inline";
*
* bridge.onrequestdisplaymode = async ({ mode }, extra) => {
* const availableModes = hostContext.availableDisplayModes ?? ["inline"];
* if (availableModes.includes(mode)) {
* currentDisplayMode = mode;
* return { mode };
* }
* // Return current mode if requested mode not available
* return { mode: currentDisplayMode };
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onrequestdisplaymode_handleRequest}
*
* @see {@link McpUiRequestDisplayModeRequest} for the request type
* @see {@link McpUiRequestDisplayModeResult} for the result type
@ -653,15 +535,7 @@ export class AppBridge extends Protocol<
* - `params.data` - Log message and optional structured data
*
* @example
* ```typescript
* bridge.onloggingmessage = ({ level, logger, data }) => {
* const prefix = logger ? `[${logger}]` : "[Guest UI]";
* console[level === "error" ? "error" : "log"](
* `${prefix} ${level.toUpperCase()}:`,
* data
* );
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onloggingmessage_handleLog}
*/
set onloggingmessage(
callback: (params: LoggingMessageNotification["params"]) => void,
@ -687,18 +561,7 @@ export class AppBridge extends Protocol<
* update received.
*
* @example
* ```typescript
* bridge.onupdatemodelcontext = async ({ content, structuredContent }, extra) => {
* // Update the model context with the new snapshot
* modelContext = {
* type: "app_context",
* content,
* structuredContent,
* timestamp: Date.now()
* };
* return {};
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onupdatemodelcontext_storeContext}
*
* @see {@link McpUiUpdateModelContextRequest} for the request type
*/
@ -729,15 +592,7 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* ```typescript
* bridge.oncalltool = async ({ name, arguments: args }, extra) => {
* return mcpClient.request(
* { method: "tools/call", params: { name, arguments: args } },
* CallToolResultSchema,
* { signal: extra.signal }
* );
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_oncalltool_forwardToServer}
*
* @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type
* @see `CallToolResult` from @modelcontextprotocol/sdk for the result type
@ -792,15 +647,7 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* ```typescript
* bridge.onlistresources = async (params, extra) => {
* return mcpClient.request(
* { method: "resources/list", params },
* ListResourcesResultSchema,
* { signal: extra.signal }
* );
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onlistresources_returnResources}
*
* @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type
@ -872,15 +719,7 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* ```typescript
* bridge.onreadresource = async ({ uri }, extra) => {
* return mcpClient.request(
* { method: "resources/read", params: { uri } },
* ReadResourceResultSchema,
* { signal: extra.signal }
* );
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onreadresource_returnResource}
*
* @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type
* @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type
@ -940,15 +779,7 @@ export class AppBridge extends Protocol<
* - `extra` - Request metadata (abort signal, session info)
*
* @example
* ```typescript
* bridge.onlistprompts = async (params, extra) => {
* return mcpClient.request(
* { method: "prompts/list", params },
* ListPromptsResultSchema,
* { signal: extra.signal }
* );
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_onlistprompts_returnPrompts}
*
* @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type
* @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type
@ -1084,17 +915,10 @@ export class AppBridge extends Protocol<
* @param hostContext - The complete new host context state
*
* @example Update theme when user toggles dark mode
* ```typescript
* bridge.setHostContext({ theme: "dark" });
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_setHostContext_updateTheme}
*
* @example Update multiple context fields
* ```typescript
* bridge.setHostContext({
* theme: "dark",
* containerDimensions: { maxHeight: 600, width: 800 }
* });
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_setHostContext_updateMultiple}
*
* @see {@link McpUiHostContext} for the context structure
* @see {@link McpUiHostContextChangedNotification} for the notification type
@ -1147,13 +971,7 @@ export class AppBridge extends Protocol<
* @param params - Complete tool call arguments
*
* @example
* ```typescript
* bridge.oninitialized = () => {
* bridge.sendToolInput({
* arguments: { location: "New York", units: "metric" }
* });
* };
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolInput_afterInit}
*
* @see {@link McpUiToolInputNotification} for the notification type
* @see {@link oninitialized} for the initialization callback
@ -1180,15 +998,7 @@ export class AppBridge extends Protocol<
* @param params - Partial tool call arguments (may be incomplete)
*
* @example Stream partial arguments as they arrive
* ```typescript
* // 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" } });
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolInputPartial_streaming}
*
* @see {@link McpUiToolInputPartialNotification} for the notification type
* @see {@link sendToolInput} for sending complete arguments
@ -1211,15 +1021,7 @@ export class AppBridge extends Protocol<
* @param params - Standard MCP tool execution result
*
* @example
* ```typescript
* import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
*
* const result = await mcpClient.request(
* { method: "tools/call", params: { name: "get_weather", arguments: args } },
* CallToolResultSchema
* );
* bridge.sendToolResult(result);
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution}
*
* @see {@link McpUiToolResultNotification} for the notification type
* @see {@link sendToolInput} for sending tool arguments before results
@ -1243,19 +1045,10 @@ export class AppBridge extends Protocol<
* - `reason`: Human-readable explanation for why the tool was cancelled
*
* @example User-initiated cancellation
* ```typescript
* // User clicked "Cancel" button
* bridge.sendToolCancelled({ reason: "User cancelled the operation" });
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolCancelled_userInitiated}
*
* @example System-level cancellation
* ```typescript
* // Sampling error or timeout
* bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
*
* // Classifier intervention
* bridge.sendToolCancelled({ reason: "Content policy violation detected" });
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolCancelled_systemLevel}
*
* @see {@link McpUiToolCancelledNotification} for the notification type
* @see {@link sendToolResult} for sending successful results
@ -1306,15 +1099,7 @@ export class AppBridge extends Protocol<
* @returns Promise resolving when Guest UI confirms readiness for teardown
*
* @example
* ```typescript
* try {
* await bridge.teardownResource({});
* // Guest UI is ready, safe to unmount iframe
* iframe.remove();
* } catch (error) {
* console.error("Teardown failed:", error);
* }
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_teardownResource_gracefulShutdown}
*/
teardownResource(
params: McpUiResourceTeardownRequest["params"],
@ -1359,32 +1144,10 @@ export class AppBridge extends Protocol<
* before calling `bridge.connect()`.
*
* @example With MCP client (automatic forwarding)
* ```typescript
* const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
* const transport = new PostMessageTransport(
* iframe.contentWindow!,
* iframe.contentWindow!,
* );
*
* bridge.oninitialized = () => {
* console.log("Guest UI ready");
* bridge.sendToolInput({ arguments: toolArgs });
* };
*
* await bridge.connect(transport);
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_connect_withMcpClient}
*
* @example Without MCP client (manual handlers)
* ```typescript
* const bridge = new AppBridge(null, hostInfo, capabilities);
*
* // Register handlers manually
* bridge.oncalltool = async (params, extra) => {
* // Custom tool call handling
* };
*
* await bridge.connect(transport);
* ```
* {@includeCode ./app-bridge.examples.ts#AppBridge_connect_withoutMcpClient}
*/
async connect(transport: Transport) {
if (this._client) {

446
src/app.examples.ts Normal file
View file

@ -0,0 +1,446 @@
/**
* Type-checked examples for {@link App} and constants in {@link ./app.ts}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import {
App,
PostMessageTransport,
McpUiToolInputNotificationSchema,
RESOURCE_URI_META_KEY,
} from "./app.js";
/**
* Example: How MCP servers use RESOURCE_URI_META_KEY (server-side, not in Apps).
*/
function RESOURCE_URI_META_KEY_serverSide(): CallToolResult {
//#region RESOURCE_URI_META_KEY_serverSide
// In an MCP server's tool handler:
return {
content: [{ type: "text", text: "Result" }],
_meta: {
[RESOURCE_URI_META_KEY]: "ui://weather/forecast",
},
};
//#endregion RESOURCE_URI_META_KEY_serverSide
}
/**
* Example: How hosts check for RESOURCE_URI_META_KEY metadata (host-side).
*/
async function RESOURCE_URI_META_KEY_hostSide(mcpClient: {
callTool: (args: {
name: string;
arguments: object;
}) => Promise<CallToolResult>;
}) {
//#region RESOURCE_URI_META_KEY_hostSide
const result = await mcpClient.callTool({ name: "weather", arguments: {} });
const uiUri = result._meta?.[RESOURCE_URI_META_KEY];
if (uiUri) {
// Load and display the UI resource
}
//#endregion RESOURCE_URI_META_KEY_hostSide
}
/**
* Example: App constructor with appInfo, capabilities, and options.
*/
function App_constructor_basic() {
//#region App_constructor_basic
const app = new App(
{ name: "MyApp", version: "1.0.0" },
{ tools: { listChanged: true } }, // capabilities
{ autoResize: true }, // options
);
//#endregion App_constructor_basic
return app;
}
/**
* Example: Basic usage of the App class with PostMessageTransport.
*/
async function App_basicUsage() {
//#region App_basicUsage
const app = new App(
{ name: "WeatherApp", version: "1.0.0" },
{}, // capabilities
);
// Register notification handler using setter (simpler)
app.ontoolinput = (params) => {
console.log("Tool arguments:", params.arguments);
};
// OR using inherited setNotificationHandler (more explicit)
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
(notification) => {
console.log("Tool arguments:", notification.params.arguments);
},
);
await app.connect(new PostMessageTransport(window.parent, window.parent));
//#endregion App_basicUsage
}
/**
* Example: Sending a message to the host's chat.
*/
async function App_sendMessage(app: App) {
//#region App_sendMessage
await app.sendMessage({
role: "user",
content: [{ type: "text", text: "Weather updated!" }],
});
//#endregion App_sendMessage
}
/**
* Example: Check host capabilities after connection.
*/
async function App_hostCapabilities_checkAfterConnection(
app: App,
transport: PostMessageTransport,
) {
//#region App_hostCapabilities_checkAfterConnection
await app.connect(transport);
const caps = app.getHostCapabilities();
if (caps === undefined) {
console.error("Not connected");
return;
}
if (caps.serverTools) {
console.log("Host supports server tool calls");
}
//#endregion App_hostCapabilities_checkAfterConnection
}
/**
* Example: Log host information after connection.
*/
async function App_hostInfo_logAfterConnection(
app: App,
transport: PostMessageTransport,
) {
//#region App_hostInfo_logAfterConnection
await app.connect(transport);
const host = app.getHostVersion();
if (host === undefined) {
console.error("Not connected");
return;
}
console.log(`Connected to ${host.name} v${host.version}`);
//#endregion App_hostInfo_logAfterConnection
}
/**
* Example: Access host context after connection.
*/
async function App_hostContext_accessAfterConnection(
app: App,
transport: PostMessageTransport,
) {
//#region App_hostContext_accessAfterConnection
await app.connect(transport);
const context = app.getHostContext();
if (context === undefined) {
console.error("Not connected");
return;
}
if (context.theme === "dark") {
document.body.classList.add("dark-theme");
}
if (context.toolInfo) {
console.log("Tool:", context.toolInfo.tool.name);
}
//#endregion App_hostContext_accessAfterConnection
}
/**
* Example: Using the ontoolinput setter (simpler approach).
*/
async function App_ontoolinput_setter(
app: App,
transport: PostMessageTransport,
) {
//#region 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(transport);
//#endregion App_ontoolinput_setter
}
/**
* Example: Using setNotificationHandler for tool input (more explicit approach).
*/
function App_ontoolinput_setNotificationHandler(app: App) {
//#region App_ontoolinput_setNotificationHandler
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
(notification) => {
console.log("Tool:", notification.params.arguments);
},
);
//#endregion App_ontoolinput_setNotificationHandler
}
/**
* Example: Progressive rendering of tool arguments using ontoolinputpartial.
*/
function App_ontoolinputpartial_progressiveRendering(app: App) {
//#region App_ontoolinputpartial_progressiveRendering
app.ontoolinputpartial = (params) => {
console.log("Partial args:", params.arguments);
// Update your UI progressively as arguments stream in
};
//#endregion App_ontoolinputpartial_progressiveRendering
}
/**
* Example: Display tool execution results using ontoolresult.
*/
function App_ontoolresult_displayResults(app: App) {
//#region App_ontoolresult_displayResults
app.ontoolresult = (params) => {
if (params.content) {
console.log("Tool output:", params.content);
}
if (params.isError) {
console.error("Tool execution failed");
}
};
//#endregion App_ontoolresult_displayResults
}
/**
* Example: Handle tool cancellation notifications.
*/
function App_ontoolcancelled_handleCancellation(app: App) {
//#region App_ontoolcancelled_handleCancellation
app.ontoolcancelled = (params) => {
console.log("Tool cancelled:", params.reason);
showCancelledMessage(params.reason ?? "Operation was cancelled");
};
//#endregion App_ontoolcancelled_handleCancellation
}
// Stub for example
declare function showCancelledMessage(message: string): void;
/**
* Example: Respond to theme changes using onhostcontextchanged.
*/
function App_onhostcontextchanged_respondToTheme(app: App) {
//#region App_onhostcontextchanged_respondToTheme
app.onhostcontextchanged = (params) => {
if (params.theme === "dark") {
document.body.classList.add("dark-theme");
} else {
document.body.classList.remove("dark-theme");
}
};
//#endregion App_onhostcontextchanged_respondToTheme
}
/**
* Example: Perform cleanup before teardown.
*/
function App_onteardown_performCleanup(app: App) {
//#region App_onteardown_performCleanup
app.onteardown = async () => {
await saveState();
closeConnections();
console.log("App ready for teardown");
return {};
};
//#endregion App_onteardown_performCleanup
}
// Stubs for example
declare function saveState(): Promise<void>;
declare function closeConnections(): void;
/**
* Example: Handle tool calls from the host.
*/
function App_oncalltool_handleFromHost(app: App) {
//#region 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}`);
};
//#endregion App_oncalltool_handleFromHost
}
/**
* Example: Return available tools from the onlisttools handler.
*/
function App_onlisttools_returnTools(app: App) {
//#region App_onlisttools_returnTools
app.onlisttools = async (params, extra) => {
return {
tools: ["calculate", "convert", "format"],
};
};
//#endregion App_onlisttools_returnTools
}
/**
* Example: Fetch updated weather data using callServerTool.
*/
async function App_callServerTool_fetchWeather(app: App) {
//#region 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);
}
//#endregion App_callServerTool_fetchWeather
}
/**
* Example: Send a text message from user interaction.
*/
async function App_sendMessage_textFromInteraction(app: App) {
//#region App_sendMessage_textFromInteraction
try {
await app.sendMessage({
role: "user",
content: [{ type: "text", text: "Show me details for item #42" }],
});
} catch (error) {
console.error("Failed to send message:", error);
// Handle error appropriately for your app
}
//#endregion App_sendMessage_textFromInteraction
}
/**
* Example: Log app state for debugging.
*/
function App_sendLog_debugState(app: App) {
//#region App_sendLog_debugState
app.sendLog({
level: "info",
data: "Weather data refreshed",
logger: "WeatherApp",
});
//#endregion App_sendLog_debugState
}
/**
* Example: Update model context with current app state.
*/
async function App_updateModelContext_appState(app: App) {
//#region App_updateModelContext_appState
await app.updateModelContext({
content: [{ type: "text", text: "User selected 3 items totaling $150.00" }],
});
//#endregion App_updateModelContext_appState
}
/**
* Example: Update with structured content.
*/
async function App_updateModelContext_structuredContent(app: App) {
//#region App_updateModelContext_structuredContent
await app.updateModelContext({
structuredContent: { selectedItems: 3, total: 150.0, currency: "USD" },
});
//#endregion App_updateModelContext_structuredContent
}
/**
* Example: Open documentation link.
*/
async function App_openLink_documentation(app: App) {
//#region App_openLink_documentation
try {
await app.openLink({ url: "https://docs.example.com" });
} catch (error) {
console.error("Failed to open link:", error);
// Optionally show fallback: display URL for manual copy
}
//#endregion App_openLink_documentation
}
/**
* Example: Request fullscreen mode.
*/
async function App_requestDisplayMode_fullscreen(app: App) {
//#region App_requestDisplayMode_fullscreen
const context = app.getHostContext();
if (context?.availableDisplayModes?.includes("fullscreen")) {
const result = await app.requestDisplayMode({ mode: "fullscreen" });
console.log("Display mode set to:", result.mode);
}
//#endregion App_requestDisplayMode_fullscreen
}
/**
* Example: Manually notify host of size change.
*/
function App_sendSizeChanged_manual(app: App) {
//#region App_sendSizeChanged_manual
app.sendSizeChanged({
width: 400,
height: 600,
});
//#endregion App_sendSizeChanged_manual
}
/**
* Example: Manual setup for custom scenarios (setupSizeChangedNotifications).
*/
async function App_setupAutoResize_manual(transport: PostMessageTransport) {
//#region 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();
//#endregion App_setupAutoResize_manual
}
/**
* Example: Connect with PostMessageTransport.
*/
async function App_connect_withPostMessageTransport() {
//#region 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);
}
//#endregion App_connect_withPostMessageTransport
}

View file

@ -70,24 +70,10 @@ export {
* in their tool handlers; App developers typically don't need to use it directly.
*
* @example How MCP servers use this key (server-side, not in Apps)
* ```typescript
* // In an MCP server's tool handler:
* return {
* content: [{ type: "text", text: "Result" }],
* _meta: {
* [RESOURCE_URI_META_KEY]: "ui://weather/forecast"
* }
* };
* ```
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_serverSide}
*
* @example How hosts check for this metadata (host-side)
* ```typescript
* const result = await mcpClient.callTool({ name: "weather", arguments: {} });
* const uiUri = result._meta?.[RESOURCE_URI_META_KEY];
* if (uiUri) {
* // Load and display the UI resource
* }
* ```
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_hostSide}
*/
export const RESOURCE_URI_META_KEY = "ui/resourceUri";
@ -167,41 +153,10 @@ type RequestHandlerExtra = Parameters<
* Both patterns work; use whichever fits your coding style better.
*
* @example Basic usage with PostMessageTransport
* ```typescript
* import {
* App,
* PostMessageTransport,
* McpUiToolInputNotificationSchema
* } from '@modelcontextprotocol/ext-apps';
*
* const app = new App(
* { name: "WeatherApp", version: "1.0.0" },
* {} // capabilities
* );
*
* // Register notification handler using setter (simpler)
* app.ontoolinput = (params) => {
* console.log("Tool arguments:", params.arguments);
* };
*
* // OR using inherited setNotificationHandler (more explicit)
* app.setNotificationHandler(
* McpUiToolInputNotificationSchema,
* (notification) => {
* console.log("Tool arguments:", notification.params.arguments);
* }
* );
*
* await app.connect(new PostMessageTransport(window.parent, window.parent));
* ```
* {@includeCode ./app.examples.ts#App_basicUsage}
*
* @example Sending a message to the host's chat
* ```typescript
* await app.sendMessage({
* role: "user",
* content: [{ type: "text", text: "Weather updated!" }]
* });
* ```
* {@includeCode ./app.examples.ts#App_sendMessage}
*/
export class App extends Protocol<AppRequest, AppNotification, AppResult> {
private _hostCapabilities?: McpUiHostCapabilities;
@ -216,13 +171,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param options - Configuration options including `autoResize` behavior
*
* @example
* ```typescript
* const app = new App(
* { name: "MyApp", version: "1.0.0" },
* { tools: { listChanged: true } }, // capabilities
* { autoResize: true } // options
* );
* ```
* {@includeCode ./app.examples.ts#App_constructor_basic}
*/
constructor(
private _appInfo: Implementation,
@ -251,17 +200,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host capabilities, or `undefined` if not yet connected
*
* @example Check host capabilities after connection
* ```typescript
* await app.connect(transport);
* const caps = app.getHostCapabilities();
* if (caps === undefined) {
* console.error("Not connected");
* return;
* }
* if (caps.serverTools) {
* console.log("Host supports server tool calls");
* }
* ```
* {@includeCode ./app.examples.ts#App_hostCapabilities_checkAfterConnection}
*
* @see {@link connect} for the initialization handshake
* @see {@link McpUiHostCapabilities} for the capabilities structure
@ -280,15 +219,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host implementation info, or `undefined` if not yet connected
*
* @example Log host information after connection
* ```typescript
* await app.connect(transport);
* const host = app.getHostVersion();
* if (host === undefined) {
* console.error("Not connected");
* return;
* }
* console.log(`Connected to ${host.name} v${host.version}`);
* ```
* {@includeCode ./app.examples.ts#App_hostInfo_logAfterConnection}
*
* @see {@link connect} for the initialization handshake
*/
@ -309,20 +240,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host context, or `undefined` if not yet connected
*
* @example Access host context after connection
* ```typescript
* await app.connect(transport);
* const context = app.getHostContext();
* if (context === undefined) {
* console.error("Not connected");
* return;
* }
* if (context.theme === "dark") {
* document.body.classList.add("dark-theme");
* }
* if (context.toolInfo) {
* console.log("Tool:", context.toolInfo.tool.name);
* }
* ```
* {@includeCode ./app.examples.ts#App_hostContext_accessAfterConnection}
*
* @see {@link connect} for the initialization handshake
* @see {@link onhostcontextchanged} for context change notifications
@ -347,24 +265,10 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with the tool input params ({@link McpUiToolInputNotification.params})
*
* @example Using the setter (simpler)
* ```typescript
* // 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(transport);
* ```
* {@includeCode ./app.examples.ts#App_ontoolinput_setter}
*
* @example Using setNotificationHandler (more explicit)
* ```typescript
* app.setNotificationHandler(
* McpUiToolInputNotificationSchema,
* (notification) => {
* console.log("Tool:", notification.params.arguments);
* }
* );
* ```
* {@includeCode ./app.examples.ts#App_ontoolinput_setNotificationHandler}
*
* @see {@link setNotificationHandler} for the underlying method
* @see {@link McpUiToolInputNotification} for the notification structure
@ -392,12 +296,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with each partial tool input update ({@link McpUiToolInputPartialNotification.params})
*
* @example Progressive rendering of tool arguments
* ```typescript
* app.ontoolinputpartial = (params) => {
* console.log("Partial args:", params.arguments);
* // Update your UI progressively as arguments stream in
* };
* ```
* {@includeCode ./app.examples.ts#App_ontoolinputpartial_progressiveRendering}
*
* @see {@link setNotificationHandler} for the underlying method
* @see {@link McpUiToolInputPartialNotification} for the notification structure
@ -426,16 +325,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with the tool result ({@link McpUiToolResultNotification.params})
*
* @example Display tool execution results
* ```typescript
* app.ontoolresult = (params) => {
* if (params.content) {
* console.log("Tool output:", params.content);
* }
* if (params.isError) {
* console.error("Tool execution failed");
* }
* };
* ```
* {@includeCode ./app.examples.ts#App_ontoolresult_displayResults}
*
* @see {@link setNotificationHandler} for the underlying method
* @see {@link McpUiToolResultNotification} for the notification structure
@ -465,12 +355,7 @@ 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}.
*
* @example Handle tool cancellation
* ```typescript
* app.ontoolcancelled = (params) => {
* console.log("Tool cancelled:", params.reason);
* showCancelledMessage(params.reason ?? "Operation was cancelled");
* };
* ```
* {@includeCode ./app.examples.ts#App_ontoolcancelled_handleCancellation}
*
* @see {@link setNotificationHandler} for the underlying method
* @see {@link McpUiToolCancelledNotification} for the notification structure
@ -504,15 +389,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param callback - Function called with the updated host context
*
* @example Respond to theme changes
* ```typescript
* app.onhostcontextchanged = (params) => {
* if (params.theme === "dark") {
* document.body.classList.add("dark-theme");
* } else {
* document.body.classList.remove("dark-theme");
* }
* };
* ```
* {@includeCode ./app.examples.ts#App_onhostcontextchanged_respondToTheme}
*
* @see {@link setNotificationHandler} for the underlying method
* @see {@link McpUiHostContextChangedNotification} for the notification structure
@ -550,14 +427,7 @@ 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
* ```typescript
* app.onteardown = async () => {
* await saveState();
* closeConnections();
* console.log("App ready for teardown");
* return {};
* };
* ```
* {@includeCode ./app.examples.ts#App_onteardown_performCleanup}
*
* @see {@link setRequestHandler} for the underlying method
* @see {@link McpUiResourceTeardownRequest} for the request structure
@ -593,15 +463,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* in the constructor.
*
* @example Handle tool calls from the host
* ```typescript
* 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}`);
* };
* ```
* {@includeCode ./app.examples.ts#App_oncalltool_handleFromHost}
*
* @see {@link setRequestHandler} for the underlying method
*/
@ -635,13 +497,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* allowed; capability validation occurs when handlers are invoked.
*
* @example Return available tools
* ```typescript
* app.onlisttools = async (params, extra) => {
* return {
* tools: ["calculate", "convert", "format"]
* };
* };
* ```
* {@includeCode ./app.examples.ts#App_onlisttools_returnTools}
*
* @see {@link setRequestHandler} for the underlying method
* @see {@link oncalltool} for handling tool execution
@ -730,21 +586,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* between transport failures (thrown) and tool execution failures (returned).
*
* @example Fetch updated weather data
* ```typescript
* 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);
* }
* ```
* {@includeCode ./app.examples.ts#App_callServerTool_fetchWeather}
*/
async callServerTool(
params: CallToolRequest["params"],
@ -770,17 +612,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If the host rejects the message
*
* @example Send a text message from user interaction
* ```typescript
* try {
* await app.sendMessage({
* role: "user",
* content: [{ type: "text", text: "Show me details for item #42" }]
* });
* } catch (error) {
* console.error("Failed to send message:", error);
* // Handle error appropriately for your app
* }
* ```
* {@includeCode ./app.examples.ts#App_sendMessage_textFromInteraction}
*
* @see {@link McpUiMessageRequest} for request structure
*/
@ -804,13 +636,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param params - Log level and message
*
* @example Log app state for debugging
* ```typescript
* app.sendLog({
* level: "info",
* data: "Weather data refreshed",
* logger: "WeatherApp"
* });
* ```
* {@includeCode ./app.examples.ts#App_sendLog_debugState}
*
* @returns Promise that resolves when the log notification is sent
*/
@ -838,18 +664,10 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If the host rejects the context update (e.g., unsupported content type)
*
* @example Update model context with current app state
* ```typescript
* await app.updateModelContext({
* content: [{ type: "text", text: "User selected 3 items totaling $150.00" }]
* });
* ```
* {@includeCode ./app.examples.ts#App_updateModelContext_appState}
*
* @example Update with structured content
* ```typescript
* await app.updateModelContext({
* structuredContent: { selectedItems: 3, total: 150.00, currency: "USD" }
* });
* ```
* {@includeCode ./app.examples.ts#App_updateModelContext_structuredContent}
*
* @returns Promise that resolves when the context update is acknowledged
*/
@ -881,14 +699,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If the request times out or the connection is lost
*
* @example Open documentation link
* ```typescript
* try {
* await app.openLink({ url: "https://docs.example.com" });
* } catch (error) {
* console.error("Failed to open link:", error);
* // Optionally show fallback: display URL for manual copy
* }
* ```
* {@includeCode ./app.examples.ts#App_openLink_documentation}
*
* @see {@link McpUiOpenLinkRequest} for request structure
*/
@ -919,13 +730,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Result containing the actual display mode that was set
*
* @example Request fullscreen mode
* ```typescript
* const context = app.getHostContext();
* if (context?.availableDisplayModes?.includes("fullscreen")) {
* const result = await app.requestDisplayMode({ mode: "fullscreen" });
* console.log("Display mode set to:", result.mode);
* }
* ```
* {@includeCode ./app.examples.ts#App_requestDisplayMode_fullscreen}
*
* @see {@link McpUiRequestDisplayModeRequest} for request structure
* @see {@link McpUiHostContext} for checking availableDisplayModes
@ -953,12 +758,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param params - New width and height in pixels
*
* @example Manually notify host of size change
* ```typescript
* app.sendSizeChanged({
* width: 400,
* height: 600
* });
* ```
* {@includeCode ./app.examples.ts#App_sendSizeChanged_manual}
*
* @returns Promise that resolves when the notification is sent
*
@ -985,16 +785,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Cleanup function to disconnect the observer
*
* @example Manual setup for custom scenarios
* ```typescript
* const app = new App(appInfo, capabilities, { autoResize: false });
* await app.connect(transport);
*
* // Later, enable auto-resize manually
* const cleanup = app.setupSizeChangedNotifications();
*
* // Clean up when done
* cleanup();
* ```
* {@includeCode ./app.examples.ts#App_setupAutoResize_manual}
*/
setupSizeChangedNotifications() {
let scheduled = false;
@ -1066,19 +857,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @throws {Error} If initialization fails or connection is lost
*
* @example Connect with PostMessageTransport
* ```typescript
* 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);
* }
* ```
* {@includeCode ./app.examples.ts#App_connect_withPostMessageTransport}
*
* @see {@link McpUiInitializeRequest} for the initialization request structure
* @see {@link McpUiInitializedNotification} for the initialized notification

View file

@ -0,0 +1,58 @@
/**
* Type-checked examples for {@link PostMessageTransport}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import { PostMessageTransport } from "./message-transport.js";
import type { App } from "./app.js";
import type { AppBridge } from "./app-bridge.js";
/**
* Example: Guest UI connecting to parent window.
*/
async function PostMessageTransport_guestUI(app: App) {
//#region PostMessageTransport_guestUI
const transport = new PostMessageTransport(window.parent, window.parent);
await app.connect(transport);
//#endregion PostMessageTransport_guestUI
}
/**
* Example: Host connecting to an iframe.
*/
async function PostMessageTransport_host(bridge: AppBridge) {
//#region PostMessageTransport_host
const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
const transport = new PostMessageTransport(
iframe.contentWindow!,
iframe.contentWindow!,
);
await bridge.connect(transport);
//#endregion PostMessageTransport_host
}
/**
* Example: Creating transport for guest UI (constructor only).
*/
function PostMessageTransport_constructor_guestUI() {
//#region PostMessageTransport_constructor_guestUI
const transport = new PostMessageTransport(window.parent, window.parent);
//#endregion PostMessageTransport_constructor_guestUI
}
/**
* Example: Creating transport for host (constructor only).
*/
function PostMessageTransport_constructor_host() {
//#region PostMessageTransport_constructor_host
const iframe = document.getElementById("app") as HTMLIFrameElement;
const transport = new PostMessageTransport(
iframe.contentWindow!,
iframe.contentWindow!,
);
//#endregion PostMessageTransport_constructor_host
}

View file

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

View file

@ -0,0 +1,26 @@
/**
* Type-checked examples for the React module overview.
*
* @module
*/
import { useApp } from "./index.js";
/**
* Example: Basic React App from module overview.
*/
function index_basicReactApp() {
//#region 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>;
}
//#endregion index_basicReactApp
}

View file

@ -17,21 +17,7 @@
* @module @modelcontextprotocol/ext-apps/react
*
* @example Basic React App
* ```tsx
* import { useApp } from '@modelcontextprotocol/ext-apps/react';
*
* 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>;
* }
* ```
* {@includeCode ./index.examples.tsx#index_basicReactApp}
*/
export * from "./useApp";
export * from "./useAutoResize";

View file

@ -0,0 +1,54 @@
/**
* Type-checked examples for the useApp hook.
*
* @module
*/
import { useApp, McpUiToolInputNotificationSchema } from "./index.js";
/**
* Example: Register a notification handler in onAppCreated.
*/
function useApp_registerHandler() {
//#region useApp_registerHandler
useApp({
appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {},
onAppCreated: (app) => {
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
(notification) => {
console.log("Tool input:", notification.params.arguments);
},
);
},
});
//#endregion useApp_registerHandler
}
/**
* Example: Basic usage of useApp hook.
*/
function useApp_basicUsage() {
//#region useApp_basicUsage
function MyApp() {
const { app, isConnected, error } = useApp({
appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {},
onAppCreated: (app) => {
// Register handlers before connection
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
(notification) => {
console.log("Tool input:", notification.params.arguments);
},
);
},
});
if (error) return <div>Error: {error.message}</div>;
if (!isConnected) return <div>Connecting...</div>;
return <div>Connected!</div>;
}
//#endregion useApp_basicUsage
}

View file

@ -30,18 +30,7 @@ export interface UseAppOptions {
* @param app - The newly created `App` instance
*
* @example Register a notification handler
* ```typescript
* import { McpUiToolInputNotificationSchema } from '@modelcontextprotocol/ext-apps/react';
*
* onAppCreated: (app) => {
* app.setNotificationHandler(
* McpUiToolInputNotificationSchema,
* (notification) => {
* console.log("Tool input:", notification.params.arguments);
* }
* );
* }
* ```
* {@includeCode ./useApp.examples.tsx#useApp_registerHandler}
*/
onAppCreated?: (app: App) => void;
}
@ -81,29 +70,7 @@ export interface AppState {
* timeouts, initialization handshake failures, or transport errors).
*
* @example Basic usage
* ```typescript
* import { useApp, McpUiToolInputNotificationSchema } from '@modelcontextprotocol/ext-apps/react';
*
* function MyApp() {
* const { app, isConnected, error } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* onAppCreated: (app) => {
* // Register handlers before connection
* app.setNotificationHandler(
* McpUiToolInputNotificationSchema,
* (notification) => {
* console.log("Tool input:", notification.params.arguments);
* }
* );
* },
* });
*
* if (error) return <div>Error: {error.message}</div>;
* if (!isConnected) return <div>Connecting...</div>;
* return <div>Connected!</div>;
* }
* ```
* {@includeCode ./useApp.examples.tsx#useApp_basicUsage}
*
* @see {@link App.connect} for the underlying connection method
* @see {@link useAutoResize} for manual auto-resize control when using custom App options

View file

@ -0,0 +1,42 @@
/**
* Type-checked examples for the useAutoResize hook.
*
* @module
*/
import { useState, useEffect } from "react";
import { App, PostMessageTransport } from "./index.js";
import { useAutoResize } from "./useAutoResize.js";
/**
* Example: Manual App creation with custom auto-resize control.
*/
function useAutoResize_manualApp() {
//#region 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>;
}
//#endregion useAutoResize_manualApp
}

View file

@ -20,32 +20,7 @@ import { App } from "../app";
* cause unnecessary effect re-runs; omit this parameter.
*
* @example Manual App creation with custom auto-resize control
* ```tsx
* 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>;
* }
* ```
* {@includeCode ./useAutoResize.examples.tsx#useAutoResize_manualApp}
*
* @see {@link App.setupSizeChangedNotifications} for the underlying implementation
* @see {@link useApp} which enables auto-resize by default

View file

@ -0,0 +1,46 @@
/**
* Type-checked examples for the useDocumentTheme hook.
*
* @module
*/
import { useDocumentTheme } from "./useDocumentTheme.js";
// Stub components for examples
declare function DarkIcon(): React.JSX.Element;
declare function LightIcon(): React.JSX.Element;
/**
* Example: Conditionally render based on theme.
*/
function useDocumentTheme_conditionalRender() {
//#region useDocumentTheme_conditionalRender
function MyApp() {
const theme = useDocumentTheme();
return <div>{theme === "dark" ? <DarkIcon /> : <LightIcon />}</div>;
}
//#endregion useDocumentTheme_conditionalRender
}
/**
* Example: Use with theme-aware styling.
*/
function useDocumentTheme_themedButton() {
//#region useDocumentTheme_themedButton
function ThemedButton() {
const theme = useDocumentTheme();
return (
<button
style={{
background: theme === "dark" ? "#333" : "#fff",
color: theme === "dark" ? "#fff" : "#333",
}}
>
Click me
</button>
);
}
//#endregion useDocumentTheme_themedButton
}

View file

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

View file

@ -0,0 +1,87 @@
/**
* Type-checked examples for the useHostStyles hooks.
*
* @module
*/
import { useState } from "react";
import {
useApp,
useHostStyleVariables,
useHostFonts,
useHostStyles,
App,
} from "./index.js";
import type { McpUiHostContext } from "../types.js";
/**
* Example: Basic usage of useHostStyleVariables.
*/
function useHostStyleVariables_basicUsage() {
//#region useHostStyleVariables_basicUsage
function MyApp() {
const { app, isConnected } = 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>
);
}
//#endregion useHostStyleVariables_basicUsage
}
/**
* Example: Basic usage of useHostFonts with useApp.
*/
function useHostFonts_basicUsage() {
//#region useHostFonts_basicUsage
function MyApp() {
const { app, isConnected } = useApp({
appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {},
});
// Automatically apply host fonts
useHostFonts(app);
return <div style={{ fontFamily: "var(--font-sans)" }}>Hello!</div>;
}
//#endregion useHostFonts_basicUsage
}
/**
* Example: useHostFonts with initial context.
*/
function useHostFonts_withInitialContext(app: App) {
//#region useHostFonts_withInitialContext
const [hostContext, setHostContext] = useState<McpUiHostContext | null>(null);
// ... get initial context from app.connect() result
useHostFonts(app, hostContext);
//#endregion useHostFonts_withInitialContext
}
/**
* Example: Basic usage of useHostStyles.
*/
function useHostStyles_basicUsage() {
const appInfo = { name: "MyApp", version: "1.0.0" };
//#region useHostStyles_basicUsage
function MyApp() {
const { app } = useApp({ appInfo, capabilities: {} });
useHostStyles(app, app?.getHostContext());
return (
<div style={{ background: "var(--color-background-primary)" }}>...</div>
);
}
//#endregion useHostStyles_basicUsage
}

View file

@ -26,25 +26,7 @@ import { McpUiHostContext } from "../types";
* If provided, styles and theme will be applied immediately on mount.
*
* @example
* ```tsx
* import { useApp, useHostStyleVariables } from '@modelcontextprotocol/ext-apps/react';
*
* function MyApp() {
* const { app, isConnected } = 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>
* );
* }
* ```
* {@includeCode ./useHostStyles.examples.tsx#useHostStyleVariables_basicUsage}
*
* @see {@link applyHostStyleVariables} for the underlying styles function
* @see {@link applyDocumentTheme} for the underlying theme function
@ -107,35 +89,10 @@ export function useHostStyleVariables(
* If provided, fonts will be applied immediately on mount.
*
* @example Basic usage with useApp
* ```tsx
* import { useApp } from '@modelcontextprotocol/ext-apps/react';
* import { useHostFonts } from '@modelcontextprotocol/ext-apps/react';
*
* function MyApp() {
* const { app, isConnected } = useApp({
* appInfo: { name: "MyApp", version: "1.0.0" },
* capabilities: {},
* });
*
* // Automatically apply host fonts
* useHostFonts(app);
*
* return (
* <div style={{ fontFamily: 'var(--font-sans)' }}>
* Hello!
* </div>
* );
* }
* ```
* {@includeCode ./useHostStyles.examples.tsx#useHostFonts_basicUsage}
*
* @example With initial context
* ```tsx
* const [hostContext, setHostContext] = useState<McpUiHostContext | null>(null);
*
* // ... get initial context from app.connect() result
*
* useHostFonts(app, hostContext);
* ```
* {@includeCode ./useHostStyles.examples.tsx#useHostFonts_withInitialContext}
*
* @see {@link applyHostFonts} for the underlying fonts function
* @see {@link useHostStyleVariables} for applying style variables and theme
@ -182,14 +139,7 @@ export function useHostFonts(
* Pass `app?.getHostContext()` to apply styles immediately on mount.
*
* @example
* ```tsx
* function MyApp() {
* const { app } = useApp({ appInfo, capabilities: {} });
* useHostStyles(app, app?.getHostContext());
*
* return <div style={{ background: 'var(--color-background-primary)' }}>...</div>;
* }
* ```
* {@includeCode ./useHostStyles.examples.tsx#useHostStyles_basicUsage}
*
* @see {@link useHostStyleVariables} for style variables and theme only
* @see {@link useHostFonts} for fonts only

View file

@ -0,0 +1,192 @@
/**
* Type-checked examples for {@link registerAppTool} and {@link registerAppResource}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "./index.js";
// Stubs for external functions used in examples
declare function fetchWeather(
location: string,
): Promise<{ temp: number; conditions: string }>;
declare function getCart(): Promise<{ items: unknown[]; total: number }>;
declare function updateCartItem(
itemId: string,
quantity: number,
): Promise<{ items: unknown[]; total: number }>;
declare const fs: { readFile(path: string, encoding: string): Promise<string> };
declare function readCallback(): Promise<{
contents: { uri: string; mimeType: string; text: string }[];
}>;
/**
* Example: Module overview showing basic registration of tools and resources.
*/
function index_overview(
server: McpServer,
handler: () => Promise<{ content: { type: "text"; text: string }[] }>,
) {
//#region index_overview
// Register a tool that displays a widget
registerAppTool(
server,
"weather",
{
description: "Get weather forecast",
_meta: { ui: { resourceUri: "ui://weather/widget.html" } },
},
handler,
);
// Register the HTML resource the tool references
registerAppResource(
server,
"Weather Widget",
"ui://weather/widget.html",
{},
readCallback,
);
//#endregion index_overview
}
/**
* Example: Basic usage of registerAppTool with input schema and handler.
*/
function registerAppTool_basicUsage(server: McpServer) {
//#region 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/widget.html" },
},
},
async (args) => {
const weather = await fetchWeather(args.location);
return { content: [{ type: "text", text: JSON.stringify(weather) }] };
},
);
//#endregion registerAppTool_basicUsage
}
/**
* Example: Tool visibility - create app-only tools for UI actions.
*/
function registerAppTool_toolVisibility(server: McpServer) {
//#region registerAppTool_toolVisibility
// Main tool - visible to both model and app (default)
registerAppTool(
server,
"show-cart",
{
description: "Display the user's shopping cart",
_meta: {
ui: {
resourceUri: "ui://shop/cart.html",
visibility: ["model", "app"],
},
},
},
async () => {
const cart = await getCart();
return { content: [{ type: "text", text: JSON.stringify(cart) }] };
},
);
// App-only tool - hidden from the model, only callable by the UI
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) }] };
},
);
//#endregion registerAppTool_toolVisibility
}
/**
* Example: Basic usage of registerAppResource.
*/
function registerAppResource_basicUsage(server: McpServer) {
//#region registerAppResource_basicUsage
registerAppResource(
server,
"Weather Widget",
"ui://weather/widget.html",
{
description: "Interactive weather display",
},
async () => ({
contents: [
{
uri: "ui://weather/widget.html",
mimeType: RESOURCE_MIME_TYPE,
text: await fs.readFile("dist/widget.html", "utf-8"),
},
],
}),
);
//#endregion registerAppResource_basicUsage
}
/**
* Example: registerAppResource with CSP configuration for external domains.
*/
function registerAppResource_withCsp(
server: McpServer,
musicPlayerHtml: string,
) {
//#region 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,
// CSP must be on the content item, not the resource config
_meta: {
ui: {
csp: {
connectDomains: ["https://api.example.com"], // For fetch/WebSocket
resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
},
},
},
},
],
}),
);
//#endregion registerAppResource_withCsp
}

View file

@ -8,18 +8,7 @@
* @module server-helpers
*
* @example
* ```typescript
* import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
*
* // Register a tool that displays a widget
* registerAppTool(server, "weather", {
* description: "Get weather forecast",
* _meta: { ui: { resourceUri: "ui://weather/widget.html" } },
* }, handler);
*
* // Register the HTML resource the tool references
* registerAppResource(server, "Weather Widget", "ui://weather/widget.html", {}, readCallback);
* ```
* {@includeCode ./index.examples.ts#index_overview}
*/
import {
@ -126,57 +115,10 @@ export interface McpUiAppResourceConfig extends ResourceMetadata {
* @param cb - Tool handler function
*
* @example Basic usage
* ```typescript
* import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
* import { z } from 'zod';
*
* registerAppTool(server, "get-weather", {
* title: "Get Weather",
* description: "Get current weather for a location",
* inputSchema: { location: z.string() },
* _meta: {
* ui: { resourceUri: "ui://weather/widget.html" },
* },
* }, async (args) => {
* const weather = await fetchWeather(args.location);
* return { content: [{ type: "text", text: JSON.stringify(weather) }] };
* });
* ```
* {@includeCode ./index.examples.ts#registerAppTool_basicUsage}
*
* @example Tool visibility - create app-only tools for UI actions
* ```typescript
* import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
* import { z } from 'zod';
*
* // Main tool - visible to both model and app (default)
* registerAppTool(server, "show-cart", {
* description: "Display the user's shopping cart",
* _meta: {
* ui: {
* resourceUri: "ui://shop/cart.html",
* visibility: ["model", "app"],
* },
* },
* }, async () => {
* const cart = await getCart();
* return { content: [{ type: "text", text: JSON.stringify(cart) }] };
* });
*
* // App-only tool - hidden from the model, only callable by the UI
* 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) }] };
* });
* ```
* {@includeCode ./index.examples.ts#registerAppTool_toolVisibility}
*
* @see {@link registerAppResource} to register the HTML resource referenced by the tool
*/
@ -225,41 +167,10 @@ export function registerAppTool<
* @param readCallback - Callback that returns the resource contents
*
* @example Basic usage
* ```typescript
* import { registerAppResource } from '@modelcontextprotocol/ext-apps/server';
*
* registerAppResource(server, "Weather Widget", "ui://weather/widget.html", {
* description: "Interactive weather display",
* }, async () => ({
* contents: [{
* uri: "ui://weather/widget.html",
* mimeType: RESOURCE_MIME_TYPE,
* text: await fs.readFile("dist/widget.html", "utf-8"),
* }],
* }));
* ```
* {@includeCode ./index.examples.ts#registerAppResource_basicUsage}
*
* @example With CSP configuration for external domains
* ```typescript
* 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: PLAYER_HTML,
* // CSP must be on the content item, not the resource config
* _meta: {
* ui: {
* csp: {
* connectDomains: ["https://api.example.com"], // For fetch/WebSocket
* resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
* },
* },
* },
* }],
* }));
* ```
* {@includeCode ./index.examples.ts#registerAppResource_withCsp}
*
* @see {@link registerAppTool} to register tools that reference this resource
*/

112
src/styles.examples.ts Normal file
View file

@ -0,0 +1,112 @@
/**
* Type-checked examples for style utilities in {@link ./styles.ts}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import type { McpUiHostContext } from "./types.js";
import { App } from "./app.js";
import {
getDocumentTheme,
applyDocumentTheme,
applyHostStyleVariables,
applyHostFonts,
} from "./styles.js";
// Stub declarations for examples
declare const app: App;
declare const hostContext: McpUiHostContext;
/**
* Example: Check current theme.
*/
function getDocumentTheme_checkCurrent() {
//#region getDocumentTheme_checkCurrent
const theme = getDocumentTheme();
console.log(`Current theme: ${theme}`);
//#endregion getDocumentTheme_checkCurrent
}
/**
* Example: Apply theme from host context.
*/
function applyDocumentTheme_fromHostContext() {
//#region applyDocumentTheme_fromHostContext
app.onhostcontextchanged = (params) => {
if (params.theme) {
applyDocumentTheme(params.theme);
}
};
//#endregion applyDocumentTheme_fromHostContext
}
/**
* Example: Apply style variables from host context.
*/
function applyHostStyleVariables_fromHostContext() {
//#region applyHostStyleVariables_fromHostContext
app.onhostcontextchanged = (params) => {
if (params.styles?.variables) {
applyHostStyleVariables(params.styles.variables);
}
};
//#endregion applyHostStyleVariables_fromHostContext
}
/**
* Example: Apply to a specific element.
*/
function applyHostStyleVariables_toElement() {
//#region applyHostStyleVariables_toElement
const container = document.getElementById("app-root");
if (container && hostContext.styles?.variables) {
applyHostStyleVariables(hostContext.styles.variables, container);
}
//#endregion applyHostStyleVariables_toElement
}
/**
* Example: Apply fonts from host context.
*/
function applyHostFonts_fromHostContext() {
//#region applyHostFonts_fromHostContext
app.onhostcontextchanged = (params) => {
if (params.styles?.css?.fonts) {
applyHostFonts(params.styles.css.fonts);
}
};
//#endregion applyHostFonts_fromHostContext
}
/**
* Example: Host providing self-hosted fonts.
*/
function applyHostFonts_selfHosted() {
//#region 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);
//#endregion applyHostFonts_selfHosted
}
/**
* Example: Host providing Google Fonts.
*/
function applyHostFonts_googleFonts() {
//#region applyHostFonts_googleFonts
// Example of what a host might provide:
const fontCss = `
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
`;
applyHostFonts(fontCss);
//#endregion applyHostFonts_googleFonts
}

Some files were not shown because too many files have changed in this diff Show more