Build an agent on hosted ggui
read as.mdNew accounts get $5 of free credit on first sign-in while the launch grant lasts — it is capped per environment, so a balance that stays at $0.00 means the cap is exhausted rather than the grant still settling. Credits, billing & BYOK covers both fallbacks. Wiring an MCP host (Claude Desktop, claude.ai, Goose) instead of writing code? Those clients run through OAuth and the ggui console mints a ggui_user_* key for you — skip the key-minting step below. This page covers the programmatic SDK path.
In 5 minutes, your agent will negotiate a UI contract and put a generated form on the wire as an MCP-Apps resource — no React code, no front-end build, no infrastructure.
One thing to know before you start, because it shapes what you see at the end: a render is a resource, not a URL, and something has to mount it before a human can fill it in. That something is an MCP-Apps host. Run this agent programmatically with no host attached and it will block at ggui_consume, waiting on a render nobody can see — correct behavior, but not a finished demo. The fastest way to watch the whole loop close is to run the same prompt inside Claude Desktop connected to ggui (three-minute setup, zero code); this page is the programmatic path that gets your own agent onto the same wire.
Your Agent → mcp.ggui.ai → MCP-Apps render (ui://ggui/render/<id>) → User submits → Agent gets typed dataPrerequisites
Section titled “Prerequisites”- Node.js 20+
- A free ggui console account. Sign in at the ggui console with Google, GitHub, or email. (The ggui console is the end-user dashboard for hosted ggui — see the glossary if the
ggui/guueysplit confuses you.)
Step 1: Pick an app and mint an SDK API key
Section titled “Step 1: Pick an app and mint an SDK API key”- Sign in at the ggui console. You land on
/apps— new accounts come pre-provisioned with one default app; create another with New App if you want to scope this quickstart to its own surface (e.g.feedback-demo). - Open the app and go to Keys (
/apps/[appId]/keys). Name the key (e.g.feedback-demo-sdk) and click Generate per-app key. - The key reveals exactly once — copy it (
ggui_user_…) immediately. Lose it and you generate a new one; there’s no recovery. You also need the app’s ID: it is an opaque 8-character string with no prefix, and the app’s overview page shows the full endpoint,mcp.ggui.ai/apps/<appId>, ready to copy.
Step 2: Install the dependencies
Section titled “Step 2: Install the dependencies”npm install @anthropic-ai/claude-agent-sdk @ggui-ai/protocol# or: pnpm add @anthropic-ai/claude-agent-sdk @ggui-ai/protocol# or: yarn add @anthropic-ai/claude-agent-sdk @ggui-ai/protocol@anthropic-ai/claude-agent-sdkruns Claude as a tool-using agent and speaks MCP natively — no wrapper needed.@ggui-ai/protocolexportsGGUI_AGENT_SYSTEM_PROMPT, the canonical system prompt that teaches Claude the handshake → render → consume loop.
Step 3: Write your agent
Section titled “Step 3: Write your agent”Create agent.ts. The Claude Agent SDK connects to mcp.ggui.ai directly via its mcpServers config — your code just streams Claude’s messages and lets the model drive the ggui tools.
import { query } from "@anthropic-ai/claude-agent-sdk";import { GGUI_AGENT_SYSTEM_PROMPT } from "@ggui-ai/protocol";
// 1. Point Claude's MCP client at your app's hosted endpoint. Bearer-auth// with the `ggui_user_*` key you minted in Step 1. Note: the per-app// cloud endpoint is the bare `/apps/<appId>` path — NO `/mcp` suffix// (that suffix is local-`ggui serve`-only).const mcpServers = { ggui: { type: "http" as const, url: "https://mcp.ggui.ai/apps/<your appId>", headers: { Authorization: `Bearer ${process.env.GGUI_MCP_BEARER!}` }, },};
// 2. Allow Claude to call every ggui tool. The `mcp__<server>__<tool>`// naming is the SDK's convention — `<server>` = `ggui` (the key above).const allowedTools = [ "mcp__ggui__ggui_handshake", "mcp__ggui__ggui_render", "mcp__ggui__ggui_update", "mcp__ggui__ggui_amend", "mcp__ggui__ggui_emit", "mcp__ggui__ggui_consume", "mcp__ggui__ggui_get_session",];
async function main() { const prompt = "Collect product feedback from the user. Show a feedback form with a " + "1-5 star rating and a comments text area, wait for them to submit, " + "then summarize what they said.";
// 3. Stream the conversation. Claude reads GGUI_AGENT_SYSTEM_PROMPT, // decides when to call ggui_handshake / ggui_render, // polls ggui_consume until the user submits, and reports back — // all without any wrapper SDK on your side. for await (const msg of query({ prompt, options: { mcpServers, allowedTools, systemPrompt: GGUI_AGENT_SYSTEM_PROMPT, }, })) { if (msg.type === "assistant") { for (const block of msg.message.content) { if (block.type === "text") console.log(block.text); } } else if (msg.type === "result") { console.log("Done:", msg.subtype); } }}
main().catch(console.error);Step 4: Run it
Section titled “Step 4: Run it”export ANTHROPIC_API_KEY="sk-ant-..." # for the Claude Agent SDKexport GGUI_MCP_BEARER="ggui_user_..." # for mcp.ggui.ainpx tsx agent.tsYou’ll see Claude narrate the handshake, call ggui_render (which returns { sessionId, resourceUri } — the render surfaces as an MCP-Apps resource at ui://ggui/render/<id>, not a clickable link), and then block on ggui_consume, polling for the user’s submission.
That block is the expected end of the programmatic path: the render exists, and nothing is mounting it. Two ways to close the loop, easiest first:
- Run the prompt in an MCP-Apps host — no code at all. Claude Desktop or claude.ai connected to ggui mounts the resource inline, you submit, and the agent’s
ggui_consumereturns. Setup is three minutes: Use ggui in Claude Desktop. - Embed it in your own app — Step 5 below mounts the render with the React host helpers. More wiring, and the right answer once you are putting this in front of your own users.
Step 5 (optional): Embed in React
Section titled “Step 5 (optional): Embed in React”Want the UI inside your own app? Install the React host helpers and the MCP-Apps host:
npm install @ggui-ai/mcp-apps-react @mcp-ui/clientA ggui render is an MCP-Apps resource. You drive the conversation with the useMcpAppsChat hook and mount each render’s sandboxed iframe with <AppRenderer> (imported directly from @mcp-ui/client — ggui doesn’t re-export it):
import { AppRenderer } from "@mcp-ui/client";import { useMcpAppsChat } from "@ggui-ai/mcp-apps-react/chat-helpers";
function Chat({ agentUrl }: { agentUrl: string }) { const { entries, sessions, send, handleAppMessage } = useMcpAppsChat({ chatEndpoint: `${agentUrl}/agent`, });
// - render `entries` as chat bubbles; call `send(prompt)` to talk to the agent // - mount each `sessions` entry with <AppRenderer> — it needs a sandbox-proxy // origin + onReadResource / onCallTool relay + onMessage={handleAppMessage}}useMcpAppsChat talks to your agent backend (the process running the Step-3 query() loop, exposed over HTTP — @ggui-ai/agent-server gives you a brand-neutral POST /agent endpoint for exactly this). <AppRenderer>’s sandbox + resource-read + tool-call relay wiring is non-trivial; the complete runnable reference is the ggui-basic-web sample. Start there.
What just happened
Section titled “What just happened”Under the hood, Claude drove these MCP tool calls against mcp.ggui.ai:
ggui_handshakenegotiated a contract from a natural-language intent + draft, returning ahandshakeId+ a server suggestion (cache / agent / synth).ggui_renderwith{ handshakeId, props }materialized the contract: ggui matched a cached blueprint (or synthesized a fresh React component), minted asessionId, and returned{ sessionId, resourceUri }— the render is an MCP-Apps resource atui://ggui/render/<id>, surfaced on the tool result’s_meta.ui.resourceUri. (There is no clickable URL on the wire.)- A host mounted that resource — your app via
<AppRenderer>(Step 5), or an MCP-Apps host like claude.ai inline — and the user submitted the form. ggui_consumedelivered the user’s submit gesture as aConsumeEventEntry({ intent, actionData, uiContext, ... }).
There is no terminal close ceremony — a render simply stays reopenable (see Rehydration); bound it with a per-render ttlSeconds if you want expiry.
Everything above the wire is GGUI_AGENT_SYSTEM_PROMPT + the Claude Agent SDK’s tool loop — no ggui-specific client code on your side.
Agent mcp.ggui.ai MCP-Apps host │ │ (your app / claude.ai) │── handshake ───────────→ │ │ │← { handshakeId, ─ │ │ │ suggestion } │ │ │── render(handshakeId, ─ │ │ │ props) ────────────→ │ │ │ │── match/synth blueprint │ │← { sessionId, ─ │ │ │ resourceUri } │ │ │ │── ui://ggui/render/<id> ──→│ (host mounts iframe) │ │ │ │── consume(sessionId) ───→ │ │ │ │←── submit gesture ────────│ │← { events } ──────────── │ │ │ │ │ │ (no explicit close — render stays reopenable) │Next steps
Section titled “Next steps”- Claude agent example — canonical reference implementation for the snippet above
- MCP protocol reference — every
ggui_*tool, request/response, and error code - React host helpers — embed ggui renders directly in your own React app with
useMcpAppsChat+<AppRenderer> - Other LLMs — raw
@modelcontextprotocol/sdkrecipe; also OpenAI, Gemini, OpenClaw - Feedback-form cookbook — the recipe above, with variations
- Troubleshooting — common issues and fixes
- Glossary — gadget vs tool vs blueprint, ggui vs guuey, and the rest
- Agentic App Builders — if your goal is to make an existing app agent-drivable rather than building a fresh agent.