Skip to content

Hosted Quickstart

read as .md

In 5 minutes, your agent will negotiate a UI contract, push a generated form to a user, and receive their submission as structured data — no React code, no front-end build, no infrastructure.

Your Agent → mcp.ggui.ai → MCP-Apps render (ui://ggui/render/<id>) → User submits → Agent gets typed data
  • Node.js 20+
  • A free console.ggui.ai account. Sign in at console.ggui.ai with email + password. (console.ggui.ai 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 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). Click Mint key, label it (e.g. feedback-demo-sdk), and submit.
  3. The key reveals exactly once — copy both the key (ggui_user_…) and the App ID (app_…) immediately. Lose the key and you mint a new one; there’s no recovery.
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_API_KEY!}` },
},
};
// 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_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_API_KEY="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 point to notice: run programmatically like this, there is no UI surface for a human to submit through yet — the agent is waiting on a render nobody can see. To actually mount the render and let a user interact, you need an MCP-Apps host. Two ways to get one:

  • Embed it in your own app — Step 5 below mounts the render with the React SDK.
  • Run the agent inside an MCP-Apps host — claude.ai or Claude Desktop renders ggui resources inline (see Clients).

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

Terminal window
npm install @ggui-ai/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/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, ... }).

Renders decay implicitly via TTL — there is no terminal close ceremony.

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 } ──────────── │ │
│ │ │
│ (render decays via TTL — no explicit close) │