| import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | |
| import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; | |
| import { | |
| CallToolRequest, | |
| CallToolRequestSchema, | |
| CallToolResult, | |
| CallToolResultSchema, | |
| EmptyResult, | |
| Implementation, | |
| ListPromptsRequest, | |
| ListPromptsRequestSchema, | |
| ListPromptsResult, | |
| ListPromptsResultSchema, | |
| ListResourcesRequest, | |
| ListResourcesRequestSchema, | |
| ListResourcesResult, | |
| ListResourcesResultSchema, | |
| ListResourceTemplatesRequest, | |
| ListResourceTemplatesRequestSchema, | |
| ListResourceTemplatesResult, | |
| ListResourceTemplatesResultSchema, | |
| LoggingMessageNotification, | |
| LoggingMessageNotificationSchema, | |
| PingRequest, | |
| PingRequestSchema, | |
| PromptListChangedNotification, | |
| PromptListChangedNotificationSchema, | |
| ReadResourceRequest, | |
| ReadResourceRequestSchema, | |
| ReadResourceResult, | |
| ReadResourceResultSchema, | |
| ResourceListChangedNotification, | |
| ResourceListChangedNotificationSchema, | |
| ToolListChangedNotification, | |
| ToolListChangedNotificationSchema, | |
| } from "@modelcontextprotocol/sdk/types.js"; | |
| import { | |
| Protocol, | |
| ProtocolOptions, | |
| RequestOptions, | |
| } from "@modelcontextprotocol/sdk/shared/protocol.js"; | |
| import { | |
| type AppNotification, | |
| type AppRequest, | |
| type AppResult, | |
| type McpUiSandboxResourceReadyNotification, | |
| type McpUiSizeChangedNotification, | |
| type McpUiToolCancelledNotification, | |
| type McpUiToolInputNotification, | |
| type McpUiToolInputPartialNotification, | |
| type McpUiToolResultNotification, | |
| LATEST_PROTOCOL_VERSION, | |
| McpUiAppCapabilities, | |
| McpUiUpdateModelContextRequest, | |
| McpUiUpdateModelContextRequestSchema, | |
| McpUiHostCapabilities, | |
| McpUiHostContext, | |
| McpUiHostContextChangedNotification, | |
| McpUiInitializedNotification, | |
| McpUiInitializedNotificationSchema, | |
| McpUiInitializeRequest, | |
| McpUiInitializeRequestSchema, | |
| McpUiInitializeResult, | |
| McpUiMessageRequest, | |
| McpUiMessageRequestSchema, | |
| McpUiMessageResult, | |
| McpUiOpenLinkRequest, | |
| McpUiOpenLinkRequestSchema, | |
| McpUiOpenLinkResult, | |
| McpUiResourceTeardownRequest, | |
| McpUiResourceTeardownResultSchema, | |
| McpUiSandboxProxyReadyNotification, | |
| McpUiSandboxProxyReadyNotificationSchema, | |
| McpUiSizeChangedNotificationSchema, | |
| McpUiRequestDisplayModeRequest, | |
| McpUiRequestDisplayModeRequestSchema, | |
| McpUiRequestDisplayModeResult, | |
| McpUiResourcePermissions, | |
| } from "./types"; | |
| export * from "./types"; | |
| export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app"; | |
| import { RESOURCE_URI_META_KEY } from "./app"; | |
| export { PostMessageTransport } from "./message-transport"; | |
| /** | |
| * Extract UI resource URI from tool metadata. | |
| * | |
| * Supports both the new nested format (`_meta.ui.resourceUri`) and the | |
| * deprecated flat format (`_meta["ui/resourceUri"]`). The new nested format | |
| * takes precedence if both are present. | |
| * | |
| * @param tool - A tool object with optional `_meta` property | |
| * @returns The UI resource URI if valid, undefined if not present | |
| * @throws Error if resourceUri is present but invalid (does not start with "ui://") | |
| * | |
| * @example | |
| * ```typescript | |
| * // New nested format (preferred) | |
| * const uri = getToolUiResourceUri({ | |
| * _meta: { ui: { resourceUri: "ui://server/app.html" } } | |
| * }); | |
| * | |
| * // Deprecated flat format (still supported) | |
| * const uri = getToolUiResourceUri({ | |
| * _meta: { "ui/resourceUri": "ui://server/app.html" } | |
| * }); | |
| * ``` | |
| */ | |
| export function getToolUiResourceUri(tool: { | |
| _meta?: Record<string, unknown>; | |
| }): string | undefined { | |
| // Try new nested format first: _meta.ui.resourceUri | |
| const uiMeta = tool._meta?.ui as { resourceUri?: unknown } | undefined; | |
| let uri: unknown = uiMeta?.resourceUri; | |
| // Fall back to deprecated flat format: _meta["ui/resourceUri"] | |
| if (uri === undefined) { | |
| uri = tool._meta?.[RESOURCE_URI_META_KEY]; | |
| } | |
| if (typeof uri === "string" && uri.startsWith("ui://")) { | |
| return uri; | |
| } else if (uri !== undefined) { | |
| throw new Error(`Invalid UI resource URI: ${JSON.stringify(uri)}`); | |
| } | |
| return undefined; | |
| } | |
| /** | |
| * Build iframe `allow` attribute string from permissions. | |
| * | |
| * Maps McpUiResourcePermissions to the Permission Policy allow attribute | |
| * format used by iframes (e.g., "microphone; clipboard-write"). | |
| * | |
| * @param permissions - Permissions requested by the UI resource | |
| * @returns Space-separated permission directives, or empty string if none | |
| * | |
| * @example | |
| * ```typescript | |
| * const allow = buildAllowAttribute({ microphone: {}, clipboardWrite: {} }); | |
| * // Returns: "microphone; clipboard-write" | |
| * iframe.setAttribute("allow", allow); | |
| * ``` | |
| */ | |
| export function buildAllowAttribute( | |
| permissions: McpUiResourcePermissions | undefined, | |
| ): string { | |
| if (!permissions) return ""; | |
| const allowList: string[] = []; | |
| if (permissions.camera) allowList.push("camera"); | |
| if (permissions.microphone) allowList.push("microphone"); | |
| if (permissions.geolocation) allowList.push("geolocation"); | |
| if (permissions.clipboardWrite) allowList.push("clipboard-write"); | |
| return allowList.join("; "); | |
| } | |
| /** | |
| * Options for configuring {@link AppBridge} behavior. | |
| * | |
| * @property hostContext - Optional initial host context to provide to the Guest UI | |
| * | |
| * @see `ProtocolOptions` from @modelcontextprotocol/sdk for available options | |
| * @see {@link McpUiHostContext} for the hostContext structure | |
| */ | |
| export type HostOptions = ProtocolOptions & { | |
| hostContext?: McpUiHostContext; | |
| }; | |
| /** | |
| * Protocol versions supported by this AppBridge implementation. | |
| * | |
| * The SDK automatically handles version negotiation during initialization. | |
| * Hosts don't need to manage protocol versions manually. | |
| */ | |
| export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION]; | |
| /** | |
| * Extra metadata passed to request handlers. | |
| * | |
| * This type represents the additional context provided by the `Protocol` class | |
| * when handling requests, including abort signals and session information. | |
| * It is extracted from the MCP SDK's request handler signature. | |
| * | |
| * @internal | |
| */ | |
| type RequestHandlerExtra = Parameters< | |
| Parameters<AppBridge["setRequestHandler"]>[1] | |
| >[1]; | |
| /** | |
| * Host-side bridge for communicating with a single Guest UI ({@link app!App}). | |
| * | |
| * `AppBridge` extends the MCP SDK's `Protocol` class and acts as a proxy between | |
| * the host application and a Guest UI running in an iframe. When an MCP client | |
| * is provided to the constructor, it automatically forwards MCP server capabilities | |
| * (tools, resources, prompts) to the Guest UI. It also handles the initialization | |
| * handshake. | |
| * | |
| * ## Architecture | |
| * | |
| * **Guest UI ↔ AppBridge ↔ Host ↔ MCP Server** | |
| * | |
| * The bridge proxies requests from the Guest UI to the MCP server and forwards | |
| * responses back. It also sends host-initiated notifications like tool input | |
| * and results to the Guest UI. | |
| * | |
| * ## Lifecycle | |
| * | |
| * 1. **Create**: Instantiate `AppBridge` with MCP client and capabilities | |
| * 2. **Connect**: Call `connect()` with transport to establish communication | |
| * 3. **Wait for init**: Guest UI sends initialize request, bridge responds | |
| * 4. **Send data**: Call {@link sendToolInput}, {@link sendToolResult}, etc. | |
| * 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); | |
| * ``` | |
| */ | |
| export class AppBridge extends Protocol< | |
| AppRequest, | |
| AppNotification, | |
| AppResult | |
| > { | |
| private _appCapabilities?: McpUiAppCapabilities; | |
| private _hostContext: McpUiHostContext = {}; | |
| private _appInfo?: Implementation; | |
| /** | |
| * Create a new AppBridge instance. | |
| * | |
| * @param _client - MCP client connected to the server, or `null`. When provided, | |
| * {@link connect} will automatically set up forwarding of MCP requests/notifications | |
| * between the Guest UI and the server. When `null`, you must register handlers | |
| * manually using the {@link oncalltool}, {@link onlistresources}, etc. setters. | |
| * @param _hostInfo - Host application identification (name and version) | |
| * @param _capabilities - Features and capabilities the host supports | |
| * @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: {} } | |
| * ); | |
| * ``` | |
| * | |
| * @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) => { ... }; | |
| * ``` | |
| */ | |
| constructor( | |
| private _client: Client | null, | |
| private _hostInfo: Implementation, | |
| private _capabilities: McpUiHostCapabilities, | |
| options?: HostOptions, | |
| ) { | |
| super(options); | |
| this._hostContext = options?.hostContext || {}; | |
| this.setRequestHandler(McpUiInitializeRequestSchema, (request) => | |
| this._oninitialize(request), | |
| ); | |
| this.setRequestHandler(PingRequestSchema, (request, extra) => { | |
| this.onping?.(request.params, extra); | |
| return {}; | |
| }); | |
| // Default handler for requestDisplayMode - returns current mode from host context. | |
| // Hosts can override this by setting bridge.onrequestdisplaymode = ... | |
| this.setRequestHandler(McpUiRequestDisplayModeRequestSchema, (request) => { | |
| const currentMode = this._hostContext.displayMode ?? "inline"; | |
| return { mode: currentMode }; | |
| }); | |
| } | |
| /** | |
| * Get the Guest UI's capabilities discovered during initialization. | |
| * | |
| * Returns the capabilities that the Guest UI advertised during its | |
| * initialization request. Returns `undefined` if called before | |
| * initialization completes. | |
| * | |
| * @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"); | |
| * } | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiAppCapabilities} for the capabilities structure | |
| */ | |
| getAppCapabilities(): McpUiAppCapabilities | undefined { | |
| return this._appCapabilities; | |
| } | |
| /** | |
| * Get the Guest UI's implementation info discovered during initialization. | |
| * | |
| * Returns the Guest UI's name and version as provided in its initialization | |
| * request. Returns `undefined` if called before initialization completes. | |
| * | |
| * @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}`); | |
| * } | |
| * }; | |
| * ``` | |
| */ | |
| getAppVersion(): Implementation | undefined { | |
| return this._appInfo; | |
| } | |
| /** | |
| * Optional handler for ping requests from the Guest UI. | |
| * | |
| * The Guest UI can send standard MCP `ping` requests to verify the connection | |
| * is alive. The {@link AppBridge} automatically responds with an empty object, but this | |
| * handler allows the host to observe or log ping activity. | |
| * | |
| * Unlike the other handlers which use setters, this is a direct property | |
| * assignment. It is optional; if not set, pings are still handled automatically. | |
| * | |
| * @param params - Empty params object from the ping request | |
| * @param extra - Request metadata (abort signal, session info) | |
| * | |
| * @example | |
| * ```typescript | |
| * bridge.onping = (params, extra) => { | |
| * console.log("Received ping from Guest UI"); | |
| * }; | |
| * ``` | |
| */ | |
| onping?: (params: PingRequest["params"], extra: RequestHandlerExtra) => void; | |
| /** | |
| * Register a handler for size change notifications from the Guest UI. | |
| * | |
| * The Guest UI sends `ui/notifications/size-changed` when its rendered content | |
| * size changes, typically via `ResizeObserver`. Set this callback to dynamically | |
| * adjust the iframe container dimensions based on the Guest UI's content. | |
| * | |
| * Note: This is for Guest UI → Host communication. To notify the Guest UI of | |
| * 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`; | |
| * } | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiSizeChangedNotification} for the notification type | |
| * @see {@link app!App.sendSizeChanged} - the Guest UI method that sends these notifications | |
| */ | |
| set onsizechange( | |
| callback: (params: McpUiSizeChangedNotification["params"]) => void, | |
| ) { | |
| this.setNotificationHandler(McpUiSizeChangedNotificationSchema, (n) => | |
| callback(n.params), | |
| ); | |
| } | |
| /** | |
| * Register a handler for sandbox proxy ready notifications. | |
| * | |
| * This is an internal callback used by web-based hosts implementing the | |
| * double-iframe sandbox architecture. The sandbox proxy sends | |
| * `ui/notifications/sandbox-proxy-ready` after it loads and is ready to receive | |
| * HTML content. | |
| * | |
| * When this fires, the host should call {@link sendSandboxResourceReady} with | |
| * the HTML content to load into the inner sandboxed iframe. | |
| * | |
| * @example | |
| * ```typescript | |
| * bridge.onsandboxready = async () => { | |
| * const resource = await mcpClient.request( | |
| * { method: "resources/read", params: { uri: "ui://my-app" } }, | |
| * ReadResourceResultSchema | |
| * ); | |
| * | |
| * bridge.sendSandboxResourceReady({ | |
| * html: resource.contents[0].text, | |
| * sandbox: "allow-scripts" | |
| * }); | |
| * }; | |
| * ``` | |
| * | |
| * @internal | |
| * @see {@link McpUiSandboxProxyReadyNotification} for the notification type | |
| * @see {@link sendSandboxResourceReady} for sending content to the sandbox | |
| */ | |
| set onsandboxready( | |
| callback: (params: McpUiSandboxProxyReadyNotification["params"]) => void, | |
| ) { | |
| this.setNotificationHandler(McpUiSandboxProxyReadyNotificationSchema, (n) => | |
| callback(n.params), | |
| ); | |
| } | |
| /** | |
| * Called when the Guest UI completes initialization. | |
| * | |
| * Set this callback to be notified when the Guest UI has finished its | |
| * 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 }); | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiInitializedNotification} for the notification type | |
| * @see {@link sendToolInput} for sending tool arguments to the Guest UI | |
| */ | |
| set oninitialized( | |
| callback: (params: McpUiInitializedNotification["params"]) => void, | |
| ) { | |
| this.setNotificationHandler(McpUiInitializedNotificationSchema, (n) => | |
| callback(n.params), | |
| ); | |
| } | |
| /** | |
| * Register a handler for message requests from the Guest UI. | |
| * | |
| * The Guest UI sends `ui/message` requests when it wants to add a message to | |
| * the host's chat interface. This enables interactive apps to communicate with | |
| * the user through the conversation thread. | |
| * | |
| * The handler should process the message (add it to the chat) and return a | |
| * result indicating success or failure. For security, the host should NOT | |
| * return conversation content or follow-up results to prevent information | |
| * leakage. | |
| * | |
| * @param callback - Handler that receives message params and returns a result | |
| * - `params.role` - Message role (currently only "user" is supported) | |
| * - `params.content` - Message content blocks (text, image, etc.) | |
| * - `extra` - Request metadata (abort signal, session info) | |
| * - 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 }; | |
| * } | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiMessageRequest} for the request type | |
| * @see {@link McpUiMessageResult} for the result type | |
| */ | |
| set onmessage( | |
| callback: ( | |
| params: McpUiMessageRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<McpUiMessageResult>, | |
| ) { | |
| this.setRequestHandler( | |
| McpUiMessageRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for external link requests from the Guest UI. | |
| * | |
| * The Guest UI sends `ui/open-link` requests when it wants to open an external | |
| * URL in the host's default browser. The handler should validate the URL and | |
| * open it according to the host's security policy and user preferences. | |
| * | |
| * The host MAY: | |
| * - Show a confirmation dialog before opening | |
| * - Block URLs based on a security policy or allowlist | |
| * - Log the request for audit purposes | |
| * - Reject the request entirely | |
| * | |
| * @param callback - Handler that receives URL params and returns a result | |
| * - `params.url` - URL to open in the host's browser | |
| * - `extra` - Request metadata (abort signal, session info) | |
| * - 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 }; | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiOpenLinkRequest} for the request type | |
| * @see {@link McpUiOpenLinkResult} for the result type | |
| */ | |
| set onopenlink( | |
| callback: ( | |
| params: McpUiOpenLinkRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<McpUiOpenLinkResult>, | |
| ) { | |
| this.setRequestHandler( | |
| McpUiOpenLinkRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for display mode change requests from the Guest UI. | |
| * | |
| * The Guest UI sends `ui/request-display-mode` requests when it wants to change | |
| * its display mode (e.g., from "inline" to "fullscreen"). The handler should | |
| * check if the requested mode is in `availableDisplayModes` from the host context, | |
| * update the display mode if supported, and return the actual mode that was set. | |
| * | |
| * If the requested mode is not available, the handler should return the current | |
| * display mode instead. | |
| * | |
| * By default, `AppBridge` returns the current `displayMode` from host context (or "inline"). | |
| * Setting this property replaces that default behavior. | |
| * | |
| * @param callback - Handler that receives the requested mode and returns the actual mode set | |
| * - `params.mode` - The display mode being requested ("inline" | "fullscreen" | "pip") | |
| * - `extra` - Request metadata (abort signal, session info) | |
| * - 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 }; | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiRequestDisplayModeRequest} for the request type | |
| * @see {@link McpUiRequestDisplayModeResult} for the result type | |
| */ | |
| set onrequestdisplaymode( | |
| callback: ( | |
| params: McpUiRequestDisplayModeRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<McpUiRequestDisplayModeResult>, | |
| ) { | |
| this.setRequestHandler( | |
| McpUiRequestDisplayModeRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for logging messages from the Guest UI. | |
| * | |
| * The Guest UI sends standard MCP `notifications/message` (logging) notifications | |
| * to report debugging information, errors, warnings, and other telemetry to the | |
| * host. The host can display these in a console, log them to a file, or send | |
| * them to a monitoring service. | |
| * | |
| * This uses the standard MCP logging notification format, not a UI-specific | |
| * message type. | |
| * | |
| * @param callback - Handler that receives logging params | |
| * - `params.level` - Log level: "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency" | |
| * - `params.logger` - Optional logger name/identifier | |
| * - `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 | |
| * ); | |
| * }; | |
| * ``` | |
| */ | |
| set onloggingmessage( | |
| callback: (params: LoggingMessageNotification["params"]) => void, | |
| ) { | |
| this.setNotificationHandler( | |
| LoggingMessageNotificationSchema, | |
| async (notification) => { | |
| callback(notification.params); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for model context updates from the Guest UI. | |
| * | |
| * The Guest UI sends `ui/update-model-context` requests to update the Host's | |
| * model context. Each request overwrites the previous context stored by the Guest UI. | |
| * Unlike logging messages, context updates are intended to be available to | |
| * the model in future turns. Unlike messages, context updates do not trigger follow-ups. | |
| * | |
| * The host will typically defer sending the context to the model until the | |
| * next user message (including `ui/message`), and will only send the last | |
| * 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 {}; | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiUpdateModelContextRequest} for the request type | |
| */ | |
| set onupdatemodelcontext( | |
| callback: ( | |
| params: McpUiUpdateModelContextRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<EmptyResult>, | |
| ) { | |
| this.setRequestHandler( | |
| McpUiUpdateModelContextRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for tool call requests from the Guest UI. | |
| * | |
| * The Guest UI sends `tools/call` requests to execute MCP server tools. This | |
| * handler allows the host to intercept and process these requests, typically | |
| * by forwarding them to the MCP server. | |
| * | |
| * @param callback - Handler that receives tool call params and returns a | |
| * `CallToolResult` | |
| * - `params` - Tool call parameters (name and arguments) | |
| * - `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 } | |
| * ); | |
| * }; | |
| * ``` | |
| * | |
| * @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type | |
| * @see `CallToolResult` from @modelcontextprotocol/sdk for the result type | |
| */ | |
| set oncalltool( | |
| callback: ( | |
| params: CallToolRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<CallToolResult>, | |
| ) { | |
| this.setRequestHandler(CallToolRequestSchema, async (request, extra) => { | |
| return callback(request.params, extra); | |
| }); | |
| } | |
| /** | |
| * Notify the Guest UI that the MCP server's tool list has changed. | |
| * | |
| * The host sends `notifications/tools/list_changed` to the Guest UI when it | |
| * receives this notification from the MCP server. This allows the Guest UI | |
| * to refresh its tool cache or UI accordingly. | |
| * | |
| * @param params - Optional notification params (typically empty) | |
| * | |
| * @example | |
| * ```typescript | |
| * // In your MCP client notification handler: | |
| * mcpClient.setNotificationHandler(ToolListChangedNotificationSchema, () => { | |
| * bridge.sendToolListChanged(); | |
| * }); | |
| * ``` | |
| * | |
| * @see `ToolListChangedNotification` from @modelcontextprotocol/sdk for the notification type | |
| */ | |
| sendToolListChanged(params: ToolListChangedNotification["params"] = {}) { | |
| return this.notification({ | |
| method: "notifications/tools/list_changed" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Register a handler for list resources requests from the Guest UI. | |
| * | |
| * The Guest UI sends `resources/list` requests to enumerate available MCP | |
| * resources. This handler allows the host to intercept and process these | |
| * requests, typically by forwarding them to the MCP server. | |
| * | |
| * @param callback - Handler that receives list params and returns a | |
| * `ListResourcesResult` | |
| * - `params` - Request params (may include cursor for pagination) | |
| * - `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 } | |
| * ); | |
| * }; | |
| * ``` | |
| * | |
| * @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type | |
| * @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type | |
| */ | |
| set onlistresources( | |
| callback: ( | |
| params: ListResourcesRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<ListResourcesResult>, | |
| ) { | |
| this.setRequestHandler( | |
| ListResourcesRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for list resource templates requests from the Guest UI. | |
| * | |
| * The Guest UI sends `resources/templates/list` requests to enumerate available | |
| * MCP resource templates. This handler allows the host to intercept and process | |
| * these requests, typically by forwarding them to the MCP server. | |
| * | |
| * @param callback - Handler that receives list params and returns a | |
| * `ListResourceTemplatesResult` | |
| * - `params` - Request params (may include cursor for pagination) | |
| * - `extra` - Request metadata (abort signal, session info) | |
| * | |
| * @example | |
| * ```typescript | |
| * bridge.onlistresourcetemplates = async (params, extra) => { | |
| * return mcpClient.request( | |
| * { method: "resources/templates/list", params }, | |
| * ListResourceTemplatesResultSchema, | |
| * { signal: extra.signal } | |
| * ); | |
| * }; | |
| * ``` | |
| * | |
| * @see `ListResourceTemplatesRequest` from @modelcontextprotocol/sdk for the request type | |
| * @see `ListResourceTemplatesResult` from @modelcontextprotocol/sdk for the result type | |
| */ | |
| set onlistresourcetemplates( | |
| callback: ( | |
| params: ListResourceTemplatesRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<ListResourceTemplatesResult>, | |
| ) { | |
| this.setRequestHandler( | |
| ListResourceTemplatesRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Register a handler for read resource requests from the Guest UI. | |
| * | |
| * The Guest UI sends `resources/read` requests to retrieve the contents of an | |
| * MCP resource. This handler allows the host to intercept and process these | |
| * requests, typically by forwarding them to the MCP server. | |
| * | |
| * @param callback - Handler that receives read params and returns a | |
| * `ReadResourceResult` | |
| * - `params` - Read parameters including the resource URI | |
| * - `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 } | |
| * ); | |
| * }; | |
| * ``` | |
| * | |
| * @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type | |
| * @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type | |
| */ | |
| set onreadresource( | |
| callback: ( | |
| params: ReadResourceRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<ReadResourceResult>, | |
| ) { | |
| this.setRequestHandler( | |
| ReadResourceRequestSchema, | |
| async (request, extra) => { | |
| return callback(request.params, extra); | |
| }, | |
| ); | |
| } | |
| /** | |
| * Notify the Guest UI that the MCP server's resource list has changed. | |
| * | |
| * The host sends `notifications/resources/list_changed` to the Guest UI when it | |
| * receives this notification from the MCP server. This allows the Guest UI | |
| * to refresh its resource cache or UI accordingly. | |
| * | |
| * @param params - Optional notification params (typically empty) | |
| * | |
| * @example | |
| * ```typescript | |
| * // In your MCP client notification handler: | |
| * mcpClient.setNotificationHandler(ResourceListChangedNotificationSchema, () => { | |
| * bridge.sendResourceListChanged(); | |
| * }); | |
| * ``` | |
| * | |
| * @see `ResourceListChangedNotification` from @modelcontextprotocol/sdk for the notification type | |
| */ | |
| sendResourceListChanged( | |
| params: ResourceListChangedNotification["params"] = {}, | |
| ) { | |
| return this.notification({ | |
| method: "notifications/resources/list_changed" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Register a handler for list prompts requests from the Guest UI. | |
| * | |
| * The Guest UI sends `prompts/list` requests to enumerate available MCP | |
| * prompts. This handler allows the host to intercept and process these | |
| * requests, typically by forwarding them to the MCP server. | |
| * | |
| * @param callback - Handler that receives list params and returns a | |
| * `ListPromptsResult` | |
| * - `params` - Request params (may include cursor for pagination) | |
| * - `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 } | |
| * ); | |
| * }; | |
| * ``` | |
| * | |
| * @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type | |
| * @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type | |
| */ | |
| set onlistprompts( | |
| callback: ( | |
| params: ListPromptsRequest["params"], | |
| extra: RequestHandlerExtra, | |
| ) => Promise<ListPromptsResult>, | |
| ) { | |
| this.setRequestHandler(ListPromptsRequestSchema, async (request, extra) => { | |
| return callback(request.params, extra); | |
| }); | |
| } | |
| /** | |
| * Notify the Guest UI that the MCP server's prompt list has changed. | |
| * | |
| * The host sends `notifications/prompts/list_changed` to the Guest UI when it | |
| * receives this notification from the MCP server. This allows the Guest UI | |
| * to refresh its prompt cache or UI accordingly. | |
| * | |
| * @param params - Optional notification params (typically empty) | |
| * | |
| * @example | |
| * ```typescript | |
| * // In your MCP client notification handler: | |
| * mcpClient.setNotificationHandler(PromptListChangedNotificationSchema, () => { | |
| * bridge.sendPromptListChanged(); | |
| * }); | |
| * ``` | |
| * | |
| * @see `PromptListChangedNotification` from @modelcontextprotocol/sdk for the notification type | |
| */ | |
| sendPromptListChanged(params: PromptListChangedNotification["params"] = {}) { | |
| return this.notification({ | |
| method: "notifications/prompts/list_changed" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Verify that the guest supports the capability required for the given request method. | |
| * @internal | |
| */ | |
| assertCapabilityForMethod(method: AppRequest["method"]): void { | |
| // TODO | |
| } | |
| /** | |
| * Verify that a request handler is registered and supported for the given method. | |
| * @internal | |
| */ | |
| assertRequestHandlerCapability(method: AppRequest["method"]): void { | |
| // TODO | |
| } | |
| /** | |
| * Verify that the host supports the capability required for the given notification method. | |
| * @internal | |
| */ | |
| assertNotificationCapability(method: AppNotification["method"]): void { | |
| // TODO | |
| } | |
| /** | |
| * Verify that task creation is supported for the given request method. | |
| * @internal | |
| */ | |
| protected assertTaskCapability(_method: string): void { | |
| throw new Error("Tasks are not supported in MCP Apps"); | |
| } | |
| /** | |
| * Verify that task handler is supported for the given method. | |
| * @internal | |
| */ | |
| protected assertTaskHandlerCapability(_method: string): void { | |
| throw new Error("Task handlers are not supported in MCP Apps"); | |
| } | |
| /** | |
| * Get the host capabilities passed to the constructor. | |
| * | |
| * @returns Host capabilities object | |
| * | |
| * @see {@link McpUiHostCapabilities} for the capabilities structure | |
| */ | |
| getCapabilities(): McpUiHostCapabilities { | |
| return this._capabilities; | |
| } | |
| /** | |
| * Handle the ui/initialize request from the guest. | |
| * @internal | |
| */ | |
| private async _oninitialize( | |
| request: McpUiInitializeRequest, | |
| ): Promise<McpUiInitializeResult> { | |
| const requestedVersion = request.params.protocolVersion; | |
| this._appCapabilities = request.params.appCapabilities; | |
| this._appInfo = request.params.appInfo; | |
| const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes( | |
| requestedVersion, | |
| ) | |
| ? requestedVersion | |
| : LATEST_PROTOCOL_VERSION; | |
| return { | |
| protocolVersion, | |
| hostCapabilities: this.getCapabilities(), | |
| hostInfo: this._hostInfo, | |
| hostContext: this._hostContext, | |
| }; | |
| } | |
| /** | |
| * Update the host context and notify the Guest UI of changes. | |
| * | |
| * Compares fields present in the new context with the current context and sends a | |
| * `ui/notifications/host-context-changed` notification containing only fields | |
| * that have been added or modified. If no fields have changed, no notification is sent. | |
| * The new context fully replaces the internal state. | |
| * | |
| * Common use cases include notifying the Guest UI when: | |
| * - Theme changes (light/dark mode toggle) | |
| * - Viewport size changes (window resize) | |
| * - Display mode changes (inline/fullscreen) | |
| * - Locale or timezone changes | |
| * | |
| * @param hostContext - The complete new host context state | |
| * | |
| * @example Update theme when user toggles dark mode | |
| * ```typescript | |
| * bridge.setHostContext({ theme: "dark" }); | |
| * ``` | |
| * | |
| * @example Update multiple context fields | |
| * ```typescript | |
| * bridge.setHostContext({ | |
| * theme: "dark", | |
| * containerDimensions: { maxHeight: 600, width: 800 } | |
| * }); | |
| * ``` | |
| * | |
| * @see {@link McpUiHostContext} for the context structure | |
| * @see {@link McpUiHostContextChangedNotification} for the notification type | |
| */ | |
| setHostContext(hostContext: McpUiHostContext) { | |
| const changes: McpUiHostContext = {}; | |
| let hasChanges = false; | |
| for (const key of Object.keys(hostContext) as Array< | |
| keyof McpUiHostContext | |
| >) { | |
| const oldValue = this._hostContext[key]; | |
| const newValue = hostContext[key]; | |
| if (deepEqual(oldValue, newValue)) { | |
| continue; | |
| } | |
| changes[key] = newValue as any; | |
| hasChanges = true; | |
| } | |
| if (hasChanges) { | |
| this._hostContext = hostContext; | |
| this.sendHostContextChange(changes); | |
| } | |
| } | |
| /** | |
| * Low-level method to notify the Guest UI of host context changes. | |
| * | |
| * Most hosts should use {@link setHostContext} instead, which automatically | |
| * detects changes and calls this method with only the modified fields. | |
| * Use this directly only when you need fine-grained control over change detection. | |
| * | |
| * @param params - The context fields that have changed (partial update) | |
| */ | |
| sendHostContextChange( | |
| params: McpUiHostContextChangedNotification["params"], | |
| ): Promise<void> | void { | |
| return this.notification({ | |
| method: "ui/notifications/host-context-changed" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Send complete tool arguments to the Guest UI. | |
| * | |
| * The host MUST send this notification after the Guest UI completes initialization | |
| * (after {@link oninitialized} callback fires) and complete tool arguments become available. | |
| * This notification is sent exactly once and is required before {@link sendToolResult}. | |
| * | |
| * @param params - Complete tool call arguments | |
| * | |
| * @example | |
| * ```typescript | |
| * bridge.oninitialized = () => { | |
| * bridge.sendToolInput({ | |
| * arguments: { location: "New York", units: "metric" } | |
| * }); | |
| * }; | |
| * ``` | |
| * | |
| * @see {@link McpUiToolInputNotification} for the notification type | |
| * @see {@link oninitialized} for the initialization callback | |
| * @see {@link sendToolResult} for sending results after execution | |
| */ | |
| sendToolInput(params: McpUiToolInputNotification["params"]) { | |
| return this.notification({ | |
| method: "ui/notifications/tool-input" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Send streaming partial tool arguments to the Guest UI. | |
| * | |
| * The host MAY send this notification zero or more times while tool arguments | |
| * are being streamed, before {@link sendToolInput} is called with complete | |
| * arguments. This enables progressive rendering of tool arguments in the | |
| * Guest UI. | |
| * | |
| * The arguments represent best-effort recovery of incomplete JSON. Guest UIs | |
| * SHOULD handle missing or changing fields gracefully between notifications. | |
| * | |
| * @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" } }); | |
| * ``` | |
| * | |
| * @see {@link McpUiToolInputPartialNotification} for the notification type | |
| * @see {@link sendToolInput} for sending complete arguments | |
| */ | |
| sendToolInputPartial(params: McpUiToolInputPartialNotification["params"]) { | |
| return this.notification({ | |
| method: "ui/notifications/tool-input-partial" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Send tool execution result to the Guest UI. | |
| * | |
| * The host MUST send this notification when tool execution completes successfully, | |
| * provided the UI is still displayed. If the UI was closed before execution | |
| * completes, the host MAY skip this notification. This must be sent after | |
| * {@link sendToolInput}. | |
| * | |
| * @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); | |
| * ``` | |
| * | |
| * @see {@link McpUiToolResultNotification} for the notification type | |
| * @see {@link sendToolInput} for sending tool arguments before results | |
| */ | |
| sendToolResult(params: McpUiToolResultNotification["params"]) { | |
| return this.notification({ | |
| method: "ui/notifications/tool-result" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Notify the Guest UI that tool execution was cancelled. | |
| * | |
| * The host MUST send this notification if tool execution was cancelled for any | |
| * reason, including user action, sampling error, classifier intervention, or | |
| * any other interruption. This allows the Guest UI to update its state and | |
| * display appropriate feedback to the user. | |
| * | |
| * @param params - Cancellation details object | |
| * - `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" }); | |
| * ``` | |
| * | |
| * @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" }); | |
| * ``` | |
| * | |
| * @see {@link McpUiToolCancelledNotification} for the notification type | |
| * @see {@link sendToolResult} for sending successful results | |
| * @see {@link sendToolInput} for sending tool arguments | |
| */ | |
| sendToolCancelled(params: McpUiToolCancelledNotification["params"]) { | |
| return this.notification({ | |
| method: "ui/notifications/tool-cancelled" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Send HTML resource to the sandbox proxy for secure loading. | |
| * | |
| * This is an internal method used by web-based hosts implementing the | |
| * double-iframe sandbox architecture. After the sandbox proxy signals readiness | |
| * via `ui/notifications/sandbox-proxy-ready`, the host sends this notification | |
| * with the HTML content to load. | |
| * | |
| * @param params - HTML content and sandbox configuration: | |
| * - `html`: The HTML content to load into the sandboxed iframe | |
| * - `sandbox`: Optional sandbox attribute value (e.g., "allow-scripts") | |
| * | |
| * @internal | |
| * @see {@link onsandboxready} for handling the sandbox proxy ready notification | |
| */ | |
| sendSandboxResourceReady( | |
| params: McpUiSandboxResourceReadyNotification["params"], | |
| ) { | |
| return this.notification({ | |
| method: "ui/notifications/sandbox-resource-ready" as const, | |
| params, | |
| }); | |
| } | |
| /** | |
| * Request graceful shutdown of the Guest UI. | |
| * | |
| * The host MUST send this request before tearing down the UI resource (before | |
| * unmounting the iframe). This gives the Guest UI an opportunity to save state, | |
| * cancel pending operations, or show confirmation dialogs. | |
| * | |
| * The host SHOULD wait for the response before unmounting to prevent data loss. | |
| * | |
| * @param params - Empty params object | |
| * @param options - Request options (timeout, etc.) | |
| * @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); | |
| * } | |
| * ``` | |
| */ | |
| teardownResource( | |
| params: McpUiResourceTeardownRequest["params"], | |
| options?: RequestOptions, | |
| ) { | |
| return this.request( | |
| { | |
| method: "ui/resource-teardown" as const, | |
| params, | |
| }, | |
| McpUiResourceTeardownResultSchema, | |
| options, | |
| ); | |
| } | |
| /** @deprecated Use {@link teardownResource} instead */ | |
| sendResourceTeardown: AppBridge["teardownResource"] = this.teardownResource; | |
| /** | |
| * Connect to the Guest UI via transport and optionally set up message forwarding. | |
| * | |
| * This method establishes the transport connection. If an MCP client was passed | |
| * to the constructor, it also automatically sets up request/notification forwarding | |
| * based on the MCP server's capabilities, proxying the following to the Guest UI: | |
| * - Tools (tools/call, notifications/tools/list_changed) | |
| * - Resources (resources/list, resources/read, resources/templates/list, notifications/resources/list_changed) | |
| * - Prompts (prompts/list, notifications/prompts/list_changed) | |
| * | |
| * If no client was passed to the constructor, no automatic forwarding is set up | |
| * and you must register handlers manually using the {@link oncalltool}, {@link onlistresources}, | |
| * etc. setters. | |
| * | |
| * After calling connect, wait for the {@link oninitialized} callback before sending | |
| * tool input and other data to the Guest UI. | |
| * | |
| * @param transport - Transport layer (typically {@link PostMessageTransport}) | |
| * @returns Promise resolving when connection is established | |
| * | |
| * @throws {Error} If a client was passed but server capabilities are not available. | |
| * This occurs when connect() is called before the MCP client has completed its | |
| * initialization with the server. Ensure `await client.connect()` completes | |
| * 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); | |
| * ``` | |
| * | |
| * @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); | |
| * ``` | |
| */ | |
| async connect(transport: Transport) { | |
| if (this._client) { | |
| // When a client was passed to the constructor, automatically forward | |
| // MCP requests/notifications between the Guest UI and the server | |
| const serverCapabilities = this._client.getServerCapabilities(); | |
| if (!serverCapabilities) { | |
| throw new Error("Client server capabilities not available"); | |
| } | |
| if (serverCapabilities.tools) { | |
| this.oncalltool = async (params, extra) => { | |
| return this._client!.request( | |
| { method: "tools/call", params }, | |
| CallToolResultSchema, | |
| { signal: extra.signal }, | |
| ); | |
| }; | |
| if (serverCapabilities.tools.listChanged) { | |
| this._client.setNotificationHandler( | |
| ToolListChangedNotificationSchema, | |
| (n) => this.sendToolListChanged(n.params), | |
| ); | |
| } | |
| } | |
| if (serverCapabilities.resources) { | |
| this.onlistresources = async (params, extra) => { | |
| return this._client!.request( | |
| { method: "resources/list", params }, | |
| ListResourcesResultSchema, | |
| { signal: extra.signal }, | |
| ); | |
| }; | |
| this.onlistresourcetemplates = async (params, extra) => { | |
| return this._client!.request( | |
| { method: "resources/templates/list", params }, | |
| ListResourceTemplatesResultSchema, | |
| { signal: extra.signal }, | |
| ); | |
| }; | |
| this.onreadresource = async (params, extra) => { | |
| return this._client!.request( | |
| { method: "resources/read", params }, | |
| ReadResourceResultSchema, | |
| { signal: extra.signal }, | |
| ); | |
| }; | |
| if (serverCapabilities.resources.listChanged) { | |
| this._client.setNotificationHandler( | |
| ResourceListChangedNotificationSchema, | |
| (n) => this.sendResourceListChanged(n.params), | |
| ); | |
| } | |
| } | |
| if (serverCapabilities.prompts) { | |
| this.onlistprompts = async (params, extra) => { | |
| return this._client!.request( | |
| { method: "prompts/list", params }, | |
| ListPromptsResultSchema, | |
| { signal: extra.signal }, | |
| ); | |
| }; | |
| if (serverCapabilities.prompts.listChanged) { | |
| this._client.setNotificationHandler( | |
| PromptListChangedNotificationSchema, | |
| (n) => this.sendPromptListChanged(n.params), | |
| ); | |
| } | |
| } | |
| } | |
| // MCP-UI specific handlers are registered by the host component | |
| // after the proxy is created. The standard MCP initialization | |
| // (via oninitialized callback set in constructor) handles the ready signal. | |
| return super.connect(transport); | |
| } | |
| } | |
| function deepEqual(a: any, b: any): boolean { | |
| return JSON.stringify(a) === JSON.stringify(b); | |
| } | |