Claude Agent
read as.md This is the canonical Claude pattern for ggui. You wire ggui as an MCP server in Claude Agent SDK’s mcpServers config, hand Claude the ggui posture prompt (GGUI_AGENT_SYSTEM_PROMPT), and Claude decides when to call ggui_handshake / ggui_render / ggui_consume based on the tool descriptions it discovers via tools/list. Zero ggui SDK code in your app — Claude drives the protocol directly.
A runnable version of this example lives at samples/agents/claude-agent-sdk/.
Install
Section titled “Install”npm install @anthropic-ai/claude-agent-sdk @ggui-ai/protocolexport ANTHROPIC_API_KEY="sk-ant-..."import { query } from "@anthropic-ai/claude-agent-sdk";import { GGUI_AGENT_SYSTEM_PROMPT } from "@ggui-ai/protocol";
// 1. Wire ggui as an MCP server. Claude Agent SDK will issue `tools/list`// against this URL on session start and discover every `ggui_*` tool.// `Bearer dev` authenticates because `ggui serve --dev-allow-all` accepts// any bearer — local dev only.const mcpServers = { ggui: { type: "http" as const, url: "http://127.0.0.1:6781/mcp", headers: { Authorization: "Bearer dev" }, },};
// 2. Whitelist the ggui tools Claude is allowed to call. The SDK auto-namespaces// every tool as `mcp__<serverName>__<toolName>`, so the prefix is `mcp__ggui__`.const GGUI_ALLOWED_TOOLS = [ "mcp__ggui__ggui_handshake", "mcp__ggui__ggui_render", "mcp__ggui__ggui_update", "mcp__ggui__ggui_emit", "mcp__ggui__ggui_consume", "mcp__ggui__ggui_get_session",];
async function chat(userPrompt: string) { console.log(`\nUser: ${userPrompt}\n`);
// 3. `query()` returns an AsyncGenerator of SDKMessage events. The SDK runs // the full tool-use loop internally — you just consume the stream. for await (const message of query({ prompt: userPrompt, options: { model: "claude-sonnet-4-6", mcpServers, allowedTools: GGUI_ALLOWED_TOOLS, // The posture-only canonical ggui prompt. Tells Claude *when* to reach // for a UI; tool descriptions tell it *how*. systemPrompt: GGUI_AGENT_SYSTEM_PROMPT, }, })) { if (message.type === "assistant") { for (const block of message.message.content) { if (block.type === "text") { process.stdout.write(block.text); } else if (block.type === "tool_use") { console.log(`\n[tool_use] ${block.name}`); } } } else if (message.type === "user") { // Tool results flow back as user-role messages from the SDK's perspective. for (const block of message.message.content) { if (block.type === "tool_result") { console.log(`[tool_result] ${block.tool_use_id}`); } } } else if (message.type === "result") { console.log(`\n\n[done] subtype=${message.subtype}`); } }}
chat("I want to book a restaurant reservation for this weekend").catch(console.error);npx tsx claude-agent.tsWhat you’ll see
Section titled “What you’ll see”query() yields a stream of SDKMessage events. The shapes you’ll observe in a typical ggui turn:
system(init) — opening event listing the model, allowed tools, and MCP servers Claude discovered.assistant— Claude’s response chunks.contentis an array of blocks:text— natural-language reply (streamed across multiple events).tool_use— Claude invoking a ggui tool (e.g.mcp__ggui__ggui_handshake). Theinputfield contains the tool arguments Claude generated.thinking— extended-thinking blocks when reasoning is on.
user— tool results flowing back into the loop.content[].type === "tool_result"withtool_use_idpointing at the matchingtool_use.result— terminal event withsubtype(success,error_max_turns,error_during_execution, …),usagetotals, andtotal_cost_usd.
A reservation booking typically streams: system → assistant(text + tool_use:ggui_handshake) → user(tool_result) → assistant(tool_use:ggui_render) → user(tool_result with sessionId + resourceUri — the host mounts the UI from the MCP-Apps resource) → [user submits the form] → assistant(tool_use:ggui_consume) → user(tool_result with submission data) → assistant(confirmation text) → result.
Patterns worth stealing
Section titled “Patterns worth stealing”- Zero Agent Code. Your file imports
queryandGGUI_AGENT_SYSTEM_PROMPTand lists tool names — nothing else. The protocol lives behind MCP; Claude reads tool descriptions and drives it directly. GGUI_AGENT_SYSTEM_PROMPTis posture, not instructions. It tells Claude when to reach for a UI (structured input, choices, visual presentation). Per-tool semantics ship from the server intools/list— you don’t restate them.- Tool namespace is structural.
mcp__<server>__<tool>is non-negotiable; it’s how the SDK routes calls. Match the prefix exactly inallowedTools. - Whitelist explicitly. Omit
allowedToolsand Claude can call every tool the server exposes (including ops/protocol tools). The list above is the minimal agent-facing surface. - Prompt caching is automatic when the SDK detects a stable
systemPrompt+mcpServersprefix across turns. Nocache_controlplumbing needed in your code.
Related
Section titled “Related”samples/agents/claude-agent-sdk/— runnable reference for this file- OpenAI agent example — same shape, function-calling transport
GGUI_AGENT_SYSTEM_PROMPTreference — what’s in the posture prompt and why- How it works — what happens between
ggui_renderand the rendered UI