Refine JSDoc examples for consistency and type safety

Follow-up to the examples extraction: pass dependencies as function
parameters instead of using global stub declarations, rename region
names to match method names (e.g., `AppBridge_guestCapabilities_*` →
`AppBridge_getAppCapabilities_*`), and simplify examples using optional
chaining.

Also clarifies `RESOURCE_URI_META_KEY` documentation to indicate it's
for tool definition metadata (from `tools/list`), not tool result
metadata, and removes the redundant `setNotificationHandler` example
in favor of the simpler event setter approach.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-18 18:24:44 -06:00
commit f0c7cc1b14
11 changed files with 197 additions and 250 deletions

View file

@ -17,6 +17,7 @@ import {
ListPromptsResultSchema, ListPromptsResultSchema,
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { AppBridge, PostMessageTransport } from "./app-bridge.js"; import { AppBridge, PostMessageTransport } from "./app-bridge.js";
import type { McpUiDisplayMode } from "./types.js";
/** /**
* Example: Basic usage of the AppBridge class with PostMessageTransport. * Example: Basic usage of the AppBridge class with PostMessageTransport.
@ -65,7 +66,6 @@ function AppBridge_constructor_withMcpClient(mcpClient: Client) {
{ openLinks: {}, serverTools: {}, logging: {} }, { openLinks: {}, serverTools: {}, logging: {} },
); );
//#endregion AppBridge_constructor_withMcpClient //#endregion AppBridge_constructor_withMcpClient
return bridge;
} }
/** /**
@ -83,42 +83,43 @@ function AppBridge_constructor_withoutMcpClient() {
return { content: [] }; return { content: [] };
}; };
//#endregion AppBridge_constructor_withoutMcpClient //#endregion AppBridge_constructor_withoutMcpClient
return bridge;
} }
/** /**
* Example: Check Guest UI capabilities after initialization. * Example: Check Guest UI capabilities after initialization.
*/ */
function AppBridge_guestCapabilities_checkAfterInit(bridge: AppBridge) { function AppBridge_getAppCapabilities_checkAfterInit(bridge: AppBridge) {
//#region AppBridge_guestCapabilities_checkAfterInit //#region AppBridge_getAppCapabilities_checkAfterInit
bridge.oninitialized = () => { bridge.oninitialized = () => {
const caps = bridge.getAppCapabilities(); const caps = bridge.getAppCapabilities();
if (caps?.tools) { if (caps?.tools) {
console.log("Guest UI provides tools"); console.log("Guest UI provides tools");
} }
}; };
//#endregion AppBridge_guestCapabilities_checkAfterInit //#endregion AppBridge_getAppCapabilities_checkAfterInit
} }
/** /**
* Example: Log Guest UI information after initialization. * Example: Log Guest UI information after initialization.
*/ */
function AppBridge_guestInfo_logAfterInit(bridge: AppBridge) { function AppBridge_getAppVersion_logAfterInit(bridge: AppBridge) {
//#region AppBridge_guestInfo_logAfterInit //#region AppBridge_getAppVersion_logAfterInit
bridge.oninitialized = () => { bridge.oninitialized = () => {
const appInfo = bridge.getAppVersion(); const appInfo = bridge.getAppVersion();
if (appInfo) { if (appInfo) {
console.log(`Guest UI: ${appInfo.name} v${appInfo.version}`); console.log(`Guest UI: ${appInfo.name} v${appInfo.version}`);
} }
}; };
//#endregion AppBridge_guestInfo_logAfterInit //#endregion AppBridge_getAppVersion_logAfterInit
} }
/** /**
* Example: Handle Guest UI initialization and send tool input. * Example: Handle Guest UI initialization and send tool input.
*/ */
function AppBridge_oninitialized_sendToolInput(bridge: AppBridge) { function AppBridge_oninitialized_sendToolInput(
const toolArgs = { location: "NYC" }; bridge: AppBridge,
toolArgs: Record<string, unknown>,
) {
//#region AppBridge_oninitialized_sendToolInput //#region AppBridge_oninitialized_sendToolInput
bridge.oninitialized = () => { bridge.oninitialized = () => {
console.log("Guest UI ready"); console.log("Guest UI ready");
@ -162,18 +163,11 @@ declare function showDialog(options: {
buttons: string[]; buttons: string[];
}): Promise<boolean>; }): Promise<boolean>;
// Stub for example code - represents a hypothetical model context storage // Stub for example code - represents a hypothetical model context manager
declare let modelContext: { declare const modelContextManager: {
type: string; update(context: { content?: unknown; structuredContent?: unknown }): void;
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. * Example: Handle external link requests from the Guest UI.
*/ */
@ -209,13 +203,8 @@ function AppBridge_onupdatemodelcontext_storeContext(bridge: AppBridge) {
{ content, structuredContent }, { content, structuredContent },
extra, extra,
) => { ) => {
// Update the model context with the new snapshot // Store the context snapshot for inclusion in the next model request
modelContext = { modelContextManager.update({ content, structuredContent });
type: "app_context",
content,
structuredContent,
timestamp: Date.now(),
};
return {}; return {};
}; };
//#endregion AppBridge_onupdatemodelcontext_storeContext //#endregion AppBridge_onupdatemodelcontext_storeContext
@ -224,11 +213,14 @@ function AppBridge_onupdatemodelcontext_storeContext(bridge: AppBridge) {
/** /**
* Example: Forward tool calls to the MCP server. * Example: Forward tool calls to the MCP server.
*/ */
function AppBridge_oncalltool_forwardToServer(bridge: AppBridge) { function AppBridge_oncalltool_forwardToServer(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_oncalltool_forwardToServer //#region AppBridge_oncalltool_forwardToServer
bridge.oncalltool = async ({ name, arguments: args }, extra) => { bridge.oncalltool = async (params, extra) => {
return mcpClient.request( return mcpClient.request(
{ method: "tools/call", params: { name, arguments: args } }, { method: "tools/call", params },
CallToolResultSchema, CallToolResultSchema,
{ signal: extra.signal }, { signal: extra.signal },
); );
@ -239,7 +231,10 @@ function AppBridge_oncalltool_forwardToServer(bridge: AppBridge) {
/** /**
* Example: Forward list resources requests to the MCP server. * Example: Forward list resources requests to the MCP server.
*/ */
function AppBridge_onlistresources_returnResources(bridge: AppBridge) { function AppBridge_onlistresources_returnResources(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onlistresources_returnResources //#region AppBridge_onlistresources_returnResources
bridge.onlistresources = async (params, extra) => { bridge.onlistresources = async (params, extra) => {
return mcpClient.request( return mcpClient.request(
@ -254,11 +249,14 @@ function AppBridge_onlistresources_returnResources(bridge: AppBridge) {
/** /**
* Example: Forward read resource requests to the MCP server. * Example: Forward read resource requests to the MCP server.
*/ */
function AppBridge_onreadresource_returnResource(bridge: AppBridge) { function AppBridge_onreadresource_returnResource(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onreadresource_returnResource //#region AppBridge_onreadresource_returnResource
bridge.onreadresource = async ({ uri }, extra) => { bridge.onreadresource = async (params, extra) => {
return mcpClient.request( return mcpClient.request(
{ method: "resources/read", params: { uri } }, { method: "resources/read", params },
ReadResourceResultSchema, ReadResourceResultSchema,
{ signal: extra.signal }, { signal: extra.signal },
); );
@ -269,7 +267,10 @@ function AppBridge_onreadresource_returnResource(bridge: AppBridge) {
/** /**
* Example: Forward list prompts requests to the MCP server. * Example: Forward list prompts requests to the MCP server.
*/ */
function AppBridge_onlistprompts_returnPrompts(bridge: AppBridge) { function AppBridge_onlistprompts_returnPrompts(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onlistprompts_returnPrompts //#region AppBridge_onlistprompts_returnPrompts
bridge.onlistprompts = async (params, extra) => { bridge.onlistprompts = async (params, extra) => {
return mcpClient.request( return mcpClient.request(
@ -281,14 +282,6 @@ function AppBridge_onlistprompts_returnPrompts(bridge: AppBridge) {
//#endregion AppBridge_onlistprompts_returnPrompts //#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. * Example: Handle ping requests from the Guest UI.
*/ */
@ -303,7 +296,10 @@ function AppBridge_onping_handleRequest(bridge: AppBridge) {
/** /**
* Example: Handle size change notifications from the Guest UI. * Example: Handle size change notifications from the Guest UI.
*/ */
function AppBridge_onsizechange_handleResize(bridge: AppBridge) { function AppBridge_onsizechange_handleResize(
bridge: AppBridge,
iframe: HTMLIFrameElement,
) {
//#region AppBridge_onsizechange_handleResize //#region AppBridge_onsizechange_handleResize
bridge.onsizechange = ({ width, height }) => { bridge.onsizechange = ({ width, height }) => {
if (width != null) { if (width != null) {
@ -319,18 +315,16 @@ function AppBridge_onsizechange_handleResize(bridge: AppBridge) {
/** /**
* Example: Handle display mode requests from the Guest UI. * Example: Handle display mode requests from the Guest UI.
*/ */
function AppBridge_onrequestdisplaymode_handleRequest(bridge: AppBridge) { function AppBridge_onrequestdisplaymode_handleRequest(
bridge: AppBridge,
currentDisplayMode: McpUiDisplayMode,
availableDisplayModes: McpUiDisplayMode[],
) {
//#region AppBridge_onrequestdisplaymode_handleRequest //#region AppBridge_onrequestdisplaymode_handleRequest
type McpUiDisplayMode = "inline" | "fullscreen" | "pip";
let currentDisplayMode: McpUiDisplayMode = "inline";
bridge.onrequestdisplaymode = async ({ mode }, extra) => { bridge.onrequestdisplaymode = async ({ mode }, extra) => {
const availableModes = hostContext.availableDisplayModes ?? ["inline"]; if (availableDisplayModes.includes(mode)) {
if (availableModes.includes(mode)) {
currentDisplayMode = mode; currentDisplayMode = mode;
return { mode };
} }
// Return current mode if requested mode not available
return { mode: currentDisplayMode }; return { mode: currentDisplayMode };
}; };
//#endregion AppBridge_onrequestdisplaymode_handleRequest //#endregion AppBridge_onrequestdisplaymode_handleRequest
@ -342,9 +336,8 @@ function AppBridge_onrequestdisplaymode_handleRequest(bridge: AppBridge) {
function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) { function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) {
//#region AppBridge_onloggingmessage_handleLog //#region AppBridge_onloggingmessage_handleLog
bridge.onloggingmessage = ({ level, logger, data }) => { bridge.onloggingmessage = ({ level, logger, data }) => {
const prefix = logger ? `[${logger}]` : "[Guest UI]";
console[level === "error" ? "error" : "log"]( console[level === "error" ? "error" : "log"](
`${prefix} ${level.toUpperCase()}:`, `[${logger ?? "Guest UI"}] ${level.toUpperCase()}:`,
data, data,
); );
}; };
@ -354,7 +347,10 @@ function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) {
/** /**
* Example: Gracefully tear down the Guest UI before unmounting. * Example: Gracefully tear down the Guest UI before unmounting.
*/ */
async function AppBridge_teardownResource_gracefulShutdown(bridge: AppBridge) { async function AppBridge_teardownResource_gracefulShutdown(
bridge: AppBridge,
iframe: HTMLIFrameElement,
) {
//#region AppBridge_teardownResource_gracefulShutdown //#region AppBridge_teardownResource_gracefulShutdown
try { try {
await bridge.teardownResource({}); await bridge.teardownResource({});
@ -422,21 +418,16 @@ function AppBridge_sendToolInputPartial_streaming(bridge: AppBridge) {
*/ */
async function AppBridge_sendToolResult_afterExecution( async function AppBridge_sendToolResult_afterExecution(
bridge: AppBridge, bridge: AppBridge,
mcpClientParam: Client, mcpClient: Client,
args: Record<string, unknown>, args: Record<string, unknown>,
) { ) {
//#region AppBridge_sendToolResult_afterExecution //#region AppBridge_sendToolResult_afterExecution
// import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
const result = await mcpClient.request( const result = await mcpClient.request(
{ method: "tools/call", params: { name: "get_weather", arguments: args } }, { method: "tools/call", params: { name: "get_weather", arguments: args } },
CallToolResultSchema, CallToolResultSchema,
); );
bridge.sendToolResult(result); bridge.sendToolResult(result);
//#endregion AppBridge_sendToolResult_afterExecution //#endregion AppBridge_sendToolResult_afterExecution
// Make the parameter used to satisfy linting
void mcpClientParam;
} }
/** /**
@ -466,7 +457,8 @@ function AppBridge_sendToolCancelled_systemLevel(bridge: AppBridge) {
* Example: Connect with MCP client for automatic forwarding. * Example: Connect with MCP client for automatic forwarding.
*/ */
async function AppBridge_connect_withMcpClient( async function AppBridge_connect_withMcpClient(
mcpClientParam: Client, iframe: HTMLIFrameElement,
mcpClient: Client,
hostInfo: { name: string; version: string }, hostInfo: { name: string; version: string },
capabilities: { openLinks: {}; serverTools: {}; logging: {} }, capabilities: { openLinks: {}; serverTools: {}; logging: {} },
toolArgs: Record<string, unknown>, toolArgs: Record<string, unknown>,

View file

@ -280,7 +280,7 @@ export class AppBridge extends Protocol<
* @returns Guest UI capabilities, or `undefined` if not yet initialized * @returns Guest UI capabilities, or `undefined` if not yet initialized
* *
* @example Check Guest UI capabilities after initialization * @example Check Guest UI capabilities after initialization
* {@includeCode ./app-bridge.examples.ts#AppBridge_guestCapabilities_checkAfterInit} * {@includeCode ./app-bridge.examples.ts#AppBridge_getAppCapabilities_checkAfterInit}
* *
* @see {@link McpUiAppCapabilities} for the capabilities structure * @see {@link McpUiAppCapabilities} for the capabilities structure
*/ */
@ -297,7 +297,7 @@ export class AppBridge extends Protocol<
* @returns Guest UI implementation info, or `undefined` if not yet initialized * @returns Guest UI implementation info, or `undefined` if not yet initialized
* *
* @example Log Guest UI information after initialization * @example Log Guest UI information after initialization
* {@includeCode ./app-bridge.examples.ts#AppBridge_guestInfo_logAfterInit} * {@includeCode ./app-bridge.examples.ts#AppBridge_getAppVersion_logAfterInit}
*/ */
getAppVersion(): Implementation | undefined { getAppVersion(): Implementation | undefined {
return this._appInfo; return this._appInfo;

View file

@ -7,43 +7,43 @@
* @module * @module
*/ */
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { import type {
App, McpServer,
PostMessageTransport, ToolCallback,
McpUiToolInputNotificationSchema, } from "@modelcontextprotocol/sdk/server/mcp.js";
RESOURCE_URI_META_KEY, import { App, PostMessageTransport, RESOURCE_URI_META_KEY } from "./app.js";
} from "./app.js";
/** /**
* Example: How MCP servers use RESOURCE_URI_META_KEY (server-side, not in Apps). * Example: How MCP servers use RESOURCE_URI_META_KEY (server-side, not in Apps).
*/ */
function RESOURCE_URI_META_KEY_serverSide(): CallToolResult { function RESOURCE_URI_META_KEY_serverSide(
server: McpServer,
handler: ToolCallback,
) {
//#region RESOURCE_URI_META_KEY_serverSide //#region RESOURCE_URI_META_KEY_serverSide
// In an MCP server's tool handler: server.registerTool(
return { "weather",
content: [{ type: "text", text: "Result" }], {
_meta: { description: "Get weather forecast",
[RESOURCE_URI_META_KEY]: "ui://weather/forecast", _meta: {
[RESOURCE_URI_META_KEY]: "ui://weather/forecast",
},
}, },
}; handler,
);
//#endregion RESOURCE_URI_META_KEY_serverSide //#endregion RESOURCE_URI_META_KEY_serverSide
} }
/** /**
* Example: How hosts check for RESOURCE_URI_META_KEY metadata (host-side). * Example: How hosts check for RESOURCE_URI_META_KEY metadata (host-side).
*/ */
async function RESOURCE_URI_META_KEY_hostSide(mcpClient: { function RESOURCE_URI_META_KEY_hostSide(tool: Tool) {
callTool: (args: {
name: string;
arguments: object;
}) => Promise<CallToolResult>;
}) {
//#region RESOURCE_URI_META_KEY_hostSide //#region RESOURCE_URI_META_KEY_hostSide
const result = await mcpClient.callTool({ name: "weather", arguments: {} }); // Check tool definition metadata (from tools/list response):
const uiUri = result._meta?.[RESOURCE_URI_META_KEY]; const uiUri = tool._meta?.[RESOURCE_URI_META_KEY];
if (uiUri) { if (typeof uiUri === "string" && uiUri.startsWith("ui://")) {
// Load and display the UI resource // Fetch the resource and display the UI
} }
//#endregion RESOURCE_URI_META_KEY_hostSide //#endregion RESOURCE_URI_META_KEY_hostSide
} }
@ -72,20 +72,12 @@ async function App_basicUsage() {
{}, // capabilities {}, // capabilities
); );
// Register notification handler using setter (simpler) // Register handlers before connecting to ensure no notifications are missed
app.ontoolinput = (params) => { app.ontoolinput = (params) => {
console.log("Tool arguments:", params.arguments); console.log("Tool arguments:", params.arguments);
}; };
// OR using inherited setNotificationHandler (more explicit) await app.connect();
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
(notification) => {
console.log("Tool arguments:", notification.params.arguments);
},
);
await app.connect(new PostMessageTransport(window.parent, window.parent));
//#endregion App_basicUsage //#endregion App_basicUsage
} }
@ -104,95 +96,62 @@ async function App_sendMessage(app: App) {
/** /**
* Example: Check host capabilities after connection. * Example: Check host capabilities after connection.
*/ */
async function App_hostCapabilities_checkAfterConnection( async function App_getHostCapabilities_checkAfterConnection(app: App) {
app: App, //#region App_getHostCapabilities_checkAfterConnection
transport: PostMessageTransport, await app.connect();
) { if (app.getHostCapabilities()?.serverTools) {
//#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"); console.log("Host supports server tool calls");
} }
//#endregion App_hostCapabilities_checkAfterConnection //#endregion App_getHostCapabilities_checkAfterConnection
} }
/** /**
* Example: Log host information after connection. * Example: Log host information after connection.
*/ */
async function App_hostInfo_logAfterConnection( async function App_getHostVersion_logAfterConnection(
app: App, app: App,
transport: PostMessageTransport, transport: PostMessageTransport,
) { ) {
//#region App_hostInfo_logAfterConnection //#region App_getHostVersion_logAfterConnection
await app.connect(transport); await app.connect(transport);
const host = app.getHostVersion(); const { name, version } = app.getHostVersion() ?? {};
if (host === undefined) { console.log(`Connected to ${name} v${version}`);
console.error("Not connected"); //#endregion App_getHostVersion_logAfterConnection
return;
}
console.log(`Connected to ${host.name} v${host.version}`);
//#endregion App_hostInfo_logAfterConnection
} }
/** /**
* Example: Access host context after connection. * Example: Access host context after connection.
*/ */
async function App_hostContext_accessAfterConnection( async function App_getHostContext_accessAfterConnection(
app: App, app: App,
transport: PostMessageTransport, transport: PostMessageTransport,
) { ) {
//#region App_hostContext_accessAfterConnection //#region App_getHostContext_accessAfterConnection
await app.connect(transport); await app.connect(transport);
const context = app.getHostContext(); const context = app.getHostContext();
if (context === undefined) { if (context?.theme === "dark") {
console.error("Not connected");
return;
}
if (context.theme === "dark") {
document.body.classList.add("dark-theme"); document.body.classList.add("dark-theme");
} }
if (context.toolInfo) { if (context?.toolInfo) {
console.log("Tool:", context.toolInfo.tool.name); console.log("Tool:", context.toolInfo.tool.name);
} }
//#endregion App_hostContext_accessAfterConnection //#endregion App_getHostContext_accessAfterConnection
} }
/** /**
* Example: Using the ontoolinput setter (simpler approach). * Example: Using the ontoolinput setter (simpler approach).
*/ */
async function App_ontoolinput_setter( async function App_ontoolinput_setter(app: App) {
app: App,
transport: PostMessageTransport,
) {
//#region App_ontoolinput_setter //#region App_ontoolinput_setter
// Register before connecting to ensure no notifications are missed // Register before connecting to ensure no notifications are missed
app.ontoolinput = (params) => { app.ontoolinput = (params) => {
console.log("Tool:", params.arguments); console.log("Tool:", params.arguments);
// Update your UI with the tool arguments // Update your UI with the tool arguments
}; };
await app.connect(transport); await app.connect();
//#endregion App_ontoolinput_setter //#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. * Example: Progressive rendering of tool arguments using ontoolinputpartial.
*/ */
@ -211,11 +170,10 @@ function App_ontoolinputpartial_progressiveRendering(app: App) {
function App_ontoolresult_displayResults(app: App) { function App_ontoolresult_displayResults(app: App) {
//#region App_ontoolresult_displayResults //#region App_ontoolresult_displayResults
app.ontoolresult = (params) => { app.ontoolresult = (params) => {
if (params.content) {
console.log("Tool output:", params.content);
}
if (params.isError) { if (params.isError) {
console.error("Tool execution failed"); console.error("Tool execution failed:", params.content);
} else if (params.content) {
console.log("Tool output:", params.content);
} }
}; };
//#endregion App_ontoolresult_displayResults //#endregion App_ontoolresult_displayResults
@ -228,14 +186,11 @@ function App_ontoolcancelled_handleCancellation(app: App) {
//#region App_ontoolcancelled_handleCancellation //#region App_ontoolcancelled_handleCancellation
app.ontoolcancelled = (params) => { app.ontoolcancelled = (params) => {
console.log("Tool cancelled:", params.reason); console.log("Tool cancelled:", params.reason);
showCancelledMessage(params.reason ?? "Operation was cancelled"); // Update your UI to show cancellation state
}; };
//#endregion App_ontoolcancelled_handleCancellation //#endregion App_ontoolcancelled_handleCancellation
} }
// Stub for example
declare function showCancelledMessage(message: string): void;
/** /**
* Example: Respond to theme changes using onhostcontextchanged. * Example: Respond to theme changes using onhostcontextchanged.
*/ */
@ -291,7 +246,7 @@ function App_onlisttools_returnTools(app: App) {
//#region App_onlisttools_returnTools //#region App_onlisttools_returnTools
app.onlisttools = async (params, extra) => { app.onlisttools = async (params, extra) => {
return { return {
tools: ["calculate", "convert", "format"], tools: ["greet", "calculate", "format"],
}; };
}; };
//#endregion App_onlisttools_returnTools //#endregion App_onlisttools_returnTools
@ -324,13 +279,17 @@ async function App_callServerTool_fetchWeather(app: App) {
async function App_sendMessage_textFromInteraction(app: App) { async function App_sendMessage_textFromInteraction(app: App) {
//#region App_sendMessage_textFromInteraction //#region App_sendMessage_textFromInteraction
try { try {
await app.sendMessage({ const result = await app.sendMessage({
role: "user", role: "user",
content: [{ type: "text", text: "Show me details for item #42" }], 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) { } catch (error) {
console.error("Failed to send message:", error); console.error("Failed to send message:", error);
// Handle error appropriately for your app // Handle transport/protocol error
} }
//#endregion App_sendMessage_textFromInteraction //#endregion App_sendMessage_textFromInteraction
} }
@ -375,11 +334,11 @@ async function App_updateModelContext_structuredContent(app: App) {
*/ */
async function App_openLink_documentation(app: App) { async function App_openLink_documentation(app: App) {
//#region App_openLink_documentation //#region App_openLink_documentation
try { const { isError } = await app.openLink({ url: "https://docs.example.com" });
await app.openLink({ url: "https://docs.example.com" }); if (isError) {
} catch (error) { // Host denied the request (e.g., blocked domain, user cancelled)
console.error("Failed to open link:", error);
// Optionally show fallback: display URL for manual copy // Optionally show fallback: display URL for manual copy
console.warn("Link request denied");
} }
//#endregion App_openLink_documentation //#endregion App_openLink_documentation
} }

View file

@ -60,14 +60,16 @@ export {
} from "./styles"; } from "./styles";
/** /**
* Metadata key for associating a resource URI with a tool call. * Metadata key for associating a UI resource URI with a tool.
* *
* MCP servers include this key in tool call result metadata to indicate which * MCP servers include this key in tool definition metadata (via `tools/list`)
* UI resource should be displayed for the tool. When hosts receive a tool result * to indicate which UI resource should be displayed when the tool is called.
* containing this metadata, they resolve and render the corresponding {@link App}. * When hosts see a tool with this metadata, they fetch and render the
* corresponding {@link App}.
* *
* **Note**: This constant is provided for reference. MCP servers set this metadata * **Note**: This constant is provided for reference. App developers typically
* in their tool handlers; App developers typically don't need to use it directly. * don't need to use it directly. Prefer using {@link server-helpers!registerAppTool}
* with the `_meta.ui.resourceUri` format instead.
* *
* @example How MCP servers use this key (server-side, not in Apps) * @example How MCP servers use this key (server-side, not in Apps)
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_serverSide} * {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_serverSide}
@ -200,7 +202,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host capabilities, or `undefined` if not yet connected * @returns Host capabilities, or `undefined` if not yet connected
* *
* @example Check host capabilities after connection * @example Check host capabilities after connection
* {@includeCode ./app.examples.ts#App_hostCapabilities_checkAfterConnection} * {@includeCode ./app.examples.ts#App_getHostCapabilities_checkAfterConnection}
* *
* @see {@link connect} for the initialization handshake * @see {@link connect} for the initialization handshake
* @see {@link McpUiHostCapabilities} for the capabilities structure * @see {@link McpUiHostCapabilities} for the capabilities structure
@ -219,7 +221,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host implementation info, or `undefined` if not yet connected * @returns Host implementation info, or `undefined` if not yet connected
* *
* @example Log host information after connection * @example Log host information after connection
* {@includeCode ./app.examples.ts#App_hostInfo_logAfterConnection} * {@includeCode ./app.examples.ts#App_getHostVersion_logAfterConnection}
* *
* @see {@link connect} for the initialization handshake * @see {@link connect} for the initialization handshake
*/ */
@ -240,7 +242,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @returns Host context, or `undefined` if not yet connected * @returns Host context, or `undefined` if not yet connected
* *
* @example Access host context after connection * @example Access host context after connection
* {@includeCode ./app.examples.ts#App_hostContext_accessAfterConnection} * {@includeCode ./app.examples.ts#App_getHostContext_accessAfterConnection}
* *
* @see {@link connect} for the initialization handshake * @see {@link connect} for the initialization handshake
* @see {@link onhostcontextchanged} for context change notifications * @see {@link onhostcontextchanged} for context change notifications
@ -264,12 +266,9 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* *
* @param callback - Function called with the tool input params ({@link McpUiToolInputNotification.params}) * @param callback - Function called with the tool input params ({@link McpUiToolInputNotification.params})
* *
* @example Using the setter (simpler) * @example
* {@includeCode ./app.examples.ts#App_ontoolinput_setter} * {@includeCode ./app.examples.ts#App_ontoolinput_setter}
* *
* @example Using setNotificationHandler (more explicit)
* {@includeCode ./app.examples.ts#App_ontoolinput_setNotificationHandler}
*
* @see {@link setNotificationHandler} for the underlying method * @see {@link setNotificationHandler} for the underlying method
* @see {@link McpUiToolInputNotification} for the notification structure * @see {@link McpUiToolInputNotification} for the notification structure
*/ */
@ -607,9 +606,9 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* *
* @param params - Message role and content * @param params - Message role and content
* @param options - Request options (timeout, etc.) * @param options - Request options (timeout, etc.)
* @returns Result indicating success or error (no message content returned) * @returns Result with optional `isError` flag indicating host rejection
* *
* @throws {Error} If the host rejects the message * @throws {Error} If the request times out or the connection is lost
* *
* @example Send a text message from user interaction * @example Send a text message from user interaction
* {@includeCode ./app.examples.ts#App_sendMessage_textFromInteraction} * {@includeCode ./app.examples.ts#App_sendMessage_textFromInteraction}
@ -662,6 +661,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param options - Request options (timeout, etc.) * @param options - Request options (timeout, etc.)
* *
* @throws {Error} If the host rejects the context update (e.g., unsupported content type) * @throws {Error} If the host rejects the context update (e.g., unsupported content type)
* @throws {Error} If the request times out or the connection is lost
* *
* @example Update model context with current app state * @example Update model context with current app state
* {@includeCode ./app.examples.ts#App_updateModelContext_appState} * {@includeCode ./app.examples.ts#App_updateModelContext_appState}
@ -689,19 +689,19 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* Request the host to open an external URL in the default browser. * Request the host to open an external URL in the default browser.
* *
* The host may deny this request based on user preferences or security policy. * The host may deny this request based on user preferences or security policy.
* Apps should handle rejection gracefully. * Apps should handle rejection gracefully by checking `result.isError`.
* *
* @param params - URL to open * @param params - URL to open
* @param options - Request options (timeout, etc.) * @param options - Request options (timeout, etc.)
* @returns Result indicating success or error * @returns Result with `isError: true` if the host denied the request (e.g., blocked domain, user cancelled)
* *
* @throws {Error} If the host denies the request (e.g., blocked domain, user cancelled)
* @throws {Error} If the request times out or the connection is lost * @throws {Error} If the request times out or the connection is lost
* *
* @example Open documentation link * @example Open documentation link
* {@includeCode ./app.examples.ts#App_openLink_documentation} * {@includeCode ./app.examples.ts#App_openLink_documentation}
* *
* @see {@link McpUiOpenLinkRequest} for request structure * @see {@link McpUiOpenLinkRequest} for request structure
* @see {@link McpUiOpenLinkResult} for result structure
*/ */
openLink(params: McpUiOpenLinkRequest["params"], options?: RequestOptions) { openLink(params: McpUiOpenLinkRequest["params"], options?: RequestOptions) {
return this.request( return this.request(

View file

@ -49,7 +49,7 @@ function PostMessageTransport_constructor_guestUI() {
*/ */
function PostMessageTransport_constructor_host() { function PostMessageTransport_constructor_host() {
//#region PostMessageTransport_constructor_host //#region PostMessageTransport_constructor_host
const iframe = document.getElementById("app") as HTMLIFrameElement; const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
const transport = new PostMessageTransport( const transport = new PostMessageTransport(
iframe.contentWindow!, iframe.contentWindow!,
iframe.contentWindow!, iframe.contentWindow!,

View file

@ -4,10 +4,12 @@
* @module * @module
*/ */
import { useApp, McpUiToolInputNotificationSchema } from "./index.js"; import { useState } from "react";
import type { McpUiHostContext } from "../types.js";
import { useApp } from "./index.js";
/** /**
* Example: Register a notification handler in onAppCreated. * Example: Register an event handler in onAppCreated.
*/ */
function useApp_registerHandler() { function useApp_registerHandler() {
//#region useApp_registerHandler //#region useApp_registerHandler
@ -15,40 +17,49 @@ function useApp_registerHandler() {
appInfo: { name: "MyApp", version: "1.0.0" }, appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {}, capabilities: {},
onAppCreated: (app) => { onAppCreated: (app) => {
app.setNotificationHandler( app.ontoolresult = (result) => {
McpUiToolInputNotificationSchema, console.log("Tool result:", result);
(notification) => { };
console.log("Tool input:", notification.params.arguments);
},
);
}, },
}); });
//#endregion useApp_registerHandler //#endregion useApp_registerHandler
} }
/** /**
* Example: Basic usage of useApp hook. * Example: Basic usage of useApp hook with common event handlers.
*/ */
function useApp_basicUsage() { function useApp_basicUsage() {
//#region useApp_basicUsage //#region useApp_basicUsage
function MyApp() { function MyApp() {
const [hostContext, setHostContext] = useState<
McpUiHostContext | undefined
>(undefined);
const { app, isConnected, error } = useApp({ const { app, isConnected, error } = useApp({
appInfo: { name: "MyApp", version: "1.0.0" }, appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {}, capabilities: {},
onAppCreated: (app) => { onAppCreated: (app) => {
// Register handlers before connection app.ontoolinput = (input) => {
app.setNotificationHandler( console.log("Tool input:", input);
McpUiToolInputNotificationSchema, };
(notification) => { app.ontoolresult = (result) => {
console.log("Tool input:", notification.params.arguments); 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 (error) return <div>Error: {error.message}</div>;
if (!isConnected) return <div>Connecting...</div>; if (!isConnected) return <div>Connecting...</div>;
return <div>Connected!</div>; return <div>Theme: {hostContext?.theme}</div>;
} }
//#endregion useApp_basicUsage //#endregion useApp_basicUsage
} }

View file

@ -29,7 +29,7 @@ export interface UseAppOptions {
* *
* @param app - The newly created `App` instance * @param app - The newly created `App` instance
* *
* @example Register a notification handler * @example Register an event handler
* {@includeCode ./useApp.examples.tsx#useApp_registerHandler} * {@includeCode ./useApp.examples.tsx#useApp_registerHandler}
*/ */
onAppCreated?: (app: App) => void; onAppCreated?: (app: App) => void;
@ -69,7 +69,7 @@ export interface AppState {
* initialization, the `error` field will contain the error (typically connection * initialization, the `error` field will contain the error (typically connection
* timeouts, initialization handshake failures, or transport errors). * timeouts, initialization handshake failures, or transport errors).
* *
* @example Basic usage * @example Basic usage of useApp hook with common event handlers
* {@includeCode ./useApp.examples.tsx#useApp_basicUsage} * {@includeCode ./useApp.examples.tsx#useApp_basicUsage}
* *
* @see {@link App.connect} for the underlying connection method * @see {@link App.connect} for the underlying connection method

View file

@ -4,15 +4,12 @@
* @module * @module
*/ */
import { useState } from "react";
import { import {
useApp, useApp,
useHostStyleVariables, useHostStyleVariables,
useHostFonts, useHostFonts,
useHostStyles, useHostStyles,
App,
} from "./index.js"; } from "./index.js";
import type { McpUiHostContext } from "../types.js";
/** /**
* Example: Basic usage of useHostStyleVariables. * Example: Basic usage of useHostStyleVariables.
@ -20,7 +17,7 @@ import type { McpUiHostContext } from "../types.js";
function useHostStyleVariables_basicUsage() { function useHostStyleVariables_basicUsage() {
//#region useHostStyleVariables_basicUsage //#region useHostStyleVariables_basicUsage
function MyApp() { function MyApp() {
const { app, isConnected } = useApp({ const { app } = useApp({
appInfo: { name: "MyApp", version: "1.0.0" }, appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {}, capabilities: {},
}); });
@ -43,44 +40,37 @@ function useHostStyleVariables_basicUsage() {
function useHostFonts_basicUsage() { function useHostFonts_basicUsage() {
//#region useHostFonts_basicUsage //#region useHostFonts_basicUsage
function MyApp() { function MyApp() {
const { app, isConnected } = useApp({ const { app } = useApp({
appInfo: { name: "MyApp", version: "1.0.0" }, appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {}, capabilities: {},
}); });
// Automatically apply host fonts // Apply host fonts - pass initial context to apply fonts from connect() immediately
useHostFonts(app); useHostFonts(app, app?.getHostContext());
return <div style={{ fontFamily: "var(--font-sans)" }}>Hello!</div>; return <div style={{ fontFamily: "var(--font-sans)" }}>Hello!</div>;
} }
//#endregion useHostFonts_basicUsage //#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. * Example: Basic usage of useHostStyles.
*/ */
function useHostStyles_basicUsage() { function useHostStyles_basicUsage() {
const appInfo = { name: "MyApp", version: "1.0.0" };
//#region useHostStyles_basicUsage //#region useHostStyles_basicUsage
function MyApp() { function MyApp() {
const { app } = useApp({ appInfo, capabilities: {} }); 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()); useHostStyles(app, app?.getHostContext());
return ( return (
<div style={{ background: "var(--color-background-primary)" }}>...</div> <div style={{ background: "var(--color-background-primary)" }}>
Hello!
</div>
); );
} }
//#endregion useHostStyles_basicUsage //#endregion useHostStyles_basicUsage

View file

@ -91,9 +91,6 @@ export function useHostStyleVariables(
* @example Basic usage with useApp * @example Basic usage with useApp
* {@includeCode ./useHostStyles.examples.tsx#useHostFonts_basicUsage} * {@includeCode ./useHostStyles.examples.tsx#useHostFonts_basicUsage}
* *
* @example With initial context
* {@includeCode ./useHostStyles.examples.tsx#useHostFonts_withInitialContext}
*
* @see {@link applyHostFonts} for the underlying fonts function * @see {@link applyHostFonts} for the underlying fonts function
* @see {@link useHostStyleVariables} for applying style variables and theme * @see {@link useHostStyleVariables} for applying style variables and theme
*/ */

View file

@ -7,7 +7,12 @@
* @module * @module
*/ */
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import * as fs from "node:fs/promises";
import type {
McpServer,
ToolCallback,
ReadResourceCallback,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod"; import { z } from "zod";
import { import {
registerAppTool, registerAppTool,
@ -24,17 +29,14 @@ declare function updateCartItem(
itemId: string, itemId: string,
quantity: number, quantity: number,
): Promise<{ items: unknown[]; total: 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. * Example: Module overview showing basic registration of tools and resources.
*/ */
function index_overview( function index_overview(
server: McpServer, server: McpServer,
handler: () => Promise<{ content: { type: "text"; text: string }[] }>, toolCallback: ToolCallback,
readCallback: ReadResourceCallback,
) { ) {
//#region index_overview //#region index_overview
// Register a tool that displays a widget // Register a tool that displays a widget
@ -45,7 +47,7 @@ function index_overview(
description: "Get weather forecast", description: "Get weather forecast",
_meta: { ui: { resourceUri: "ui://weather/widget.html" } }, _meta: { ui: { resourceUri: "ui://weather/widget.html" } },
}, },
handler, toolCallback,
); );
// Register the HTML resource the tool references // Register the HTML resource the tool references
@ -175,12 +177,11 @@ function registerAppResource_withCsp(
uri: "ui://music/player.html", uri: "ui://music/player.html",
mimeType: RESOURCE_MIME_TYPE, mimeType: RESOURCE_MIME_TYPE,
text: musicPlayerHtml, text: musicPlayerHtml,
// CSP must be on the content item, not the resource config
_meta: { _meta: {
ui: { ui: {
csp: { csp: {
connectDomains: ["https://api.example.com"], // For fetch/WebSocket
resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
connectDomains: ["https://api.example.com"], // For fetch/WebSocket
}, },
}, },
}, },

View file

@ -7,7 +7,6 @@
* @module * @module
*/ */
import type { McpUiHostContext } from "./types.js";
import { App } from "./app.js"; import { App } from "./app.js";
import { import {
getDocumentTheme, getDocumentTheme,
@ -16,24 +15,20 @@ import {
applyHostFonts, applyHostFonts,
} from "./styles.js"; } from "./styles.js";
// Stub declarations for examples
declare const app: App;
declare const hostContext: McpUiHostContext;
/** /**
* Example: Check current theme. * Example: Check current theme.
*/ */
function getDocumentTheme_checkCurrent() { function getDocumentTheme_checkCurrent() {
//#region getDocumentTheme_checkCurrent //#region getDocumentTheme_checkCurrent
const theme = getDocumentTheme(); const theme = getDocumentTheme();
console.log(`Current theme: ${theme}`); const isDark = theme === "dark";
//#endregion getDocumentTheme_checkCurrent //#endregion getDocumentTheme_checkCurrent
} }
/** /**
* Example: Apply theme from host context. * Example: Apply theme from host context.
*/ */
function applyDocumentTheme_fromHostContext() { function applyDocumentTheme_fromHostContext(app: App) {
//#region applyDocumentTheme_fromHostContext //#region applyDocumentTheme_fromHostContext
app.onhostcontextchanged = (params) => { app.onhostcontextchanged = (params) => {
if (params.theme) { if (params.theme) {
@ -46,7 +41,7 @@ function applyDocumentTheme_fromHostContext() {
/** /**
* Example: Apply style variables from host context. * Example: Apply style variables from host context.
*/ */
function applyHostStyleVariables_fromHostContext() { function applyHostStyleVariables_fromHostContext(app: App) {
//#region applyHostStyleVariables_fromHostContext //#region applyHostStyleVariables_fromHostContext
app.onhostcontextchanged = (params) => { app.onhostcontextchanged = (params) => {
if (params.styles?.variables) { if (params.styles?.variables) {
@ -59,19 +54,21 @@ function applyHostStyleVariables_fromHostContext() {
/** /**
* Example: Apply to a specific element. * Example: Apply to a specific element.
*/ */
function applyHostStyleVariables_toElement() { function applyHostStyleVariables_toElement(app: App) {
//#region applyHostStyleVariables_toElement //#region applyHostStyleVariables_toElement
const container = document.getElementById("app-root"); app.onhostcontextchanged = (params) => {
if (container && hostContext.styles?.variables) { const container = document.getElementById("app-root");
applyHostStyleVariables(hostContext.styles.variables, container); if (container && params.styles?.variables) {
} applyHostStyleVariables(params.styles.variables, container);
}
};
//#endregion applyHostStyleVariables_toElement //#endregion applyHostStyleVariables_toElement
} }
/** /**
* Example: Apply fonts from host context. * Example: Apply fonts from host context.
*/ */
function applyHostFonts_fromHostContext() { function applyHostFonts_fromHostContext(app: App) {
//#region applyHostFonts_fromHostContext //#region applyHostFonts_fromHostContext
app.onhostcontextchanged = (params) => { app.onhostcontextchanged = (params) => {
if (params.styles?.css?.fonts) { if (params.styles?.css?.fonts) {