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:
parent
e5c90c7385
commit
ad04e89745
22 changed files with 1661 additions and 838 deletions
|
|
@ -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
509
src/app-bridge.examples.ts
Normal 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
|
||||
}
|
||||
|
|
@ -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
446
src/app.examples.ts
Normal 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
|
||||
}
|
||||
275
src/app.ts
275
src/app.ts
|
|
@ -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
|
||||
|
|
|
|||
58
src/message-transport.examples.ts
Normal file
58
src/message-transport.examples.ts
Normal 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
26
src/react/index.examples.tsx
Normal file
26
src/react/index.examples.tsx
Normal 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
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
54
src/react/useApp.examples.tsx
Normal file
54
src/react/useApp.examples.tsx
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
42
src/react/useAutoResize.examples.tsx
Normal file
42
src/react/useAutoResize.examples.tsx
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
46
src/react/useDocumentTheme.examples.tsx
Normal file
46
src/react/useDocumentTheme.examples.tsx
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
87
src/react/useHostStyles.examples.tsx
Normal file
87
src/react/useHostStyles.examples.tsx
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
192
src/server/index.examples.ts
Normal file
192
src/server/index.examples.ts
Normal 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
|
||||
}
|
||||
|
|
@ -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
112
src/styles.examples.ts
Normal 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
Loading…
Reference in a new issue