Skip to content

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/.

Terminal window
npm install @anthropic-ai/claude-agent-sdk @ggui-ai/protocol
Terminal window
export ANTHROPIC_API_KEY="sk-ant-..."
claude-agent.ts
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);
Terminal window
npx tsx claude-agent.ts

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. content is 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). The input field 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" with tool_use_id pointing at the matching tool_use.
  • result — terminal event with subtype (success, error_max_turns, error_during_execution, …), usage totals, and total_cost_usd.

A reservation booking typically streams: systemassistant(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.

  • Zero Agent Code. Your file imports query and GGUI_AGENT_SYSTEM_PROMPT and lists tool names — nothing else. The protocol lives behind MCP; Claude reads tool descriptions and drives it directly.
  • GGUI_AGENT_SYSTEM_PROMPT is posture, not instructions. It tells Claude when to reach for a UI (structured input, choices, visual presentation). Per-tool semantics ship from the server in tools/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 in allowedTools.
  • Whitelist explicitly. Omit allowedTools and 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 + mcpServers prefix across turns. No cache_control plumbing needed in your code.