update examples

This commit is contained in:
ochafik 2025-11-23 03:58:51 +00:00
commit b6da14cefd
3 changed files with 93 additions and 47 deletions

View file

@ -1,4 +1,20 @@
import { /**
* @file App that does NOT depend on Apps SDK runtime.
*
* The Raw UI example has no runtime dependency to the Apps SDK
* but still imports its types for static type safety.
* Types can be just stripped, e.g. w/ the command line:
*
* <code>
* npx esbuild src/ui-raw.ts --bundle --outfile=dist/ui-raw.js --minify --sourcemap --platform=browser
* </code>
*
* We implement a barebones JSON-RPC message sender/receiver (see `app` object below),
* but without timeouts or runtime type validation of any kind
* (for that, use the Apps SDK / see ui-vanilla.ts or ui-react.ts).
*/
import type {
McpUiInitializeRequest, McpUiInitializeRequest,
McpUiInitializeResult, McpUiInitializeResult,
McpUiInitializedNotification, McpUiInitializedNotification,
@ -11,7 +27,8 @@ import {
McpUiOpenLinkRequest, McpUiOpenLinkRequest,
McpUiOpenLinkResult, McpUiOpenLinkResult,
} from "@modelcontextprotocol/ext-apps"; } from "@modelcontextprotocol/ext-apps";
import {
import type {
CallToolRequest, CallToolRequest,
CallToolResult, CallToolResult,
JSONRPCMessage, JSONRPCMessage,
@ -19,18 +36,21 @@ import {
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
const app = (() => { const app = (() => {
type Sendable = { method: string; params: any };
let nextId = 1; let nextId = 1;
return { return {
sendRequest({ method, params }: { method: string; params: any }) { sendRequest<T extends Sendable, Result>({ method, params }: T) {
const id = nextId++; const id = nextId++;
window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*"); window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*");
return new Promise((resolve, reject) => { return new Promise<Result>((resolve, reject) => {
window.addEventListener("message", function listener(event) { window.addEventListener("message", function listener(event) {
const data: JSONRPCMessage = event.data; const data: JSONRPCMessage = event.data;
if (event.data?.id === id) { if (event.data?.id === id) {
window.removeEventListener("message", listener); window.removeEventListener("message", listener);
if (event.data?.result) { if (event.data?.result) {
resolve(true); resolve(event.data.result as Result);
} else if (event.data?.error) { } else if (event.data?.error) {
reject(new Error(event.data.error)); reject(new Error(event.data.error));
} }
@ -40,10 +60,13 @@ const app = (() => {
}); });
}); });
}, },
sendNotification({ method, params }: { method: string; params: any }) { sendNotification<T extends Sendable>({ method, params }: T) {
window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*"); window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*");
}, },
onNotification(method: string, handler: (params: any) => void) { onNotification<T extends Sendable>(
method: T["method"],
handler: (params: T["params"]) => void,
) {
window.addEventListener("message", function listener(event) { window.addEventListener("message", function listener(event) {
if (event.data?.method === method) { if (event.data?.method === method) {
handler(event.data.params); handler(event.data.params);
@ -69,35 +92,40 @@ window.addEventListener("load", async () => {
{ style: "color: red;" }, { style: "color: red;" },
); );
app.onNotification( app.onNotification<McpUiToolInputNotification>(
"ui/notifications/tool-result" as McpUiToolResultNotification["method"], "ui/notifications/tool-input",
async (params: McpUiToolResultNotification["params"]) => { async (params) => {
appendText(`Tool call result: ${JSON.stringify(params)}`);
},
);
app.onNotification(
"ui/notifications/host-context-changed" as McpUiHostContextChangedNotification["method"],
async (params: McpUiHostContextChangedNotification["params"]) => {
appendText(`Host context changed: ${JSON.stringify(params)}`);
},
);
app.onNotification(
"ui/notifications/tool-input" as McpUiToolInputNotification["method"],
async (params: McpUiToolInputNotification["params"]) => {
appendText(`Tool call input: ${JSON.stringify(params)}`); appendText(`Tool call input: ${JSON.stringify(params)}`);
}, },
); );
app.onNotification<McpUiToolResultNotification>(
"ui/notifications/tool-result",
async (params) => {
appendText(`Tool call result: ${JSON.stringify(params)}`);
},
);
app.onNotification<McpUiHostContextChangedNotification>(
"ui/notifications/host-context-changed",
async (params) => {
appendText(`Host context changed: ${JSON.stringify(params)}`);
},
);
const initializeResult = (await app.sendRequest(<McpUiInitializeRequest>{ const initializeResult = await app.sendRequest<
McpUiInitializeRequest,
McpUiInitializeResult
>({
method: "ui/initialize", method: "ui/initialize",
params: { params: {
appCapabilities: {}, appCapabilities: {},
appInfo: { name: "My UI", version: "1.0.0" }, appInfo: { name: "My UI", version: "1.0.0" },
protocolVersion: "2025-06-18", protocolVersion: "2025-06-18",
}, },
})) as McpUiInitializeResult; });
app.sendNotification(<McpUiInitializedNotification>{ appendText(`Initialize result: ${JSON.stringify(initializeResult)}`);
app.sendNotification<McpUiInitializedNotification>({
method: "ui/notifications/initialized", method: "ui/notifications/initialized",
params: {}, params: {},
}); });
@ -116,7 +144,7 @@ window.addEventListener("load", async () => {
(parseFloat(htmlStyle.borderTop) || 0) + (parseFloat(htmlStyle.borderTop) || 0) +
(parseFloat(htmlStyle.borderBottom) || 0); (parseFloat(htmlStyle.borderBottom) || 0);
app.sendNotification(<McpUiSizeChangeNotification>{ app.sendNotification<McpUiSizeChangeNotification>({
method: "ui/notifications/size-change", method: "ui/notifications/size-change",
params: { width, height }, params: { width, height },
}); });
@ -127,13 +155,15 @@ window.addEventListener("load", async () => {
textContent: "Get Weather (Tool)", textContent: "Get Weather (Tool)",
onclick: async () => { onclick: async () => {
try { try {
const result = (await app.sendRequest(<CallToolRequest>{ const result = await app.sendRequest<CallToolRequest, CallToolResult>(
method: "tools/call", {
params: { method: "tools/call",
name: "get-weather", params: {
arguments: { location: "Tokyo" }, name: "get-weather",
arguments: { location: "Tokyo" },
},
}, },
})) as CallToolResult; );
appendText(`Weather tool result: ${JSON.stringify(result)}`); appendText(`Weather tool result: ${JSON.stringify(result)}`);
} catch (e) { } catch (e) {
@ -147,7 +177,7 @@ window.addEventListener("load", async () => {
Object.assign(document.createElement("button"), { Object.assign(document.createElement("button"), {
textContent: "Notify Cart Updated", textContent: "Notify Cart Updated",
onclick: async () => { onclick: async () => {
app.sendNotification(<LoggingMessageNotification>{ app.sendNotification<LoggingMessageNotification>({
method: "notifications/message", method: "notifications/message",
params: { params: {
level: "info", level: "info",
@ -163,7 +193,10 @@ window.addEventListener("load", async () => {
textContent: "Prompt Weather in Tokyo", textContent: "Prompt Weather in Tokyo",
onclick: async () => { onclick: async () => {
try { try {
const { isError } = (await app.sendRequest(<McpUiMessageRequest>{ const { isError } = await app.sendRequest<
McpUiMessageRequest,
McpUiMessageResult
>({
method: "ui/message", method: "ui/message",
params: { params: {
role: "user", role: "user",
@ -174,7 +207,7 @@ window.addEventListener("load", async () => {
}, },
], ],
}, },
})) as McpUiMessageResult; });
appendText(`Message result: ${isError ? "error" : "success"}`); appendText(`Message result: ${isError ? "error" : "success"}`);
} catch (e) { } catch (e) {
@ -189,12 +222,15 @@ window.addEventListener("load", async () => {
textContent: "Open Link to Google", textContent: "Open Link to Google",
onclick: async () => { onclick: async () => {
try { try {
const { isError } = (await app.sendRequest(<McpUiOpenLinkRequest>{ const { isError } = await app.sendRequest<
McpUiOpenLinkRequest,
McpUiOpenLinkResult
>({
method: "ui/open-link", method: "ui/open-link",
params: { params: {
url: "https://www.google.com", url: "https://www.google.com",
}, },
})) as McpUiOpenLinkResult; });
appendText(`Link result: ${isError ? "error" : "success"}`); appendText(`Link result: ${isError ? "error" : "success"}`);
} catch (e) { } catch (e) {
appendError(e); appendError(e);

View file

@ -1,3 +1,6 @@
/**
* @file App that demonstrates a few features using React + the Apps SDK.
*/
import { useState, useCallback } from "react"; import { useState, useCallback } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { import {
@ -13,8 +16,8 @@ import type {
const APP_INFO: Implementation = { const APP_INFO: Implementation = {
name: "MCP UI React Example Client", name: "MCP UI React Example Client",
version: "1.0.0", version: "1.0.0",
protocolVersion: "2025-06-18",
}; };
export function McpClientApp() { export function McpClientApp() {
const [toolResults, setToolResults] = useState<CallToolResult[]>([]); const [toolResults, setToolResults] = useState<CallToolResult[]>([]);
const [messages, setMessages] = useState<string[]>([]); const [messages, setMessages] = useState<string[]>([]);

View file

@ -1,9 +1,18 @@
/**
* @file Demonstrate a few Apps SDK features.
*
* The vanilla (no React) UI uses the Apps SDK.
*
* The Apps SDK offers advantages over the Raw UI example,
* such as ability to set timeouts, strong runtime type validation
* and simpler methods for each request/response interaction.
*/
import { import {
App, App,
PostMessageTransport, PostMessageTransport,
McpUiToolInputNotificationSchema, McpUiToolInputNotificationSchema,
McpUiSizeChangeNotificationSchema,
McpUiToolResultNotificationSchema, McpUiToolResultNotificationSchema,
McpUiHostContextChangedNotificationSchema,
} from "@modelcontextprotocol/ext-apps"; } from "@modelcontextprotocol/ext-apps";
window.addEventListener("load", async () => { window.addEventListener("load", async () => {
@ -27,6 +36,14 @@ window.addEventListener("load", async () => {
version: "1.0.0", version: "1.0.0",
}); });
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
async ({ params }) => {
appendText(
`Tool call input received: ${JSON.stringify(params.arguments)}`,
);
},
);
app.setNotificationHandler( app.setNotificationHandler(
McpUiToolResultNotificationSchema, McpUiToolResultNotificationSchema,
async ({ params: { content, structuredContent, isError } }) => { async ({ params: { content, structuredContent, isError } }) => {
@ -36,19 +53,9 @@ window.addEventListener("load", async () => {
}, },
); );
app.setNotificationHandler( app.setNotificationHandler(
McpUiSizeChangeNotificationSchema, McpUiHostContextChangedNotificationSchema,
async ({ params: { width, height } }) => { async (params) => {
appendText( appendText(`Host context changed: ${JSON.stringify(params)}`);
`Size change notification received: width=${width}, height=${height}`,
);
},
);
app.setNotificationHandler(
McpUiToolInputNotificationSchema,
async ({ params }) => {
appendText(
`Tool call input received: ${JSON.stringify(params.arguments)}`,
);
}, },
); );