diff --git a/examples/simple-server/src/ui-raw.ts b/examples/simple-server/src/ui-raw.ts
index 1c09fb3..d91ab9f 100644
--- a/examples/simple-server/src/ui-raw.ts
+++ b/examples/simple-server/src/ui-raw.ts
@@ -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:
+ *
+ *
+ * npx esbuild src/ui-raw.ts --bundle --outfile=dist/ui-raw.js --minify --sourcemap --platform=browser
+ *
+ *
+ * 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,
McpUiInitializeResult,
McpUiInitializedNotification,
@@ -11,7 +27,8 @@ import {
McpUiOpenLinkRequest,
McpUiOpenLinkResult,
} from "@modelcontextprotocol/ext-apps";
-import {
+
+import type {
CallToolRequest,
CallToolResult,
JSONRPCMessage,
@@ -19,18 +36,21 @@ import {
} from "@modelcontextprotocol/sdk/types.js";
const app = (() => {
+ type Sendable = { method: string; params: any };
+
let nextId = 1;
+
return {
- sendRequest({ method, params }: { method: string; params: any }) {
+ sendRequest({ method, params }: T) {
const id = nextId++;
window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*");
- return new Promise((resolve, reject) => {
+ return new Promise((resolve, reject) => {
window.addEventListener("message", function listener(event) {
const data: JSONRPCMessage = event.data;
if (event.data?.id === id) {
window.removeEventListener("message", listener);
if (event.data?.result) {
- resolve(true);
+ resolve(event.data.result as Result);
} else if (event.data?.error) {
reject(new Error(event.data.error));
}
@@ -40,10 +60,13 @@ const app = (() => {
});
});
},
- sendNotification({ method, params }: { method: string; params: any }) {
+ sendNotification({ method, params }: T) {
window.parent.postMessage({ jsonrpc: "2.0", method, params }, "*");
},
- onNotification(method: string, handler: (params: any) => void) {
+ onNotification(
+ method: T["method"],
+ handler: (params: T["params"]) => void,
+ ) {
window.addEventListener("message", function listener(event) {
if (event.data?.method === method) {
handler(event.data.params);
@@ -69,35 +92,40 @@ window.addEventListener("load", async () => {
{ style: "color: red;" },
);
- app.onNotification(
- "ui/notifications/tool-result" as McpUiToolResultNotification["method"],
- async (params: McpUiToolResultNotification["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"]) => {
+ app.onNotification(
+ "ui/notifications/tool-input",
+ async (params) => {
appendText(`Tool call input: ${JSON.stringify(params)}`);
},
);
+ app.onNotification(
+ "ui/notifications/tool-result",
+ async (params) => {
+ appendText(`Tool call result: ${JSON.stringify(params)}`);
+ },
+ );
+ app.onNotification(
+ "ui/notifications/host-context-changed",
+ async (params) => {
+ appendText(`Host context changed: ${JSON.stringify(params)}`);
+ },
+ );
- const initializeResult = (await app.sendRequest({
+ const initializeResult = await app.sendRequest<
+ McpUiInitializeRequest,
+ McpUiInitializeResult
+ >({
method: "ui/initialize",
params: {
appCapabilities: {},
appInfo: { name: "My UI", version: "1.0.0" },
protocolVersion: "2025-06-18",
},
- })) as McpUiInitializeResult;
+ });
- app.sendNotification({
+ appendText(`Initialize result: ${JSON.stringify(initializeResult)}`);
+
+ app.sendNotification({
method: "ui/notifications/initialized",
params: {},
});
@@ -116,7 +144,7 @@ window.addEventListener("load", async () => {
(parseFloat(htmlStyle.borderTop) || 0) +
(parseFloat(htmlStyle.borderBottom) || 0);
- app.sendNotification({
+ app.sendNotification({
method: "ui/notifications/size-change",
params: { width, height },
});
@@ -127,13 +155,15 @@ window.addEventListener("load", async () => {
textContent: "Get Weather (Tool)",
onclick: async () => {
try {
- const result = (await app.sendRequest({
- method: "tools/call",
- params: {
- name: "get-weather",
- arguments: { location: "Tokyo" },
+ const result = await app.sendRequest(
+ {
+ method: "tools/call",
+ params: {
+ name: "get-weather",
+ arguments: { location: "Tokyo" },
+ },
},
- })) as CallToolResult;
+ );
appendText(`Weather tool result: ${JSON.stringify(result)}`);
} catch (e) {
@@ -147,7 +177,7 @@ window.addEventListener("load", async () => {
Object.assign(document.createElement("button"), {
textContent: "Notify Cart Updated",
onclick: async () => {
- app.sendNotification({
+ app.sendNotification({
method: "notifications/message",
params: {
level: "info",
@@ -163,7 +193,10 @@ window.addEventListener("load", async () => {
textContent: "Prompt Weather in Tokyo",
onclick: async () => {
try {
- const { isError } = (await app.sendRequest({
+ const { isError } = await app.sendRequest<
+ McpUiMessageRequest,
+ McpUiMessageResult
+ >({
method: "ui/message",
params: {
role: "user",
@@ -174,7 +207,7 @@ window.addEventListener("load", async () => {
},
],
},
- })) as McpUiMessageResult;
+ });
appendText(`Message result: ${isError ? "error" : "success"}`);
} catch (e) {
@@ -189,12 +222,15 @@ window.addEventListener("load", async () => {
textContent: "Open Link to Google",
onclick: async () => {
try {
- const { isError } = (await app.sendRequest({
+ const { isError } = await app.sendRequest<
+ McpUiOpenLinkRequest,
+ McpUiOpenLinkResult
+ >({
method: "ui/open-link",
params: {
url: "https://www.google.com",
},
- })) as McpUiOpenLinkResult;
+ });
appendText(`Link result: ${isError ? "error" : "success"}`);
} catch (e) {
appendError(e);
diff --git a/examples/simple-server/src/ui-react.tsx b/examples/simple-server/src/ui-react.tsx
index 19dd799..1c9f026 100644
--- a/examples/simple-server/src/ui-react.tsx
+++ b/examples/simple-server/src/ui-react.tsx
@@ -1,3 +1,6 @@
+/**
+ * @file App that demonstrates a few features using React + the Apps SDK.
+ */
import { useState, useCallback } from "react";
import { createRoot } from "react-dom/client";
import {
@@ -13,8 +16,8 @@ import type {
const APP_INFO: Implementation = {
name: "MCP UI React Example Client",
version: "1.0.0",
- protocolVersion: "2025-06-18",
};
+
export function McpClientApp() {
const [toolResults, setToolResults] = useState([]);
const [messages, setMessages] = useState([]);
diff --git a/examples/simple-server/src/ui-vanilla.ts b/examples/simple-server/src/ui-vanilla.ts
index 78ecf0f..a470355 100644
--- a/examples/simple-server/src/ui-vanilla.ts
+++ b/examples/simple-server/src/ui-vanilla.ts
@@ -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 {
App,
PostMessageTransport,
McpUiToolInputNotificationSchema,
- McpUiSizeChangeNotificationSchema,
McpUiToolResultNotificationSchema,
+ McpUiHostContextChangedNotificationSchema,
} from "@modelcontextprotocol/ext-apps";
window.addEventListener("load", async () => {
@@ -27,6 +36,14 @@ window.addEventListener("load", async () => {
version: "1.0.0",
});
+ app.setNotificationHandler(
+ McpUiToolInputNotificationSchema,
+ async ({ params }) => {
+ appendText(
+ `Tool call input received: ${JSON.stringify(params.arguments)}`,
+ );
+ },
+ );
app.setNotificationHandler(
McpUiToolResultNotificationSchema,
async ({ params: { content, structuredContent, isError } }) => {
@@ -36,19 +53,9 @@ window.addEventListener("load", async () => {
},
);
app.setNotificationHandler(
- McpUiSizeChangeNotificationSchema,
- async ({ params: { width, height } }) => {
- appendText(
- `Size change notification received: width=${width}, height=${height}`,
- );
- },
- );
- app.setNotificationHandler(
- McpUiToolInputNotificationSchema,
- async ({ params }) => {
- appendText(
- `Tool call input received: ${JSON.stringify(params.arguments)}`,
- );
+ McpUiHostContextChangedNotificationSchema,
+ async (params) => {
+ appendText(`Host context changed: ${JSON.stringify(params)}`);
},
);