import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo } from "./implementation"; import styles from "./index.module.css"; /** * Extract default values from a tool's JSON Schema inputSchema. * Returns a formatted JSON string with defaults, or "{}" if none found. */ function getToolDefaults(tool: Tool | undefined): string { if (!tool?.inputSchema?.properties) return "{}"; const defaults: Record = {}; for (const [key, prop] of Object.entries(tool.inputSchema.properties)) { if (prop && typeof prop === "object" && "default" in prop) { defaults[key] = prop.default; } } return Object.keys(defaults).length > 0 ? JSON.stringify(defaults, null, 2) : "{}"; } // Host passes serversPromise to CallToolPanel interface HostProps { serversPromise: Promise; } type ToolCallEntry = ToolCallInfo & { id: number }; let nextToolCallId = 0; function Host({ serversPromise }: HostProps) { const [toolCalls, setToolCalls] = useState([]); const [destroyingIds, setDestroyingIds] = useState>(new Set()); const requestClose = (id: number) => { setDestroyingIds((s) => new Set(s).add(id)); }; const completeClose = (id: number) => { setDestroyingIds((s) => { const next = new Set(s); next.delete(id); return next; }); setToolCalls((calls) => calls.filter((c) => c.id !== id)); }; return ( <> {toolCalls.map((info) => ( requestClose(info.id)} onCloseComplete={() => completeClose(info.id)} /> ))} setToolCalls([...toolCalls, { ...info, id: nextToolCallId++ }])} /> ); } // CallToolPanel renders the unified form with Suspense around ServerSelect interface CallToolPanelProps { serversPromise: Promise; addToolCall: (info: ToolCallInfo) => void; } function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) { const [selectedServer, setSelectedServer] = useState(null); const [selectedTool, setSelectedTool] = useState(""); const [inputJson, setInputJson] = useState("{}"); const toolNames = selectedServer ? Array.from(selectedServer.tools.keys()) : []; const isValidJson = useMemo(() => { try { JSON.parse(inputJson); return true; } catch { return false; } }, [inputJson]); const handleServerSelect = (server: ServerInfo) => { setSelectedServer(server); const [firstTool] = server.tools.keys(); setSelectedTool(firstTool ?? ""); // Set input JSON to tool defaults (if any) setInputJson(getToolDefaults(server.tools.get(firstTool ?? ""))); }; const handleToolSelect = (toolName: string) => { setSelectedTool(toolName); // Set input JSON to tool defaults (if any) setInputJson(getToolDefaults(selectedServer?.tools.get(toolName))); }; const handleSubmit = () => { if (!selectedServer) return; const toolCallInfo = callTool(selectedServer, selectedTool, JSON.parse(inputJson)); addToolCall(toolCallInfo); }; return (
{ e.preventDefault(); handleSubmit(); }}>