import { useState } from "react"; import { Button, Input, InputArea, Surface, Text } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, CodeExplanation, HighlightedJson, type CodeSection } from "../../components"; import { useLogs, useToast } from "../../hooks"; const codeSections: CodeSection[] = [ { title: "Create an MCP server agent", description: 'Extend McpAgent instead of Agent. Create an McpServer instance and register tools, resources, and prompts in the init() method. Deploy with McpAgent.serve("/mcp") to expose the MCP protocol endpoint.', code: `import { McpAgent } from "agents/mcp"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; class PlaygroundMcpServer extends McpAgent { server = new McpServer({ name: "playground", version: "1.0.0" }); async init() { this.server.registerTool( "roll_dice", { description: "Roll dice with N sides", inputSchema: { sides: z.number().default(6) }, }, async ({ sides }) => ({ content: [{ type: "text", text: String(Math.floor(Math.random() * sides) + 1) }], }) ); this.server.resource("info", "playground://info", async (uri) => ({ contents: [{ uri: uri.href, text: "Server info..." }], })); } } // Expose the MCP server at /mcp export default PlaygroundMcpServer.serve("/mcp", { binding: "PlaygroundMcpServer", });` }, { title: "Connect from any MCP client", description: "Once deployed, any MCP-compatible client (Claude, Cursor, custom apps) can connect to your agent's URL and use its tools and resources.", code: `// In Claude Desktop or Cursor settings: { "mcpServers": { "playground": { "url": "https://your-app.workers.dev/mcp" } } }` } ]; const TOOLS = [ { name: "roll_dice", description: "Roll one or more dice with a given number of sides", defaultArgs: { sides: 6, count: 2 } }, { name: "generate_uuid", description: "Generate one or more random UUIDs", defaultArgs: { count: 3 } }, { name: "word_count", description: "Count words, characters, and lines in text", defaultArgs: { text: "The quick brown fox jumps over the lazy dog" } }, { name: "hash_text", description: "Compute SHA-256 hash of text", defaultArgs: { text: "hello world" } } ]; const MCP_HEADERS = { "Content-Type": "application/json", Accept: "application/json, text/event-stream" }; async function mcpRequest( url: string, method: string, params: Record, sessionId?: string ): Promise<{ data: unknown; sessionId: string | null }> { const headers: Record = { ...MCP_HEADERS }; if (sessionId) { headers["Mcp-Session-Id"] = sessionId; } const response = await fetch(url, { method: "POST", headers, body: JSON.stringify({ jsonrpc: "2.0", id: crypto.randomUUID(), method, params }) }); const newSessionId = response.headers.get("Mcp-Session-Id"); const contentType = response.headers.get("Content-Type") ?? ""; if (contentType.includes("text/event-stream")) { const text = await response.text(); const lines = text.split("\n"); let lastData: unknown = null; for (const line of lines) { if (line.startsWith("data: ")) { try { lastData = JSON.parse(line.slice(6)); } catch { // skip non-JSON data lines } } } return { data: lastData, sessionId: newSessionId }; } const data = await response.json(); return { data, sessionId: newSessionId }; } async function ensureSession( url: string, currentSessionId: string | null ): Promise { if (currentSessionId) return currentSessionId; const { sessionId } = await mcpRequest(url, "initialize", { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "playground", version: "1.0.0" } }); if (sessionId) { await mcpRequest(url, "notifications/initialized", {}, sessionId); } return sessionId ?? ""; } export function McpServerDemo() { const { logs, addLog, clearLogs } = useLogs(); const { toast } = useToast(); const [selectedTool, setSelectedTool] = useState(0); const [argsText, setArgsText] = useState( JSON.stringify(TOOLS[0].defaultArgs, null, 2) ); const [result, setResult] = useState(null); const [isRunning, setIsRunning] = useState(false); const [sessionId, setSessionId] = useState(null); const mcpUrl = `${window.location.origin}/mcp-server`; const handleSelectTool = (index: number) => { setSelectedTool(index); setArgsText(JSON.stringify(TOOLS[index].defaultArgs, null, 2)); setResult(null); }; const handleCallTool = async () => { const tool = TOOLS[selectedTool]; let args: Record; try { args = JSON.parse(argsText); } catch { addLog("error", "error", "Invalid JSON in arguments"); return; } setIsRunning(true); setResult(null); try { const sid = await ensureSession(mcpUrl, sessionId); if (!sessionId && sid) { setSessionId(sid); addLog("info", "session", { id: sid }); } addLog("out", "call_tool", { name: tool.name, args }); const { data } = await mcpRequest( mcpUrl, "tools/call", { name: tool.name, arguments: args }, sid ); addLog("in", "result", data); setResult(data); toast(tool.name + " called", "success"); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); toast(e instanceof Error ? e.message : String(e), "error"); setSessionId(null); } finally { setIsRunning(false); } }; return ( This playground exposes a real MCP server at{" "} /mcp-server . It registers tools (roll_dice, generate_uuid, word_count, hash_text) and a resource. Any MCP-compatible client — Claude, Cursor, or the MCP Client demo on the next page — can connect and use them. Test the tools below. } >
{/* Controls */}
{/* Server URL */}
MCP Server URL

Add this URL to Claude Desktop, Cursor, or any MCP client

{/* Available Tools */}
Available Tools
{TOOLS.map((tool, i) => ( ))}
{/* Test Tool */}
Test: {TOOLS[selectedTool].name}
setArgsText(e.target.value)} className="w-full h-24 font-mono text-sm mb-3" />
{/* Result */} {result !== null && (
Result
)}
{/* Logs */}
); }