import { useState, useEffect, useCallback, useRef } from "react"; import { createRoot } from "react-dom/client"; import { ThemeProvider } from "@cloudflare/agents-ui/hooks"; import { ConnectionIndicator, ModeToggle, PoweredByAgents, type ConnectionStatus } from "@cloudflare/agents-ui"; import { Button, Badge, Surface, Text, Empty } from "@cloudflare/kumo"; import { WrenchIcon, DatabaseIcon, PaperPlaneRightIcon, TrashIcon, ArrowClockwiseIcon, PlugIcon, InfoIcon, WarningCircleIcon, CheckCircleIcon } from "@phosphor-icons/react"; import "./styles.css"; interface McpTool { name: string; description?: string; inputSchema?: { type: string; properties?: Record; required?: string[]; }; } interface McpResource { name: string; uri: string; description?: string; } interface ServerInfo { name: string; version: string; } interface ToolResult { label: string; text: string; isError: boolean; timestamp: number; } interface JsonRpcResponse { jsonrpc: string; id?: number; result?: Record; error?: { code: number; message: string }; } let nextId = 0; async function mcpFetch( endpoint: string, method: string, params: Record, sessionId: string | null ): Promise<{ data: JsonRpcResponse | null; sessionId: string | null }> { const headers: Record = { "Content-Type": "application/json", Accept: "application/json, text/event-stream" }; if (sessionId) headers["mcp-session-id"] = sessionId; const body: Record = { jsonrpc: "2.0", method, params }; const isNotification = method.startsWith("notifications/"); if (!isNotification) body.id = ++nextId; const res = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify(body) }); const newSessionId = res.headers.get("mcp-session-id") || sessionId; if (isNotification || res.status === 202) { return { data: null, sessionId: newSessionId }; } const contentType = res.headers.get("content-type") || ""; let data: JsonRpcResponse; if (contentType.includes("text/event-stream")) { const text = await res.text(); const match = text.match(/^data: (.+)$/m); data = match ? JSON.parse(match[1]) : { jsonrpc: "2.0" }; } else { data = await res.json(); } return { data, sessionId: newSessionId }; } function ToolCard({ tool, onCall }: { tool: McpTool; onCall: (name: string, args: Record) => Promise; }) { const [args, setArgs] = useState>({}); const [loading, setLoading] = useState(false); const properties = tool.inputSchema?.properties || {}; const propertyEntries = Object.entries(properties); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); const typedArgs: Record = {}; for (const [key, value] of Object.entries(args)) { const prop = properties[key]; if (prop?.type === "number" || prop?.type === "integer") { typedArgs[key] = Number(value); } else if (prop?.type === "boolean") { typedArgs[key] = value === "true"; } else { typedArgs[key] = value; } } await onCall(tool.name, typedArgs); setLoading(false); }; return ( {tool.name} {tool.description && ( {tool.description} )}
{propertyEntries.map(([key, schema]) => (
))}
); } function App() { const [status, setStatus] = useState("connecting"); const [serverInfo, setServerInfo] = useState(null); const [tools, setTools] = useState([]); const [resources, setResources] = useState([]); const [results, setResults] = useState([]); const sessionRef = useRef(null); const connect = useCallback(async () => { try { setStatus("connecting"); const init = await mcpFetch( "/mcp", "initialize", { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "browser-tester", version: "1.0.0" } }, null ); sessionRef.current = init.sessionId; const initResult = init.data?.result as | { serverInfo?: ServerInfo } | undefined; setServerInfo(initResult?.serverInfo ?? null); await mcpFetch("/mcp", "notifications/initialized", {}, init.sessionId); const toolsRes = await mcpFetch("/mcp", "tools/list", {}, init.sessionId); const toolsResult = toolsRes.data?.result as | { tools?: McpTool[] } | undefined; setTools(toolsResult?.tools ?? []); try { const resourcesRes = await mcpFetch( "/mcp", "resources/list", {}, init.sessionId ); const resourcesResult = resourcesRes.data?.result as | { resources?: McpResource[] } | undefined; setResources(resourcesResult?.resources ?? []); } catch { // Server may not support resources } setStatus("connected"); } catch { setStatus("disconnected"); } }, []); useEffect(() => { connect(); }, [connect]); const handleCallTool = async ( name: string, args: Record ) => { try { const res = await mcpFetch( "/mcp", "tools/call", { name, arguments: args }, sessionRef.current ); const result = res.data?.result as | { content?: Array<{ type: string; text?: string }>; isError?: boolean; } | undefined; const text = result?.content?.[0]?.text ?? JSON.stringify(result); setResults((prev) => [ { label: name, text, isError: result?.isError ?? false, timestamp: Date.now() }, ...prev ]); } catch (err) { setResults((prev) => [ { label: name, text: err instanceof Error ? err.message : String(err), isError: true, timestamp: Date.now() }, ...prev ]); } }; const handleReadResource = async (uri: string) => { try { const res = await mcpFetch( "/mcp", "resources/read", { uri }, sessionRef.current ); const result = res.data?.result as | { contents?: Array<{ text?: string; uri?: string }> } | undefined; const text = result?.contents?.[0]?.text ?? JSON.stringify(result); setResults((prev) => [ { label: uri, text, isError: false, timestamp: Date.now() }, ...prev ]); } catch (err) { setResults((prev) => [ { label: uri, text: err instanceof Error ? err.message : String(err), isError: true, timestamp: Date.now() }, ...prev ]); } }; return (

{serverInfo?.name ?? "MCP Server"}

{serverInfo && ( v{serverInfo.version} )}
{status === "disconnected" && ( )}
Stateless MCP Server (createMcpHandler) The simplest way to run an MCP server on Cloudflare Workers. Uses{" "} createMcpHandler {" "} from the Agents SDK to wrap an{" "} McpServer {" "} into a Worker-compatible fetch handler in one line — no Durable Objects, no persistent state.
{status === "disconnected" && ( } title="Disconnected" description="Could not connect to the MCP server. Make sure it is running and try reconnecting." /> )} {status === "connected" && ( <>
Tools {tools.length}
{tools.length === 0 ? ( } title="No tools" description="This server has no registered tools." /> ) : (
{tools.map((tool) => ( ))}
)}
{resources.length > 0 && (
Resources {resources.length}
{resources.map((r) => (
{r.name} {r.uri}
))}
)} {results.length > 0 && (
Results
{results.map((r) => (
{r.isError ? ( ) : ( )}
{r.label}

{r.text}

{new Date(r.timestamp).toLocaleTimeString()}
))}
)} )}
); } createRoot(document.getElementById("root")!).render( );