diff --git a/AGENTS.md b/AGENTS.md index 623d561b6..bcf105b94 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ MCP Apps SDK (`@modelcontextprotocol/ext-apps`) enables MCP servers to display i Key abstractions: -- **Guest** - UI running in an iframe, uses `App` class with `PostMessageTransport` to communicate with host +- **View** - UI running in an iframe, uses `App` class with `PostMessageTransport` to communicate with host - **Host** - Chat client embedding the iframe, uses `AppBridge` class to proxy MCP requests - **Server** - MCP server that registers tools/resources with UI metadata @@ -67,14 +67,14 @@ rm -fR package-lock.json node_modules && \ ### Protocol Flow ``` -Guest UI (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server +View (App) <--PostMessageTransport--> Host (AppBridge) <--MCP Client--> MCP Server ``` -1. Host creates iframe with Guest UI HTML -2. Guest UI creates `App` instance and calls `connect()` with `PostMessageTransport` -3. App sends `ui/initialize` request, receives host capabilities and context +1. Host creates iframe with view HTML +2. View creates `App` instance and calls `connect()` with `PostMessageTransport` +3. View sends `ui/initialize` request, receives host capabilities and context 4. Host sends `sendToolInput()` with tool arguments after initialization -5. Guest UI can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()` +5. View can call server tools via `app.callServerTool()` or send messages via `app.sendMessage()` 6. Host sends `sendToolResult()` when tool execution completes 7. Host calls `teardownResource()` before unmounting iframe diff --git a/docs/migrate_from_openai_apps.md b/docs/migrate_from_openai_apps.md index c7b43a0fa..01008cb41 100644 --- a/docs/migrate_from_openai_apps.md +++ b/docs/migrate_from_openai_apps.md @@ -72,7 +72,7 @@ function createServer() { inputSchema: { userId: z.string() }, annotations: { readOnlyHint: true }, _meta: { - "openai/outputTemplate": "ui://widget/cart.html", + "openai/outputTemplate": "ui://view/cart.html", "openai/toolInvocation/invoking": "Loading cart...", "openai/toolInvocation/invoked": "Cart ready", "openai/widgetAccessible": true, @@ -89,13 +89,13 @@ function createServer() { // Register UI resource server.registerResource( - "Cart Widget", - "ui://widget/cart.html", + "Cart View", + "ui://view/cart.html", { mimeType: "text/html+skybridge" }, async () => ({ contents: [ { - uri: "ui://widget/cart.html", + uri: "ui://view/cart.html", mimeType: "text/html+skybridge", text: getCartHtml(), _meta: { @@ -137,7 +137,7 @@ function createServer() { description: "Display the user's shopping cart", inputSchema: { userId: z.string() }, annotations: { readOnlyHint: true }, - _meta: { ui: { resourceUri: "ui://widget/cart.html" } }, + _meta: { ui: { resourceUri: "ui://view/cart.html" } }, }, async (args) => { const cart = await getCart(args.userId); @@ -151,13 +151,13 @@ function createServer() { // Register UI resource registerAppResource( server, - "Cart Widget", - "ui://widget/cart.html", + "Cart View", + "ui://view/cart.html", { description: "Shopping cart UI" }, async () => ({ contents: [ { - uri: "ui://widget/cart.html", + uri: "ui://view/cart.html", mimeType: RESOURCE_MIME_TYPE, text: getCartHtml(), _meta: { diff --git a/docs/patterns.md b/docs/patterns.md index 46e608ebf..662714bb8 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -82,11 +82,11 @@ When you need to send more data than fits in a message, use {@link app!App.updat _See [`examples/transcript-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server) for a full implementation of this pattern._ -## Persisting widget state +## Persisting view state -To persist widget state across conversation reloads (e.g., current page in a PDF viewer, camera position in a map), use [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) with a stable identifier provided by the server. +To persist view state across conversation reloads (e.g., current page in a PDF viewer, camera position in a map), use [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) with a stable identifier provided by the server. -**Server-side**: Tool handler generates a unique `widgetUUID` and returns it in `CallToolResult._meta.widgetUUID`: +**Server-side**: Tool handler generates a unique `viewUUID` and returns it in `CallToolResult._meta.viewUUID`: {@includeCode ./patterns.tsx#persistDataServer} @@ -96,9 +96,9 @@ To persist widget state across conversation reloads (e.g., current page in a PDF _See [`examples/map-server/`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server) for a full implementation of this pattern._ -## Pausing computation-heavy widgets when out of view +## Pausing computation-heavy views when out of view -Widgets with animations, WebGL rendering, or polling can consume significant CPU/GPU even when scrolled out of view. Use [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to pause expensive operations when the widget isn't visible: +Views with animations, WebGL rendering, or polling can consume significant CPU/GPU even when scrolled out of view. Use [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to pause expensive operations when the view isn't visible: {@includeCode ./patterns.tsx#visibilityBasedPause} diff --git a/docs/patterns.tsx b/docs/patterns.tsx index f21ff5c96..f13268fd6 100644 --- a/docs/patterns.tsx +++ b/docs/patterns.tsx @@ -206,21 +206,17 @@ function hostStylingReact() { } /** - * Example: Persisting widget state (server-side) + * Example: Persisting view state (server-side) */ -function persistWidgetStateServer( - url: string, - title: string, - pageCount: number, -) { +function persistViewStateServer(url: string, title: string, pageCount: number) { function toolCallback(): CallToolResult { //#region persistDataServer - // In your tool callback, include widgetUUID in the result metadata. + // In your tool callback, include viewUUID in the result metadata. return { content: [{ type: "text", text: `Displaying PDF viewer for "${title}"` }], structuredContent: { url, title, pageCount, initialPage: 1 }, _meta: { - widgetUUID: randomUUID(), + viewUUID: randomUUID(), }, }; //#endregion persistDataServer @@ -228,39 +224,39 @@ function persistWidgetStateServer( } /** - * Example: Persisting widget state (client-side) + * Example: Persisting view state (client-side) */ -function persistWidgetState(app: App) { +function persistViewState(app: App) { //#region persistData - // Store the widgetUUID received from the server - let widgetUUID: string | undefined; + // Store the viewUUID received from the server + let viewUUID: string | undefined; // Helper to save state to localStorage function saveState(state: T): void { - if (!widgetUUID) return; + if (!viewUUID) return; try { - localStorage.setItem(widgetUUID, JSON.stringify(state)); + localStorage.setItem(viewUUID, JSON.stringify(state)); } catch (err) { - console.error("Failed to save widget state:", err); + console.error("Failed to save view state:", err); } } // Helper to load state from localStorage function loadState(): T | null { - if (!widgetUUID) return null; + if (!viewUUID) return null; try { - const saved = localStorage.getItem(widgetUUID); + const saved = localStorage.getItem(viewUUID); return saved ? (JSON.parse(saved) as T) : null; } catch (err) { - console.error("Failed to load widget state:", err); + console.error("Failed to load view state:", err); return null; } } - // Receive widgetUUID from the tool result + // Receive viewUUID from the tool result app.ontoolresult = (result) => { - widgetUUID = result._meta?.widgetUUID - ? String(result._meta.widgetUUID) + viewUUID = result._meta?.viewUUID + ? String(result._meta.viewUUID) : undefined; // Restore any previously saved state @@ -270,13 +266,13 @@ function persistWidgetState(app: App) { } }; - // Call saveState() whenever your widget state changes + // Call saveState() whenever your view state changes // e.g., saveState({ currentPage: 5 }); //#endregion persistData } /** - * Example: Pausing computation-heavy widgets when out of view + * Example: Pausing computation-heavy views when out of view */ function visibilityBasedPause( app: App, @@ -284,7 +280,7 @@ function visibilityBasedPause( animation: { play: () => void; pause: () => void }, ) { //#region visibilityBasedPause - // Use IntersectionObserver to pause when widget scrolls out of view + // Use IntersectionObserver to pause when view scrolls out of view const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { @@ -296,7 +292,7 @@ function visibilityBasedPause( }); observer.observe(container); - // Clean up when the host tears down the widget + // Clean up when the host tears down the view app.onteardown = async () => { observer.disconnect(); animation.pause(); @@ -310,6 +306,6 @@ void chunkedDataServer; void chunkedDataClient; void hostStylingVanillaJs; void hostStylingReact; -void persistWidgetStateServer; -void persistWidgetState; +void persistViewStateServer; +void persistViewState; void visibilityBasedPause; diff --git a/examples/basic-host/src/implementation.ts b/examples/basic-host/src/implementation.ts index 6ab5be02d..b77b8afa0 100644 --- a/examples/basic-host/src/implementation.ts +++ b/examples/basic-host/src/implementation.ts @@ -277,7 +277,7 @@ export function newAppBridge( }, }); - // Register all handlers before calling connect(). The Guest UI can start + // Register all handlers before calling connect(). The view can start // sending requests immediately after the initialization handshake, so any // handlers registered after connect() might miss early requests. diff --git a/examples/basic-host/src/sandbox.ts b/examples/basic-host/src/sandbox.ts index d4a4e5773..1caf1c5f1 100644 --- a/examples/basic-host/src/sandbox.ts +++ b/examples/basic-host/src/sandbox.ts @@ -56,7 +56,7 @@ const PROXY_READY_NOTIFICATION: McpUiSandboxProxyReadyNotification["method"] = // Message relay: This Sandbox (outer iframe) acts as a bidirectional bridge, // forwarding messages between: // -// Host (parent window) ↔ Sandbox (outer frame) ↔ Guest UI (inner iframe) +// Host (parent window) ↔ Sandbox (outer frame) ↔ View (inner iframe) // // Reason: the parent window and inner iframe have different origins and can't // communicate directly, so the outer iframe forwards messages in both @@ -64,7 +64,7 @@ const PROXY_READY_NOTIFICATION: McpUiSandboxProxyReadyNotification["method"] = // // Special case: The "ui/notifications/sandbox-proxy-ready" message is // intercepted here (not relayed) because the Sandbox uses it to configure and -// load the inner iframe with the Guest UI HTML content. +// load the inner iframe with the view HTML content. // // Security: CSP is enforced via HTTP headers on sandbox.html (set by serve.ts // based on ?csp= query param). This is tamper-proof unlike meta tags. @@ -128,7 +128,7 @@ window.addEventListener("message", async (event) => { } }); -// Notify the Host that the Sandbox is ready to receive Guest UI HTML. +// Notify the Host that the Sandbox is ready to receive view HTML. // Use specific origin instead of "*" to ensure only the expected host receives this. window.parent.postMessage({ jsonrpc: "2.0", diff --git a/examples/map-server/server.ts b/examples/map-server/server.ts index e6ff56422..dbe2154e1 100644 --- a/examples/map-server/server.ts +++ b/examples/map-server/server.ts @@ -185,7 +185,7 @@ export function createServer(): McpServer { }, ], _meta: { - widgetUUID: randomUUID(), + viewUUID: randomUUID(), }, }), ); diff --git a/examples/map-server/src/mcp-app.ts b/examples/map-server/src/mcp-app.ts index 891ef2c45..f6edfa56e 100644 --- a/examples/map-server/src/mcp-app.ts +++ b/examples/map-server/src/mcp-app.ts @@ -71,7 +71,7 @@ let persistViewTimer: ReturnType | null = null; // Track whether tool input has been received (to know if we should restore persisted state) let hasReceivedToolInput = false; -let widgetUUID: string | undefined = undefined; +let viewUUID: string | undefined = undefined; /** * Persisted camera state for localStorage @@ -122,7 +122,7 @@ function schedulePersistViewState(cesiumViewer: any): void { * Persist current view state to localStorage */ function persistViewState(cesiumViewer: any): void { - if (!widgetUUID) { + if (!viewUUID) { log.info("No storage key available, skipping view persistence"); return; } @@ -132,8 +132,8 @@ function persistViewState(cesiumViewer: any): void { try { const value = JSON.stringify(state); - localStorage.setItem(widgetUUID, value); - log.info("Persisted view state:", widgetUUID, value); + localStorage.setItem(viewUUID, value); + log.info("Persisted view state:", viewUUID, value); } catch (e) { log.warn("Failed to persist view state:", e); } @@ -143,10 +143,10 @@ function persistViewState(cesiumViewer: any): void { * Load persisted view state from localStorage */ function loadPersistedViewState(): PersistedCameraState | null { - if (!widgetUUID) return null; + if (!viewUUID) return null; try { - const stored = localStorage.getItem(widgetUUID); + const stored = localStorage.getItem(viewUUID); if (!stored) { console.info("No persisted view state found"); return null; @@ -938,16 +938,14 @@ app.ontoolinput = async (params) => { // }, // ); -// Handle tool result - extract widgetUUID and restore persisted view if available +// Handle tool result - extract viewUUID and restore persisted view if available app.ontoolresult = async (result) => { - widgetUUID = result._meta?.widgetUUID - ? String(result._meta.widgetUUID) - : undefined; - log.info("Tool result received, widgetUUID:", widgetUUID); + viewUUID = result._meta?.viewUUID ? String(result._meta.viewUUID) : undefined; + log.info("Tool result received, viewUUID:", viewUUID); - // Now that we have widgetUUID, try to restore persisted view + // Now that we have viewUUID, try to restore persisted view // This overrides the tool input position if a saved state exists - if (viewer && widgetUUID) { + if (viewer && viewUUID) { const restored = restorePersistedView(viewer); if (restored) { log.info("Restored persisted view from tool result handler"); diff --git a/examples/pdf-server/server.ts b/examples/pdf-server/server.ts index 77e6f3341..ae32c44bd 100644 --- a/examples/pdf-server/server.ts +++ b/examples/pdf-server/server.ts @@ -189,7 +189,7 @@ The viewer supports zoom, navigation, text selection, and fullscreen mode.`, ], structuredContent: result, _meta: { - widgetUUID: randomUUID(), + viewUUID: randomUUID(), }, }; }, diff --git a/examples/pdf-server/src/mcp-app.ts b/examples/pdf-server/src/mcp-app.ts index 28bf93670..6cb41928d 100644 --- a/examples/pdf-server/src/mcp-app.ts +++ b/examples/pdf-server/src/mcp-app.ts @@ -35,7 +35,7 @@ let totalPages = 0; let scale = 1.0; let pdfUrl = ""; let pdfTitle: string | undefined; -let widgetUUID: string | undefined; +let viewUUID: string | undefined; let currentRenderTask: { cancel: () => void } | null = null; // DOM Elements @@ -404,10 +404,10 @@ async function renderPage() { } function saveCurrentPage() { - log.info("saveCurrentPage: key=", widgetUUID, "page=", currentPage); - if (widgetUUID) { + log.info("saveCurrentPage: key=", viewUUID, "page=", currentPage); + if (viewUUID) { try { - localStorage.setItem(widgetUUID, String(currentPage)); + localStorage.setItem(viewUUID, String(currentPage)); log.info("saveCurrentPage: saved successfully"); } catch (err) { log.error("saveCurrentPage: error", err); @@ -416,10 +416,10 @@ function saveCurrentPage() { } function loadSavedPage(): number | null { - log.info("loadSavedPage: key=", widgetUUID); - if (!widgetUUID) return null; + log.info("loadSavedPage: key=", viewUUID); + if (!viewUUID) return null; try { - const saved = localStorage.getItem(widgetUUID); + const saved = localStorage.getItem(viewUUID); log.info("loadSavedPage: saved value=", saved); if (saved) { const page = parseInt(saved, 10); @@ -706,9 +706,7 @@ app.ontoolresult = async (result) => { pdfUrl = parsed.url; pdfTitle = parsed.title; totalPages = parsed.pageCount; - widgetUUID = result._meta?.widgetUUID - ? String(result._meta.widgetUUID) - : undefined; + viewUUID = result._meta?.viewUUID ? String(result._meta.viewUUID) : undefined; // Restore saved page or use initial page const savedPage = loadSavedPage(); diff --git a/examples/qr-server/README.md b/examples/qr-server/README.md index 9ad3da6f1..5c7a61fd2 100644 --- a/examples/qr-server/README.md +++ b/examples/qr-server/README.md @@ -1,6 +1,6 @@ # QR Code MCP Server -A minimal Python MCP server that generates customizable QR codes with an interactive widget UI. +A minimal Python MCP server that generates customizable QR codes with an interactive view UI. ![Screenshot](https://modelcontextprotocol.github.io/ext-apps/screenshots/qr-server/screenshot.png) @@ -33,7 +33,7 @@ Then add to your MCP client configuration (stdio transport), replacing the path - Generate QR codes from any text or URL - Customizable colors, size, and error correction -- Interactive widget that displays in MCP-UI enabled clients +- Interactive view that displays in MCP-UI enabled clients - Supports both HTTP (for web clients) and stdio (for MCP clients) ## Prerequisites @@ -151,19 +151,19 @@ Generate a QR code with optional customization. ``` qr-server/ ├── server.py # MCP server (FastMCP + uvicorn, deps inline via PEP 723) -├── widget.html # Interactive UI widget +├── view.html # Interactive UI view └── README.md ``` ### Protocol -The widget uses MCP Apps SDK protocol: +The view uses MCP Apps SDK protocol: -1. Widget sends `ui/initialize` request +1. The view sends `ui/initialize` request 2. Host responds with capabilities -3. Widget sends `ui/notifications/initialized` +3. The view sends `ui/notifications/initialized` 4. Host sends `ui/notifications/tool-result` with QR image -5. Widget renders image and sends `ui/notifications/size-changed` +5. The view renders image and sends `ui/notifications/size-changed` ## Dependencies diff --git a/examples/qr-server/server.py b/examples/qr-server/server.py index 883814309..415a520b0 100755 --- a/examples/qr-server/server.py +++ b/examples/qr-server/server.py @@ -22,14 +22,14 @@ from mcp import types from starlette.middleware.cors import CORSMiddleware -WIDGET_URI = "ui://qr-server/widget.html" +VIEW_URI = "ui://qr-server/view.html" HOST = os.environ.get("HOST", "0.0.0.0") # 0.0.0.0 for Docker compatibility PORT = int(os.environ.get("PORT", "3108")) mcp = FastMCP("QR Code Server") -# Embedded widget HTML for self-contained usage (uv run or unbundled) -EMBEDDED_WIDGET_HTML = """ +# Embedded View HTML for self-contained usage (uv run or unbundled) +EMBEDDED_VIEW_HTML = """ @@ -60,7 +60,7 @@ """ -def get_widget_html() -> str: - """Get the widget HTML, preferring built version from dist/.""" +def get_view_html() -> str: + """Get the View HTML, preferring built version from dist/.""" # Prefer built version from dist/ (local development with npm run build) dist_path = Path(__file__).parent / "dist" / "mcp-app.html" if dist_path.exists(): return dist_path.read_text() - # Fallback to embedded widget (for `uv run ` or unbundled usage) - return EMBEDDED_WIDGET_HTML + # Fallback to embedded View (for `uv run ` or unbundled usage) + return EMBEDDED_VIEW_HTML # IMPORTANT: all the external domains used by app must be listed # in the meta.ui.csp.resourceDomains - otherwise they will be blocked by CSP policy @mcp.resource( - WIDGET_URI, + VIEW_URI, mime_type="text/html;profile=mcp-app", meta={"ui": {"csp": {"resourceDomains": ["https://esm.sh", "https://unpkg.com"]}}}, ) -def widget() -> str: - """Widget HTML resource with CSP metadata for external dependencies.""" - return get_widget_html() +def view() -> str: + """View HTML resource with CSP metadata for external dependencies.""" + return get_view_html() # ------------------------------------------------------ diff --git a/examples/shadertoy-server/src/mcp-app.css b/examples/shadertoy-server/src/mcp-app.css index 151deef0c..0449ea5ea 100644 --- a/examples/shadertoy-server/src/mcp-app.css +++ b/examples/shadertoy-server/src/mcp-app.css @@ -69,7 +69,7 @@ html, body { align-items: center; justify-content: center; transition: background 0.2s, opacity 0.2s; - opacity: 0; /* Initially invisible, shown on widget hover */ + opacity: 0; /* Initially invisible, shown on View hover */ z-index: 100; } @@ -91,7 +91,7 @@ html, body { display: flex; } -/* Show button on widget hover */ +/* Show button on View hover */ .main:hover .fullscreen-btn.available { opacity: 0.7; } diff --git a/examples/threejs-server/README.md b/examples/threejs-server/README.md index 1419c218c..e343421bc 100644 --- a/examples/threejs-server/README.md +++ b/examples/threejs-server/README.md @@ -134,7 +134,7 @@ React component that: ### Visibility-Based Pause -Animation automatically pauses when the widget scrolls out of view: +Animation automatically pauses when the view scrolls out of view: - Uses `IntersectionObserver` to track visibility (browser-native, no polling) - Wraps `requestAnimationFrame` to skip frames when not visible diff --git a/examples/threejs-server/mcp-app.html b/examples/threejs-server/mcp-app.html index b549f0cef..94f21efd7 100644 --- a/examples/threejs-server/mcp-app.html +++ b/examples/threejs-server/mcp-app.html @@ -4,7 +4,7 @@ - Three.js Widget + Three.js View diff --git a/examples/threejs-server/server.ts b/examples/threejs-server/server.ts index 971e1d10b..ae320df80 100644 --- a/examples/threejs-server/server.ts +++ b/examples/threejs-server/server.ts @@ -19,7 +19,7 @@ const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") : import.meta.dirname; -// Default code example for the Three.js widget +// Default code example for the Three.js view const DEFAULT_THREEJS_CODE = `const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true }); @@ -45,7 +45,7 @@ function animate() { } animate();`; -const THREEJS_DOCUMENTATION = `# Three.js Widget Documentation +const THREEJS_DOCUMENTATION = `# Three.js view Documentation ## Available Globals - \`THREE\` - Three.js library (r181) @@ -191,8 +191,7 @@ export function createServer(): McpServer { "learn_threejs", { title: "Learn Three.js", - description: - "Get documentation and examples for using the Three.js widget", + description: "Get documentation and examples for using the Three.js View", inputSchema: {}, }, async () => { @@ -207,7 +206,7 @@ export function createServer(): McpServer { server, resourceUri, resourceUri, - { mimeType: RESOURCE_MIME_TYPE, description: "Three.js Widget UI" }, + { mimeType: RESOURCE_MIME_TYPE, description: "Three.js View UI" }, async (): Promise => { const html = await fs.readFile( path.join(DIST_DIR, "mcp-app.html"), diff --git a/examples/threejs-server/src/mcp-app-wrapper.tsx b/examples/threejs-server/src/mcp-app-wrapper.tsx index a2a8f96c4..4313fbbd5 100644 --- a/examples/threejs-server/src/mcp-app-wrapper.tsx +++ b/examples/threejs-server/src/mcp-app-wrapper.tsx @@ -1,8 +1,8 @@ /** - * Three.js Widget - MCP App Wrapper + * Three.js view - MCP App Wrapper * * Generic wrapper that handles MCP App connection and passes all relevant - * props to the actual widget component. + * props to the actual view component. */ import type { App, McpUiHostContext } from "@modelcontextprotocol/ext-apps"; import { useApp, useHostStyles } from "@modelcontextprotocol/ext-apps/react"; @@ -17,10 +17,10 @@ import "./global.css"; // ============================================================================= /** - * Props passed to the widget component. - * This interface can be reused for other widgets. + * Props passed to the view component. + * This interface can be reused for other views. */ -export interface WidgetProps> { +export interface ViewProps> { /** Complete tool input (after streaming finishes) */ toolInputs: TToolInput | null; /** Partial tool input (during streaming) */ @@ -55,7 +55,7 @@ function McpAppWrapper() { const [hostContext, setHostContext] = useState(null); const { app, error } = useApp({ - appInfo: { name: "Three.js Widget", version: "1.0.0" }, + appInfo: { name: "Three.js View", version: "1.0.0" }, capabilities: {}, onAppCreated: (app) => { // Complete tool input (streaming finished) diff --git a/examples/threejs-server/src/threejs-app.tsx b/examples/threejs-server/src/threejs-app.tsx index 649e7e6e2..2b99266b0 100644 --- a/examples/threejs-server/src/threejs-app.tsx +++ b/examples/threejs-server/src/threejs-app.tsx @@ -10,7 +10,7 @@ import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js"; import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js"; -import type { WidgetProps } from "./mcp-app-wrapper.tsx"; +import type { ViewProps } from "./mcp-app-wrapper.tsx"; // ============================================================================= // Types @@ -21,7 +21,7 @@ interface ThreeJSToolInput { height?: number; } -type ThreeJSAppProps = WidgetProps; +type ThreeJSAppProps = ViewProps; // ============================================================================= // Constants diff --git a/examples/video-resource-server/README.md b/examples/video-resource-server/README.md index 01d89e634..1c38cbe5f 100644 --- a/examples/video-resource-server/README.md +++ b/examples/video-resource-server/README.md @@ -40,6 +40,6 @@ npm run dev ## How It Works 1. The `play_video` tool returns a `videoUri` pointing to an MCP resource -2. The widget fetches the resource via `resources/read` +2. The view fetches the resource via `resources/read` 3. The server fetches the video from CDN and returns it as a base64 blob -4. The widget decodes the blob and plays it in a `