From f6ebefe238cc052694a9d51ec9703b3d0c2fc576 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Mon, 8 Dec 2025 18:45:50 +0100 Subject: [PATCH] Add server selector to basic-host example (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add server selector to basic-host example - Add ServerSelector component to switch between MCP servers - Run all example servers concurrently on ports 3101-3107 - Update examples:start to launch all servers with assigned ports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * Move server selector into tool call panel - Server selector now in same component as tool selector - Tool call results persist across server switches - Connection caching avoids reconnects when switching servers - Display server name with tool name in results (ServerName:ToolName) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- examples/basic-host/src/index.module.css | 9 ++ examples/basic-host/src/index.tsx | 167 ++++++++++++++++------- package.json | 11 +- 3 files changed, 138 insertions(+), 49 deletions(-) diff --git a/examples/basic-host/src/index.module.css b/examples/basic-host/src/index.module.css index bbaa542..961d078 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 26df1ff..1db4d29 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; - -interface HostProps { - serverInfoPromise: Promise; +function serverUrl(port: number): string { + return `http://localhost:${port}/mcp`; } -function Host({ serverInfoPromise }: HostProps) { - const serverInfo = use(serverInfoPromise); - const [toolCallInfos, setToolCallInfos] = useState([]); + +// 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; +} + + +// 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(); }}> - -