Skip to content

Claude Agent

Build an agent that uses Claude to decide when to show a UI, pushes a ggui interface to the user, collects the response, and continues the conversation.

Terminal window
npm install @ggui-ai/mcp-client @anthropic-ai/sdk

Set environment variables:

Terminal window
export ANTHROPIC_API_KEY="sk-ant-..."
export GGUI_API_KEY="ggui_sk_..."
export GGUI_APP_ID="app_..."
claude-agent.ts
import Anthropic from "@anthropic-ai/sdk";
import { GguiClient } from "@ggui-ai/mcp-client";
const anthropic = new Anthropic();
const ggui = new GguiClient({
apiKey: process.env.GGUI_API_KEY!,
appId: process.env.GGUI_APP_ID!,
});
// Define ggui as a tool that Claude can use
const tools: Anthropic.Tool[] = [
{
name: "show_ui",
description:
"Show an interactive UI to the user. Use this when you need to collect structured data, " +
"show a form, display information visually, or present choices. " +
"Describe the UI you want in natural language.",
input_schema: {
type: "object" as const,
properties: {
prompt: {
type: "string",
description: "Natural language description of the UI to show",
},
context: {
type: "object",
description: "Data to make available to the generated UI",
},
},
required: ["prompt"],
},
},
];
async function handleToolCall(
toolName: string,
toolInput: { prompt: string; context?: Record<string, unknown> }
): Promise<string> {
if (toolName !== "show_ui") {
return JSON.stringify({ error: `Unknown tool: ${toolName}` });
}
const { sessionId, url } = await ggui.push({
story: {
intent: toolInput.prompt,
prompt: toolInput.prompt,
context: toolInput.context,
},
});
console.log(`\n--- UI Ready ---`);
console.log(`Open this URL: ${url}`);
console.log(`Waiting for user response...\n`);
const events = await ggui.waitForCompletion(sessionId, {
pollInterval: 2000,
maxWait: 300_000,
});
await ggui.close(sessionId);
const submitEvent = events.find((e) => e.type === "data:submit");
if (submitEvent) {
return JSON.stringify({ status: "submitted", data: submitEvent.payload });
}
return JSON.stringify({
status: "no_submission",
events: events.map((e) => ({ type: e.type, payload: e.payload })),
});
}
async function chat(userMessage: string) {
console.log(`\nUser: ${userMessage}`);
const messages: Anthropic.MessageParam[] = [{ role: "user", content: userMessage }];
let response = await anthropic.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
system:
"You are a helpful assistant. When you need to collect information from the user, " +
"show them an interactive form UI using the show_ui tool. " +
"Always use the UI for structured data collection rather than asking via text.",
tools,
messages,
});
while (response.stop_reason === "tool_use") {
const toolUseBlock = response.content.find(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);
if (!toolUseBlock) break;
const toolResult = await handleToolCall(
toolUseBlock.name,
toolUseBlock.input as { prompt: string; context?: Record<string, unknown> }
);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUseBlock.id,
content: toolResult,
},
],
});
response = await anthropic.messages.create({
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
tools,
messages,
});
}
const textBlock = response.content.find(
(block): block is Anthropic.TextBlock => block.type === "text"
);
console.log(`\nAssistant: ${textBlock?.text ?? "(no response)"}`);
}
// Run the agent
chat("I want to book a restaurant reservation for this weekend").catch(console.error);
Terminal window
npx tsx claude-agent.ts
  1. You ask Claude to book a restaurant reservation
  2. Claude decides to use show_ui to collect the details
  3. ggui generates a reservation form (date, time, party size, restaurant preferences)
  4. You open the URL, fill out the form, and submit
  5. Claude receives the form data and confirms the reservation details
  • Tool definition: Claude learns about ggui through the show_ui tool description
  • Agentic loop: The while (stop_reason === 'tool_use') loop handles multi-turn tool calls
  • Context passing: Use context to pass data into the generated UI (e.g., available restaurants, user preferences)