diff --git a/examples/basic-host/src/index.module.css b/examples/basic-host/src/index.module.css index 9681e969..31fcf8c7 100644 --- a/examples/basic-host/src/index.module.css +++ b/examples/basic-host/src/index.module.css @@ -1,3 +1,9 @@ +.connecting { + padding: 1rem 0; + text-align: center; + color: #666; +} + .callToolPanel, .toolCallInfoPanel { margin: 0 auto; padding: 1rem; @@ -10,6 +16,9 @@ } .callToolPanel { + display: flex; + flex-direction: column; + gap: 1rem; max-width: 480px; form { diff --git a/examples/basic-host/src/index.tsx b/examples/basic-host/src/index.tsx index 26df1ffa..1db4d29e 100644 --- a/examples/basic-host/src/index.tsx +++ b/examples/basic-host/src/index.tsx @@ -4,35 +4,113 @@ import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, import styles from "./index.module.css"; -const MCP_SERVER_URL = new URL("http://localhost:3001/mcp"); +// Available MCP servers - using ports 3101+ to avoid conflicts with common dev ports +const SERVERS = [ + { name: "Basic React", port: 3101 }, + { name: "Vanilla JS", port: 3102 }, + { name: "Budget Allocator", port: 3103 }, + { name: "Cohort Heatmap", port: 3104 }, + { name: "Customer Segmentation", port: 3105 }, + { name: "Scenario Modeler", port: 3106 }, + { name: "System Monitor", port: 3107 }, +] as const; +function serverUrl(port: number): string { + return `http://localhost:${port}/mcp`; +} -interface HostProps { - serverInfoPromise: Promise; +// Cache server connections to avoid reconnecting when switching between servers +const serverInfoCache = new Map>(); + +function getServerInfo(port: number): Promise { + let promise = serverInfoCache.get(port); + if (!promise) { + promise = connectToServer(new URL(serverUrl(port))); + // Remove from cache on failure so retry is possible + promise.catch(() => serverInfoCache.delete(port)); + serverInfoCache.set(port, promise); + } + return promise; } -function Host({ serverInfoPromise }: HostProps) { - const serverInfo = use(serverInfoPromise); - const [toolCallInfos, setToolCallInfos] = useState([]); + + +// Wrapper to track server name with each tool call +interface ToolCallEntry { + serverName: string; + info: ToolCallInfo; +} + +// Host just manages tool call results - no server dependency +function Host() { + const [toolCalls, setToolCalls] = useState([]); return ( <> - {toolCallInfos.map((info, i) => ( - + {toolCalls.map((entry, i) => ( + ))} setToolCallInfos([...toolCallInfos, info])} + addToolCall={(serverName, info) => setToolCalls([...toolCalls, { serverName, info }])} /> ); } +// CallToolPanel includes server selection with its own Suspense boundary interface CallToolPanelProps { - serverInfo: ServerInfo; - addToolCallInfo: (toolCallInfo: ToolCallInfo) => void; + addToolCall: (serverName: string, info: ToolCallInfo) => void; } -function CallToolPanel({ serverInfo, addToolCallInfo }: CallToolPanelProps) { +function CallToolPanel({ addToolCall }: CallToolPanelProps) { + const [selectedServer, setSelectedServer] = useState(SERVERS[0]); + const [serverInfoPromise, setServerInfoPromise] = useState( + () => getServerInfo(selectedServer.port) + ); + + const handleServerChange = (port: number) => { + const server = SERVERS.find(s => s.port === port) ?? SERVERS[0]; + setSelectedServer(server); + setServerInfoPromise(getServerInfo(port)); + }; + + return ( +
+ + + Connecting to {serverUrl(selectedServer.port)}...

}> + +
+
+
+ ); +} + + +// ToolCallForm renders inside Suspense - needs serverInfo for tool list +interface ToolCallFormProps { + serverName: string; + serverInfoPromise: Promise; + addToolCall: (serverName: string, info: ToolCallInfo) => void; +} +function ToolCallForm({ serverName, serverInfoPromise, addToolCall }: ToolCallFormProps) { + const serverInfo = use(serverInfoPromise); const toolNames = Array.from(serverInfo.tools.keys()); const [selectedTool, setSelectedTool] = useState(toolNames[0] ?? ""); const [inputJson, setInputJson] = useState("{}"); @@ -48,48 +126,47 @@ function CallToolPanel({ serverInfo, addToolCallInfo }: CallToolPanelProps) { const handleSubmit = () => { const toolCallInfo = callTool(serverInfo, selectedTool, JSON.parse(inputJson)); - addToolCallInfo(toolCallInfo); + addToolCall(serverName, toolCallInfo); }; return ( -
-
{ e.preventDefault(); handleSubmit(); }}> - -