ext-apps/src/app-bridge.examples.ts
Liad Yosef 4fc71656a3
docs: standardize UI terminology to use "view" consistently (#325)
* docs: standardize UI terminology to use "view" consistently

Replaces "widget" and "Guest UI" terminology with "view" across
the codebase as decided in the community survey.

This change updates:
- Specification document
- SDK source code and JSDoc comments
- Example servers and documentation
- Plugin documentation and skills

Closes #71

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 04:36:52 +02:00

501 lines
14 KiB
TypeScript

/**
* Type-checked examples for {@link AppBridge `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";
import type { McpUiDisplayMode } from "./types.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 View
const bridge = new AppBridge(
client,
{ name: "MyHost", version: "1.0.0" },
{ openLinks: {}, serverTools: {}, logging: {} },
);
// Set up iframe and connect
const iframe = document.getElementById("app") as HTMLIFrameElement;
const transport = new PostMessageTransport(
iframe.contentWindow!,
iframe.contentWindow!,
);
bridge.oninitialized = () => {
console.log("View initialized");
// Now safe to send tool input
bridge.sendToolInput({ arguments: { location: "NYC" } });
};
await bridge.connect(transport);
//#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
}
/**
* 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
}
/**
* Example: Check View capabilities after initialization.
*/
function AppBridge_getAppCapabilities_checkAfterInit(bridge: AppBridge) {
//#region AppBridge_getAppCapabilities_checkAfterInit
bridge.oninitialized = () => {
const caps = bridge.getAppCapabilities();
if (caps?.tools) {
console.log("View provides tools");
}
};
//#endregion AppBridge_getAppCapabilities_checkAfterInit
}
/**
* Example: Log View information after initialization.
*/
function AppBridge_getAppVersion_logAfterInit(bridge: AppBridge) {
//#region AppBridge_getAppVersion_logAfterInit
bridge.oninitialized = () => {
const appInfo = bridge.getAppVersion();
if (appInfo) {
console.log(`View: ${appInfo.name} v${appInfo.version}`);
}
};
//#endregion AppBridge_getAppVersion_logAfterInit
}
/**
* Example: Handle View initialization and send tool input.
*/
function AppBridge_oninitialized_sendToolInput(
bridge: AppBridge,
toolArgs: Record<string, unknown>,
) {
//#region AppBridge_oninitialized_sendToolInput
bridge.oninitialized = () => {
console.log("View ready");
bridge.sendToolInput({ arguments: toolArgs });
};
//#endregion AppBridge_oninitialized_sendToolInput
}
/**
* Example: Handle message requests from the View.
*/
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 manager
declare const modelContextManager: {
update(context: { content?: unknown; structuredContent?: unknown }): void;
};
/**
* Example: Handle external link requests from the View.
*/
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 View.
*/
function AppBridge_onupdatemodelcontext_storeContext(bridge: AppBridge) {
//#region AppBridge_onupdatemodelcontext_storeContext
bridge.onupdatemodelcontext = async (
{ content, structuredContent },
extra,
) => {
// Store the context snapshot for inclusion in the next model request
modelContextManager.update({ content, structuredContent });
return {};
};
//#endregion AppBridge_onupdatemodelcontext_storeContext
}
/**
* Example: Forward tool calls to the MCP server.
*/
function AppBridge_oncalltool_forwardToServer(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_oncalltool_forwardToServer
bridge.oncalltool = async (params, extra) => {
return mcpClient.request(
{ method: "tools/call", params },
CallToolResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_oncalltool_forwardToServer
}
/**
* Example: Forward list resources requests to the MCP server.
*/
function AppBridge_onlistresources_returnResources(
bridge: AppBridge,
mcpClient: Client,
) {
//#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,
mcpClient: Client,
) {
//#region AppBridge_onreadresource_returnResource
bridge.onreadresource = async (params, extra) => {
return mcpClient.request(
{ method: "resources/read", params },
ReadResourceResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onreadresource_returnResource
}
/**
* Example: Forward list prompts requests to the MCP server.
*/
function AppBridge_onlistprompts_returnPrompts(
bridge: AppBridge,
mcpClient: Client,
) {
//#region AppBridge_onlistprompts_returnPrompts
bridge.onlistprompts = async (params, extra) => {
return mcpClient.request(
{ method: "prompts/list", params },
ListPromptsResultSchema,
{ signal: extra.signal },
);
};
//#endregion AppBridge_onlistprompts_returnPrompts
}
/**
* Example: Handle ping requests from the View.
*/
function AppBridge_onping_handleRequest(bridge: AppBridge) {
//#region AppBridge_onping_handleRequest
bridge.onping = (params, extra) => {
console.log("Received ping from view");
};
//#endregion AppBridge_onping_handleRequest
}
/**
* Example: Handle size change notifications from the View.
*/
function AppBridge_onsizechange_handleResize(
bridge: AppBridge,
iframe: HTMLIFrameElement,
) {
//#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 View.
*/
function AppBridge_onrequestdisplaymode_handleRequest(
bridge: AppBridge,
currentDisplayMode: McpUiDisplayMode,
availableDisplayModes: McpUiDisplayMode[],
) {
//#region AppBridge_onrequestdisplaymode_handleRequest
bridge.onrequestdisplaymode = async ({ mode }, extra) => {
if (availableDisplayModes.includes(mode)) {
currentDisplayMode = mode;
}
return { mode: currentDisplayMode };
};
//#endregion AppBridge_onrequestdisplaymode_handleRequest
}
/**
* Example: Handle logging messages from the View.
*/
function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) {
//#region AppBridge_onloggingmessage_handleLog
bridge.onloggingmessage = ({ level, logger, data }) => {
console[level === "error" ? "error" : "log"](
`[${logger ?? "View"}] ${level.toUpperCase()}:`,
data,
);
};
//#endregion AppBridge_onloggingmessage_handleLog
}
/**
* Example: Gracefully tear down the View before unmounting.
*/
async function AppBridge_teardownResource_gracefulShutdown(
bridge: AppBridge,
iframe: HTMLIFrameElement,
) {
//#region AppBridge_teardownResource_gracefulShutdown
try {
await bridge.teardownResource({});
// View 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,
mcpClient: Client,
args: Record<string, unknown>,
) {
//#region AppBridge_sendToolResult_afterExecution
const result = await mcpClient.request(
{ method: "tools/call", params: { name: "get_weather", arguments: args } },
CallToolResultSchema,
);
bridge.sendToolResult(result);
//#endregion AppBridge_sendToolResult_afterExecution
}
/**
* 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(
iframe: HTMLIFrameElement,
mcpClient: 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("View 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
}