From 54a48a4a1344fb0a1a2a2a820c153f151d7d3a47 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Mon, 24 Nov 2025 16:20:45 +0000 Subject: [PATCH] rename: AppRenderer --- ...edToolCallRenderer.tsx => AppRenderer.tsx} | 14 ++--- .../simple-host/src/example-host-react.tsx | 51 +++++++++---------- src/types.ts | 4 +- 3 files changed, 34 insertions(+), 35 deletions(-) rename examples/simple-host/src/{react/UITemplatedToolCallRenderer.tsx => AppRenderer.tsx} (96%) diff --git a/examples/simple-host/src/react/UITemplatedToolCallRenderer.tsx b/examples/simple-host/src/AppRenderer.tsx similarity index 96% rename from examples/simple-host/src/react/UITemplatedToolCallRenderer.tsx rename to examples/simple-host/src/AppRenderer.tsx index c929797..8e1e9a1 100644 --- a/examples/simple-host/src/react/UITemplatedToolCallRenderer.tsx +++ b/examples/simple-host/src/AppRenderer.tsx @@ -17,16 +17,16 @@ import { getToolUiResourceUri, readToolUiResourceHtml, setupSandboxProxyIframe, -} from "../app-host-utils"; +} from "./app-host-utils"; type RequestExtra = Parameters< Parameters[1] >[1]; /** - * Props for the UITemplatedToolCallRenderer component. + * Props for the AppRenderer component. */ -export interface UITemplatedToolCallRendererProps { +export interface AppRendererProps { /** URL to the sandbox proxy HTML that will host the tool UI iframe */ sandboxProxyUrl: URL; @@ -66,7 +66,7 @@ export interface UITemplatedToolCallRendererProps { * * @example * ```tsx - * { const { client, @@ -224,7 +224,7 @@ export const UITemplatedToolCallRenderer = ( // Step 5: Store proxy in state setAppBridge(appBridge); } catch (err) { - console.error("[UITemplatedToolCallRenderer] Error:", err); + console.error("[AppRenderer] Error:", err); if (!mounted) return; const error = err instanceof Error ? err : new Error(String(err)); setError(error); diff --git a/examples/simple-host/src/example-host-react.tsx b/examples/simple-host/src/example-host-react.tsx index c7c8c9f..8ab0d18 100644 --- a/examples/simple-host/src/example-host-react.tsx +++ b/examples/simple-host/src/example-host-react.tsx @@ -1,21 +1,22 @@ +import { useEffect, useState } from "react"; +import { createRoot } from "react-dom/client"; + import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { Tool } from "@modelcontextprotocol/sdk/types.js"; -import { useEffect, useState } from "react"; -import { createRoot } from "react-dom/client"; -import { UIActionResult } from "../src/mcp-ui-types.js"; -import { UITemplatedToolCallRenderer } from "../src/react/UITemplatedToolCallRenderer.js"; +import { AppRenderer,AppRendererProps } from "../src/AppRenderer"; +import { AppBridge } from "../../../dist/src/app-bridge"; -const SANDBOX_PROXY_URL = URL.parse("/sandbox.html", location.href); +const SANDBOX_PROXY_URL = URL.parse("/sandbox.html", location.href)!; /** - * Example React application demonstrating the UITemplatedToolCallRenderer component. + * Example React application demonstrating the AppRenderer component. * * This shows how to: * - Connect to an MCP server * - List available tools - * - Render tool UIs using UITemplatedToolCallRenderer + * - Render tool UIs using AppRenderer * - Handle UI actions from the tool */ function ExampleApp() { @@ -93,23 +94,19 @@ function ExampleApp() { setActiveTools((prev) => prev.filter((t) => t.id !== id)); }; - const handleUIAction = async (toolId: string, action: UIActionResult) => { - console.log(`[React Host] UI Action from ${toolId}:`, action); + const handleMessage: AppRendererProps["onmessage"] = async (params, _extra) => { + console.log("[React Host] Message:", params); + return {}; + }; - if (action.type === "intent") { - console.log( - "[React Host] Intent:", - action.payload.intent, - action.payload.params, - ); - } else if (action.type === "link") { - console.log("[React Host] Opening link:", action.payload.url); - window.open(action.payload.url, "_blank", "noopener,noreferrer"); - } else if (action.type === "prompt") { - console.log("[React Host] Prompt:", action.payload.prompt); - } else if (action.type === "notify") { - console.log("[React Host] Notification:", action.payload.message); - } + const handleLoggingMessage: AppRendererProps["onloggingmessage"] = (params) => { + console.log("[React Host] Logging message:", params); + }; + + const handleOpenLink: AppRendererProps["onopenlink"] = async (params, _extra) => { + console.log("[React Host] Open link request:", params); + window.open(params.url, "_blank", "noopener,noreferrer"); + return { isError: false }; }; const handleError = (toolId: string, err: Error) => { @@ -223,13 +220,15 @@ function ExampleApp() { - handleUIAction(tool.id, action)} - onError={(err) => handleError(tool.id, err)} + onmessage={handleMessage} + onloggingmessage={handleLoggingMessage} + onopenlink={handleOpenLink} + onerror={(err) => handleError(tool.id, err)} /> ))} diff --git a/src/types.ts b/src/types.ts index 35445ab..40553ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,7 +20,7 @@ export const McpUiOpenLinkRequestSchema = RequestSchema.extend({ export type McpUiOpenLinkRequest = z.infer; export const McpUiOpenLinkResultSchema = z.object({ - isError: z.boolean(), + isError: z.boolean().optional(), }); export type McpUiOpenLinkResult = z.infer; @@ -36,7 +36,7 @@ export type McpUiMessageRequest = z.infer; export const McpUiMessageResultSchema = z.object({ // Note: we don't return the result from follow up messages as they might leak info from the chat. // We do tell the caller if it errored, though. - isError: z.boolean(), + isError: z.boolean().optional(), }); export type McpUiMessageResult = z.infer;