---
title: Build an agent on hosted ggui
description: Ship your first agent UI against hosted ggui (mcp.ggui.ai) in under 5 minutes — no infrastructure required.
---

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](/hosted/billing/) covers both fallbacks. Wiring an MCP host (Claude Desktop, claude.ai, Goose) instead of writing code? Those clients run through [OAuth](/clients/claude-desktop/) and the ggui console mints a `ggui_user_*` key for you — skip the key-minting step below. This page covers the programmatic SDK path.

:::tip[Zero-wrapper architecture]
ggui has **no client SDK wrapper**. Your agent talks to `mcp.ggui.ai` directly over MCP — either via the Claude Agent SDK's native `mcpServers` config (Pattern A below) or via `@modelcontextprotocol/sdk` for any other LLM. See [the Claude agent example](/examples/claude-agent/) for the canonical reference implementation.
:::

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](/quickstart/claude-desktop/), 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
```

## Prerequisites

- **Node.js** 20+
- **A free ggui console account.** Sign in at [the ggui console](https://console.ggui.ai) with Google, GitHub, or email. (The ggui console is the end-user dashboard for hosted ggui — see the [glossary](/glossary/) if the `ggui` / `guuey` split confuses you.)

## Step 1: Pick an app and mint an SDK API key

1. Sign in at [the ggui console](https://console.ggui.ai). 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.

:::caution
Never commit `ggui_user_*` keys. Use `.env` files and a secret manager in production.
:::

:::tip[Prefer the CLI?]
`ggui keys create --name feedback-demo-sdk` does the same thing from your terminal once you've run [`ggui login`](/cli/login/). The minted key still lands as one row in the console's keys table.
:::

## Step 2: Install the dependencies

```bash
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.

:::tip[Other LLMs]
Not using Claude? Use the raw [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) instead — see [`/examples/generic-mcp/`](/examples/generic-mcp/) for the low-level pattern, and [`/examples/openai-agent/`](/examples/openai-agent/) / [`/examples/gemini-agent/`](/examples/gemini-agent/) for vendor-specific wiring.
:::

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

```typescript
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

```bash
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](/quickstart/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.

:::note[How this differs from a wrapper SDK]
There is no `GguiClient` import, no `await ggui.handshake(…)`, no `waitForCompletion` helper. The Claude Agent SDK calls the ggui MCP tools directly; `GGUI_AGENT_SYSTEM_PROMPT` is what teaches the model the protocol's handshake → render → consume sequence. Same wire protocol, zero wrapper code.
:::

## Step 5 (optional): Embed in React

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

```bash
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):

```tsx
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`](https://github.com/ggui-ai/ggui/tree/main/samples/apps/ggui-basic-web) sample. **Start there.**

:::note[The render hosts its own live channel]
You don't open a WebSocket in your app to receive render updates. The sandboxed iframe owns its own live channel; your host code just mounts `<AppRenderer>`. Real-time updates (see [Real-Time Dashboard](/cookbook/real-time-dashboard/)) flow inside the iframe.
:::

## What just happened

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](/concepts/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

- **[Claude agent example](/examples/claude-agent/)** — canonical reference implementation for the snippet above
- **[MCP protocol reference](/api/mcp-protocol/)** — every `ggui_*` tool, request/response, and error code
- **[React host helpers](/sdk/react/)** — embed ggui renders directly in your own React app with `useMcpAppsChat` + `<AppRenderer>`
- **[Other LLMs](/examples/generic-mcp/)** — raw `@modelcontextprotocol/sdk` recipe; also [OpenAI](/examples/openai-agent/), [Gemini](/examples/gemini-agent/), [OpenClaw](/examples/openclaw-agent/)
- **[Feedback-form cookbook](/cookbook/feedback-form/)** — the recipe above, with variations
- **[Troubleshooting](/troubleshooting/)** — common issues and fixes
- **[Glossary](/glossary/)** — gadget vs tool vs blueprint, ggui vs guuey, and the rest
- **[Agentic App Builders](/agentic-app-builders/)** — if your goal is to make an existing app agent-drivable rather than building a fresh agent.