Merge pull request #302 from jonathanhefner/checked-examples
Move JSDoc examples to type-checked `.examples.ts` files
This commit is contained in:
commit
26ae13df0f
23 changed files with 1637 additions and 867 deletions
|
|
@ -78,9 +78,13 @@ Guest UI (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP
|
|||
6. Host sends `sendToolResult()` when tool execution completes
|
||||
7. Host calls `teardownResource()` before unmounting iframe
|
||||
|
||||
## Examples
|
||||
## Documentation
|
||||
|
||||
Uses npm workspaces. Examples in `examples/` are separate packages:
|
||||
JSDoc `@example` tags use `{@includeCode ./file.examples.ts#regionName}` to pull in type-checked code from companion `.examples.ts`/`.examples.tsx` files. Regions are marked with `//#region name` and `//#endregion name`, wrapped in functions (whose parameters provide types for external values). Region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `useApp_basicUsage`, `App_hostCapabilities_checkAfterConnection`).
|
||||
|
||||
## Full Examples
|
||||
|
||||
Uses npm workspaces. Full examples in `examples/` are separate packages:
|
||||
|
||||
- `basic-server-*` - Starter templates (vanillajs, react, vue, svelte, preact, solid). Use these as the basis for new examples.
|
||||
- `basic-host` - Reference host implementation
|
||||
|
|
|
|||
22
README.md
22
README.md
|
|
@ -58,22 +58,22 @@ A [Claude Code plugin](https://github.com/modelcontextprotocol/ext-apps/tree/mai
|
|||
<!-- prettier-ignore-start -->
|
||||
| | | |
|
||||
|:---:|:---:|:---:|
|
||||
| [](examples/map-server) | [](examples/threejs-server) | [](examples/shadertoy-server) |
|
||||
| [**Map**](examples/map-server) | [**Three.js**](examples/threejs-server) | [**ShaderToy**](examples/shadertoy-server) |
|
||||
| [](examples/sheet-music-server) | [](examples/wiki-explorer-server) | [](examples/cohort-heatmap-server) |
|
||||
| [**Sheet Music**](examples/sheet-music-server) | [**Wiki Explorer**](examples/wiki-explorer-server) | [**Cohort Heatmap**](examples/cohort-heatmap-server) |
|
||||
| [](examples/scenario-modeler-server) | [](examples/budget-allocator-server) | [](examples/customer-segmentation-server) |
|
||||
| [**Scenario Modeler**](examples/scenario-modeler-server) | [**Budget Allocator**](examples/budget-allocator-server) | [**Customer Segmentation**](examples/customer-segmentation-server) |
|
||||
| [](examples/system-monitor-server) | [](examples/transcript-server) | [](examples/video-resource-server) |
|
||||
| [**System Monitor**](examples/system-monitor-server) | [**Transcript**](examples/transcript-server) | [**Video Resource**](examples/video-resource-server) |
|
||||
| [](examples/pdf-server) | [](examples/qr-server) | [](examples/say-server) |
|
||||
| [**PDF Server**](examples/pdf-server) | [**QR Code (Python)**](examples/qr-server) | [**Say Demo**](examples/say-server) |
|
||||
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) |
|
||||
| [**Map**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) | [**Three.js**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server) | [**ShaderToy**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server) |
|
||||
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/wiki-explorer-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/cohort-heatmap-server) |
|
||||
| [**Sheet Music**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) | [**Wiki Explorer**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/wiki-explorer-server) | [**Cohort Heatmap**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/cohort-heatmap-server) |
|
||||
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/scenario-modeler-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/budget-allocator-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server) |
|
||||
| [**Scenario Modeler**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/scenario-modeler-server) | [**Budget Allocator**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/budget-allocator-server) | [**Customer Segmentation**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server) |
|
||||
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server) |
|
||||
| [**System Monitor**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server) | [**Transcript**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) | [**Video Resource**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server) |
|
||||
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server) | [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/say-server) |
|
||||
| [**PDF Server**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server) | [**QR Code (Python)**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server) | [**Say Demo**](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/say-server) |
|
||||
|
||||
### Starter Templates
|
||||
|
||||
| | |
|
||||
|:---:|:---|
|
||||
| [](examples/basic-server-react) | The same app built with different frameworks — pick your favorite!<br><br>[React](examples/basic-server-react) · [Vue](examples/basic-server-vue) · [Svelte](examples/basic-server-svelte) · [Preact](examples/basic-server-preact) · [Solid](examples/basic-server-solid) · [Vanilla JS](examples/basic-server-vanillajs) |
|
||||
| [](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) | The same app built with different frameworks — pick your favorite!<br><br>[React](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react) · [Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue) · [Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte) · [Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact) · [Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid) · [Vanilla JS](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs) |
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
The [`examples/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples) directory contains additional demo apps showcasing real-world use cases.
|
||||
|
|
|
|||
501
src/app-bridge.examples.ts
Normal file
501
src/app-bridge.examples.ts
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
/**
|
||||
* Type-checked examples for {@link AppBridge}.
|
||||
*
|
||||
* These examples are included in the API documentation via `@includeCode` tags.
|
||||
* Each function's region markers define the code snippet that appears in the docs.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
import {
|
||||
CallToolResult,
|
||||
CallToolResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ReadResourceResultSchema,
|
||||
ListPromptsResultSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { AppBridge, PostMessageTransport } from "./app-bridge.js";
|
||||
import type { McpUiDisplayMode } from "./types.js";
|
||||
|
||||
/**
|
||||
* Example: Basic usage of the AppBridge class with PostMessageTransport.
|
||||
*/
|
||||
async function AppBridge_basicUsage(serverTransport: Transport) {
|
||||
//#region AppBridge_basicUsage
|
||||
// Create MCP client for the server
|
||||
const client = new Client({
|
||||
name: "MyHost",
|
||||
version: "1.0.0",
|
||||
});
|
||||
await client.connect(serverTransport);
|
||||
|
||||
// Create bridge for the Guest UI
|
||||
const bridge = new AppBridge(
|
||||
client,
|
||||
{ name: "MyHost", version: "1.0.0" },
|
||||
{ openLinks: {}, serverTools: {}, logging: {} },
|
||||
);
|
||||
|
||||
// Set up iframe and connect
|
||||
const iframe = document.getElementById("app") as HTMLIFrameElement;
|
||||
const transport = new PostMessageTransport(
|
||||
iframe.contentWindow!,
|
||||
iframe.contentWindow!,
|
||||
);
|
||||
|
||||
bridge.oninitialized = () => {
|
||||
console.log("Guest UI initialized");
|
||||
// Now safe to send tool input
|
||||
bridge.sendToolInput({ arguments: { location: "NYC" } });
|
||||
};
|
||||
|
||||
await bridge.connect(transport);
|
||||
//#endregion AppBridge_basicUsage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Creating an AppBridge with an MCP client for automatic forwarding.
|
||||
*/
|
||||
function AppBridge_constructor_withMcpClient(mcpClient: Client) {
|
||||
//#region AppBridge_constructor_withMcpClient
|
||||
const bridge = new AppBridge(
|
||||
mcpClient,
|
||||
{ name: "MyHost", version: "1.0.0" },
|
||||
{ openLinks: {}, serverTools: {}, logging: {} },
|
||||
);
|
||||
//#endregion AppBridge_constructor_withMcpClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Creating an AppBridge without an MCP client, using manual handlers.
|
||||
*/
|
||||
function AppBridge_constructor_withoutMcpClient() {
|
||||
//#region AppBridge_constructor_withoutMcpClient
|
||||
const bridge = new AppBridge(
|
||||
null,
|
||||
{ name: "MyHost", version: "1.0.0" },
|
||||
{ openLinks: {}, serverTools: {}, logging: {} },
|
||||
);
|
||||
bridge.oncalltool = async (params, extra) => {
|
||||
// Handle tool calls manually
|
||||
return { content: [] };
|
||||
};
|
||||
//#endregion AppBridge_constructor_withoutMcpClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Check Guest UI capabilities after initialization.
|
||||
*/
|
||||
function AppBridge_getAppCapabilities_checkAfterInit(bridge: AppBridge) {
|
||||
//#region AppBridge_getAppCapabilities_checkAfterInit
|
||||
bridge.oninitialized = () => {
|
||||
const caps = bridge.getAppCapabilities();
|
||||
if (caps?.tools) {
|
||||
console.log("Guest UI provides tools");
|
||||
}
|
||||
};
|
||||
//#endregion AppBridge_getAppCapabilities_checkAfterInit
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Log Guest UI information after initialization.
|
||||
*/
|
||||
function AppBridge_getAppVersion_logAfterInit(bridge: AppBridge) {
|
||||
//#region AppBridge_getAppVersion_logAfterInit
|
||||
bridge.oninitialized = () => {
|
||||
const appInfo = bridge.getAppVersion();
|
||||
if (appInfo) {
|
||||
console.log(`Guest UI: ${appInfo.name} v${appInfo.version}`);
|
||||
}
|
||||
};
|
||||
//#endregion AppBridge_getAppVersion_logAfterInit
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle Guest UI initialization and send tool input.
|
||||
*/
|
||||
function AppBridge_oninitialized_sendToolInput(
|
||||
bridge: AppBridge,
|
||||
toolArgs: Record<string, unknown>,
|
||||
) {
|
||||
//#region AppBridge_oninitialized_sendToolInput
|
||||
bridge.oninitialized = () => {
|
||||
console.log("Guest UI ready");
|
||||
bridge.sendToolInput({ arguments: toolArgs });
|
||||
};
|
||||
//#endregion AppBridge_oninitialized_sendToolInput
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle message requests from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onmessage_logMessage(bridge: AppBridge) {
|
||||
//#region AppBridge_onmessage_logMessage
|
||||
bridge.onmessage = async ({ role, content }, extra) => {
|
||||
try {
|
||||
await chatManager.addMessage({ role, content, source: "app" });
|
||||
return {}; // Success
|
||||
} catch (error) {
|
||||
console.error("Failed to add message:", error);
|
||||
return { isError: true };
|
||||
}
|
||||
};
|
||||
//#endregion AppBridge_onmessage_logMessage
|
||||
}
|
||||
|
||||
// Stub for example code - represents a hypothetical chat manager
|
||||
declare const chatManager: {
|
||||
addMessage(message: {
|
||||
role: string;
|
||||
content: unknown;
|
||||
source: string;
|
||||
}): Promise<void>;
|
||||
};
|
||||
|
||||
// Stub for example code - represents a hypothetical URL validator
|
||||
declare function isAllowedDomain(url: string): boolean;
|
||||
|
||||
// Stub for example code - represents a hypothetical dialog API
|
||||
declare function showDialog(options: {
|
||||
message: string;
|
||||
buttons: string[];
|
||||
}): Promise<boolean>;
|
||||
|
||||
// Stub for example code - represents a hypothetical model context manager
|
||||
declare const modelContextManager: {
|
||||
update(context: { content?: unknown; structuredContent?: unknown }): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Example: Handle external link requests from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onopenlink_handleRequest(bridge: AppBridge) {
|
||||
//#region AppBridge_onopenlink_handleRequest
|
||||
bridge.onopenlink = async ({ url }, extra) => {
|
||||
if (!isAllowedDomain(url)) {
|
||||
console.warn("Blocked external link:", url);
|
||||
return { isError: true };
|
||||
}
|
||||
|
||||
const confirmed = await showDialog({
|
||||
message: `Open external link?\n${url}`,
|
||||
buttons: ["Open", "Cancel"],
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
return {};
|
||||
}
|
||||
|
||||
return { isError: true };
|
||||
};
|
||||
//#endregion AppBridge_onopenlink_handleRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Store model context updates from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onupdatemodelcontext_storeContext(bridge: AppBridge) {
|
||||
//#region AppBridge_onupdatemodelcontext_storeContext
|
||||
bridge.onupdatemodelcontext = async (
|
||||
{ content, structuredContent },
|
||||
extra,
|
||||
) => {
|
||||
// Store the context snapshot for inclusion in the next model request
|
||||
modelContextManager.update({ content, structuredContent });
|
||||
return {};
|
||||
};
|
||||
//#endregion AppBridge_onupdatemodelcontext_storeContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Forward tool calls to the MCP server.
|
||||
*/
|
||||
function AppBridge_oncalltool_forwardToServer(
|
||||
bridge: AppBridge,
|
||||
mcpClient: Client,
|
||||
) {
|
||||
//#region AppBridge_oncalltool_forwardToServer
|
||||
bridge.oncalltool = async (params, extra) => {
|
||||
return mcpClient.request(
|
||||
{ method: "tools/call", params },
|
||||
CallToolResultSchema,
|
||||
{ signal: extra.signal },
|
||||
);
|
||||
};
|
||||
//#endregion AppBridge_oncalltool_forwardToServer
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Forward list resources requests to the MCP server.
|
||||
*/
|
||||
function AppBridge_onlistresources_returnResources(
|
||||
bridge: AppBridge,
|
||||
mcpClient: Client,
|
||||
) {
|
||||
//#region AppBridge_onlistresources_returnResources
|
||||
bridge.onlistresources = async (params, extra) => {
|
||||
return mcpClient.request(
|
||||
{ method: "resources/list", params },
|
||||
ListResourcesResultSchema,
|
||||
{ signal: extra.signal },
|
||||
);
|
||||
};
|
||||
//#endregion AppBridge_onlistresources_returnResources
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Forward read resource requests to the MCP server.
|
||||
*/
|
||||
function AppBridge_onreadresource_returnResource(
|
||||
bridge: AppBridge,
|
||||
mcpClient: Client,
|
||||
) {
|
||||
//#region AppBridge_onreadresource_returnResource
|
||||
bridge.onreadresource = async (params, extra) => {
|
||||
return mcpClient.request(
|
||||
{ method: "resources/read", params },
|
||||
ReadResourceResultSchema,
|
||||
{ signal: extra.signal },
|
||||
);
|
||||
};
|
||||
//#endregion AppBridge_onreadresource_returnResource
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Forward list prompts requests to the MCP server.
|
||||
*/
|
||||
function AppBridge_onlistprompts_returnPrompts(
|
||||
bridge: AppBridge,
|
||||
mcpClient: Client,
|
||||
) {
|
||||
//#region AppBridge_onlistprompts_returnPrompts
|
||||
bridge.onlistprompts = async (params, extra) => {
|
||||
return mcpClient.request(
|
||||
{ method: "prompts/list", params },
|
||||
ListPromptsResultSchema,
|
||||
{ signal: extra.signal },
|
||||
);
|
||||
};
|
||||
//#endregion AppBridge_onlistprompts_returnPrompts
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle ping requests from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onping_handleRequest(bridge: AppBridge) {
|
||||
//#region AppBridge_onping_handleRequest
|
||||
bridge.onping = (params, extra) => {
|
||||
console.log("Received ping from Guest UI");
|
||||
};
|
||||
//#endregion AppBridge_onping_handleRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle size change notifications from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onsizechange_handleResize(
|
||||
bridge: AppBridge,
|
||||
iframe: HTMLIFrameElement,
|
||||
) {
|
||||
//#region AppBridge_onsizechange_handleResize
|
||||
bridge.onsizechange = ({ width, height }) => {
|
||||
if (width != null) {
|
||||
iframe.style.width = `${width}px`;
|
||||
}
|
||||
if (height != null) {
|
||||
iframe.style.height = `${height}px`;
|
||||
}
|
||||
};
|
||||
//#endregion AppBridge_onsizechange_handleResize
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle display mode requests from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onrequestdisplaymode_handleRequest(
|
||||
bridge: AppBridge,
|
||||
currentDisplayMode: McpUiDisplayMode,
|
||||
availableDisplayModes: McpUiDisplayMode[],
|
||||
) {
|
||||
//#region AppBridge_onrequestdisplaymode_handleRequest
|
||||
bridge.onrequestdisplaymode = async ({ mode }, extra) => {
|
||||
if (availableDisplayModes.includes(mode)) {
|
||||
currentDisplayMode = mode;
|
||||
}
|
||||
return { mode: currentDisplayMode };
|
||||
};
|
||||
//#endregion AppBridge_onrequestdisplaymode_handleRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle logging messages from the Guest UI.
|
||||
*/
|
||||
function AppBridge_onloggingmessage_handleLog(bridge: AppBridge) {
|
||||
//#region AppBridge_onloggingmessage_handleLog
|
||||
bridge.onloggingmessage = ({ level, logger, data }) => {
|
||||
console[level === "error" ? "error" : "log"](
|
||||
`[${logger ?? "Guest UI"}] ${level.toUpperCase()}:`,
|
||||
data,
|
||||
);
|
||||
};
|
||||
//#endregion AppBridge_onloggingmessage_handleLog
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Gracefully tear down the Guest UI before unmounting.
|
||||
*/
|
||||
async function AppBridge_teardownResource_gracefulShutdown(
|
||||
bridge: AppBridge,
|
||||
iframe: HTMLIFrameElement,
|
||||
) {
|
||||
//#region AppBridge_teardownResource_gracefulShutdown
|
||||
try {
|
||||
await bridge.teardownResource({});
|
||||
// Guest UI is ready, safe to unmount iframe
|
||||
iframe.remove();
|
||||
} catch (error) {
|
||||
console.error("Teardown failed:", error);
|
||||
}
|
||||
//#endregion AppBridge_teardownResource_gracefulShutdown
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Update theme when user toggles dark mode.
|
||||
*/
|
||||
function AppBridge_setHostContext_updateTheme(bridge: AppBridge) {
|
||||
//#region AppBridge_setHostContext_updateTheme
|
||||
bridge.setHostContext({ theme: "dark" });
|
||||
//#endregion AppBridge_setHostContext_updateTheme
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Update multiple context fields at once.
|
||||
*/
|
||||
function AppBridge_setHostContext_updateMultiple(bridge: AppBridge) {
|
||||
//#region AppBridge_setHostContext_updateMultiple
|
||||
bridge.setHostContext({
|
||||
theme: "dark",
|
||||
containerDimensions: { maxHeight: 600, width: 800 },
|
||||
});
|
||||
//#endregion AppBridge_setHostContext_updateMultiple
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Send tool input after initialization.
|
||||
*/
|
||||
function AppBridge_sendToolInput_afterInit(bridge: AppBridge) {
|
||||
//#region AppBridge_sendToolInput_afterInit
|
||||
bridge.oninitialized = () => {
|
||||
bridge.sendToolInput({
|
||||
arguments: { location: "New York", units: "metric" },
|
||||
});
|
||||
};
|
||||
//#endregion AppBridge_sendToolInput_afterInit
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Stream partial arguments as they arrive.
|
||||
*/
|
||||
function AppBridge_sendToolInputPartial_streaming(bridge: AppBridge) {
|
||||
//#region AppBridge_sendToolInputPartial_streaming
|
||||
// As streaming progresses...
|
||||
bridge.sendToolInputPartial({ arguments: { loc: "N" } });
|
||||
bridge.sendToolInputPartial({ arguments: { location: "New" } });
|
||||
bridge.sendToolInputPartial({ arguments: { location: "New York" } });
|
||||
|
||||
// When complete, send final input
|
||||
bridge.sendToolInput({
|
||||
arguments: { location: "New York", units: "metric" },
|
||||
});
|
||||
//#endregion AppBridge_sendToolInputPartial_streaming
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Send tool result after execution.
|
||||
*/
|
||||
async function AppBridge_sendToolResult_afterExecution(
|
||||
bridge: AppBridge,
|
||||
mcpClient: Client,
|
||||
args: Record<string, unknown>,
|
||||
) {
|
||||
//#region AppBridge_sendToolResult_afterExecution
|
||||
const result = await mcpClient.request(
|
||||
{ method: "tools/call", params: { name: "get_weather", arguments: args } },
|
||||
CallToolResultSchema,
|
||||
);
|
||||
bridge.sendToolResult(result);
|
||||
//#endregion AppBridge_sendToolResult_afterExecution
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: User-initiated cancellation.
|
||||
*/
|
||||
function AppBridge_sendToolCancelled_userInitiated(bridge: AppBridge) {
|
||||
//#region AppBridge_sendToolCancelled_userInitiated
|
||||
// User clicked "Cancel" button
|
||||
bridge.sendToolCancelled({ reason: "User cancelled the operation" });
|
||||
//#endregion AppBridge_sendToolCancelled_userInitiated
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: System-level cancellation.
|
||||
*/
|
||||
function AppBridge_sendToolCancelled_systemLevel(bridge: AppBridge) {
|
||||
//#region AppBridge_sendToolCancelled_systemLevel
|
||||
// Sampling error or timeout
|
||||
bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
|
||||
|
||||
// Classifier intervention
|
||||
bridge.sendToolCancelled({ reason: "Content policy violation detected" });
|
||||
//#endregion AppBridge_sendToolCancelled_systemLevel
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Connect with MCP client for automatic forwarding.
|
||||
*/
|
||||
async function AppBridge_connect_withMcpClient(
|
||||
iframe: HTMLIFrameElement,
|
||||
mcpClient: Client,
|
||||
hostInfo: { name: string; version: string },
|
||||
capabilities: { openLinks: {}; serverTools: {}; logging: {} },
|
||||
toolArgs: Record<string, unknown>,
|
||||
) {
|
||||
//#region AppBridge_connect_withMcpClient
|
||||
const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
|
||||
const transport = new PostMessageTransport(
|
||||
iframe.contentWindow!,
|
||||
iframe.contentWindow!,
|
||||
);
|
||||
|
||||
bridge.oninitialized = () => {
|
||||
console.log("Guest UI ready");
|
||||
bridge.sendToolInput({ arguments: toolArgs });
|
||||
};
|
||||
|
||||
await bridge.connect(transport);
|
||||
//#endregion AppBridge_connect_withMcpClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Connect without MCP client using manual handlers.
|
||||
*/
|
||||
async function AppBridge_connect_withoutMcpClient(
|
||||
hostInfo: { name: string; version: string },
|
||||
capabilities: { openLinks: {}; serverTools: {}; logging: {} },
|
||||
transport: Transport,
|
||||
) {
|
||||
//#region AppBridge_connect_withoutMcpClient
|
||||
const bridge = new AppBridge(null, hostInfo, capabilities);
|
||||
|
||||
// Register handlers manually
|
||||
bridge.oncalltool = async (params, extra) => {
|
||||
// Custom tool call handling
|
||||
return { content: [] };
|
||||
};
|
||||
|
||||
await bridge.connect(transport);
|
||||
//#endregion AppBridge_connect_withoutMcpClient
|
||||
}
|
||||
|
|
@ -215,39 +215,7 @@ type RequestHandlerExtra = Parameters<
|
|||
* 5. **Teardown**: Call {@link teardownResource} before unmounting iframe
|
||||
*
|
||||
* @example Basic usage
|
||||
* ```typescript
|
||||
* import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
|
||||
* import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
*
|
||||
* // Create MCP client for the server
|
||||
* const client = new Client({
|
||||
* name: "MyHost",
|
||||
* version: "1.0.0",
|
||||
* });
|
||||
* await client.connect(serverTransport);
|
||||
*
|
||||
* // Create bridge for the Guest UI
|
||||
* const bridge = new AppBridge(
|
||||
* client,
|
||||
* { name: "MyHost", version: "1.0.0" },
|
||||
* { openLinks: {}, serverTools: {}, logging: {} }
|
||||
* );
|
||||
*
|
||||
* // Set up iframe and connect
|
||||
* const iframe = document.getElementById('app') as HTMLIFrameElement;
|
||||
* const transport = new PostMessageTransport(
|
||||
* iframe.contentWindow!,
|
||||
* iframe.contentWindow!,
|
||||
* );
|
||||
*
|
||||
* bridge.oninitialized = () => {
|
||||
* console.log("Guest UI initialized");
|
||||
* // Now safe to send tool input
|
||||
* bridge.sendToolInput({ arguments: { location: "NYC" } });
|
||||
* };
|
||||
*
|
||||
* await bridge.connect(transport);
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_basicUsage}
|
||||
*/
|
||||
export class AppBridge extends Protocol<
|
||||
AppRequest,
|
||||
|
|
@ -270,23 +238,10 @@ export class AppBridge extends Protocol<
|
|||
* @param options - Configuration options (inherited from Protocol)
|
||||
*
|
||||
* @example With MCP client (automatic forwarding)
|
||||
* ```typescript
|
||||
* const bridge = new AppBridge(
|
||||
* mcpClient,
|
||||
* { name: "MyHost", version: "1.0.0" },
|
||||
* { openLinks: {}, serverTools: {}, logging: {} }
|
||||
* );
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_constructor_withMcpClient}
|
||||
*
|
||||
* @example Without MCP client (manual handlers)
|
||||
* ```typescript
|
||||
* const bridge = new AppBridge(
|
||||
* null,
|
||||
* { name: "MyHost", version: "1.0.0" },
|
||||
* { openLinks: {}, serverTools: {}, logging: {} }
|
||||
* );
|
||||
* bridge.oncalltool = async (params, extra) => { ... };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_constructor_withoutMcpClient}
|
||||
*/
|
||||
constructor(
|
||||
private _client: Client | null,
|
||||
|
|
@ -325,14 +280,7 @@ export class AppBridge extends Protocol<
|
|||
* @returns Guest UI capabilities, or `undefined` if not yet initialized
|
||||
*
|
||||
* @example Check Guest UI capabilities after initialization
|
||||
* ```typescript
|
||||
* bridge.oninitialized = () => {
|
||||
* const caps = bridge.getAppCapabilities();
|
||||
* if (caps?.tools) {
|
||||
* console.log("Guest UI provides tools");
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_getAppCapabilities_checkAfterInit}
|
||||
*
|
||||
* @see {@link McpUiAppCapabilities} for the capabilities structure
|
||||
*/
|
||||
|
|
@ -349,14 +297,7 @@ export class AppBridge extends Protocol<
|
|||
* @returns Guest UI implementation info, or `undefined` if not yet initialized
|
||||
*
|
||||
* @example Log Guest UI information after initialization
|
||||
* ```typescript
|
||||
* bridge.oninitialized = () => {
|
||||
* const appInfo = bridge.getAppVersion();
|
||||
* if (appInfo) {
|
||||
* console.log(`Guest UI: ${appInfo.name} v${appInfo.version}`);
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_getAppVersion_logAfterInit}
|
||||
*/
|
||||
getAppVersion(): Implementation | undefined {
|
||||
return this._appInfo;
|
||||
|
|
@ -376,11 +317,7 @@ export class AppBridge extends Protocol<
|
|||
* @param extra - Request metadata (abort signal, session info)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onping = (params, extra) => {
|
||||
* console.log("Received ping from Guest UI");
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onping_handleRequest}
|
||||
*/
|
||||
onping?: (params: PingRequest["params"], extra: RequestHandlerExtra) => void;
|
||||
|
||||
|
|
@ -395,16 +332,7 @@ export class AppBridge extends Protocol<
|
|||
* host container dimension changes, use {@link setHostContext}.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onsizechange = ({ width, height }) => {
|
||||
* if (width != null) {
|
||||
* iframe.style.width = `${width}px`;
|
||||
* }
|
||||
* if (height != null) {
|
||||
* iframe.style.height = `${height}px`;
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onsizechange_handleResize}
|
||||
*
|
||||
* @see {@link McpUiSizeChangedNotification} for the notification type
|
||||
* @see {@link app!App.sendSizeChanged} - the Guest UI method that sends these notifications
|
||||
|
|
@ -462,12 +390,7 @@ export class AppBridge extends Protocol<
|
|||
* initialization handshake and is ready to receive tool input and other data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.oninitialized = () => {
|
||||
* console.log("Guest UI ready");
|
||||
* bridge.sendToolInput({ arguments: toolArgs });
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_oninitialized_sendToolInput}
|
||||
*
|
||||
* @see {@link McpUiInitializedNotification} for the notification type
|
||||
* @see {@link sendToolInput} for sending tool arguments to the Guest UI
|
||||
|
|
@ -499,17 +422,7 @@ export class AppBridge extends Protocol<
|
|||
* - Returns: `Promise<McpUiMessageResult>` with optional `isError` flag
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onmessage = async ({ role, content }, extra) => {
|
||||
* try {
|
||||
* await chatManager.addMessage({ role, content, source: "app" });
|
||||
* return {}; // Success
|
||||
* } catch (error) {
|
||||
* console.error("Failed to add message:", error);
|
||||
* return { isError: true };
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onmessage_logMessage}
|
||||
*
|
||||
* @see {@link McpUiMessageRequest} for the request type
|
||||
* @see {@link McpUiMessageResult} for the result type
|
||||
|
|
@ -547,26 +460,7 @@ export class AppBridge extends Protocol<
|
|||
* - Returns: `Promise<McpUiOpenLinkResult>` with optional `isError` flag
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onopenlink = async ({ url }, extra) => {
|
||||
* if (!isAllowedDomain(url)) {
|
||||
* console.warn("Blocked external link:", url);
|
||||
* return { isError: true };
|
||||
* }
|
||||
*
|
||||
* const confirmed = await showDialog({
|
||||
* message: `Open external link?\n${url}`,
|
||||
* buttons: ["Open", "Cancel"]
|
||||
* });
|
||||
*
|
||||
* if (confirmed) {
|
||||
* window.open(url, "_blank", "noopener,noreferrer");
|
||||
* return {};
|
||||
* }
|
||||
*
|
||||
* return { isError: true };
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onopenlink_handleRequest}
|
||||
*
|
||||
* @see {@link McpUiOpenLinkRequest} for the request type
|
||||
* @see {@link McpUiOpenLinkResult} for the result type
|
||||
|
|
@ -605,19 +499,7 @@ export class AppBridge extends Protocol<
|
|||
* - Returns: `Promise<McpUiRequestDisplayModeResult>` with the actual mode set
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* let currentDisplayMode: McpUiDisplayMode = "inline";
|
||||
*
|
||||
* bridge.onrequestdisplaymode = async ({ mode }, extra) => {
|
||||
* const availableModes = hostContext.availableDisplayModes ?? ["inline"];
|
||||
* if (availableModes.includes(mode)) {
|
||||
* currentDisplayMode = mode;
|
||||
* return { mode };
|
||||
* }
|
||||
* // Return current mode if requested mode not available
|
||||
* return { mode: currentDisplayMode };
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onrequestdisplaymode_handleRequest}
|
||||
*
|
||||
* @see {@link McpUiRequestDisplayModeRequest} for the request type
|
||||
* @see {@link McpUiRequestDisplayModeResult} for the result type
|
||||
|
|
@ -653,15 +535,7 @@ export class AppBridge extends Protocol<
|
|||
* - `params.data` - Log message and optional structured data
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onloggingmessage = ({ level, logger, data }) => {
|
||||
* const prefix = logger ? `[${logger}]` : "[Guest UI]";
|
||||
* console[level === "error" ? "error" : "log"](
|
||||
* `${prefix} ${level.toUpperCase()}:`,
|
||||
* data
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onloggingmessage_handleLog}
|
||||
*/
|
||||
set onloggingmessage(
|
||||
callback: (params: LoggingMessageNotification["params"]) => void,
|
||||
|
|
@ -687,18 +561,7 @@ export class AppBridge extends Protocol<
|
|||
* update received.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onupdatemodelcontext = async ({ content, structuredContent }, extra) => {
|
||||
* // Update the model context with the new snapshot
|
||||
* modelContext = {
|
||||
* type: "app_context",
|
||||
* content,
|
||||
* structuredContent,
|
||||
* timestamp: Date.now()
|
||||
* };
|
||||
* return {};
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onupdatemodelcontext_storeContext}
|
||||
*
|
||||
* @see {@link McpUiUpdateModelContextRequest} for the request type
|
||||
*/
|
||||
|
|
@ -729,15 +592,7 @@ export class AppBridge extends Protocol<
|
|||
* - `extra` - Request metadata (abort signal, session info)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.oncalltool = async ({ name, arguments: args }, extra) => {
|
||||
* return mcpClient.request(
|
||||
* { method: "tools/call", params: { name, arguments: args } },
|
||||
* CallToolResultSchema,
|
||||
* { signal: extra.signal }
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_oncalltool_forwardToServer}
|
||||
*
|
||||
* @see `CallToolRequest` from @modelcontextprotocol/sdk for the request type
|
||||
* @see `CallToolResult` from @modelcontextprotocol/sdk for the result type
|
||||
|
|
@ -792,15 +647,7 @@ export class AppBridge extends Protocol<
|
|||
* - `extra` - Request metadata (abort signal, session info)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onlistresources = async (params, extra) => {
|
||||
* return mcpClient.request(
|
||||
* { method: "resources/list", params },
|
||||
* ListResourcesResultSchema,
|
||||
* { signal: extra.signal }
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onlistresources_returnResources}
|
||||
*
|
||||
* @see `ListResourcesRequest` from @modelcontextprotocol/sdk for the request type
|
||||
* @see `ListResourcesResult` from @modelcontextprotocol/sdk for the result type
|
||||
|
|
@ -872,15 +719,7 @@ export class AppBridge extends Protocol<
|
|||
* - `extra` - Request metadata (abort signal, session info)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onreadresource = async ({ uri }, extra) => {
|
||||
* return mcpClient.request(
|
||||
* { method: "resources/read", params: { uri } },
|
||||
* ReadResourceResultSchema,
|
||||
* { signal: extra.signal }
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onreadresource_returnResource}
|
||||
*
|
||||
* @see `ReadResourceRequest` from @modelcontextprotocol/sdk for the request type
|
||||
* @see `ReadResourceResult` from @modelcontextprotocol/sdk for the result type
|
||||
|
|
@ -940,15 +779,7 @@ export class AppBridge extends Protocol<
|
|||
* - `extra` - Request metadata (abort signal, session info)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.onlistprompts = async (params, extra) => {
|
||||
* return mcpClient.request(
|
||||
* { method: "prompts/list", params },
|
||||
* ListPromptsResultSchema,
|
||||
* { signal: extra.signal }
|
||||
* );
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_onlistprompts_returnPrompts}
|
||||
*
|
||||
* @see `ListPromptsRequest` from @modelcontextprotocol/sdk for the request type
|
||||
* @see `ListPromptsResult` from @modelcontextprotocol/sdk for the result type
|
||||
|
|
@ -1084,17 +915,10 @@ export class AppBridge extends Protocol<
|
|||
* @param hostContext - The complete new host context state
|
||||
*
|
||||
* @example Update theme when user toggles dark mode
|
||||
* ```typescript
|
||||
* bridge.setHostContext({ theme: "dark" });
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_setHostContext_updateTheme}
|
||||
*
|
||||
* @example Update multiple context fields
|
||||
* ```typescript
|
||||
* bridge.setHostContext({
|
||||
* theme: "dark",
|
||||
* containerDimensions: { maxHeight: 600, width: 800 }
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_setHostContext_updateMultiple}
|
||||
*
|
||||
* @see {@link McpUiHostContext} for the context structure
|
||||
* @see {@link McpUiHostContextChangedNotification} for the notification type
|
||||
|
|
@ -1147,13 +971,7 @@ export class AppBridge extends Protocol<
|
|||
* @param params - Complete tool call arguments
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* bridge.oninitialized = () => {
|
||||
* bridge.sendToolInput({
|
||||
* arguments: { location: "New York", units: "metric" }
|
||||
* });
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolInput_afterInit}
|
||||
*
|
||||
* @see {@link McpUiToolInputNotification} for the notification type
|
||||
* @see {@link oninitialized} for the initialization callback
|
||||
|
|
@ -1180,15 +998,7 @@ export class AppBridge extends Protocol<
|
|||
* @param params - Partial tool call arguments (may be incomplete)
|
||||
*
|
||||
* @example Stream partial arguments as they arrive
|
||||
* ```typescript
|
||||
* // As streaming progresses...
|
||||
* bridge.sendToolInputPartial({ arguments: { loc: "N" } });
|
||||
* bridge.sendToolInputPartial({ arguments: { location: "New" } });
|
||||
* bridge.sendToolInputPartial({ arguments: { location: "New York" } });
|
||||
*
|
||||
* // When complete, send final input
|
||||
* bridge.sendToolInput({ arguments: { location: "New York", units: "metric" } });
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolInputPartial_streaming}
|
||||
*
|
||||
* @see {@link McpUiToolInputPartialNotification} for the notification type
|
||||
* @see {@link sendToolInput} for sending complete arguments
|
||||
|
|
@ -1211,15 +1021,7 @@ export class AppBridge extends Protocol<
|
|||
* @param params - Standard MCP tool execution result
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
||||
*
|
||||
* const result = await mcpClient.request(
|
||||
* { method: "tools/call", params: { name: "get_weather", arguments: args } },
|
||||
* CallToolResultSchema
|
||||
* );
|
||||
* bridge.sendToolResult(result);
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolResult_afterExecution}
|
||||
*
|
||||
* @see {@link McpUiToolResultNotification} for the notification type
|
||||
* @see {@link sendToolInput} for sending tool arguments before results
|
||||
|
|
@ -1243,19 +1045,10 @@ export class AppBridge extends Protocol<
|
|||
* - `reason`: Human-readable explanation for why the tool was cancelled
|
||||
*
|
||||
* @example User-initiated cancellation
|
||||
* ```typescript
|
||||
* // User clicked "Cancel" button
|
||||
* bridge.sendToolCancelled({ reason: "User cancelled the operation" });
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolCancelled_userInitiated}
|
||||
*
|
||||
* @example System-level cancellation
|
||||
* ```typescript
|
||||
* // Sampling error or timeout
|
||||
* bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
|
||||
*
|
||||
* // Classifier intervention
|
||||
* bridge.sendToolCancelled({ reason: "Content policy violation detected" });
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_sendToolCancelled_systemLevel}
|
||||
*
|
||||
* @see {@link McpUiToolCancelledNotification} for the notification type
|
||||
* @see {@link sendToolResult} for sending successful results
|
||||
|
|
@ -1306,15 +1099,7 @@ export class AppBridge extends Protocol<
|
|||
* @returns Promise resolving when Guest UI confirms readiness for teardown
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* try {
|
||||
* await bridge.teardownResource({});
|
||||
* // Guest UI is ready, safe to unmount iframe
|
||||
* iframe.remove();
|
||||
* } catch (error) {
|
||||
* console.error("Teardown failed:", error);
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_teardownResource_gracefulShutdown}
|
||||
*/
|
||||
teardownResource(
|
||||
params: McpUiResourceTeardownRequest["params"],
|
||||
|
|
@ -1359,32 +1144,10 @@ export class AppBridge extends Protocol<
|
|||
* before calling `bridge.connect()`.
|
||||
*
|
||||
* @example With MCP client (automatic forwarding)
|
||||
* ```typescript
|
||||
* const bridge = new AppBridge(mcpClient, hostInfo, capabilities);
|
||||
* const transport = new PostMessageTransport(
|
||||
* iframe.contentWindow!,
|
||||
* iframe.contentWindow!,
|
||||
* );
|
||||
*
|
||||
* bridge.oninitialized = () => {
|
||||
* console.log("Guest UI ready");
|
||||
* bridge.sendToolInput({ arguments: toolArgs });
|
||||
* };
|
||||
*
|
||||
* await bridge.connect(transport);
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_connect_withMcpClient}
|
||||
*
|
||||
* @example Without MCP client (manual handlers)
|
||||
* ```typescript
|
||||
* const bridge = new AppBridge(null, hostInfo, capabilities);
|
||||
*
|
||||
* // Register handlers manually
|
||||
* bridge.oncalltool = async (params, extra) => {
|
||||
* // Custom tool call handling
|
||||
* };
|
||||
*
|
||||
* await bridge.connect(transport);
|
||||
* ```
|
||||
* {@includeCode ./app-bridge.examples.ts#AppBridge_connect_withoutMcpClient}
|
||||
*/
|
||||
async connect(transport: Transport) {
|
||||
if (this._client) {
|
||||
|
|
|
|||
405
src/app.examples.ts
Normal file
405
src/app.examples.ts
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
/**
|
||||
* Type-checked examples for {@link App} and constants in {@link ./app.ts}.
|
||||
*
|
||||
* These examples are included in the API documentation via `@includeCode` tags.
|
||||
* Each function's region markers define the code snippet that appears in the docs.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type {
|
||||
McpServer,
|
||||
ToolCallback,
|
||||
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { App, PostMessageTransport, RESOURCE_URI_META_KEY } from "./app.js";
|
||||
|
||||
/**
|
||||
* Example: How MCP servers use RESOURCE_URI_META_KEY (server-side, not in Apps).
|
||||
*/
|
||||
function RESOURCE_URI_META_KEY_serverSide(
|
||||
server: McpServer,
|
||||
handler: ToolCallback,
|
||||
) {
|
||||
//#region RESOURCE_URI_META_KEY_serverSide
|
||||
server.registerTool(
|
||||
"weather",
|
||||
{
|
||||
description: "Get weather forecast",
|
||||
_meta: {
|
||||
[RESOURCE_URI_META_KEY]: "ui://weather/forecast",
|
||||
},
|
||||
},
|
||||
handler,
|
||||
);
|
||||
//#endregion RESOURCE_URI_META_KEY_serverSide
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: How hosts check for RESOURCE_URI_META_KEY metadata (host-side).
|
||||
*/
|
||||
function RESOURCE_URI_META_KEY_hostSide(tool: Tool) {
|
||||
//#region RESOURCE_URI_META_KEY_hostSide
|
||||
// Check tool definition metadata (from tools/list response):
|
||||
const uiUri = tool._meta?.[RESOURCE_URI_META_KEY];
|
||||
if (typeof uiUri === "string" && uiUri.startsWith("ui://")) {
|
||||
// Fetch the resource and display the UI
|
||||
}
|
||||
//#endregion RESOURCE_URI_META_KEY_hostSide
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: App constructor with appInfo, capabilities, and options.
|
||||
*/
|
||||
function App_constructor_basic() {
|
||||
//#region App_constructor_basic
|
||||
const app = new App(
|
||||
{ name: "MyApp", version: "1.0.0" },
|
||||
{ tools: { listChanged: true } }, // capabilities
|
||||
{ autoResize: true }, // options
|
||||
);
|
||||
//#endregion App_constructor_basic
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Basic usage of the App class with PostMessageTransport.
|
||||
*/
|
||||
async function App_basicUsage() {
|
||||
//#region App_basicUsage
|
||||
const app = new App(
|
||||
{ name: "WeatherApp", version: "1.0.0" },
|
||||
{}, // capabilities
|
||||
);
|
||||
|
||||
// Register handlers before connecting to ensure no notifications are missed
|
||||
app.ontoolinput = (params) => {
|
||||
console.log("Tool arguments:", params.arguments);
|
||||
};
|
||||
|
||||
await app.connect();
|
||||
//#endregion App_basicUsage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Sending a message to the host's chat.
|
||||
*/
|
||||
async function App_sendMessage(app: App) {
|
||||
//#region App_sendMessage
|
||||
await app.sendMessage({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Weather updated!" }],
|
||||
});
|
||||
//#endregion App_sendMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Check host capabilities after connection.
|
||||
*/
|
||||
async function App_getHostCapabilities_checkAfterConnection(app: App) {
|
||||
//#region App_getHostCapabilities_checkAfterConnection
|
||||
await app.connect();
|
||||
if (app.getHostCapabilities()?.serverTools) {
|
||||
console.log("Host supports server tool calls");
|
||||
}
|
||||
//#endregion App_getHostCapabilities_checkAfterConnection
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Log host information after connection.
|
||||
*/
|
||||
async function App_getHostVersion_logAfterConnection(
|
||||
app: App,
|
||||
transport: PostMessageTransport,
|
||||
) {
|
||||
//#region App_getHostVersion_logAfterConnection
|
||||
await app.connect(transport);
|
||||
const { name, version } = app.getHostVersion() ?? {};
|
||||
console.log(`Connected to ${name} v${version}`);
|
||||
//#endregion App_getHostVersion_logAfterConnection
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Access host context after connection.
|
||||
*/
|
||||
async function App_getHostContext_accessAfterConnection(
|
||||
app: App,
|
||||
transport: PostMessageTransport,
|
||||
) {
|
||||
//#region App_getHostContext_accessAfterConnection
|
||||
await app.connect(transport);
|
||||
const context = app.getHostContext();
|
||||
if (context?.theme === "dark") {
|
||||
document.body.classList.add("dark-theme");
|
||||
}
|
||||
if (context?.toolInfo) {
|
||||
console.log("Tool:", context.toolInfo.tool.name);
|
||||
}
|
||||
//#endregion App_getHostContext_accessAfterConnection
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Using the ontoolinput setter (simpler approach).
|
||||
*/
|
||||
async function App_ontoolinput_setter(app: App) {
|
||||
//#region App_ontoolinput_setter
|
||||
// Register before connecting to ensure no notifications are missed
|
||||
app.ontoolinput = (params) => {
|
||||
console.log("Tool:", params.arguments);
|
||||
// Update your UI with the tool arguments
|
||||
};
|
||||
await app.connect();
|
||||
//#endregion App_ontoolinput_setter
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Progressive rendering of tool arguments using ontoolinputpartial.
|
||||
*/
|
||||
function App_ontoolinputpartial_progressiveRendering(app: App) {
|
||||
//#region App_ontoolinputpartial_progressiveRendering
|
||||
app.ontoolinputpartial = (params) => {
|
||||
console.log("Partial args:", params.arguments);
|
||||
// Update your UI progressively as arguments stream in
|
||||
};
|
||||
//#endregion App_ontoolinputpartial_progressiveRendering
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Display tool execution results using ontoolresult.
|
||||
*/
|
||||
function App_ontoolresult_displayResults(app: App) {
|
||||
//#region App_ontoolresult_displayResults
|
||||
app.ontoolresult = (params) => {
|
||||
if (params.isError) {
|
||||
console.error("Tool execution failed:", params.content);
|
||||
} else if (params.content) {
|
||||
console.log("Tool output:", params.content);
|
||||
}
|
||||
};
|
||||
//#endregion App_ontoolresult_displayResults
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Handle tool cancellation notifications.
|
||||
*/
|
||||
function App_ontoolcancelled_handleCancellation(app: App) {
|
||||
//#region App_ontoolcancelled_handleCancellation
|
||||
app.ontoolcancelled = (params) => {
|
||||
console.log("Tool cancelled:", params.reason);
|
||||
// Update your UI to show cancellation state
|
||||
};
|
||||
//#endregion App_ontoolcancelled_handleCancellation
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Respond to theme changes using onhostcontextchanged.
|
||||
*/
|
||||
function App_onhostcontextchanged_respondToTheme(app: App) {
|
||||
//#region App_onhostcontextchanged_respondToTheme
|
||||
app.onhostcontextchanged = (params) => {
|
||||
if (params.theme === "dark") {
|
||||
document.body.classList.add("dark-theme");
|
||||
} else {
|
||||
document.body.classList.remove("dark-theme");
|
||||
}
|
||||
};
|
||||
//#endregion App_onhostcontextchanged_respondToTheme
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Perform cleanup before teardown.
|
||||
*/
|
||||
function App_onteardown_performCleanup(app: App) {
|
||||
//#region App_onteardown_performCleanup
|
||||
app.onteardown = async () => {
|
||||
await saveState();
|
||||
closeConnections();
|
||||
console.log("App ready for teardown");
|
||||
return {};
|
||||
};
|
||||
//#endregion App_onteardown_performCleanup
|
||||
}
|
||||
|
||||
// Stubs for example
|
||||
declare function saveState(): Promise<void>;
|
||||
declare function closeConnections(): void;
|
||||
|
||||
/**
|
||||
* Example: Handle tool calls from the host.
|
||||
*/
|
||||
function App_oncalltool_handleFromHost(app: App) {
|
||||
//#region App_oncalltool_handleFromHost
|
||||
app.oncalltool = async (params, extra) => {
|
||||
if (params.name === "greet") {
|
||||
const name = params.arguments?.name ?? "World";
|
||||
return { content: [{ type: "text", text: `Hello, ${name}!` }] };
|
||||
}
|
||||
throw new Error(`Unknown tool: ${params.name}`);
|
||||
};
|
||||
//#endregion App_oncalltool_handleFromHost
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Return available tools from the onlisttools handler.
|
||||
*/
|
||||
function App_onlisttools_returnTools(app: App) {
|
||||
//#region App_onlisttools_returnTools
|
||||
app.onlisttools = async (params, extra) => {
|
||||
return {
|
||||
tools: ["greet", "calculate", "format"],
|
||||
};
|
||||
};
|
||||
//#endregion App_onlisttools_returnTools
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Fetch updated weather data using callServerTool.
|
||||
*/
|
||||
async function App_callServerTool_fetchWeather(app: App) {
|
||||
//#region App_callServerTool_fetchWeather
|
||||
try {
|
||||
const result = await app.callServerTool({
|
||||
name: "get_weather",
|
||||
arguments: { location: "Tokyo" },
|
||||
});
|
||||
if (result.isError) {
|
||||
console.error("Tool returned error:", result.content);
|
||||
} else {
|
||||
console.log(result.content);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Tool call failed:", error);
|
||||
}
|
||||
//#endregion App_callServerTool_fetchWeather
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Send a text message from user interaction.
|
||||
*/
|
||||
async function App_sendMessage_textFromInteraction(app: App) {
|
||||
//#region App_sendMessage_textFromInteraction
|
||||
try {
|
||||
const result = await app.sendMessage({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Show me details for item #42" }],
|
||||
});
|
||||
if (result.isError) {
|
||||
console.error("Host rejected the message");
|
||||
// Handle rejection appropriately for your app
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to send message:", error);
|
||||
// Handle transport/protocol error
|
||||
}
|
||||
//#endregion App_sendMessage_textFromInteraction
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Log app state for debugging.
|
||||
*/
|
||||
function App_sendLog_debugState(app: App) {
|
||||
//#region App_sendLog_debugState
|
||||
app.sendLog({
|
||||
level: "info",
|
||||
data: "Weather data refreshed",
|
||||
logger: "WeatherApp",
|
||||
});
|
||||
//#endregion App_sendLog_debugState
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Update model context with current app state.
|
||||
*/
|
||||
async function App_updateModelContext_appState(app: App) {
|
||||
//#region App_updateModelContext_appState
|
||||
await app.updateModelContext({
|
||||
content: [{ type: "text", text: "User selected 3 items totaling $150.00" }],
|
||||
});
|
||||
//#endregion App_updateModelContext_appState
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Update with structured content.
|
||||
*/
|
||||
async function App_updateModelContext_structuredContent(app: App) {
|
||||
//#region App_updateModelContext_structuredContent
|
||||
await app.updateModelContext({
|
||||
structuredContent: { selectedItems: 3, total: 150.0, currency: "USD" },
|
||||
});
|
||||
//#endregion App_updateModelContext_structuredContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Open documentation link.
|
||||
*/
|
||||
async function App_openLink_documentation(app: App) {
|
||||
//#region App_openLink_documentation
|
||||
const { isError } = await app.openLink({ url: "https://docs.example.com" });
|
||||
if (isError) {
|
||||
// Host denied the request (e.g., blocked domain, user cancelled)
|
||||
// Optionally show fallback: display URL for manual copy
|
||||
console.warn("Link request denied");
|
||||
}
|
||||
//#endregion App_openLink_documentation
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Request fullscreen mode.
|
||||
*/
|
||||
async function App_requestDisplayMode_fullscreen(app: App) {
|
||||
//#region App_requestDisplayMode_fullscreen
|
||||
const context = app.getHostContext();
|
||||
if (context?.availableDisplayModes?.includes("fullscreen")) {
|
||||
const result = await app.requestDisplayMode({ mode: "fullscreen" });
|
||||
console.log("Display mode set to:", result.mode);
|
||||
}
|
||||
//#endregion App_requestDisplayMode_fullscreen
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Manually notify host of size change.
|
||||
*/
|
||||
function App_sendSizeChanged_manual(app: App) {
|
||||
//#region App_sendSizeChanged_manual
|
||||
app.sendSizeChanged({
|
||||
width: 400,
|
||||
height: 600,
|
||||
});
|
||||
//#endregion App_sendSizeChanged_manual
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Manual setup for custom scenarios (setupSizeChangedNotifications).
|
||||
*/
|
||||
async function App_setupAutoResize_manual(transport: PostMessageTransport) {
|
||||
//#region App_setupAutoResize_manual
|
||||
const app = new App(
|
||||
{ name: "MyApp", version: "1.0.0" },
|
||||
{},
|
||||
{ autoResize: false },
|
||||
);
|
||||
await app.connect(transport);
|
||||
|
||||
// Later, enable auto-resize manually
|
||||
const cleanup = app.setupSizeChangedNotifications();
|
||||
|
||||
// Clean up when done
|
||||
cleanup();
|
||||
//#endregion App_setupAutoResize_manual
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Connect with PostMessageTransport.
|
||||
*/
|
||||
async function App_connect_withPostMessageTransport() {
|
||||
//#region App_connect_withPostMessageTransport
|
||||
const app = new App({ name: "MyApp", version: "1.0.0" }, {});
|
||||
|
||||
try {
|
||||
await app.connect(new PostMessageTransport(window.parent, window.parent));
|
||||
console.log("Connected successfully!");
|
||||
} catch (error) {
|
||||
console.error("Failed to connect:", error);
|
||||
}
|
||||
//#endregion App_connect_withPostMessageTransport
|
||||
}
|
||||
303
src/app.ts
303
src/app.ts
|
|
@ -60,34 +60,22 @@ export {
|
|||
} from "./styles";
|
||||
|
||||
/**
|
||||
* Metadata key for associating a resource URI with a tool call.
|
||||
* Metadata key for associating a UI resource URI with a tool.
|
||||
*
|
||||
* MCP servers include this key in tool call result metadata to indicate which
|
||||
* UI resource should be displayed for the tool. When hosts receive a tool result
|
||||
* containing this metadata, they resolve and render the corresponding {@link App}.
|
||||
* MCP servers include this key in tool definition metadata (via `tools/list`)
|
||||
* to indicate which UI resource should be displayed when the tool is called.
|
||||
* When hosts see a tool with this metadata, they fetch and render the
|
||||
* corresponding {@link App}.
|
||||
*
|
||||
* **Note**: This constant is provided for reference. MCP servers set this metadata
|
||||
* in their tool handlers; App developers typically don't need to use it directly.
|
||||
* **Note**: This constant is provided for reference. App developers typically
|
||||
* don't need to use it directly. Prefer using {@link server-helpers!registerAppTool}
|
||||
* with the `_meta.ui.resourceUri` format instead.
|
||||
*
|
||||
* @example How MCP servers use this key (server-side, not in Apps)
|
||||
* ```typescript
|
||||
* // In an MCP server's tool handler:
|
||||
* return {
|
||||
* content: [{ type: "text", text: "Result" }],
|
||||
* _meta: {
|
||||
* [RESOURCE_URI_META_KEY]: "ui://weather/forecast"
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_serverSide}
|
||||
*
|
||||
* @example How hosts check for this metadata (host-side)
|
||||
* ```typescript
|
||||
* const result = await mcpClient.callTool({ name: "weather", arguments: {} });
|
||||
* const uiUri = result._meta?.[RESOURCE_URI_META_KEY];
|
||||
* if (uiUri) {
|
||||
* // Load and display the UI resource
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#RESOURCE_URI_META_KEY_hostSide}
|
||||
*/
|
||||
export const RESOURCE_URI_META_KEY = "ui/resourceUri";
|
||||
|
||||
|
|
@ -167,41 +155,10 @@ type RequestHandlerExtra = Parameters<
|
|||
* Both patterns work; use whichever fits your coding style better.
|
||||
*
|
||||
* @example Basic usage with PostMessageTransport
|
||||
* ```typescript
|
||||
* import {
|
||||
* App,
|
||||
* PostMessageTransport,
|
||||
* McpUiToolInputNotificationSchema
|
||||
* } from '@modelcontextprotocol/ext-apps';
|
||||
*
|
||||
* const app = new App(
|
||||
* { name: "WeatherApp", version: "1.0.0" },
|
||||
* {} // capabilities
|
||||
* );
|
||||
*
|
||||
* // Register notification handler using setter (simpler)
|
||||
* app.ontoolinput = (params) => {
|
||||
* console.log("Tool arguments:", params.arguments);
|
||||
* };
|
||||
*
|
||||
* // OR using inherited setNotificationHandler (more explicit)
|
||||
* app.setNotificationHandler(
|
||||
* McpUiToolInputNotificationSchema,
|
||||
* (notification) => {
|
||||
* console.log("Tool arguments:", notification.params.arguments);
|
||||
* }
|
||||
* );
|
||||
*
|
||||
* await app.connect(new PostMessageTransport(window.parent, window.parent));
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_basicUsage}
|
||||
*
|
||||
* @example Sending a message to the host's chat
|
||||
* ```typescript
|
||||
* await app.sendMessage({
|
||||
* role: "user",
|
||||
* content: [{ type: "text", text: "Weather updated!" }]
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_sendMessage}
|
||||
*/
|
||||
export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
||||
private _hostCapabilities?: McpUiHostCapabilities;
|
||||
|
|
@ -216,13 +173,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param options - Configuration options including `autoResize` behavior
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const app = new App(
|
||||
* { name: "MyApp", version: "1.0.0" },
|
||||
* { tools: { listChanged: true } }, // capabilities
|
||||
* { autoResize: true } // options
|
||||
* );
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_constructor_basic}
|
||||
*/
|
||||
constructor(
|
||||
private _appInfo: Implementation,
|
||||
|
|
@ -251,17 +202,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @returns Host capabilities, or `undefined` if not yet connected
|
||||
*
|
||||
* @example Check host capabilities after connection
|
||||
* ```typescript
|
||||
* await app.connect(transport);
|
||||
* const caps = app.getHostCapabilities();
|
||||
* if (caps === undefined) {
|
||||
* console.error("Not connected");
|
||||
* return;
|
||||
* }
|
||||
* if (caps.serverTools) {
|
||||
* console.log("Host supports server tool calls");
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_getHostCapabilities_checkAfterConnection}
|
||||
*
|
||||
* @see {@link connect} for the initialization handshake
|
||||
* @see {@link McpUiHostCapabilities} for the capabilities structure
|
||||
|
|
@ -280,15 +221,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @returns Host implementation info, or `undefined` if not yet connected
|
||||
*
|
||||
* @example Log host information after connection
|
||||
* ```typescript
|
||||
* await app.connect(transport);
|
||||
* const host = app.getHostVersion();
|
||||
* if (host === undefined) {
|
||||
* console.error("Not connected");
|
||||
* return;
|
||||
* }
|
||||
* console.log(`Connected to ${host.name} v${host.version}`);
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_getHostVersion_logAfterConnection}
|
||||
*
|
||||
* @see {@link connect} for the initialization handshake
|
||||
*/
|
||||
|
|
@ -309,20 +242,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @returns Host context, or `undefined` if not yet connected
|
||||
*
|
||||
* @example Access host context after connection
|
||||
* ```typescript
|
||||
* await app.connect(transport);
|
||||
* const context = app.getHostContext();
|
||||
* if (context === undefined) {
|
||||
* console.error("Not connected");
|
||||
* return;
|
||||
* }
|
||||
* if (context.theme === "dark") {
|
||||
* document.body.classList.add("dark-theme");
|
||||
* }
|
||||
* if (context.toolInfo) {
|
||||
* console.log("Tool:", context.toolInfo.tool.name);
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_getHostContext_accessAfterConnection}
|
||||
*
|
||||
* @see {@link connect} for the initialization handshake
|
||||
* @see {@link onhostcontextchanged} for context change notifications
|
||||
|
|
@ -346,25 +266,8 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
*
|
||||
* @param callback - Function called with the tool input params ({@link McpUiToolInputNotification.params})
|
||||
*
|
||||
* @example Using the setter (simpler)
|
||||
* ```typescript
|
||||
* // Register before connecting to ensure no notifications are missed
|
||||
* app.ontoolinput = (params) => {
|
||||
* console.log("Tool:", params.arguments);
|
||||
* // Update your UI with the tool arguments
|
||||
* };
|
||||
* await app.connect(transport);
|
||||
* ```
|
||||
*
|
||||
* @example Using setNotificationHandler (more explicit)
|
||||
* ```typescript
|
||||
* app.setNotificationHandler(
|
||||
* McpUiToolInputNotificationSchema,
|
||||
* (notification) => {
|
||||
* console.log("Tool:", notification.params.arguments);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
* @example
|
||||
* {@includeCode ./app.examples.ts#App_ontoolinput_setter}
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolInputNotification} for the notification structure
|
||||
|
|
@ -392,12 +295,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param callback - Function called with each partial tool input update ({@link McpUiToolInputPartialNotification.params})
|
||||
*
|
||||
* @example Progressive rendering of tool arguments
|
||||
* ```typescript
|
||||
* app.ontoolinputpartial = (params) => {
|
||||
* console.log("Partial args:", params.arguments);
|
||||
* // Update your UI progressively as arguments stream in
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_ontoolinputpartial_progressiveRendering}
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolInputPartialNotification} for the notification structure
|
||||
|
|
@ -426,16 +324,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param callback - Function called with the tool result ({@link McpUiToolResultNotification.params})
|
||||
*
|
||||
* @example Display tool execution results
|
||||
* ```typescript
|
||||
* app.ontoolresult = (params) => {
|
||||
* if (params.content) {
|
||||
* console.log("Tool output:", params.content);
|
||||
* }
|
||||
* if (params.isError) {
|
||||
* console.error("Tool execution failed");
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_ontoolresult_displayResults}
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolResultNotification} for the notification structure
|
||||
|
|
@ -465,12 +354,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param callback - Function called when tool execution is cancelled. Receives optional cancellation reason — see {@link McpUiToolCancelledNotification.params}.
|
||||
*
|
||||
* @example Handle tool cancellation
|
||||
* ```typescript
|
||||
* app.ontoolcancelled = (params) => {
|
||||
* console.log("Tool cancelled:", params.reason);
|
||||
* showCancelledMessage(params.reason ?? "Operation was cancelled");
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_ontoolcancelled_handleCancellation}
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiToolCancelledNotification} for the notification structure
|
||||
|
|
@ -504,15 +388,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param callback - Function called with the updated host context
|
||||
*
|
||||
* @example Respond to theme changes
|
||||
* ```typescript
|
||||
* app.onhostcontextchanged = (params) => {
|
||||
* if (params.theme === "dark") {
|
||||
* document.body.classList.add("dark-theme");
|
||||
* } else {
|
||||
* document.body.classList.remove("dark-theme");
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_onhostcontextchanged_respondToTheme}
|
||||
*
|
||||
* @see {@link setNotificationHandler} for the underlying method
|
||||
* @see {@link McpUiHostContextChangedNotification} for the notification structure
|
||||
|
|
@ -550,14 +426,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* Must return `McpUiResourceTeardownResult` (can be an empty object `{}`) or a Promise resolving to it.
|
||||
*
|
||||
* @example Perform cleanup before teardown
|
||||
* ```typescript
|
||||
* app.onteardown = async () => {
|
||||
* await saveState();
|
||||
* closeConnections();
|
||||
* console.log("App ready for teardown");
|
||||
* return {};
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_onteardown_performCleanup}
|
||||
*
|
||||
* @see {@link setRequestHandler} for the underlying method
|
||||
* @see {@link McpUiResourceTeardownRequest} for the request structure
|
||||
|
|
@ -593,15 +462,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* in the constructor.
|
||||
*
|
||||
* @example Handle tool calls from the host
|
||||
* ```typescript
|
||||
* app.oncalltool = async (params, extra) => {
|
||||
* if (params.name === "greet") {
|
||||
* const name = params.arguments?.name ?? "World";
|
||||
* return { content: [{ type: "text", text: `Hello, ${name}!` }] };
|
||||
* }
|
||||
* throw new Error(`Unknown tool: ${params.name}`);
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_oncalltool_handleFromHost}
|
||||
*
|
||||
* @see {@link setRequestHandler} for the underlying method
|
||||
*/
|
||||
|
|
@ -635,13 +496,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* allowed; capability validation occurs when handlers are invoked.
|
||||
*
|
||||
* @example Return available tools
|
||||
* ```typescript
|
||||
* app.onlisttools = async (params, extra) => {
|
||||
* return {
|
||||
* tools: ["calculate", "convert", "format"]
|
||||
* };
|
||||
* };
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_onlisttools_returnTools}
|
||||
*
|
||||
* @see {@link setRequestHandler} for the underlying method
|
||||
* @see {@link oncalltool} for handling tool execution
|
||||
|
|
@ -730,21 +585,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* between transport failures (thrown) and tool execution failures (returned).
|
||||
*
|
||||
* @example Fetch updated weather data
|
||||
* ```typescript
|
||||
* try {
|
||||
* const result = await app.callServerTool({
|
||||
* name: "get_weather",
|
||||
* arguments: { location: "Tokyo" }
|
||||
* });
|
||||
* if (result.isError) {
|
||||
* console.error("Tool returned error:", result.content);
|
||||
* } else {
|
||||
* console.log(result.content);
|
||||
* }
|
||||
* } catch (error) {
|
||||
* console.error("Tool call failed:", error);
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_callServerTool_fetchWeather}
|
||||
*/
|
||||
async callServerTool(
|
||||
params: CallToolRequest["params"],
|
||||
|
|
@ -765,22 +606,12 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
*
|
||||
* @param params - Message role and content
|
||||
* @param options - Request options (timeout, etc.)
|
||||
* @returns Result indicating success or error (no message content returned)
|
||||
* @returns Result with optional `isError` flag indicating host rejection
|
||||
*
|
||||
* @throws {Error} If the host rejects the message
|
||||
* @throws {Error} If the request times out or the connection is lost
|
||||
*
|
||||
* @example Send a text message from user interaction
|
||||
* ```typescript
|
||||
* try {
|
||||
* await app.sendMessage({
|
||||
* role: "user",
|
||||
* content: [{ type: "text", text: "Show me details for item #42" }]
|
||||
* });
|
||||
* } catch (error) {
|
||||
* console.error("Failed to send message:", error);
|
||||
* // Handle error appropriately for your app
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_sendMessage_textFromInteraction}
|
||||
*
|
||||
* @see {@link McpUiMessageRequest} for request structure
|
||||
*/
|
||||
|
|
@ -804,13 +635,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param params - Log level and message
|
||||
*
|
||||
* @example Log app state for debugging
|
||||
* ```typescript
|
||||
* app.sendLog({
|
||||
* level: "info",
|
||||
* data: "Weather data refreshed",
|
||||
* logger: "WeatherApp"
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_sendLog_debugState}
|
||||
*
|
||||
* @returns Promise that resolves when the log notification is sent
|
||||
*/
|
||||
|
|
@ -836,20 +661,13 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param options - Request options (timeout, etc.)
|
||||
*
|
||||
* @throws {Error} If the host rejects the context update (e.g., unsupported content type)
|
||||
* @throws {Error} If the request times out or the connection is lost
|
||||
*
|
||||
* @example Update model context with current app state
|
||||
* ```typescript
|
||||
* await app.updateModelContext({
|
||||
* content: [{ type: "text", text: "User selected 3 items totaling $150.00" }]
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_updateModelContext_appState}
|
||||
*
|
||||
* @example Update with structured content
|
||||
* ```typescript
|
||||
* await app.updateModelContext({
|
||||
* structuredContent: { selectedItems: 3, total: 150.00, currency: "USD" }
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_updateModelContext_structuredContent}
|
||||
*
|
||||
* @returns Promise that resolves when the context update is acknowledged
|
||||
*/
|
||||
|
|
@ -871,26 +689,19 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* 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.
|
||||
* Apps should handle rejection gracefully by checking `result.isError`.
|
||||
*
|
||||
* @param params - URL to open
|
||||
* @param options - Request options (timeout, etc.)
|
||||
* @returns Result indicating success or error
|
||||
* @returns Result with `isError: true` if the host denied the request (e.g., blocked domain, user cancelled)
|
||||
*
|
||||
* @throws {Error} If the host denies the request (e.g., blocked domain, user cancelled)
|
||||
* @throws {Error} If the request times out or the connection is lost
|
||||
*
|
||||
* @example Open documentation link
|
||||
* ```typescript
|
||||
* try {
|
||||
* await app.openLink({ url: "https://docs.example.com" });
|
||||
* } catch (error) {
|
||||
* console.error("Failed to open link:", error);
|
||||
* // Optionally show fallback: display URL for manual copy
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_openLink_documentation}
|
||||
*
|
||||
* @see {@link McpUiOpenLinkRequest} for request structure
|
||||
* @see {@link McpUiOpenLinkResult} for result structure
|
||||
*/
|
||||
openLink(params: McpUiOpenLinkRequest["params"], options?: RequestOptions) {
|
||||
return this.request(
|
||||
|
|
@ -919,13 +730,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @returns Result containing the actual display mode that was set
|
||||
*
|
||||
* @example Request fullscreen mode
|
||||
* ```typescript
|
||||
* const context = app.getHostContext();
|
||||
* if (context?.availableDisplayModes?.includes("fullscreen")) {
|
||||
* const result = await app.requestDisplayMode({ mode: "fullscreen" });
|
||||
* console.log("Display mode set to:", result.mode);
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_requestDisplayMode_fullscreen}
|
||||
*
|
||||
* @see {@link McpUiRequestDisplayModeRequest} for request structure
|
||||
* @see {@link McpUiHostContext} for checking availableDisplayModes
|
||||
|
|
@ -953,12 +758,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @param params - New width and height in pixels
|
||||
*
|
||||
* @example Manually notify host of size change
|
||||
* ```typescript
|
||||
* app.sendSizeChanged({
|
||||
* width: 400,
|
||||
* height: 600
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_sendSizeChanged_manual}
|
||||
*
|
||||
* @returns Promise that resolves when the notification is sent
|
||||
*
|
||||
|
|
@ -985,16 +785,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @returns Cleanup function to disconnect the observer
|
||||
*
|
||||
* @example Manual setup for custom scenarios
|
||||
* ```typescript
|
||||
* const app = new App(appInfo, capabilities, { autoResize: false });
|
||||
* await app.connect(transport);
|
||||
*
|
||||
* // Later, enable auto-resize manually
|
||||
* const cleanup = app.setupSizeChangedNotifications();
|
||||
*
|
||||
* // Clean up when done
|
||||
* cleanup();
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_setupAutoResize_manual}
|
||||
*/
|
||||
setupSizeChangedNotifications() {
|
||||
let scheduled = false;
|
||||
|
|
@ -1066,19 +857,7 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
|
|||
* @throws {Error} If initialization fails or connection is lost
|
||||
*
|
||||
* @example Connect with PostMessageTransport
|
||||
* ```typescript
|
||||
* const app = new App(
|
||||
* { name: "MyApp", version: "1.0.0" },
|
||||
* {}
|
||||
* );
|
||||
*
|
||||
* try {
|
||||
* await app.connect(new PostMessageTransport(window.parent, window.parent));
|
||||
* console.log("Connected successfully!");
|
||||
* } catch (error) {
|
||||
* console.error("Failed to connect:", error);
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./app.examples.ts#App_connect_withPostMessageTransport}
|
||||
*
|
||||
* @see {@link McpUiInitializeRequest} for the initialization request structure
|
||||
* @see {@link McpUiInitializedNotification} for the initialized notification
|
||||
|
|
|
|||
58
src/message-transport.examples.ts
Normal file
58
src/message-transport.examples.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Type-checked examples for {@link PostMessageTransport}.
|
||||
*
|
||||
* These examples are included in the API documentation via `@includeCode` tags.
|
||||
* Each function's region markers define the code snippet that appears in the docs.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { PostMessageTransport } from "./message-transport.js";
|
||||
import type { App } from "./app.js";
|
||||
import type { AppBridge } from "./app-bridge.js";
|
||||
|
||||
/**
|
||||
* Example: Guest UI connecting to parent window.
|
||||
*/
|
||||
async function PostMessageTransport_guestUI(app: App) {
|
||||
//#region PostMessageTransport_guestUI
|
||||
const transport = new PostMessageTransport(window.parent, window.parent);
|
||||
await app.connect(transport);
|
||||
//#endregion PostMessageTransport_guestUI
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Host connecting to an iframe.
|
||||
*/
|
||||
async function PostMessageTransport_host(bridge: AppBridge) {
|
||||
//#region PostMessageTransport_host
|
||||
const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
|
||||
const transport = new PostMessageTransport(
|
||||
iframe.contentWindow!,
|
||||
iframe.contentWindow!,
|
||||
);
|
||||
await bridge.connect(transport);
|
||||
//#endregion PostMessageTransport_host
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Creating transport for guest UI (constructor only).
|
||||
*/
|
||||
function PostMessageTransport_constructor_guestUI() {
|
||||
//#region PostMessageTransport_constructor_guestUI
|
||||
const transport = new PostMessageTransport(window.parent, window.parent);
|
||||
//#endregion PostMessageTransport_constructor_guestUI
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Creating transport for host (constructor only).
|
||||
*/
|
||||
function PostMessageTransport_constructor_host() {
|
||||
//#region PostMessageTransport_constructor_host
|
||||
const iframe = document.getElementById("app-iframe") as HTMLIFrameElement;
|
||||
const transport = new PostMessageTransport(
|
||||
iframe.contentWindow!,
|
||||
iframe.contentWindow!,
|
||||
);
|
||||
//#endregion PostMessageTransport_constructor_host
|
||||
}
|
||||
|
|
@ -24,20 +24,10 @@ import {
|
|||
* ## Usage
|
||||
*
|
||||
* **Guest UI**:
|
||||
* ```typescript
|
||||
* const transport = new PostMessageTransport(window.parent, window.parent);
|
||||
* await app.connect(transport);
|
||||
* ```
|
||||
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_guestUI}
|
||||
*
|
||||
* **Host**:
|
||||
* ```typescript
|
||||
* const iframe = document.getElementById('app-iframe') as HTMLIFrameElement;
|
||||
* const transport = new PostMessageTransport(
|
||||
* iframe.contentWindow!,
|
||||
* iframe.contentWindow!
|
||||
* );
|
||||
* await bridge.connect(transport);
|
||||
* ```
|
||||
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_host}
|
||||
*
|
||||
* @see {@link app!App.connect} for Guest UI usage
|
||||
* @see {@link app-bridge!AppBridge.connect} for Host usage
|
||||
|
|
@ -56,18 +46,10 @@ export class PostMessageTransport implements Transport {
|
|||
* `window.parent`. For hosts, pass `iframe.contentWindow`.
|
||||
*
|
||||
* @example Guest UI connecting to parent
|
||||
* ```typescript
|
||||
* const transport = new PostMessageTransport(window.parent, window.parent);
|
||||
* ```
|
||||
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_constructor_guestUI}
|
||||
*
|
||||
* @example Host connecting to iframe
|
||||
* ```typescript
|
||||
* const iframe = document.getElementById('app') as HTMLIFrameElement;
|
||||
* const transport = new PostMessageTransport(
|
||||
* iframe.contentWindow!,
|
||||
* iframe.contentWindow!
|
||||
* );
|
||||
* ```
|
||||
* {@includeCode ./message-transport.examples.ts#PostMessageTransport_constructor_host}
|
||||
*/
|
||||
constructor(
|
||||
private eventTarget: Window = window.parent,
|
||||
|
|
|
|||
26
src/react/index.examples.tsx
Normal file
26
src/react/index.examples.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Type-checked examples for the React module overview.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useApp } from "./index.js";
|
||||
|
||||
/**
|
||||
* Example: Basic React App from module overview.
|
||||
*/
|
||||
function index_basicReactApp() {
|
||||
//#region index_basicReactApp
|
||||
function MyApp() {
|
||||
const { app, isConnected, error } = useApp({
|
||||
appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
if (!isConnected) return <div>Connecting...</div>;
|
||||
|
||||
return <div>Connected!</div>;
|
||||
}
|
||||
//#endregion index_basicReactApp
|
||||
}
|
||||
|
|
@ -17,21 +17,7 @@
|
|||
* @module @modelcontextprotocol/ext-apps/react
|
||||
*
|
||||
* @example Basic React App
|
||||
* ```tsx
|
||||
* import { useApp } from '@modelcontextprotocol/ext-apps/react';
|
||||
*
|
||||
* function MyApp() {
|
||||
* const { app, isConnected, error } = useApp({
|
||||
* appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
* capabilities: {}
|
||||
* });
|
||||
*
|
||||
* if (error) return <div>Error: {error.message}</div>;
|
||||
* if (!isConnected) return <div>Connecting...</div>;
|
||||
*
|
||||
* return <div>Connected!</div>;
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./index.examples.tsx#index_basicReactApp}
|
||||
*/
|
||||
export * from "./useApp";
|
||||
export * from "./useAutoResize";
|
||||
|
|
|
|||
65
src/react/useApp.examples.tsx
Normal file
65
src/react/useApp.examples.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Type-checked examples for the useApp hook.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import type { McpUiHostContext } from "../types.js";
|
||||
import { useApp } from "./index.js";
|
||||
|
||||
/**
|
||||
* Example: Register an event handler in onAppCreated.
|
||||
*/
|
||||
function useApp_registerHandler() {
|
||||
//#region useApp_registerHandler
|
||||
useApp({
|
||||
appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
onAppCreated: (app) => {
|
||||
app.ontoolresult = (result) => {
|
||||
console.log("Tool result:", result);
|
||||
};
|
||||
},
|
||||
});
|
||||
//#endregion useApp_registerHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Basic usage of useApp hook with common event handlers.
|
||||
*/
|
||||
function useApp_basicUsage() {
|
||||
//#region useApp_basicUsage
|
||||
function MyApp() {
|
||||
const [hostContext, setHostContext] = useState<
|
||||
McpUiHostContext | undefined
|
||||
>(undefined);
|
||||
|
||||
const { app, isConnected, error } = useApp({
|
||||
appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
onAppCreated: (app) => {
|
||||
app.ontoolinput = (input) => {
|
||||
console.log("Tool input:", input);
|
||||
};
|
||||
app.ontoolresult = (result) => {
|
||||
console.log("Tool result:", result);
|
||||
};
|
||||
app.ontoolcancelled = (params) => {
|
||||
console.log("Tool cancelled:", params.reason);
|
||||
};
|
||||
app.onerror = (error) => {
|
||||
console.log("Error:", error);
|
||||
};
|
||||
app.onhostcontextchanged = (params) => {
|
||||
setHostContext((prev) => ({ ...prev, ...params }));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
if (!isConnected) return <div>Connecting...</div>;
|
||||
return <div>Theme: {hostContext?.theme}</div>;
|
||||
}
|
||||
//#endregion useApp_basicUsage
|
||||
}
|
||||
|
|
@ -29,19 +29,8 @@ export interface UseAppOptions {
|
|||
*
|
||||
* @param app - The newly created `App` instance
|
||||
*
|
||||
* @example Register a notification handler
|
||||
* ```typescript
|
||||
* import { McpUiToolInputNotificationSchema } from '@modelcontextprotocol/ext-apps/react';
|
||||
*
|
||||
* onAppCreated: (app) => {
|
||||
* app.setNotificationHandler(
|
||||
* McpUiToolInputNotificationSchema,
|
||||
* (notification) => {
|
||||
* console.log("Tool input:", notification.params.arguments);
|
||||
* }
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* @example Register an event handler
|
||||
* {@includeCode ./useApp.examples.tsx#useApp_registerHandler}
|
||||
*/
|
||||
onAppCreated?: (app: App) => void;
|
||||
}
|
||||
|
|
@ -80,30 +69,8 @@ export interface AppState {
|
|||
* 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>;
|
||||
* }
|
||||
* ```
|
||||
* @example Basic usage of useApp hook with common event handlers
|
||||
* {@includeCode ./useApp.examples.tsx#useApp_basicUsage}
|
||||
*
|
||||
* @see {@link App.connect} for the underlying connection method
|
||||
* @see {@link useAutoResize} for manual auto-resize control when using custom App options
|
||||
|
|
|
|||
42
src/react/useAutoResize.examples.tsx
Normal file
42
src/react/useAutoResize.examples.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Type-checked examples for the useAutoResize hook.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { App, PostMessageTransport } from "./index.js";
|
||||
import { useAutoResize } from "./useAutoResize.js";
|
||||
|
||||
/**
|
||||
* Example: Manual App creation with custom auto-resize control.
|
||||
*/
|
||||
function useAutoResize_manualApp() {
|
||||
//#region useAutoResize_manualApp
|
||||
function MyComponent() {
|
||||
// For custom App options, create App manually instead of using useApp
|
||||
const [app, setApp] = useState<App | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const myApp = new App(
|
||||
{ name: "MyApp", version: "1.0.0" },
|
||||
{}, // capabilities
|
||||
{ autoResize: false }, // Disable default auto-resize
|
||||
);
|
||||
|
||||
const transport = new PostMessageTransport(window.parent, window.parent);
|
||||
myApp
|
||||
.connect(transport)
|
||||
.then(() => setApp(myApp))
|
||||
.catch((err) => setError(err));
|
||||
}, []);
|
||||
|
||||
// Add manual auto-resize control
|
||||
useAutoResize(app);
|
||||
|
||||
if (error) return <div>Connection failed: {error.message}</div>;
|
||||
return <div>My content</div>;
|
||||
}
|
||||
//#endregion useAutoResize_manualApp
|
||||
}
|
||||
|
|
@ -20,32 +20,7 @@ import { App } from "../app";
|
|||
* cause unnecessary effect re-runs; omit this parameter.
|
||||
*
|
||||
* @example Manual App creation with custom auto-resize control
|
||||
* ```tsx
|
||||
* function MyComponent() {
|
||||
* // For custom App options, create App manually instead of using useApp
|
||||
* const [app, setApp] = useState<App | null>(null);
|
||||
* const [error, setError] = useState<Error | null>(null);
|
||||
*
|
||||
* useEffect(() => {
|
||||
* const myApp = new App(
|
||||
* { name: "MyApp", version: "1.0.0" },
|
||||
* {}, // capabilities
|
||||
* { autoResize: false } // Disable default auto-resize
|
||||
* );
|
||||
*
|
||||
* const transport = new PostMessageTransport(window.parent, window.parent);
|
||||
* myApp.connect(transport)
|
||||
* .then(() => setApp(myApp))
|
||||
* .catch((err) => setError(err));
|
||||
* }, []);
|
||||
*
|
||||
* // Add manual auto-resize control
|
||||
* useAutoResize(app);
|
||||
*
|
||||
* if (error) return <div>Connection failed: {error.message}</div>;
|
||||
* return <div>My content</div>;
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./useAutoResize.examples.tsx#useAutoResize_manualApp}
|
||||
*
|
||||
* @see {@link App.setupSizeChangedNotifications} for the underlying implementation
|
||||
* @see {@link useApp} which enables auto-resize by default
|
||||
|
|
|
|||
46
src/react/useDocumentTheme.examples.tsx
Normal file
46
src/react/useDocumentTheme.examples.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Type-checked examples for the useDocumentTheme hook.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useDocumentTheme } from "./useDocumentTheme.js";
|
||||
|
||||
// Stub components for examples
|
||||
declare function DarkIcon(): React.JSX.Element;
|
||||
declare function LightIcon(): React.JSX.Element;
|
||||
|
||||
/**
|
||||
* Example: Conditionally render based on theme.
|
||||
*/
|
||||
function useDocumentTheme_conditionalRender() {
|
||||
//#region useDocumentTheme_conditionalRender
|
||||
function MyApp() {
|
||||
const theme = useDocumentTheme();
|
||||
|
||||
return <div>{theme === "dark" ? <DarkIcon /> : <LightIcon />}</div>;
|
||||
}
|
||||
//#endregion useDocumentTheme_conditionalRender
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Use with theme-aware styling.
|
||||
*/
|
||||
function useDocumentTheme_themedButton() {
|
||||
//#region useDocumentTheme_themedButton
|
||||
function ThemedButton() {
|
||||
const theme = useDocumentTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
style={{
|
||||
background: theme === "dark" ? "#333" : "#fff",
|
||||
color: theme === "dark" ? "#fff" : "#333",
|
||||
}}
|
||||
>
|
||||
Click me
|
||||
</button>
|
||||
);
|
||||
}
|
||||
//#endregion useDocumentTheme_themedButton
|
||||
}
|
||||
|
|
@ -15,35 +15,10 @@ import { McpUiTheme } from "../types";
|
|||
* @returns The current theme ("light" or "dark")
|
||||
*
|
||||
* @example Conditionally render based on theme
|
||||
* ```tsx
|
||||
* import { useDocumentTheme } from '@modelcontextprotocol/ext-apps/react';
|
||||
*
|
||||
* function MyApp() {
|
||||
* const theme = useDocumentTheme();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* {theme === 'dark' ? <DarkIcon /> : <LightIcon />}
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./useDocumentTheme.examples.tsx#useDocumentTheme_conditionalRender}
|
||||
*
|
||||
* @example Use with theme-aware styling
|
||||
* ```tsx
|
||||
* function ThemedButton() {
|
||||
* const theme = useDocumentTheme();
|
||||
*
|
||||
* return (
|
||||
* <button style={{
|
||||
* background: theme === 'dark' ? '#333' : '#fff',
|
||||
* color: theme === 'dark' ? '#fff' : '#333',
|
||||
* }}>
|
||||
* Click me
|
||||
* </button>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./useDocumentTheme.examples.tsx#useDocumentTheme_themedButton}
|
||||
*
|
||||
* @see {@link getDocumentTheme} for the underlying function
|
||||
* @see {@link applyDocumentTheme} to set the theme
|
||||
|
|
|
|||
77
src/react/useHostStyles.examples.tsx
Normal file
77
src/react/useHostStyles.examples.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Type-checked examples for the useHostStyles hooks.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import {
|
||||
useApp,
|
||||
useHostStyleVariables,
|
||||
useHostFonts,
|
||||
useHostStyles,
|
||||
} from "./index.js";
|
||||
|
||||
/**
|
||||
* Example: Basic usage of useHostStyleVariables.
|
||||
*/
|
||||
function useHostStyleVariables_basicUsage() {
|
||||
//#region useHostStyleVariables_basicUsage
|
||||
function MyApp() {
|
||||
const { app } = useApp({
|
||||
appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
});
|
||||
|
||||
// Apply host styles - pass initial context to apply styles from connect() immediately
|
||||
useHostStyleVariables(app, app?.getHostContext());
|
||||
|
||||
return (
|
||||
<div style={{ background: "var(--color-background-primary)" }}>
|
||||
Hello!
|
||||
</div>
|
||||
);
|
||||
}
|
||||
//#endregion useHostStyleVariables_basicUsage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Basic usage of useHostFonts with useApp.
|
||||
*/
|
||||
function useHostFonts_basicUsage() {
|
||||
//#region useHostFonts_basicUsage
|
||||
function MyApp() {
|
||||
const { app } = useApp({
|
||||
appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
});
|
||||
|
||||
// Apply host fonts - pass initial context to apply fonts from connect() immediately
|
||||
useHostFonts(app, app?.getHostContext());
|
||||
|
||||
return <div style={{ fontFamily: "var(--font-sans)" }}>Hello!</div>;
|
||||
}
|
||||
//#endregion useHostFonts_basicUsage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Basic usage of useHostStyles.
|
||||
*/
|
||||
function useHostStyles_basicUsage() {
|
||||
//#region useHostStyles_basicUsage
|
||||
function MyApp() {
|
||||
const { app } = useApp({
|
||||
appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
capabilities: {},
|
||||
});
|
||||
|
||||
// Apply all host styles - pass initial context to apply styles from connect() immediately
|
||||
useHostStyles(app, app?.getHostContext());
|
||||
|
||||
return (
|
||||
<div style={{ background: "var(--color-background-primary)" }}>
|
||||
Hello!
|
||||
</div>
|
||||
);
|
||||
}
|
||||
//#endregion useHostStyles_basicUsage
|
||||
}
|
||||
|
|
@ -26,25 +26,7 @@ import { McpUiHostContext } from "../types";
|
|||
* If provided, styles and theme will be applied immediately on mount.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* import { useApp, useHostStyleVariables } from '@modelcontextprotocol/ext-apps/react';
|
||||
*
|
||||
* function MyApp() {
|
||||
* const { app, isConnected } = useApp({
|
||||
* appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
* capabilities: {},
|
||||
* });
|
||||
*
|
||||
* // Apply host styles - pass initial context to apply styles from connect() immediately
|
||||
* useHostStyleVariables(app, app?.getHostContext());
|
||||
*
|
||||
* return (
|
||||
* <div style={{ background: 'var(--color-background-primary)' }}>
|
||||
* Hello!
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./useHostStyles.examples.tsx#useHostStyleVariables_basicUsage}
|
||||
*
|
||||
* @see {@link applyHostStyleVariables} for the underlying styles function
|
||||
* @see {@link applyDocumentTheme} for the underlying theme function
|
||||
|
|
@ -107,35 +89,7 @@ export function useHostStyleVariables(
|
|||
* If provided, fonts will be applied immediately on mount.
|
||||
*
|
||||
* @example Basic usage with useApp
|
||||
* ```tsx
|
||||
* import { useApp } from '@modelcontextprotocol/ext-apps/react';
|
||||
* import { useHostFonts } from '@modelcontextprotocol/ext-apps/react';
|
||||
*
|
||||
* function MyApp() {
|
||||
* const { app, isConnected } = useApp({
|
||||
* appInfo: { name: "MyApp", version: "1.0.0" },
|
||||
* capabilities: {},
|
||||
* });
|
||||
*
|
||||
* // Automatically apply host fonts
|
||||
* useHostFonts(app);
|
||||
*
|
||||
* return (
|
||||
* <div style={{ fontFamily: 'var(--font-sans)' }}>
|
||||
* Hello!
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @example With initial context
|
||||
* ```tsx
|
||||
* const [hostContext, setHostContext] = useState<McpUiHostContext | null>(null);
|
||||
*
|
||||
* // ... get initial context from app.connect() result
|
||||
*
|
||||
* useHostFonts(app, hostContext);
|
||||
* ```
|
||||
* {@includeCode ./useHostStyles.examples.tsx#useHostFonts_basicUsage}
|
||||
*
|
||||
* @see {@link applyHostFonts} for the underlying fonts function
|
||||
* @see {@link useHostStyleVariables} for applying style variables and theme
|
||||
|
|
@ -182,14 +136,7 @@ export function useHostFonts(
|
|||
* Pass `app?.getHostContext()` to apply styles immediately on mount.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* function MyApp() {
|
||||
* const { app } = useApp({ appInfo, capabilities: {} });
|
||||
* useHostStyles(app, app?.getHostContext());
|
||||
*
|
||||
* return <div style={{ background: 'var(--color-background-primary)' }}>...</div>;
|
||||
* }
|
||||
* ```
|
||||
* {@includeCode ./useHostStyles.examples.tsx#useHostStyles_basicUsage}
|
||||
*
|
||||
* @see {@link useHostStyleVariables} for style variables and theme only
|
||||
* @see {@link useHostFonts} for fonts only
|
||||
|
|
|
|||
193
src/server/index.examples.ts
Normal file
193
src/server/index.examples.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* Type-checked examples for {@link registerAppTool} and {@link registerAppResource}.
|
||||
*
|
||||
* These examples are included in the API documentation via `@includeCode` tags.
|
||||
* Each function's region markers define the code snippet that appears in the docs.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import type {
|
||||
McpServer,
|
||||
ToolCallback,
|
||||
ReadResourceCallback,
|
||||
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
registerAppTool,
|
||||
registerAppResource,
|
||||
RESOURCE_MIME_TYPE,
|
||||
} from "./index.js";
|
||||
|
||||
// Stubs for external functions used in examples
|
||||
declare function fetchWeather(
|
||||
location: string,
|
||||
): Promise<{ temp: number; conditions: string }>;
|
||||
declare function getCart(): Promise<{ items: unknown[]; total: number }>;
|
||||
declare function updateCartItem(
|
||||
itemId: string,
|
||||
quantity: number,
|
||||
): Promise<{ items: unknown[]; total: number }>;
|
||||
|
||||
/**
|
||||
* Example: Module overview showing basic registration of tools and resources.
|
||||
*/
|
||||
function index_overview(
|
||||
server: McpServer,
|
||||
toolCallback: ToolCallback,
|
||||
readCallback: ReadResourceCallback,
|
||||
) {
|
||||
//#region index_overview
|
||||
// Register a tool that displays a widget
|
||||
registerAppTool(
|
||||
server,
|
||||
"weather",
|
||||
{
|
||||
description: "Get weather forecast",
|
||||
_meta: { ui: { resourceUri: "ui://weather/widget.html" } },
|
||||
},
|
||||
toolCallback,
|
||||
);
|
||||
|
||||
// Register the HTML resource the tool references
|
||||
registerAppResource(
|
||||
server,
|
||||
"Weather Widget",
|
||||
"ui://weather/widget.html",
|
||||
{},
|
||||
readCallback,
|
||||
);
|
||||
//#endregion index_overview
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Basic usage of registerAppTool with input schema and handler.
|
||||
*/
|
||||
function registerAppTool_basicUsage(server: McpServer) {
|
||||
//#region registerAppTool_basicUsage
|
||||
registerAppTool(
|
||||
server,
|
||||
"get-weather",
|
||||
{
|
||||
title: "Get Weather",
|
||||
description: "Get current weather for a location",
|
||||
inputSchema: { location: z.string() },
|
||||
_meta: {
|
||||
ui: { resourceUri: "ui://weather/widget.html" },
|
||||
},
|
||||
},
|
||||
async (args) => {
|
||||
const weather = await fetchWeather(args.location);
|
||||
return { content: [{ type: "text", text: JSON.stringify(weather) }] };
|
||||
},
|
||||
);
|
||||
//#endregion registerAppTool_basicUsage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Tool visibility - create app-only tools for UI actions.
|
||||
*/
|
||||
function registerAppTool_toolVisibility(server: McpServer) {
|
||||
//#region registerAppTool_toolVisibility
|
||||
// Main tool - visible to both model and app (default)
|
||||
registerAppTool(
|
||||
server,
|
||||
"show-cart",
|
||||
{
|
||||
description: "Display the user's shopping cart",
|
||||
_meta: {
|
||||
ui: {
|
||||
resourceUri: "ui://shop/cart.html",
|
||||
visibility: ["model", "app"],
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
const cart = await getCart();
|
||||
return { content: [{ type: "text", text: JSON.stringify(cart) }] };
|
||||
},
|
||||
);
|
||||
|
||||
// App-only tool - hidden from the model, only callable by the UI
|
||||
registerAppTool(
|
||||
server,
|
||||
"update-quantity",
|
||||
{
|
||||
description: "Update item quantity in cart",
|
||||
inputSchema: { itemId: z.string(), quantity: z.number() },
|
||||
_meta: {
|
||||
ui: {
|
||||
resourceUri: "ui://shop/cart.html",
|
||||
visibility: ["app"],
|
||||
},
|
||||
},
|
||||
},
|
||||
async ({ itemId, quantity }) => {
|
||||
const cart = await updateCartItem(itemId, quantity);
|
||||
return { content: [{ type: "text", text: JSON.stringify(cart) }] };
|
||||
},
|
||||
);
|
||||
//#endregion registerAppTool_toolVisibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Basic usage of registerAppResource.
|
||||
*/
|
||||
function registerAppResource_basicUsage(server: McpServer) {
|
||||
//#region registerAppResource_basicUsage
|
||||
registerAppResource(
|
||||
server,
|
||||
"Weather Widget",
|
||||
"ui://weather/widget.html",
|
||||
{
|
||||
description: "Interactive weather display",
|
||||
},
|
||||
async () => ({
|
||||
contents: [
|
||||
{
|
||||
uri: "ui://weather/widget.html",
|
||||
mimeType: RESOURCE_MIME_TYPE,
|
||||
text: await fs.readFile("dist/widget.html", "utf-8"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
//#endregion registerAppResource_basicUsage
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: registerAppResource with CSP configuration for external domains.
|
||||
*/
|
||||
function registerAppResource_withCsp(
|
||||
server: McpServer,
|
||||
musicPlayerHtml: string,
|
||||
) {
|
||||
//#region registerAppResource_withCsp
|
||||
registerAppResource(
|
||||
server,
|
||||
"Music Player",
|
||||
"ui://music/player.html",
|
||||
{
|
||||
description: "Audio player with external soundfonts",
|
||||
},
|
||||
async () => ({
|
||||
contents: [
|
||||
{
|
||||
uri: "ui://music/player.html",
|
||||
mimeType: RESOURCE_MIME_TYPE,
|
||||
text: musicPlayerHtml,
|
||||
_meta: {
|
||||
ui: {
|
||||
csp: {
|
||||
resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
|
||||
connectDomains: ["https://api.example.com"], // For fetch/WebSocket
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
//#endregion registerAppResource_withCsp
|
||||
}
|
||||
|
|
@ -8,18 +8,7 @@
|
|||
* @module server-helpers
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
|
||||
*
|
||||
* // Register a tool that displays a widget
|
||||
* registerAppTool(server, "weather", {
|
||||
* description: "Get weather forecast",
|
||||
* _meta: { ui: { resourceUri: "ui://weather/widget.html" } },
|
||||
* }, handler);
|
||||
*
|
||||
* // Register the HTML resource the tool references
|
||||
* registerAppResource(server, "Weather Widget", "ui://weather/widget.html", {}, readCallback);
|
||||
* ```
|
||||
* {@includeCode ./index.examples.ts#index_overview}
|
||||
*/
|
||||
|
||||
import {
|
||||
|
|
@ -126,57 +115,10 @@ export interface McpUiAppResourceConfig extends ResourceMetadata {
|
|||
* @param cb - Tool handler function
|
||||
*
|
||||
* @example Basic usage
|
||||
* ```typescript
|
||||
* import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
|
||||
* import { z } from 'zod';
|
||||
*
|
||||
* registerAppTool(server, "get-weather", {
|
||||
* title: "Get Weather",
|
||||
* description: "Get current weather for a location",
|
||||
* inputSchema: { location: z.string() },
|
||||
* _meta: {
|
||||
* ui: { resourceUri: "ui://weather/widget.html" },
|
||||
* },
|
||||
* }, async (args) => {
|
||||
* const weather = await fetchWeather(args.location);
|
||||
* return { content: [{ type: "text", text: JSON.stringify(weather) }] };
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./index.examples.ts#registerAppTool_basicUsage}
|
||||
*
|
||||
* @example Tool visibility - create app-only tools for UI actions
|
||||
* ```typescript
|
||||
* import { registerAppTool } from '@modelcontextprotocol/ext-apps/server';
|
||||
* import { z } from 'zod';
|
||||
*
|
||||
* // Main tool - visible to both model and app (default)
|
||||
* registerAppTool(server, "show-cart", {
|
||||
* description: "Display the user's shopping cart",
|
||||
* _meta: {
|
||||
* ui: {
|
||||
* resourceUri: "ui://shop/cart.html",
|
||||
* visibility: ["model", "app"],
|
||||
* },
|
||||
* },
|
||||
* }, async () => {
|
||||
* const cart = await getCart();
|
||||
* return { content: [{ type: "text", text: JSON.stringify(cart) }] };
|
||||
* });
|
||||
*
|
||||
* // App-only tool - hidden from the model, only callable by the UI
|
||||
* registerAppTool(server, "update-quantity", {
|
||||
* description: "Update item quantity in cart",
|
||||
* inputSchema: { itemId: z.string(), quantity: z.number() },
|
||||
* _meta: {
|
||||
* ui: {
|
||||
* resourceUri: "ui://shop/cart.html",
|
||||
* visibility: ["app"],
|
||||
* },
|
||||
* },
|
||||
* }, async ({ itemId, quantity }) => {
|
||||
* const cart = await updateCartItem(itemId, quantity);
|
||||
* return { content: [{ type: "text", text: JSON.stringify(cart) }] };
|
||||
* });
|
||||
* ```
|
||||
* {@includeCode ./index.examples.ts#registerAppTool_toolVisibility}
|
||||
*
|
||||
* @see {@link registerAppResource} to register the HTML resource referenced by the tool
|
||||
*/
|
||||
|
|
@ -225,41 +167,10 @@ export function registerAppTool<
|
|||
* @param readCallback - Callback that returns the resource contents
|
||||
*
|
||||
* @example Basic usage
|
||||
* ```typescript
|
||||
* import { registerAppResource } from '@modelcontextprotocol/ext-apps/server';
|
||||
*
|
||||
* registerAppResource(server, "Weather Widget", "ui://weather/widget.html", {
|
||||
* description: "Interactive weather display",
|
||||
* }, async () => ({
|
||||
* contents: [{
|
||||
* uri: "ui://weather/widget.html",
|
||||
* mimeType: RESOURCE_MIME_TYPE,
|
||||
* text: await fs.readFile("dist/widget.html", "utf-8"),
|
||||
* }],
|
||||
* }));
|
||||
* ```
|
||||
* {@includeCode ./index.examples.ts#registerAppResource_basicUsage}
|
||||
*
|
||||
* @example With CSP configuration for external domains
|
||||
* ```typescript
|
||||
* registerAppResource(server, "Music Player", "ui://music/player.html", {
|
||||
* description: "Audio player with external soundfonts",
|
||||
* }, async () => ({
|
||||
* contents: [{
|
||||
* uri: "ui://music/player.html",
|
||||
* mimeType: RESOURCE_MIME_TYPE,
|
||||
* text: PLAYER_HTML,
|
||||
* // CSP must be on the content item, not the resource config
|
||||
* _meta: {
|
||||
* ui: {
|
||||
* csp: {
|
||||
* connectDomains: ["https://api.example.com"], // For fetch/WebSocket
|
||||
* resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* }],
|
||||
* }));
|
||||
* ```
|
||||
* {@includeCode ./index.examples.ts#registerAppResource_withCsp}
|
||||
*
|
||||
* @see {@link registerAppTool} to register tools that reference this resource
|
||||
*/
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue