Add API docs (#29)
* Refactor types.ts to use explicit interfaces with JSDoc Replace z.infer<typeof Schema> patterns with explicit interface definitions for all MCP UI types. Add VerifySchemaMatches helper type to ensure compile-time verification that schemas match their interfaces. Add comprehensive JSDoc documentation to all exported types including: - Clear descriptions of purpose and usage - @see references to related types and methods - Code examples where helpful - @internal markers for implementation details 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add JSDoc documentation and TypeDoc tooling Add comprehensive JSDoc documentation to: - AppBridge class and all methods - App class and all methods - PostMessageTransport class and callbacks - React hooks (useApp, useAutoResize) - React module index Set up TypeDoc for API documentation generation: - Add typedoc.json configuration - Add docs and docs:watch npm scripts - Add typedoc dev dependency - Update .gitignore to exclude generated docs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add JSDoc documentation for callback handler setters Document all notification and request handler setters in AppBridge and App classes with comprehensive JSDoc comments including: - Clear descriptions of when handlers are called - Parameter documentation and types - Practical code examples with error handling - Cross-references to related types and methods - Security considerations where relevant Also update types.ts to document optional isError fields in result types and make McpUiInitializedNotification params optional. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add JSDoc for new App methods and fix result type compatibility Document new public APIs introduced in merge commit 74a7b1f: - getHostCapabilities() - getter for host capabilities after connect - getHostVersion() - getter for host implementation info after connect - oncalltool setter - convenience handler for tool call requests - onlisttools setter - convenience handler for tool listing requests - RESOURCE_URI_META_KEY constant - metadata key for resource URIs Add index signatures to McpUiMessageResult and McpUiOpenLinkResult for MCP SDK Protocol class compatibility, matching the existing pattern in McpUiInitializeResult. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add comprehensive JSDoc documentation across library - Document App methods with detailed descriptions and examples - Add JSDoc to AppBridge methods including getters and setters - Document PostMessageTransport callback properties - Improve React hook documentation (useApp, useAutoResize) - Add documentation for types.ts result and notification types - Add @see cross-references between related methods - Fix code examples to use correct syntax and patterns - Clarify error handling behavior in method descriptions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix TypeDoc cross-module link resolution warnings Replace unqualified @link references with module-qualified syntax to fix "Failed to resolve link" warnings during docs generation. TypeDoc cannot resolve links like {@link App.sendOpenLink} without imports, so use the module-qualified format {@link app.App.sendOpenLink} instead. Changes: - types.ts: Fix 9 cross-module links to App and AppBridge methods - app-bridge.ts: Fix 2 references to App.sendSizeChange - message-transport.ts: Fix 2 references to App.connect and AppBridge.connect This eliminates all "Failed to resolve link" warnings without requiring circular imports or removing documentation cross-references. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove TypeDoc links to undocumented external types Remove @link references to types that are either: - Not exported (AppOptions) - internal implementation detail - From external packages (@modelcontextprotocol/sdk) - not included in docs This eliminates warnings about resolved but undocumented types. The type information is still available through TypeScript's type system and parameter documentation, but without broken links in the generated documentation. Removed links to: - AppOptions (internal type) - Implementation, CallToolRequest, CallToolResult (MCP SDK types) - ListToolsRequest, LoggingMessageNotification (MCP SDK types) Reduces warnings from 12 to 4 (remaining are about missing example files). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Bump @modelcontextprotocol/sdk to ^1.23.0 Update SDK dependency to fix CI build failures caused by version mismatch. CI was installing 1.23.0 (latest compatible with ^1.22.0) while local builds used 1.22.0. Note: package-lock.json is gitignored, which prevents reproducible builds. Consider committing it to ensure CI uses the same versions as local development. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix Zod type compatibility with @modelcontextprotocol/sdk The SDK imports Zod from 'zod/v4' while this package imported from 'zod'. Although both resolve to the same version (3.25.x), TypeScript sees them as separate modules with incompatible type declarations. This caused type errors when: - Extending SDK schemas like RequestSchema.extend() - Passing SDK schemas to setRequestHandler/setNotificationHandler - Using ZodObject/ZodLiteral types from different import paths Changes: - Import zod from 'zod/v4' to match SDK import path - Update z.record() calls to use Zod v4 signature (requires both key and value schemas) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add GitHub Actions workflow to deploy docs to gh-pages Adds a workflow with manual trigger (workflow_dispatch) that generates TypeDoc documentation and publishes it to the gh-pages branch using peaceiris/actions-gh-pages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add docs index.html redirect to API documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Revise README for clarity and audience focus - Add API documentation badge linking to GitHub Pages - Reorganize content around two audiences: App Developers and Host Developers - Convert relative links to absolute GitHub URLs for better portability - Simplify installation and examples sections - Add Resources section with links to docs, spec, and discussion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Update src/app-bridge.ts --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
This commit is contained in:
parent
e6b1e640ab
commit
16a9e6f034
13 changed files with 2136 additions and 133 deletions
29
.github/workflows/docs.yml
vendored
Normal file
29
.github/workflows/docs.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Future: Uncomment to auto-deploy on release
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: npm run docs
|
||||
- uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs
|
||||
publish_branch: gh-pages
|
||||
force_orphan: true
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,3 +4,5 @@ node_modules/
|
|||
package-lock.json
|
||||
yarn.lock
|
||||
.vscode/
|
||||
docs/api/
|
||||
tmp/
|
||||
|
|
|
|||
80
README.md
80
README.md
|
|
@ -1,59 +1,37 @@
|
|||
# @modelcontextprotocol/ext-apps
|
||||
|
||||
This repo contains the SDK and [specification](./specification/draft/apps.mdx) for MCP Apps Extension ([SEP-1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)).
|
||||
[](https://modelcontextprotocol.github.io/ext-apps/)
|
||||
|
||||
MCP Apps are proposed standard inspired by [MCP-UI](https://mcpui.dev/) and [OpenAI's Apps SDK](https://developers.openai.com/apps-sdk/) to allow MCP Servers to display interactive UI elements in conversational MCP clients / chatbots.
|
||||
This repo contains the SDK and [specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) for MCP Apps Extension ([SEP-1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)).
|
||||
|
||||
This repo provides:
|
||||
MCP Apps are a proposed standard inspired by [MCP-UI](https://mcpui.dev/) and [OpenAI's Apps SDK](https://developers.openai.com/apps-sdk/) to allow MCP Servers to display interactive UI elements in conversational MCP clients / chatbots.
|
||||
|
||||
- [specification/draft/apps.mdx](./specification/draft/apps.mdx): The Draft Extension Specification. It's still... in flux! Feedback welcome! (also see discussions in [SEP-1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)).
|
||||
## Overview
|
||||
|
||||
- [types.ts](./src/types.ts): Types of JSON-RPC messages used to communicate between Apps & their host
|
||||
- Note that MCP Apps also use some standard MCP messages (e.g. `tools/call` for the App to trigger actions on its originating Server - these calls are proxied through the Host), but these types are the additional messages defined by the extension
|
||||
- [examples/simple-example](./examples/simple-server): Example Server + Apps
|
||||
- [server.ts](./examples/simple-server/server.ts): MCP server with three tools that declare UI resources of Apps to be show in the chat when called
|
||||
- [ui-react.tsx](./examples/simple-server/src/ui-react.tsx): React App returned by the `create-ui-react` tool shows how to use the `useApp` hook to register MCP callbacks
|
||||
- [ui-vanilla.tsx](./examples/simple-server/src/ui-vanilla.ts): vanilla App returned by the `create-ui-vanilla`
|
||||
- [ui-raw.tsx](./examples/simple-server/src/ui-raw.ts): same as vanilla App but doesn't use the SDK runtime (just its types)
|
||||
This SDK serves two audiences:
|
||||
|
||||
- [examples/simple-host](./examples/simple-host): bare-bone examples on how to host MCP Apps (both use the [AppBridge](./src/app-bridge.ts) class to talk to a hosted App)
|
||||
- [example-host-react.tsx](./examples/simple-host/src/example-host-react.tsx) uses React (esp. [AppRenderer.tsx](./examples/simple-host/src/AppRenderer.tsx))
|
||||
- [example-host-vanilla.tsx](./examples/simple-host/src/example-host-vanilla.tsx) doesn't use React
|
||||
### App Developers
|
||||
|
||||
- [message-transport](./src/message-transport.ts): `PostMessageTransport` class that uses `postMessage` to exchange JSON-RPC messages between windows / iframes
|
||||
Build interactive UIs that run inside MCP-enabled chat clients.
|
||||
|
||||
- [app.ts](./src/app.ts): `App` class used by an App to talk to its host
|
||||
- **SDK for Apps**: `@modelcontextprotocol/ext-apps` — [API Docs](https://modelcontextprotocol.github.io/ext-apps/modules/_modelcontextprotocol_ext_apps.html)
|
||||
- **React hooks**: `@modelcontextprotocol/ext-apps/react` — [API Docs](https://modelcontextprotocol.github.io/ext-apps/modules/_modelcontextprotocol_ext_apps_react.html)
|
||||
|
||||
- [app-bridge.ts](./src/app-bridge.ts): `AppBridge` class used by the host to talk to a single App
|
||||
### Host Developers
|
||||
|
||||
- _Soon_: more examples!
|
||||
Embed and communicate with MCP Apps in your chat application.
|
||||
|
||||
What this repo does NOT provide:
|
||||
- **SDK for Hosts**: `@modelcontextprotocol/ext-apps/app-bridge` — [API Docs](https://modelcontextprotocol.github.io/ext-apps/modules/_modelcontextprotocol_ext_apps_app_bridge.html)
|
||||
|
||||
- There's no _supported_ host implementation in this repo (beyond the [examples/simple-host](./examples/simple-host) example)
|
||||
- We have [contributed a tentative implementation](https://github.com/MCP-UI-Org/mcp-ui/pull/147) of hosting / iframing / sandboxing logic to the [MCP-UI](https://github.com/idosal/mcp-ui) repository, and expect OSS clients may use it, while other clients might roll their own hosting logic.
|
||||
There's no _supported_ host implementation in this repo (beyond the [examples/simple-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/simple-host) example).
|
||||
|
||||
## Using the SDK
|
||||
We have [contributed a tentative implementation](https://github.com/MCP-UI-Org/mcp-ui/pull/147) of hosting / iframing / sandboxing logic to the [MCP-UI](https://github.com/idosal/mcp-ui) repository, and expect OSS clients may use it, while other clients might roll their own hosting logic.
|
||||
|
||||
### Run examples
|
||||
## Installation
|
||||
|
||||
Run the examples in this repo end-to-end:
|
||||
This repo is in flux and isn't published to npm yet. When it is, it will use the `@modelcontextprotocol/ext-apps` package.
|
||||
|
||||
```
|
||||
npm i
|
||||
npm start
|
||||
```
|
||||
|
||||
open http://localhost:8080/
|
||||
|
||||
> [!NOTE]
|
||||
> Please bear with us while we add more examples!
|
||||
|
||||
### Using the SDK in your project
|
||||
|
||||
This repo is in flux and isn't published to npm yet: when it is, it will use the `@modelcontextprotocol/ext-apps` package.
|
||||
|
||||
In the meantime you can depend on the SDK library in a Node.js project by installing it w/ its git URL:
|
||||
In the meantime you can depend on the SDK library in a Node.js project by installing it with its git URL:
|
||||
|
||||
```bash
|
||||
npm install -S git+https://github.com/modelcontextprotocol/ext-apps.git
|
||||
|
|
@ -63,15 +41,33 @@ Your `package.json` will then look like:
|
|||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"dependencies": {
|
||||
...
|
||||
"@modelcontextprotocol/ext-apps": "git+https://github.com/modelcontextprotocol/ext-apps.git"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> [!NOTE]
|
||||
> The build tools (`esbuild`, `tsx`, `typescript`) are in `dependencies` rather than `devDependencies`. This is intentional: it allows the `prepare` script to run when the package is installed from git, since npm doesn't install devDependencies for git dependencies.
|
||||
>
|
||||
> Once the package is published to npm with pre-built `dist/`, these can be moved back to `devDependencies`.
|
||||
|
||||
## Examples
|
||||
|
||||
- [examples/simple-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/simple-server) — Example MCP server with tools that return UI Apps
|
||||
- [examples/simple-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/simple-host) — Bare-bones example of hosting MCP Apps
|
||||
|
||||
To run the examples end-to-end:
|
||||
|
||||
```
|
||||
npm i
|
||||
npm start
|
||||
```
|
||||
|
||||
Then open http://localhost:8080/
|
||||
|
||||
## Resources
|
||||
|
||||
- [API Documentation](https://modelcontextprotocol.github.io/ext-apps/)
|
||||
- [Draft Specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)
|
||||
- [SEP-1865 Discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)
|
||||
|
|
|
|||
12
docs/index.html
Normal file
12
docs/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="0; url=api/">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to <a href="api/">API Documentation</a>...</p>
|
||||
<script>window.location.href = 'api/';</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -31,6 +31,8 @@
|
|||
"start": "NODE_ENV=development npm run build && concurrently 'npm run start:example-host' 'npm run start:example-mcp-server'",
|
||||
"build": "bun build.bun.ts",
|
||||
"prepare": "npm run build && husky",
|
||||
"docs": "typedoc",
|
||||
"docs:watch": "typedoc --watch",
|
||||
"prettier:base-cmd": "prettier -u --ignore-path ./.gitignore --ignore-path ./.prettierignore",
|
||||
"prettier": "yarn prettier:base-cmd \"$(pwd)/**/*.{js,jsx,ts,tsx,mjs,json,md,yml,yaml}\" --check",
|
||||
"prettier:fix": "yarn prettier:base-cmd \"$(pwd)/**/*.{js,jsx,ts,tsx,mjs,json,md,yml,yaml}\" --write --list-different"
|
||||
|
|
@ -45,10 +47,11 @@
|
|||
"express": "^5.1.0",
|
||||
"husky": "^9.1.7",
|
||||
"prettier": "^3.6.2",
|
||||
"typedoc": "^0.28.14",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.22.0",
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
"bun": "^1.3.2",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { ZodLiteral, ZodObject } from "zod";
|
||||
import { ZodLiteral, ZodObject } from "zod/v4";
|
||||
|
||||
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
import {
|
||||
|
|
@ -59,18 +59,114 @@ import {
|
|||
export * from "./types";
|
||||
export { PostMessageTransport } from "./message-transport";
|
||||
|
||||
type HostOptions = ProtocolOptions;
|
||||
/**
|
||||
* Options for configuring AppBridge behavior.
|
||||
*
|
||||
* @see ProtocolOptions from @modelcontextprotocol/sdk for available options
|
||||
*/
|
||||
export type HostOptions = ProtocolOptions;
|
||||
|
||||
/**
|
||||
* 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 (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. It automatically
|
||||
* forwards MCP server capabilities (tools, resources, prompts) to the Guest UI
|
||||
* and 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 `sendToolInput()`, `sendToolResult()`, etc.
|
||||
* 5. **Teardown**: Call `sendResourceTeardown()` 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<Request, Notification, Result> {
|
||||
private _appCapabilities?: McpUiAppCapabilities;
|
||||
private _appInfo?: Implementation;
|
||||
|
||||
/**
|
||||
* Create a new AppBridge instance.
|
||||
*
|
||||
* @param _client - MCP client connected to the server (for proxying requests)
|
||||
* @param _hostInfo - Host application identification (name and version)
|
||||
* @param _capabilities - Features and capabilities the host supports
|
||||
* @param options - Configuration options (inherited from Protocol)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const bridge = new AppBridge(
|
||||
* mcpClient,
|
||||
* { name: "MyHost", version: "1.0.0" },
|
||||
* { openLinks: {}, serverTools: {}, logging: {} }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private _client: Client,
|
||||
private _hostInfo: Implementation,
|
||||
|
|
@ -89,15 +185,100 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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-change` 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 viewport changes, use {@link app.App.sendSizeChange}.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onsizechange = ({ width, height }) => {
|
||||
* if (width != null) {
|
||||
* iframe.style.width = `${width}px`;
|
||||
* }
|
||||
* if (height != null) {
|
||||
* iframe.style.height = `${height}px`;
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiSizeChangeNotification} for the notification type
|
||||
* @see {@link app.App.sendSizeChange} for Host → Guest UI size notifications
|
||||
*/
|
||||
set onsizechange(
|
||||
callback: (params: McpUiSizeChangeNotification["params"]) => void,
|
||||
) {
|
||||
|
|
@ -105,6 +286,37 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
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,
|
||||
) {
|
||||
|
|
@ -112,6 +324,24 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
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,
|
||||
) {
|
||||
|
|
@ -119,6 +349,41 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
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"],
|
||||
|
|
@ -132,6 +397,50 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"],
|
||||
|
|
@ -145,6 +454,34 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
) {
|
||||
|
|
@ -156,20 +493,45 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the guest supports the capability required for the given request method.
|
||||
* @internal
|
||||
*/
|
||||
assertCapabilityForMethod(method: Request["method"]): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a request handler is registered and supported for the given method.
|
||||
* @internal
|
||||
*/
|
||||
assertRequestHandlerCapability(method: Request["method"]): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the host supports the capability required for the given notification method.
|
||||
* @internal
|
||||
*/
|
||||
assertNotificationCapability(method: Notification["method"]): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
|
@ -194,12 +556,59 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(<McpUiToolInputNotification>{
|
||||
method: "ui/notifications/tool-input",
|
||||
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(<McpUiToolResultNotification>{
|
||||
method: "ui/notifications/tool-result",
|
||||
|
|
@ -207,6 +616,21 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"],
|
||||
) {
|
||||
|
|
@ -216,6 +640,30 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.sendResourceTeardown({});
|
||||
* // Guest UI is ready, safe to unmount iframe
|
||||
* iframe.remove();
|
||||
* } catch (error) {
|
||||
* console.error("Teardown failed:", error);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
sendResourceTeardown(
|
||||
params: McpUiResourceTeardownRequest["params"],
|
||||
options?: RequestOptions,
|
||||
|
|
@ -253,6 +701,44 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
|
|||
await this._client.notification(notification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the Guest UI via transport and set up message forwarding.
|
||||
*
|
||||
* This method establishes the transport connection and automatically sets up
|
||||
* request/notification forwarding based on the MCP server's capabilities.
|
||||
* It proxies the following server capabilities to the Guest UI:
|
||||
* - Tools (tools/call, tools/list_changed)
|
||||
* - Resources (resources/list, resources/read, resources/templates/list, resources/list_changed)
|
||||
* - Prompts (prompts/list, prompts/list_changed)
|
||||
*
|
||||
* After calling connect, wait for the `oninitialized` callback before sending
|
||||
* tool input and other data to the Guest UI.
|
||||
*
|
||||
* @param transport - Transport layer (typically PostMessageTransport)
|
||||
* @returns Promise resolving when connection is established
|
||||
*
|
||||
* @throws {Error} If 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
|
||||
* ```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);
|
||||
* ```
|
||||
*/
|
||||
async connect(transport: Transport) {
|
||||
// Forward core available MCP features
|
||||
const serverCapabilities = this._client.getServerCapabilities();
|
||||
|
|
|
|||
578
src/app.ts
578
src/app.ts
|
|
@ -44,9 +44,55 @@ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|||
export { PostMessageTransport } from "./message-transport.js";
|
||||
export * from "./types";
|
||||
|
||||
/**
|
||||
* Metadata key for associating a resource URI with a tool call.
|
||||
*
|
||||
* MCP servers include this key in tool call result metadata to indicate which
|
||||
* UI resource should be displayed for the tool. When hosts receive a tool result
|
||||
* containing this metadata, they resolve and render the corresponding App.
|
||||
*
|
||||
* **Note**: This constant is provided for reference. MCP servers set this metadata
|
||||
* 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"
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @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
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const RESOURCE_URI_META_KEY = "ui/resourceUri";
|
||||
|
||||
/**
|
||||
* Options for configuring App behavior.
|
||||
*
|
||||
* Extends ProtocolOptions from the MCP SDK with App-specific configuration.
|
||||
*
|
||||
* @see ProtocolOptions from @modelcontextprotocol/sdk for inherited options
|
||||
*/
|
||||
type AppOptions = ProtocolOptions & {
|
||||
/**
|
||||
* Automatically report size changes to the host using ResizeObserver.
|
||||
*
|
||||
* When enabled, the App monitors `document.body` and `document.documentElement`
|
||||
* for size changes and automatically sends `ui/notifications/size-change`
|
||||
* notifications to the host.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
autoResize?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -54,10 +100,104 @@ type RequestHandlerExtra = Parameters<
|
|||
Parameters<App["setRequestHandler"]>[1]
|
||||
>[1];
|
||||
|
||||
/**
|
||||
* Main class for MCP Apps to communicate with their host.
|
||||
*
|
||||
* The App class provides a framework-agnostic way to build interactive MCP Apps
|
||||
* that run inside host applications. It extends the MCP SDK's Protocol class and
|
||||
* handles the connection lifecycle, initialization handshake, and bidirectional
|
||||
* communication with the host.
|
||||
*
|
||||
* ## Architecture
|
||||
*
|
||||
* Guest UIs (Apps) act as MCP clients connecting to the host via {@link PostMessageTransport}.
|
||||
* The host proxies requests to the actual MCP server and forwards
|
||||
* responses back to the App.
|
||||
*
|
||||
* ## Lifecycle
|
||||
*
|
||||
* 1. **Create**: Instantiate App with info and capabilities
|
||||
* 2. **Connect**: Call `connect()` to establish transport and perform handshake
|
||||
* 3. **Interactive**: Send requests, receive notifications, call tools
|
||||
* 4. **Cleanup**: Host sends teardown request before unmounting
|
||||
*
|
||||
* ## Inherited Methods
|
||||
*
|
||||
* As a subclass of Protocol, App inherits key methods for handling communication:
|
||||
* - `setRequestHandler()` - Register handlers for requests from host
|
||||
* - `setNotificationHandler()` - Register handlers for notifications from host
|
||||
*
|
||||
* @see Protocol from @modelcontextprotocol/sdk for all inherited methods
|
||||
*
|
||||
* ## Notification Setters
|
||||
*
|
||||
* For common notifications, the App class provides convenient setter properties
|
||||
* that simplify handler registration:
|
||||
* - `ontoolinput` - Complete tool arguments from host
|
||||
* - `ontoolinputpartial` - Streaming partial tool arguments
|
||||
* - `ontoolresult` - Tool execution results
|
||||
* - `onhostcontextchanged` - Host context changes (theme, viewport, etc.)
|
||||
*
|
||||
* These setters are convenience wrappers around `setNotificationHandler()`.
|
||||
* 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));
|
||||
* ```
|
||||
*
|
||||
* @example Sending a message to the host's chat
|
||||
* ```typescript
|
||||
* await app.sendMessage({
|
||||
* role: "user",
|
||||
* content: [{ type: "text", text: "Weather updated!" }]
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class App extends Protocol<Request, Notification, Result> {
|
||||
private _hostCapabilities?: McpUiHostCapabilities;
|
||||
private _hostInfo?: Implementation;
|
||||
|
||||
/**
|
||||
* Create a new MCP App instance.
|
||||
*
|
||||
* @param _appInfo - App identification (name and version)
|
||||
* @param _capabilities - Features and capabilities this app provides
|
||||
* @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
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private _appInfo: Implementation,
|
||||
private _capabilities: McpUiAppCapabilities = {},
|
||||
|
|
@ -71,14 +211,98 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host's capabilities discovered during initialization.
|
||||
*
|
||||
* Returns the capabilities that the host advertised during the
|
||||
* {@link connect} handshake. Returns `undefined` if called before
|
||||
* connection is established.
|
||||
*
|
||||
* @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");
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link connect} for the initialization handshake
|
||||
* @see {@link McpUiHostCapabilities} for the capabilities structure
|
||||
*/
|
||||
getHostCapabilities(): McpUiHostCapabilities | undefined {
|
||||
return this._hostCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host's implementation info discovered during initialization.
|
||||
*
|
||||
* Returns the host's name and version as advertised during the
|
||||
* {@link connect} handshake. Returns `undefined` if called before
|
||||
* connection is established.
|
||||
*
|
||||
* @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}`);
|
||||
* ```
|
||||
*
|
||||
* @see {@link connect} for the initialization handshake
|
||||
*/
|
||||
getHostVersion(): Implementation | undefined {
|
||||
return this._hostInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience handler for receiving complete tool input from the host.
|
||||
*
|
||||
* Set this property to register a handler that will be called when the host
|
||||
* sends a tool's complete arguments. This is sent after a tool call begins
|
||||
* and before the tool result is available.
|
||||
*
|
||||
* This setter is a convenience wrapper around `setNotificationHandler()` that
|
||||
* automatically handles the notification schema and extracts the params for you.
|
||||
*
|
||||
* Register handlers before calling {@link connect} to avoid missing notifications.
|
||||
*
|
||||
* @param callback - Function called with the tool input 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);
|
||||
* ```
|
||||
*
|
||||
* @example Using setNotificationHandler (more explicit)
|
||||
* ```typescript
|
||||
* app.setNotificationHandler(
|
||||
* McpUiToolInputNotificationSchema,
|
||||
* (notification) => {
|
||||
* console.log("Tool:", notification.params.arguments);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolInputNotification} for the notification structure
|
||||
*/
|
||||
set ontoolinput(
|
||||
callback: (params: McpUiToolInputNotification["params"]) => void,
|
||||
) {
|
||||
|
|
@ -86,6 +310,33 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
callback(n.params),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience handler for receiving streaming partial tool input from the host.
|
||||
*
|
||||
* Set this property to register a handler that will be called as the host
|
||||
* streams partial tool arguments during tool call initialization. This enables
|
||||
* progressive rendering of tool arguments before they're complete.
|
||||
*
|
||||
* This setter is a convenience wrapper around `setNotificationHandler()` that
|
||||
* automatically handles the notification schema and extracts the params for you.
|
||||
*
|
||||
* Register handlers before calling {@link connect} to avoid missing notifications.
|
||||
*
|
||||
* @param callback - Function called with each partial tool input update
|
||||
*
|
||||
* @example Progressive rendering of tool arguments
|
||||
* ```typescript
|
||||
* app.ontoolinputpartial = (params) => {
|
||||
* console.log("Partial args:", params.arguments);
|
||||
* // Update your UI progressively as arguments stream in
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolInputPartialNotification} for the notification structure
|
||||
* @see {@link ontoolinput} for the complete tool input handler
|
||||
*/
|
||||
set ontoolinputpartial(
|
||||
callback: (params: McpUiToolInputPartialNotification["params"]) => void,
|
||||
) {
|
||||
|
|
@ -93,6 +344,37 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
callback(n.params),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience handler for receiving tool execution results from the host.
|
||||
*
|
||||
* Set this property to register a handler that will be called when the host
|
||||
* sends the result of a tool execution. This is sent after the tool completes
|
||||
* on the MCP server, allowing your app to display the results or update its state.
|
||||
*
|
||||
* This setter is a convenience wrapper around `setNotificationHandler()` that
|
||||
* automatically handles the notification schema and extracts the params for you.
|
||||
*
|
||||
* Register handlers before calling {@link connect} to avoid missing notifications.
|
||||
*
|
||||
* @param callback - Function called with the tool result
|
||||
*
|
||||
* @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");
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolResultNotification} for the notification structure
|
||||
* @see {@link ontoolinput} for the initial tool input handler
|
||||
*/
|
||||
set ontoolresult(
|
||||
callback: (params: McpUiToolResultNotification["params"]) => void,
|
||||
) {
|
||||
|
|
@ -100,6 +382,37 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
callback(n.params),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience handler for host context changes (theme, viewport, locale, etc.).
|
||||
*
|
||||
* Set this property to register a handler that will be called when the host's
|
||||
* context changes, such as theme switching (light/dark), viewport size changes,
|
||||
* locale changes, or other environmental updates. Apps should respond by
|
||||
* updating their UI accordingly.
|
||||
*
|
||||
* This setter is a convenience wrapper around `setNotificationHandler()` that
|
||||
* automatically handles the notification schema and extracts the params for you.
|
||||
*
|
||||
* Register handlers before calling {@link connect} to avoid missing notifications.
|
||||
*
|
||||
* @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");
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiHostContextChangedNotification} for the notification structure
|
||||
* @see {@link McpUiHostContext} for the full context structure
|
||||
*/
|
||||
set onhostcontextchanged(
|
||||
callback: (params: McpUiHostContextChangedNotification["params"]) => void,
|
||||
) {
|
||||
|
|
@ -108,6 +421,38 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
(n) => callback(n.params),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience handler for tool call requests from the host.
|
||||
*
|
||||
* Set this property to register a handler that will be called when the host
|
||||
* requests this app to execute a tool. This enables apps to provide their own
|
||||
* tools that can be called by the host or LLM.
|
||||
*
|
||||
* The app must declare tool capabilities in the constructor to use this handler.
|
||||
*
|
||||
* This setter is a convenience wrapper around `setRequestHandler()` that
|
||||
* automatically handles the request schema and extracts the params for you.
|
||||
*
|
||||
* Register handlers before calling {@link connect} to avoid missing requests.
|
||||
*
|
||||
* @param callback - Async function that executes the tool and returns the result.
|
||||
* The callback will only be invoked if the app declared tool capabilities
|
||||
* 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}`);
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @see {@link setRequestHandler} for the underlying method
|
||||
*/
|
||||
set oncalltool(
|
||||
callback: (
|
||||
params: CallToolRequest["params"],
|
||||
|
|
@ -118,6 +463,37 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
callback(request.params, extra),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience handler for listing available tools.
|
||||
*
|
||||
* Set this property to register a handler that will be called when the host
|
||||
* requests a list of tools this app provides. This enables dynamic tool
|
||||
* discovery by the host or LLM.
|
||||
*
|
||||
* The app must declare tool capabilities in the constructor to use this handler.
|
||||
*
|
||||
* This setter is a convenience wrapper around `setRequestHandler()` that
|
||||
* automatically handles the request schema and extracts the params for you.
|
||||
*
|
||||
* Register handlers before calling {@link connect} to avoid missing requests.
|
||||
*
|
||||
* @param callback - Async function that returns the list of available tools.
|
||||
* The callback will only be invoked if the app declared tool capabilities
|
||||
* in the constructor.
|
||||
*
|
||||
* @example Return available tools
|
||||
* ```typescript
|
||||
* app.onlisttools = async (params, extra) => {
|
||||
* return {
|
||||
* tools: ["calculate", "convert", "format"]
|
||||
* };
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @see {@link setRequestHandler} for the underlying method
|
||||
* @see {@link oncalltool} for handling tool execution
|
||||
*/
|
||||
set onlisttools(
|
||||
callback: (
|
||||
params: ListToolsRequest["params"],
|
||||
|
|
@ -129,9 +505,18 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the host supports the capability required for the given request method.
|
||||
* @internal
|
||||
*/
|
||||
assertCapabilityForMethod(method: Request["method"]): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the app declared the capability required for the given request method.
|
||||
* @internal
|
||||
*/
|
||||
assertRequestHandlerCapability(method: Request["method"]): void {
|
||||
switch (method) {
|
||||
case "tools/call":
|
||||
|
|
@ -148,10 +533,50 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
throw new Error(`No handler for method ${method} registered`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the app supports the capability required for the given notification method.
|
||||
* @internal
|
||||
*/
|
||||
assertNotificationCapability(method: Notification["method"]): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a tool on the originating MCP server (proxied through the host).
|
||||
*
|
||||
* Apps can call tools to fetch fresh data or trigger server-side actions.
|
||||
* The host proxies the request to the actual MCP server and returns the result.
|
||||
*
|
||||
* @param params - Tool name and arguments
|
||||
* @param options - Request options (timeout, etc.)
|
||||
* @returns Tool execution result
|
||||
*
|
||||
* @throws {Error} If the tool does not exist on the server
|
||||
* @throws {Error} If the request times out or the connection is lost
|
||||
* @throws {Error} If the host rejects the request
|
||||
*
|
||||
* Note: Tool-level execution errors are returned in the result with `isError: true`
|
||||
* rather than throwing exceptions. Always check `result.isError` to distinguish
|
||||
* 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);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async callServerTool(
|
||||
params: CallToolRequest["params"],
|
||||
options?: RequestOptions,
|
||||
|
|
@ -163,6 +588,33 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the host's chat interface.
|
||||
*
|
||||
* Enables the app to add messages to the conversation thread. Useful for
|
||||
* user-initiated messages or app-to-conversation communication.
|
||||
*
|
||||
* @param params - Message role and content
|
||||
* @param options - Request options (timeout, etc.)
|
||||
* @returns Result indicating success or error (no message content returned)
|
||||
*
|
||||
* @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
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiMessageRequest} for request structure
|
||||
*/
|
||||
sendMessage(params: McpUiMessageRequest["params"], options?: RequestOptions) {
|
||||
return this.request(
|
||||
<McpUiMessageRequest>{
|
||||
|
|
@ -174,6 +626,25 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send log messages to the host for debugging and telemetry.
|
||||
*
|
||||
* Logs are not added to the conversation but may be recorded by the host
|
||||
* for debugging purposes.
|
||||
*
|
||||
* @param params - Log level and message
|
||||
*
|
||||
* @example Log app state for debugging
|
||||
* ```typescript
|
||||
* app.sendLog({
|
||||
* level: "info",
|
||||
* data: "Weather data refreshed",
|
||||
* logger: "WeatherApp"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @returns Promise that resolves when the log notification is sent
|
||||
*/
|
||||
sendLog(params: LoggingMessageNotification["params"]) {
|
||||
return this.notification(<LoggingMessageNotification>{
|
||||
method: "notifications/message",
|
||||
|
|
@ -181,6 +652,31 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the host to open an external URL in the default browser.
|
||||
*
|
||||
* The host may deny this request based on user preferences or security policy.
|
||||
* Apps should handle rejection gracefully.
|
||||
*
|
||||
* @param params - URL to open
|
||||
* @param options - Request options (timeout, etc.)
|
||||
* @returns Result indicating success or error
|
||||
*
|
||||
* @throws {Error} If the host denies the request (e.g., blocked domain, user cancelled)
|
||||
* @throws {Error} If the request times out or the connection is lost
|
||||
*
|
||||
* @example Open documentation link
|
||||
* ```typescript
|
||||
* try {
|
||||
* await app.sendOpenLink({ url: "https://docs.example.com" });
|
||||
* } catch (error) {
|
||||
* console.error("Failed to open link:", error);
|
||||
* // Optionally show fallback: display URL for manual copy
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiOpenLinkRequest} for request structure
|
||||
*/
|
||||
sendOpenLink(
|
||||
params: McpUiOpenLinkRequest["params"],
|
||||
options?: RequestOptions,
|
||||
|
|
@ -195,6 +691,26 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host of UI size changes.
|
||||
*
|
||||
* Apps can manually report size changes to help the host adjust the container.
|
||||
* If `autoResize` is enabled (default), this is called automatically.
|
||||
*
|
||||
* @param params - New width and height in pixels
|
||||
*
|
||||
* @example Manually notify host of size change
|
||||
* ```typescript
|
||||
* app.sendSizeChange({
|
||||
* width: 400,
|
||||
* height: 600
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @returns Promise that resolves when the notification is sent
|
||||
*
|
||||
* @see {@link McpUiSizeChangeNotification} for notification structure
|
||||
*/
|
||||
sendSizeChange(params: McpUiSizeChangeNotification["params"]) {
|
||||
return this.notification(<McpUiSizeChangeNotification>{
|
||||
method: "ui/notifications/size-change",
|
||||
|
|
@ -202,6 +718,31 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up automatic size change notifications using ResizeObserver.
|
||||
*
|
||||
* Observes both `document.documentElement` and `document.body` for size changes
|
||||
* and automatically sends `ui/notifications/size-change` notifications to the host.
|
||||
* The notifications are debounced using requestAnimationFrame to avoid duplicates.
|
||||
*
|
||||
* Note: This method is automatically called by `connect()` if the `autoResize`
|
||||
* option is true (default). You typically don't need to call this manually unless
|
||||
* you disabled autoResize and want to enable it later.
|
||||
*
|
||||
* @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.setupSizeChangeNotifications();
|
||||
*
|
||||
* // Clean up when done
|
||||
* cleanup();
|
||||
* ```
|
||||
*/
|
||||
setupSizeChangeNotifications() {
|
||||
let scheduled = false;
|
||||
const sendBodySizeChange = () => {
|
||||
|
|
@ -230,6 +771,43 @@ export class App extends Protocol<Request, Notification, Result> {
|
|||
return () => resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish connection with the host and perform initialization handshake.
|
||||
*
|
||||
* This method performs the following steps:
|
||||
* 1. Connects the transport layer
|
||||
* 2. Sends `ui/initialize` request with app info and capabilities
|
||||
* 3. Receives host capabilities and context in response
|
||||
* 4. Sends `ui/notifications/initialized` notification
|
||||
* 5. Sets up auto-resize using {@link setupSizeChangeNotifications} if enabled (default)
|
||||
*
|
||||
* If initialization fails, the connection is automatically closed and an error
|
||||
* is thrown.
|
||||
*
|
||||
* @param transport - Transport layer (typically PostMessageTransport)
|
||||
* @param options - Request options for the initialize request
|
||||
*
|
||||
* @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));
|
||||
* console.log("Connected successfully!");
|
||||
* } catch (error) {
|
||||
* console.error("Failed to connect:", error);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiInitializeRequest} for the initialization request structure
|
||||
* @see {@link McpUiInitializedNotification} for the initialized notification
|
||||
* @see {@link PostMessageTransport} for the typical transport implementation
|
||||
*/
|
||||
override async connect(
|
||||
transport: Transport,
|
||||
options?: RequestOptions,
|
||||
|
|
|
|||
|
|
@ -8,12 +8,69 @@ import {
|
|||
TransportSendOptions,
|
||||
} from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
|
||||
/**
|
||||
* JSON-RPC transport using window.postMessage for iframe↔parent communication.
|
||||
*
|
||||
* This transport enables bidirectional communication between MCP Apps running in
|
||||
* iframes and their host applications using the browser's postMessage API. It
|
||||
* implements the MCP SDK's Transport interface.
|
||||
*
|
||||
* ## Security
|
||||
*
|
||||
* The `eventSource` parameter provides origin validation by filtering messages
|
||||
* from specific sources. Guest UIs typically don't need to specify this (they only
|
||||
* communicate with their parent), but hosts should validate the iframe source for
|
||||
* security.
|
||||
*
|
||||
* ## Usage
|
||||
*
|
||||
* **Guest UI (default)**:
|
||||
* ```typescript
|
||||
* const transport = new PostMessageTransport(window.parent);
|
||||
* await app.connect(transport);
|
||||
* ```
|
||||
*
|
||||
* **Host (with source validation)**:
|
||||
* ```typescript
|
||||
* const iframe = document.getElementById('app-iframe') as HTMLIFrameElement;
|
||||
* const transport = new PostMessageTransport(
|
||||
* iframe.contentWindow!,
|
||||
* iframe.contentWindow // Validate messages from this iframe only
|
||||
* );
|
||||
* await bridge.connect(transport);
|
||||
* ```
|
||||
*
|
||||
* @see {@link app.App.connect} for Guest UI usage
|
||||
* @see {@link app-bridge.AppBridge.connect} for Host usage
|
||||
*/
|
||||
export class PostMessageTransport implements Transport {
|
||||
private messageListener: (
|
||||
this: Window,
|
||||
ev: WindowEventMap["message"],
|
||||
) => any | undefined;
|
||||
|
||||
/**
|
||||
* Create a new PostMessageTransport.
|
||||
*
|
||||
* @param eventTarget - Target window to send messages to (default: window.parent)
|
||||
* @param eventSource - Optional source validation. If specified, only messages from
|
||||
* this source will be accepted. Guest UIs typically don't need this (they only
|
||||
* receive from parent), but hosts should validate the iframe source.
|
||||
*
|
||||
* @example Guest UI connecting to parent
|
||||
* ```typescript
|
||||
* const transport = new PostMessageTransport(window.parent);
|
||||
* ```
|
||||
*
|
||||
* @example Host connecting to iframe with validation
|
||||
* ```typescript
|
||||
* const iframe = document.getElementById('app') as HTMLIFrameElement;
|
||||
* const transport = new PostMessageTransport(
|
||||
* iframe.contentWindow!,
|
||||
* iframe.contentWindow // Only accept messages from this iframe
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
private eventTarget: Window = window.parent,
|
||||
private eventSource?: MessageEventSource,
|
||||
|
|
@ -37,20 +94,84 @@ export class PostMessageTransport implements Transport {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin listening for messages from the event source.
|
||||
*
|
||||
* Registers a message event listener on the window. Must be called before
|
||||
* messages can be received.
|
||||
*/
|
||||
async start() {
|
||||
window.addEventListener("message", this.messageListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON-RPC message to the target window.
|
||||
*
|
||||
* Messages are sent using postMessage with "*" origin, meaning they are visible
|
||||
* to all frames. The receiver should validate the message source for security.
|
||||
*
|
||||
* @param message - JSON-RPC message to send
|
||||
* @param options - Optional send options (currently unused)
|
||||
*/
|
||||
async send(message: JSONRPCMessage, options?: TransportSendOptions) {
|
||||
console.info("[host] Sending message", message);
|
||||
this.eventTarget.postMessage(message, "*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening for messages and cleanup.
|
||||
*
|
||||
* Removes the message event listener and calls the {@link onclose} callback if set.
|
||||
*/
|
||||
async close() {
|
||||
window.removeEventListener("message", this.messageListener);
|
||||
this.onclose?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the transport is closed.
|
||||
*
|
||||
* Set this handler to be notified when {@link close} is called.
|
||||
*/
|
||||
onclose?: () => void;
|
||||
|
||||
/**
|
||||
* Called when a message parsing error occurs.
|
||||
*
|
||||
* This handler is invoked when a received message fails JSON-RPC schema
|
||||
* validation. The error parameter contains details about the validation failure.
|
||||
*
|
||||
* @param error - Error describing the validation failure
|
||||
*/
|
||||
onerror?: (error: Error) => void;
|
||||
|
||||
/**
|
||||
* Called when a valid JSON-RPC message is received.
|
||||
*
|
||||
* This handler is invoked after message validation succeeds. The {@link start}
|
||||
* method must be called before messages will be received.
|
||||
*
|
||||
* @param message - The validated JSON-RPC message
|
||||
* @param extra - Optional metadata about the message (unused in this transport)
|
||||
*/
|
||||
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;
|
||||
|
||||
/**
|
||||
* Optional session identifier for this transport connection.
|
||||
*
|
||||
* Set by the MCP SDK to track the connection session. Not required for
|
||||
* PostMessageTransport functionality.
|
||||
*/
|
||||
sessionId?: string;
|
||||
|
||||
/**
|
||||
* Callback to set the negotiated protocol version.
|
||||
*
|
||||
* The MCP SDK calls this during initialization to communicate the protocol
|
||||
* version negotiated with the peer.
|
||||
*
|
||||
* @param version - The negotiated protocol version string
|
||||
*/
|
||||
setProtocolVersion?: (version: string) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,34 @@
|
|||
/**
|
||||
* React utilities for building MCP Apps.
|
||||
*
|
||||
* This module provides React hooks and utilities for easily building
|
||||
* interactive MCP Apps using React. This is optional - the core SDK
|
||||
* ({@link App}, {@link PostMessageTransport}) is framework-agnostic and can be
|
||||
* used with any UI framework or vanilla JavaScript.
|
||||
*
|
||||
* ## Main Exports
|
||||
*
|
||||
* - {@link useApp} - React hook to create and connect an MCP App
|
||||
* - {@link useAutoResize} - React hook for manual auto-resize control (rarely needed)
|
||||
*
|
||||
* @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>;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export * from "./useApp";
|
||||
export * from "./useAutoResize";
|
||||
|
|
|
|||
|
|
@ -4,22 +4,105 @@ import { Client } from "@modelcontextprotocol/sdk/client";
|
|||
import { App, McpUiAppCapabilities, PostMessageTransport } from "../app";
|
||||
export * from "../app";
|
||||
|
||||
/**
|
||||
* Options for configuring the useApp hook.
|
||||
*
|
||||
* Note: This interface does NOT expose App options like `autoResize`.
|
||||
* The hook creates the App with default options (autoResize: true). If you need
|
||||
* custom App options, create the App manually instead of using this hook.
|
||||
*
|
||||
* @see {@link useApp} for the hook that uses these options
|
||||
* @see {@link useAutoResize} for manual auto-resize control with custom App options
|
||||
*/
|
||||
export interface UseAppOptions {
|
||||
/** App identification (name and version) */
|
||||
appInfo: Implementation;
|
||||
/** Features and capabilities this app provides */
|
||||
capabilities: McpUiAppCapabilities;
|
||||
/**
|
||||
* Called after client is created but before connection.
|
||||
* Use this to register handlers via app.ontoolinput, app.toolresult, etc.
|
||||
* Called after App is created but before connection.
|
||||
*
|
||||
* Use this to register request/notification handlers that need to be in place
|
||||
* before the initialization handshake completes.
|
||||
*
|
||||
* @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);
|
||||
* }
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
onAppCreated?: (app: App) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* State returned by the useApp hook.
|
||||
*/
|
||||
export interface AppState {
|
||||
/** The connected App instance, null during initialization */
|
||||
app: App | null;
|
||||
/** Whether initialization completed successfully */
|
||||
isConnected: boolean;
|
||||
/** Connection error if initialization failed, null otherwise */
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook to create and connect an MCP App.
|
||||
*
|
||||
* This hook manages the complete lifecycle of an {@link App}: creation, connection,
|
||||
* and cleanup. It automatically creates a {@link PostMessageTransport} to window.parent
|
||||
* and handles initialization.
|
||||
*
|
||||
* **Important**: The hook intentionally does NOT re-run when options change
|
||||
* to avoid reconnection loops. Options are only used during the initial mount.
|
||||
*
|
||||
* **Note**: This is part of the optional React integration. The core SDK
|
||||
* (App, PostMessageTransport) is framework-agnostic and can be
|
||||
* used with any UI framework or vanilla JavaScript.
|
||||
*
|
||||
* @param options - Configuration for the app
|
||||
* @returns Current connection state and app instance. If connection fails during
|
||||
* initialization, the `error` field will contain the error (typically connection
|
||||
* 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>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link App.connect} for the underlying connection method
|
||||
* @see {@link useAutoResize} for manual auto-resize control when using custom App options
|
||||
*/
|
||||
export function useApp({
|
||||
appInfo,
|
||||
capabilities,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,52 @@ import { useEffect, RefObject } from "react";
|
|||
import { App } from "../app";
|
||||
|
||||
/**
|
||||
* Custom hook that automatically reports size changes to the parent window.
|
||||
* React hook that automatically reports UI size changes to the host.
|
||||
*
|
||||
* @param client - MCP UI client for sending size notifications
|
||||
* Uses ResizeObserver to watch `document.body` and `document.documentElement` for
|
||||
* size changes and sends `ui/notifications/size-change` notifications.
|
||||
*
|
||||
* **Note**: This hook is rarely needed since the {@link useApp} hook automatically enables
|
||||
* auto-resize by default. This hook is provided for advanced cases where you
|
||||
* create the {@link App} manually with `autoResize: false` and want to add auto-resize
|
||||
* behavior later.
|
||||
*
|
||||
* @param app - The connected App instance, or null during initialization
|
||||
* @param elementRef - Currently unused. The hook always observes `document.body`
|
||||
* and `document.documentElement` regardless of this value. Passing a ref will
|
||||
* 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);
|
||||
* 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>;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link App.setupSizeChangeNotifications} for the underlying implementation
|
||||
* @see {@link useApp} which enables auto-resize by default
|
||||
* @see {@link App} constructor for configuring `autoResize` option
|
||||
*/
|
||||
export function useAutoResize(
|
||||
app: App | null,
|
||||
|
|
|
|||
767
src/types.ts
767
src/types.ts
|
|
@ -1,29 +1,133 @@
|
|||
import {
|
||||
CallToolResult,
|
||||
CallToolResultSchema,
|
||||
ContentBlock,
|
||||
ContentBlockSchema,
|
||||
EmptyResultSchema,
|
||||
Implementation,
|
||||
ImplementationSchema,
|
||||
RequestId,
|
||||
RequestIdSchema,
|
||||
RequestSchema,
|
||||
Tool,
|
||||
ToolSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { z } from "zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
* Type-level assertion that validates a Zod schema produces the expected interface.
|
||||
*
|
||||
* This helper is used for request and notification schemas that cannot use
|
||||
* `z.ZodType<Interface>` type annotations. Adding `: z.ZodType<Interface>` to
|
||||
* schemas widens the type from the specific `ZodObject` to the generic `ZodType`,
|
||||
* which breaks MCP SDK's `setRequestHandler()` and `setNotificationHandler()`
|
||||
* methods that require the specific `ZodObject` type.
|
||||
*
|
||||
* By using this type-level assertion instead, we get compile-time validation that
|
||||
* the schema matches the interface without affecting the runtime schema type.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
type VerifySchemaMatches<TSchema extends z.ZodTypeAny, TInterface> =
|
||||
z.infer<TSchema> extends TInterface
|
||||
? TInterface extends z.infer<TSchema>
|
||||
? true
|
||||
: never
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Current protocol version supported by this SDK.
|
||||
*
|
||||
* The SDK automatically handles version negotiation during initialization.
|
||||
* Apps and hosts don't need to manage protocol versions manually.
|
||||
*/
|
||||
export const LATEST_PROTOCOL_VERSION = "2025-11-21";
|
||||
|
||||
/**
|
||||
* Request to open an external URL in the host's default browser.
|
||||
*
|
||||
* Sent from the Guest UI to the Host when requesting to open an external link.
|
||||
* The host may deny the request based on user preferences or security policy.
|
||||
*
|
||||
* @see {@link app.App.sendOpenLink} for the method that sends this request
|
||||
*/
|
||||
export interface McpUiOpenLinkRequest {
|
||||
method: "ui/open-link";
|
||||
params: {
|
||||
/** URL to open in the host's browser */
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiOpenLinkRequest}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiOpenLinkRequestSchema = RequestSchema.extend({
|
||||
method: z.literal("ui/open-link"),
|
||||
params: z.object({
|
||||
url: z.string().url(),
|
||||
}),
|
||||
});
|
||||
export type McpUiOpenLinkRequest = z.infer<typeof McpUiOpenLinkRequestSchema>;
|
||||
|
||||
export const McpUiOpenLinkResultSchema = z.object({
|
||||
isError: z.boolean().optional(),
|
||||
});
|
||||
export type McpUiOpenLinkResult = z.infer<typeof McpUiOpenLinkResultSchema>;
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyOpenLinkRequest = VerifySchemaMatches<
|
||||
typeof McpUiOpenLinkRequestSchema,
|
||||
McpUiOpenLinkRequest
|
||||
>;
|
||||
|
||||
/**
|
||||
* Result from a {@link McpUiOpenLinkRequest}.
|
||||
*
|
||||
* The host returns this result after attempting to open the requested URL.
|
||||
*
|
||||
* @see {@link McpUiOpenLinkRequest}
|
||||
*/
|
||||
export interface McpUiOpenLinkResult {
|
||||
/**
|
||||
* True if the host failed to open the URL (e.g., due to security policy,
|
||||
* user cancellation, or system error). False or undefined indicates success.
|
||||
*/
|
||||
isError?: boolean;
|
||||
/**
|
||||
* Index signature required for MCP SDK `Protocol` class compatibility.
|
||||
* Note: The schema intentionally omits this to enforce strict validation.
|
||||
*/
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiOpenLinkResult}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiOpenLinkResultSchema: z.ZodType<McpUiOpenLinkResult> =
|
||||
z.object({
|
||||
isError: z.boolean().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Request to send a message to the host's chat interface.
|
||||
*
|
||||
* Sent from the Guest UI to the Host when the app wants to add a message to the
|
||||
* conversation thread. This enables interactive apps to communicate with the user
|
||||
* through the host's chat interface.
|
||||
*
|
||||
* @see {@link app.App.sendMessage} for the method that sends this request
|
||||
*/
|
||||
export interface McpUiMessageRequest {
|
||||
method: "ui/message";
|
||||
params: {
|
||||
/** Message role, currently only "user" is supported */
|
||||
role: "user";
|
||||
/** Message content blocks (text, image, etc.) */
|
||||
content: ContentBlock[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiMessageRequest}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiMessageRequestSchema = RequestSchema.extend({
|
||||
method: z.literal("ui/message"),
|
||||
params: z.object({
|
||||
|
|
@ -31,38 +135,146 @@ export const McpUiMessageRequestSchema = RequestSchema.extend({
|
|||
content: z.array(ContentBlockSchema),
|
||||
}),
|
||||
});
|
||||
export type McpUiMessageRequest = z.infer<typeof McpUiMessageRequestSchema>;
|
||||
|
||||
export const McpUiMessageResultSchema = z.object({
|
||||
// Note: we don't return the result from follow up messages as they might leak info from the chat.
|
||||
// We do tell the caller if it errored, though.
|
||||
isError: z.boolean().optional(),
|
||||
});
|
||||
export type McpUiMessageResult = z.infer<typeof McpUiMessageResultSchema>;
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyMessageRequest = VerifySchemaMatches<
|
||||
typeof McpUiMessageRequestSchema,
|
||||
McpUiMessageRequest
|
||||
>;
|
||||
|
||||
/**
|
||||
* Result from a {@link McpUiMessageRequest}.
|
||||
*
|
||||
* Note: The host does not return message content or follow-up results to prevent
|
||||
* leaking information from the conversation. Only error status is provided.
|
||||
*
|
||||
* @see {@link McpUiMessageRequest}
|
||||
*/
|
||||
export interface McpUiMessageResult {
|
||||
/**
|
||||
* True if the host rejected or failed to deliver the message (e.g., due to
|
||||
* rate limiting, content policy, or system error). False or undefined
|
||||
* indicates the message was accepted.
|
||||
*/
|
||||
isError?: boolean;
|
||||
/**
|
||||
* Index signature required for MCP SDK `Protocol` class compatibility.
|
||||
* Note: The schema intentionally omits this to enforce strict validation.
|
||||
*/
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiMessageResult}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiMessageResultSchema: z.ZodType<McpUiMessageResult> = z.object(
|
||||
{
|
||||
isError: z.boolean().optional(),
|
||||
},
|
||||
);
|
||||
|
||||
// McpUiIframeReadyNotification removed - replaced by standard MCP initialization
|
||||
// The SDK's oninitialized callback now handles the ready signal
|
||||
|
||||
/**
|
||||
* Notification that the sandbox proxy iframe is ready to receive content.
|
||||
*
|
||||
* This is an internal message used by web-based hosts implementing the
|
||||
* double-iframe sandbox architecture. The sandbox proxy sends this to the host
|
||||
* after it loads and is ready to receive HTML content via
|
||||
* {@link McpUiSandboxResourceReadyNotification}.
|
||||
*
|
||||
* @internal
|
||||
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
|
||||
*/
|
||||
export interface McpUiSandboxProxyReadyNotification {
|
||||
method: "ui/notifications/sandbox-proxy-ready";
|
||||
params: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiSandboxProxyReadyNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiSandboxProxyReadyNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/sandbox-proxy-ready"),
|
||||
params: z.object({}),
|
||||
});
|
||||
export type McpUiSandboxProxyReadyNotification = z.infer<
|
||||
typeof McpUiSandboxProxyReadyNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifySandboxProxyReadyNotification = VerifySchemaMatches<
|
||||
typeof McpUiSandboxProxyReadyNotificationSchema,
|
||||
McpUiSandboxProxyReadyNotification
|
||||
>;
|
||||
|
||||
/**
|
||||
* Notification containing HTML resource for the sandbox proxy to load.
|
||||
*
|
||||
* This is an internal message used by web-based hosts implementing the
|
||||
* double-iframe sandbox architecture. After the sandbox proxy signals readiness,
|
||||
* the host sends this notification with the HTML content and optional sandbox
|
||||
* attributes to load into the inner iframe.
|
||||
*
|
||||
* @internal
|
||||
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
|
||||
*/
|
||||
export interface McpUiSandboxResourceReadyNotification {
|
||||
method: "ui/notifications/sandbox-resource-ready";
|
||||
params: {
|
||||
/** HTML content to load into the inner iframe */
|
||||
html: string;
|
||||
/** Optional override for the inner iframe's sandbox attribute */
|
||||
sandbox?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiSandboxResourceReadyNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiSandboxResourceReadyNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/sandbox-resource-ready"),
|
||||
params: z.object({
|
||||
html: z.string(), //ReadResourceResultSchema,
|
||||
html: z.string(),
|
||||
sandbox: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
export type McpUiSandboxResourceReadyNotification = z.infer<
|
||||
typeof McpUiSandboxResourceReadyNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifySandboxResourceReadyNotification = VerifySchemaMatches<
|
||||
typeof McpUiSandboxResourceReadyNotificationSchema,
|
||||
McpUiSandboxResourceReadyNotification
|
||||
>;
|
||||
|
||||
// Fired by the iframe when its body changes, and by the host when the viewport size changes.
|
||||
/**
|
||||
* Notification of UI size changes (bidirectional: Guest ↔ Host).
|
||||
*
|
||||
* **Guest UI → Host**: Sent by the Guest UI when its rendered content size changes,
|
||||
* typically using ResizeObserver. This helps the host adjust the iframe container.
|
||||
* If {@link app.App} is configured with `autoResize: true` (default), this is sent
|
||||
* automatically.
|
||||
*
|
||||
* **Host → Guest UI**: Sent by the Host when the viewport size changes (e.g.,
|
||||
* window resize, orientation change). This allows the Guest UI to adjust its layout.
|
||||
*
|
||||
* @see {@link app.App.sendSizeChange} for the method to send this from Guest UI
|
||||
* @see {@link app.App.setupSizeChangeNotifications} for automatic size reporting
|
||||
*/
|
||||
export interface McpUiSizeChangeNotification {
|
||||
method: "ui/notifications/size-change";
|
||||
params: {
|
||||
/** New width in pixels */
|
||||
width?: number;
|
||||
/** New height in pixels */
|
||||
height?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiSizeChangeNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiSizeChangeNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/size-change"),
|
||||
params: z.object({
|
||||
|
|
@ -70,45 +282,223 @@ export const McpUiSizeChangeNotificationSchema = z.object({
|
|||
height: z.number().optional(),
|
||||
}),
|
||||
});
|
||||
export type McpUiSizeChangeNotification = z.infer<
|
||||
typeof McpUiSizeChangeNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifySizeChangeNotification = VerifySchemaMatches<
|
||||
typeof McpUiSizeChangeNotificationSchema,
|
||||
McpUiSizeChangeNotification
|
||||
>;
|
||||
|
||||
/**
|
||||
* Notification containing complete tool arguments (Host → Guest UI).
|
||||
*
|
||||
* The host MUST send this notification after the Guest UI's initialize request
|
||||
* completes, when complete tool arguments become available. This notification is
|
||||
* sent exactly once and is required before {@link McpUiToolResultNotification}.
|
||||
*
|
||||
* The arguments object contains the complete tool call parameters that triggered
|
||||
* this App instance.
|
||||
*/
|
||||
export interface McpUiToolInputNotification {
|
||||
method: "ui/notifications/tool-input";
|
||||
params: {
|
||||
/** Complete tool call arguments as key-value pairs */
|
||||
arguments?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiToolInputNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiToolInputNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/tool-input"),
|
||||
params: z.object({
|
||||
arguments: z.record(z.unknown()).optional(),
|
||||
arguments: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
});
|
||||
export type McpUiToolInputNotification = z.infer<
|
||||
typeof McpUiToolInputNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyToolInputNotification = VerifySchemaMatches<
|
||||
typeof McpUiToolInputNotificationSchema,
|
||||
McpUiToolInputNotification
|
||||
>;
|
||||
|
||||
/**
|
||||
* Notification containing partial/streaming tool arguments (Host → Guest UI).
|
||||
*
|
||||
* The host MAY send this notification zero or more times while the agent is
|
||||
* streaming tool arguments, before {@link McpUiToolInputNotification} is sent
|
||||
* with complete arguments.
|
||||
*
|
||||
* The arguments object represents best-effort recovery of incomplete JSON, with
|
||||
* unclosed structures automatically closed to produce valid JSON. Guest UIs may
|
||||
* ignore these notifications or use them to render progressive loading states.
|
||||
*
|
||||
* Guest UIs MUST NOT rely on partial arguments for critical operations and SHOULD
|
||||
* gracefully handle missing or changing fields between notifications.
|
||||
*/
|
||||
export interface McpUiToolInputPartialNotification {
|
||||
method: "ui/notifications/tool-input-partial";
|
||||
params: {
|
||||
/** Partial tool call arguments (incomplete, may change) */
|
||||
arguments?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiToolInputPartialNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiToolInputPartialNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/tool-input-partial"),
|
||||
params: z.object({
|
||||
arguments: z.record(z.unknown()).optional(),
|
||||
arguments: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
});
|
||||
export type McpUiToolInputPartialNotification = z.infer<
|
||||
typeof McpUiToolInputPartialNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyToolInputPartialNotification = VerifySchemaMatches<
|
||||
typeof McpUiToolInputPartialNotificationSchema,
|
||||
McpUiToolInputPartialNotification
|
||||
>;
|
||||
|
||||
// Fired once both tool call returned *AND* host received ui/ui-lifecycle-iframe-ready.
|
||||
/**
|
||||
* Notification containing tool execution result (Host → 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 notification is sent after
|
||||
* {@link McpUiToolInputNotification}.
|
||||
*
|
||||
* The result follows the standard MCP CallToolResult format, containing content
|
||||
* for the model and optionally structuredContent optimized for UI rendering.
|
||||
*/
|
||||
export interface McpUiToolResultNotification {
|
||||
method: "ui/notifications/tool-result";
|
||||
/** Standard MCP tool execution result */
|
||||
params: CallToolResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiToolResultNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiToolResultNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/tool-result"),
|
||||
params: CallToolResultSchema,
|
||||
});
|
||||
export type McpUiToolResultNotification = z.infer<
|
||||
typeof McpUiToolResultNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyToolResultNotification = VerifySchemaMatches<
|
||||
typeof McpUiToolResultNotificationSchema,
|
||||
McpUiToolResultNotification
|
||||
>;
|
||||
|
||||
export const McpUiHostContextSchema = z.object({
|
||||
/**
|
||||
* Rich context about the host environment provided to Guest UIs.
|
||||
*
|
||||
* Hosts provide this context in the {@link McpUiInitializeResult} response and send
|
||||
* updates via {@link McpUiHostContextChangedNotification} when values change.
|
||||
* All fields are optional and Guest UIs should handle missing fields gracefully.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Received during initialization
|
||||
* const result = await app.connect(transport);
|
||||
* const context = result.hostContext;
|
||||
*
|
||||
* if (context.theme === "dark") {
|
||||
* document.body.classList.add("dark-mode");
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface McpUiHostContext {
|
||||
/** Metadata of the tool call that instantiated this App */
|
||||
toolInfo?: {
|
||||
/** JSON-RPC id of the tools/call request */
|
||||
id: RequestId;
|
||||
/** Tool definition including name, inputSchema, etc. */
|
||||
tool: Tool;
|
||||
};
|
||||
/**
|
||||
* Current color theme preference.
|
||||
* @example "dark"
|
||||
*/
|
||||
theme?: "light" | "dark" | "system";
|
||||
/**
|
||||
* How the UI is currently displayed.
|
||||
* @example "inline"
|
||||
*/
|
||||
displayMode?: "inline" | "fullscreen" | "pip" | "carousel";
|
||||
/**
|
||||
* Display modes the host supports.
|
||||
* Apps can use this to offer mode-switching UI if applicable.
|
||||
*/
|
||||
availableDisplayModes?: string[];
|
||||
/** Current and maximum dimensions available to the UI */
|
||||
viewport?: {
|
||||
/** Current viewport width in pixels */
|
||||
width: number;
|
||||
/** Current viewport height in pixels */
|
||||
height: number;
|
||||
/** Maximum available height in pixels (if constrained) */
|
||||
maxHeight?: number;
|
||||
/** Maximum available width in pixels (if constrained) */
|
||||
maxWidth?: number;
|
||||
};
|
||||
/**
|
||||
* User's language and region preference in BCP 47 format.
|
||||
* @example "en-US", "fr-CA", "ja-JP"
|
||||
*/
|
||||
locale?: string;
|
||||
/**
|
||||
* User's timezone in IANA format.
|
||||
* @example "America/New_York", "Europe/London", "Asia/Tokyo"
|
||||
*/
|
||||
timeZone?: string;
|
||||
/**
|
||||
* Host application identifier.
|
||||
* @example "claude-desktop/1.0.0"
|
||||
*/
|
||||
userAgent?: string;
|
||||
/**
|
||||
* Platform type for responsive design decisions.
|
||||
* @example "desktop"
|
||||
*/
|
||||
platform?: "web" | "desktop" | "mobile";
|
||||
/** Device input capabilities */
|
||||
deviceCapabilities?: {
|
||||
/** Whether the device supports touch input */
|
||||
touch?: boolean;
|
||||
/** Whether the device supports hover interactions */
|
||||
hover?: boolean;
|
||||
};
|
||||
/**
|
||||
* Mobile safe area boundaries in pixels.
|
||||
* Used to avoid notches, rounded corners, and system UI on mobile devices.
|
||||
*/
|
||||
safeAreaInsets?: {
|
||||
/** Top safe area inset in pixels */
|
||||
top: number;
|
||||
/** Right safe area inset in pixels */
|
||||
right: number;
|
||||
/** Bottom safe area inset in pixels */
|
||||
bottom: number;
|
||||
/** Left safe area inset in pixels */
|
||||
left: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiHostContext}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiHostContextSchema: z.ZodType<McpUiHostContext> = z.object({
|
||||
toolInfo: z
|
||||
.object({
|
||||
// Metadata of the tool call that instantiated the App
|
||||
id: RequestIdSchema, // JSON-RPC id of the tools/call request
|
||||
tool: ToolSchema, // contains name, inputSchema, etc…
|
||||
id: RequestIdSchema,
|
||||
tool: ToolSchema,
|
||||
})
|
||||
.optional(),
|
||||
theme: z.enum(["light", "dark", "system"]).optional(),
|
||||
|
|
@ -122,8 +512,8 @@ export const McpUiHostContextSchema = z.object({
|
|||
maxWidth: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
locale: z.string().optional(), // BCP 47, e.g., "en-US"
|
||||
timeZone: z.string().optional(), // IANA, e.g., "America/New_York"
|
||||
locale: z.string().optional(),
|
||||
timeZone: z.string().optional(),
|
||||
userAgent: z.string().optional(),
|
||||
platform: z.enum(["web", "desktop", "mobile"]).optional(),
|
||||
deviceCapabilities: z
|
||||
|
|
@ -141,64 +531,219 @@ export const McpUiHostContextSchema = z.object({
|
|||
})
|
||||
.optional(),
|
||||
});
|
||||
export type McpUiHostContext = z.infer<typeof McpUiHostContextSchema>;
|
||||
|
||||
/**
|
||||
* Notification that host context has changed (Host → Guest UI).
|
||||
*
|
||||
* The host MAY send this notification when any context field changes, such as:
|
||||
* - Theme toggled (light/dark)
|
||||
* - Display mode changed (inline/fullscreen)
|
||||
* - Device orientation changed
|
||||
* - Window/panel resized
|
||||
*
|
||||
* This notification contains partial updates. Guest UIs SHOULD merge received
|
||||
* fields with their current context state rather than replacing it entirely.
|
||||
*
|
||||
* @see {@link McpUiHostContext} for the full context structure
|
||||
*/
|
||||
export interface McpUiHostContextChangedNotification {
|
||||
method: "ui/notifications/host-context-changed";
|
||||
/** Partial context update containing only changed fields */
|
||||
params: McpUiHostContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiHostContextChangedNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiHostContextChangedNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/host-context-changed"),
|
||||
params: McpUiHostContextSchema,
|
||||
});
|
||||
export type McpUiHostContextChangedNotification = z.infer<
|
||||
typeof McpUiHostContextChangedNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyHostContextChangedNotification = VerifySchemaMatches<
|
||||
typeof McpUiHostContextChangedNotificationSchema,
|
||||
McpUiHostContextChangedNotification
|
||||
>;
|
||||
|
||||
/**
|
||||
* Request for graceful shutdown of the Guest UI (Host → Guest UI).
|
||||
*
|
||||
* The host MUST send this request before tearing down the UI resource, for any
|
||||
* reason including user action, resource reallocation, or app closure. 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 the iframe to prevent
|
||||
* data loss.
|
||||
*
|
||||
* @see {@link app-bridge.AppBridge.sendResourceTeardown} for the host method that sends this
|
||||
*/
|
||||
export interface McpUiResourceTeardownRequest {
|
||||
method: "ui/resource-teardown";
|
||||
params: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiResourceTeardownRequest}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiResourceTeardownRequestSchema = RequestSchema.extend({
|
||||
method: z.literal("ui/resource-teardown"),
|
||||
params: z.object({}),
|
||||
});
|
||||
export type McpUiResourceTeardownRequest = z.infer<
|
||||
typeof McpUiResourceTeardownRequestSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyResourceTeardownRequest = VerifySchemaMatches<
|
||||
typeof McpUiResourceTeardownRequestSchema,
|
||||
McpUiResourceTeardownRequest
|
||||
>;
|
||||
|
||||
export const McpUiResourceTeardownResultSchema = EmptyResultSchema;
|
||||
export type McpUiResourceTeardownResult = z.infer<
|
||||
typeof McpUiResourceTeardownResultSchema
|
||||
>;
|
||||
/**
|
||||
* Result from graceful shutdown request.
|
||||
*
|
||||
* Empty result indicates the Guest UI has completed cleanup and is ready to be
|
||||
* torn down.
|
||||
*
|
||||
* @see {@link McpUiResourceTeardownRequest}
|
||||
*/
|
||||
export interface McpUiResourceTeardownResult {}
|
||||
|
||||
export const McpUiHostCapabilitiesSchema = z.object({
|
||||
experimental: z.object({}).optional(),
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiResourceTeardownResult}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiResourceTeardownResultSchema: z.ZodType<McpUiResourceTeardownResult> =
|
||||
EmptyResultSchema;
|
||||
|
||||
openLinks: z.object({}).optional(),
|
||||
/**
|
||||
* Capabilities supported by the host application.
|
||||
*
|
||||
* Hosts declare these capabilities during the initialization handshake. Guest UIs
|
||||
* can check capabilities before attempting to use specific features.
|
||||
*
|
||||
* @example Check if host supports opening links
|
||||
* ```typescript
|
||||
* const result = await app.connect(transport);
|
||||
* if (result.hostCapabilities.openLinks) {
|
||||
* await app.sendOpenLink({ url: "https://example.com" });
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities
|
||||
*/
|
||||
export interface McpUiHostCapabilities {
|
||||
/** Experimental features (structure TBD) */
|
||||
experimental?: {};
|
||||
/** Host supports opening external URLs via {@link app.App.sendOpenLink} */
|
||||
openLinks?: {};
|
||||
/** Host can proxy tool calls to the MCP server */
|
||||
serverTools?: {
|
||||
/** Host supports tools/list_changed notifications */
|
||||
listChanged?: boolean;
|
||||
};
|
||||
/** Host can proxy resource reads to the MCP server */
|
||||
serverResources?: {
|
||||
/** Host supports resources/list_changed notifications */
|
||||
listChanged?: boolean;
|
||||
};
|
||||
/** Host accepts log messages via {@link app.App.sendLog} */
|
||||
logging?: {};
|
||||
}
|
||||
|
||||
serverTools: z
|
||||
.object({
|
||||
listChanged: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiHostCapabilities}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiHostCapabilitiesSchema: z.ZodType<McpUiHostCapabilities> =
|
||||
z.object({
|
||||
experimental: z.object({}).optional(),
|
||||
openLinks: z.object({}).optional(),
|
||||
serverTools: z
|
||||
.object({
|
||||
listChanged: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
serverResources: z
|
||||
.object({
|
||||
listChanged: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
logging: z.object({}).optional(),
|
||||
});
|
||||
|
||||
serverResources: z
|
||||
.object({
|
||||
listChanged: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Capabilities provided by the Guest UI (App).
|
||||
*
|
||||
* Apps declare these capabilities during the initialization handshake to indicate
|
||||
* what features they provide to the host.
|
||||
*
|
||||
* @example Declare tool capabilities
|
||||
* ```typescript
|
||||
* const app = new App(
|
||||
* { name: "MyApp", version: "1.0.0" },
|
||||
* { tools: { listChanged: true } }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @see {@link McpUiInitializeRequest} for the initialization request that includes these capabilities
|
||||
*/
|
||||
export interface McpUiAppCapabilities {
|
||||
/** Experimental features (structure TBD) */
|
||||
experimental?: {};
|
||||
/**
|
||||
* App exposes MCP-style tools that the host can call.
|
||||
* These are app-specific tools, not proxied from the server.
|
||||
*/
|
||||
tools?: {
|
||||
/** App supports tools/list_changed notifications */
|
||||
listChanged?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
logging: z.object({}).optional(),
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiAppCapabilities}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiAppCapabilitiesSchema: z.ZodType<McpUiAppCapabilities> =
|
||||
z.object({
|
||||
experimental: z.object({}).optional(),
|
||||
tools: z
|
||||
.object({
|
||||
listChanged: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// TODO: elicitation, sampling...
|
||||
});
|
||||
export type McpUiHostCapabilities = z.infer<typeof McpUiHostCapabilitiesSchema>;
|
||||
|
||||
export const McpUiAppCapabilitiesSchema = z.object({
|
||||
experimental: z.object({}).optional(),
|
||||
|
||||
// WebMCP-style tools exposed by the app to the host
|
||||
tools: z
|
||||
.object({
|
||||
listChanged: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
export type McpUiAppCapabilities = z.infer<typeof McpUiAppCapabilitiesSchema>;
|
||||
/**
|
||||
* Initialization request sent from Guest UI to Host.
|
||||
*
|
||||
* This is the first message sent by the Guest UI after loading. The host responds
|
||||
* with {@link McpUiInitializeResult} containing host capabilities and context.
|
||||
* After receiving the response, the Guest UI MUST send
|
||||
* {@link McpUiInitializedNotification}.
|
||||
*
|
||||
* This replaces the custom iframe-ready pattern used in pre-SEP MCP-UI.
|
||||
*
|
||||
* @see {@link app.App.connect} for the method that sends this request
|
||||
*/
|
||||
export interface McpUiInitializeRequest {
|
||||
method: "ui/initialize";
|
||||
params: {
|
||||
/** App identification (name and version) */
|
||||
appInfo: Implementation;
|
||||
/** Features and capabilities this app provides */
|
||||
appCapabilities: McpUiAppCapabilities;
|
||||
/** Protocol version this app supports */
|
||||
protocolVersion: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiInitializeRequest}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiInitializeRequestSchema = RequestSchema.extend({
|
||||
method: z.literal("ui/initialize"),
|
||||
params: z.object({
|
||||
|
|
@ -207,22 +752,74 @@ export const McpUiInitializeRequestSchema = RequestSchema.extend({
|
|||
protocolVersion: z.string(),
|
||||
}),
|
||||
});
|
||||
export type McpUiInitializeRequest = z.infer<
|
||||
typeof McpUiInitializeRequestSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyInitializeRequest = VerifySchemaMatches<
|
||||
typeof McpUiInitializeRequestSchema,
|
||||
McpUiInitializeRequest
|
||||
>;
|
||||
|
||||
export const McpUiInitializeResultSchema = z.object({
|
||||
protocolVersion: z.string(),
|
||||
hostInfo: ImplementationSchema,
|
||||
hostCapabilities: McpUiHostCapabilitiesSchema,
|
||||
hostContext: McpUiHostContextSchema,
|
||||
});
|
||||
export type McpUiInitializeResult = z.infer<typeof McpUiInitializeResultSchema>;
|
||||
/**
|
||||
* Initialization result returned from Host to Guest UI.
|
||||
*
|
||||
* Contains the negotiated protocol version, host information, capabilities,
|
||||
* and rich context about the host environment.
|
||||
*
|
||||
* @see {@link McpUiInitializeRequest}
|
||||
*/
|
||||
export interface McpUiInitializeResult {
|
||||
/** Negotiated protocol version string (e.g., "2025-11-21") */
|
||||
protocolVersion: string;
|
||||
/** Host application identification and version */
|
||||
hostInfo: Implementation;
|
||||
/** Features and capabilities provided by the host */
|
||||
hostCapabilities: McpUiHostCapabilities;
|
||||
/** Rich context about the host environment */
|
||||
hostContext: McpUiHostContext;
|
||||
/**
|
||||
* Index signature required for MCP SDK `Protocol` class compatibility.
|
||||
* Note: The schema intentionally omits this to enforce strict validation.
|
||||
*/
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiInitializeResult}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiInitializeResultSchema: z.ZodType<McpUiInitializeResult> =
|
||||
z.object({
|
||||
protocolVersion: z.string(),
|
||||
hostInfo: ImplementationSchema,
|
||||
hostCapabilities: McpUiHostCapabilitiesSchema,
|
||||
hostContext: McpUiHostContextSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* Notification that Guest UI has completed initialization (Guest UI → Host).
|
||||
*
|
||||
* The Guest UI MUST send this notification after receiving
|
||||
* {@link McpUiInitializeResult} and completing any setup. The host waits for this
|
||||
* notification before sending tool input and other data to the Guest UI.
|
||||
*
|
||||
* @see {@link app.App.connect} for the method that sends this notification
|
||||
*/
|
||||
export interface McpUiInitializedNotification {
|
||||
method: "ui/notifications/initialized";
|
||||
params?: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime validation schema for {@link McpUiInitializedNotification}.
|
||||
* @internal
|
||||
*/
|
||||
export const McpUiInitializedNotificationSchema = z.object({
|
||||
method: z.literal("ui/notifications/initialized"),
|
||||
params: z.object({}).optional(),
|
||||
});
|
||||
export type McpUiInitializedNotification = z.infer<
|
||||
typeof McpUiInitializedNotificationSchema
|
||||
|
||||
/** @internal - Compile-time verification that schema matches interface */
|
||||
type _VerifyInitializedNotification = VerifySchemaMatches<
|
||||
typeof McpUiInitializedNotificationSchema,
|
||||
McpUiInitializedNotification
|
||||
>;
|
||||
|
|
|
|||
21
typedoc.json
Normal file
21
typedoc.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPoints": [
|
||||
"src/app.ts",
|
||||
"src/app-bridge.ts",
|
||||
"src/types.ts",
|
||||
"src/message-transport.ts",
|
||||
"src/react/index.tsx"
|
||||
],
|
||||
"out": "docs/api",
|
||||
"gitRevision": "main",
|
||||
"excludePrivate": true,
|
||||
"excludeInternal": false,
|
||||
"categorizeByGroup": true,
|
||||
"navigationLinks": {
|
||||
"GitHub": "https://github.com/modelcontextprotocol/ext-apps",
|
||||
"Specification": "https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx"
|
||||
},
|
||||
"readme": "README.md",
|
||||
"includeVersion": true
|
||||
}
|
||||
Loading…
Reference in a new issue