import { useAgent } from "agents/react"; import { useState } from "react"; import { Button, Input, InputArea, Surface, Text } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, ConnectionStatus, CodeExplanation, HighlightedJson, type CodeSection } from "../../components"; import { useLogs, useUserId, useToast } from "../../hooks"; import type { McpClientAgent, McpClientState } from "./mcp-client-agent"; const codeSections: CodeSection[] = [ { title: "Connect to external MCP servers", description: "Use this.addMcpServer() to connect your agent to any MCP server. The connection persists across restarts — the agent automatically reconnects.", code: `import { Agent, callable } from "agents"; class McpClientAgent extends Agent { @callable() async connectToServer(url: string) { const result = await this.addMcpServer("my-server", url); return result; // { id: "...", state: "ready" } } }` }, { title: "Reactive MCP updates with onMcpUpdate", description: "On the client, useAgent provides an onMcpUpdate callback that fires whenever the agent's MCP state changes — tools, resources, and server status arrive automatically after connecting. No need to poll.", code: `const agent = useAgent({ agent: "mcp-client-agent", name: "demo", onMcpUpdate: (mcpState) => { // Fires automatically when MCP servers connect/disconnect console.log("Tools:", mcpState.tools); console.log("Resources:", mcpState.resources); console.log("Servers:", mcpState.servers); }, }); // Call tools via @callable on the agent await agent.call("callTool", [toolName, serverId, args]);` } ]; interface ToolInfo { name: string; description?: string; inputSchema?: unknown; serverId?: string; } export function McpClientDemo() { const userId = useUserId(); const { logs, addLog, clearLogs } = useLogs(); const { toast } = useToast(); const [mcpUrl, setMcpUrl] = useState(`${window.location.origin}/mcp-server`); const [isConnecting, setIsConnecting] = useState(false); const [isConnected, setIsConnected] = useState(false); const [tools, setTools] = useState([]); const [resources, setResources] = useState([]); const [selectedTool, setSelectedTool] = useState(null); const [argsText, setArgsText] = useState("{}"); const [toolResult, setToolResult] = useState(null); const [isCallingTool, setIsCallingTool] = useState(false); const agent = useAgent({ agent: "mcp-client-agent", name: `mcp-client-${userId}`, onOpen: () => addLog("info", "connected"), onClose: () => addLog("info", "disconnected"), onError: () => addLog("error", "error", "Connection error"), onStateUpdate: (newState) => { if (newState?.connectedServer) { setIsConnected(true); } else { setIsConnected(false); setTools([]); setResources([]); } }, onMcpUpdate: (mcpState) => { const discoveredTools = (mcpState.tools ?? []) as ToolInfo[]; setTools(discoveredTools); setResources(mcpState.resources ?? []); addLog("in", "mcp_update", { tools: discoveredTools.length, resources: (mcpState.resources ?? []).length, servers: Object.keys(mcpState.servers ?? {}).length }); } }); const handleConnect = async () => { if (!mcpUrl.trim()) return; setIsConnecting(true); addLog("out", "connectToServer", { url: mcpUrl }); try { const result = await agent.call("connectToServer", [mcpUrl]); addLog("in", "connected", result); setIsConnected(true); toast("Connected to MCP server", "success"); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); } finally { setIsConnecting(false); } }; const handleDisconnect = async () => { addLog("out", "disconnectServer"); try { await agent.call("disconnectServer"); setIsConnected(false); setTools([]); setResources([]); setSelectedTool(null); setToolResult(null); addLog("in", "disconnected"); toast("Disconnected", "info"); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); } }; const handleCallTool = async () => { if (!selectedTool) return; let args: Record; try { args = JSON.parse(argsText); } catch { addLog("error", "error", "Invalid JSON in arguments"); return; } const tool = tools.find((t) => t.name === selectedTool); const serverId = tool?.serverId ?? ""; setIsCallingTool(true); setToolResult(null); addLog("out", "callTool", { name: selectedTool, serverId, args }); try { const result = await agent.call("callTool", [ selectedTool, serverId, args ]); addLog("in", "tool_result", result); setToolResult(result); toast(selectedTool + " called", "success"); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); } finally { setIsCallingTool(false); } }; const handleSelectTool = (name: string) => { setSelectedTool(name); setToolResult(null); setArgsText("{}"); }; return ( This agent connects to external MCP servers using{" "} this.addMcpServer() . Tools and resources are discovered automatically via the{" "} onMcpUpdate {" "} callback — no polling needed. Try connecting to the playground's own MCP server. } statusIndicator={ } >
{/* Controls */}
{/* Connect */}
Connect to MCP Server
) => setMcpUrl(e.target.value) } className="flex-1 font-mono text-xs" placeholder="https://..." disabled={isConnected} />
{isConnected ? ( ) : ( )}
{/* Discovered Tools */} {tools.length > 0 && (
Discovered Tools ({tools.length})
{tools.map((tool) => ( ))}
)} {/* Discovered Resources */} {resources.length > 0 ? (
Resources ({resources.length})
) : null} {/* Call Tool */} {selectedTool && (
Call: {selectedTool}
setArgsText(e.target.value)} className="w-full h-20 font-mono text-sm mb-3" />
)} {/* Tool Result */} {toolResult !== null && (
Result
)}
{/* Logs */}
); }