Skip to content
Sneak peek — you found hosted ggui early · official launch soon

Build an agent on hosted ggui

read as .md

New 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 data
  • 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 / guuey split 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”
  1. 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).
  2. Open the app and go to Keys (/apps/[appId]/keys). Name the key (e.g. feedback-demo-sdk) and click Generate per-app key.
  3. 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.
Terminal window
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-sdk runs Claude as a tool-using agent and speaks MCP natively — no wrapper needed.
  • @ggui-ai/protocol exports GGUI_AGENT_SYSTEM_PROMPT, the canonical system prompt that teaches Claude the handshake → render → consume loop.

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);
Terminal window
export ANTHROPIC_API_KEY="sk-ant-..." # for the Claude Agent SDK
export GGUI_MCP_BEARER="ggui_user_..." # for mcp.ggui.ai
npx tsx agent.ts

You’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_consume returns. 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.

Want the UI inside your own app? Install the React host helpers and the MCP-Apps host:

Terminal window
npm install @ggui-ai/mcp-apps-react @mcp-ui/client

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

Under the hood, Claude drove these MCP tool calls against mcp.ggui.ai:

  1. ggui_handshake negotiated a contract from a natural-language intent + draft, returning a handshakeId + a server suggestion (cache / agent / synth).
  2. ggui_render with { handshakeId, props } materialized the contract: ggui matched a cached blueprint (or synthesized a fresh React component), minted a sessionId, and returned { sessionId, resourceUri } — the render is an MCP-Apps resource at ui://ggui/render/<id>, surfaced on the tool result’s _meta.ui.resourceUri. (There is no clickable URL on the wire.)
  3. 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.
  4. ggui_consume delivered the user’s submit gesture as a ConsumeEventEntry ({ 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) │