The dev loop
read as.mdYour agent talks to ggui over MCP and nothing else. That single fact is what makes the
local-to-hosted move small: the tools, the contracts, and the system prompt are identical
on both sides, so promoting an agent from your laptop to mcp.ggui.ai is a change of URL
and bearer, not a change of code.
This page is the loop that follows from that: iterate locally where restarts are instant and generation runs on your own provider key, then flip the endpoint when you need a real user in front of the render.
The two local servers
Section titled “The two local servers”The CLI ships two local runtimes and they are not interchangeable.
ggui dev |
ggui serve |
|
|---|---|---|
| Default bind | 127.0.0.1:6780 |
127.0.0.1:6781 |
| Serves MCP | No | Yes, at /mcp |
| For | Iterating on blueprints and gadgets in the dev hub | Running the protocol the way production runs it |
ggui dev is a blueprint registry plus a dev hub — it indexes the ggui.ui.json manifests
declared in ggui.json#blueprints.include, previews them at /hub/preview?ui=<id>, and with
--agent <entry> supervises your agent process in the same shell. It deliberately does not
run an MCP server.
ggui serve is the one your agent connects to. It boots @ggui-ai/mcp-server and, by
default, the agent declared in ggui.json#agent.entry. Both can run side by side; the ports
do not collide.
ggui serve --dev-allow-all # MCP at http://127.0.0.1:6781/mcp, any bearer acceptedggui dev # blueprint hub at http://127.0.0.1:6780/hub--dev-allow-all accepts any bearer (or none) as a builder identity. It is a loopback
convenience and the boot banner says so — never put it on a public URL. Without it, ggui serve
requires a pair-minted bearer.
The endpoint shape differs, and it is the first thing that breaks
Section titled “The endpoint shape differs, and it is the first thing that breaks”The two deployments do not spell their MCP endpoint the same way.
- Local
ggui serveserves MCP at the/mcpsub-path:http://127.0.0.1:6781/mcp. - Hosted ggui serves each app at its bare per-app path:
https://mcp.ggui.ai/apps/<appId>.
The /mcp suffix is a local-ggui serve convention only. Append it to the hosted URL and the
per-app pod returns 404, which surfaces as an agent that discovers zero ggui tools rather than
as an obvious connection error. Carry the suffix over from your local config and this is the
symptom you will see.
Swap the deployment with environment variables
Section titled “Swap the deployment with environment variables”The reference samples read the endpoint and the bearer from the environment, so the two
deployments are two .env.local files rather than two code paths.
# .env.local — hosted gguiGGUI_MCP_URL=https://mcp.ggui.ai/apps/aB3kP9xYGGUI_MCP_BEARER=ggui_user_xxxxxxxxANTHROPIC_API_KEY=sk-ant-...Both values come out of the ggui console or the CLI: ggui login, then
ggui keys create --name my-agent mints the ggui_user_* connector key. The app ID is an opaque
8-character string with no prefix — the app’s page shows the whole endpoint, mcp.ggui.ai/apps/<appId>,
ready to copy.
# .env.local — local ggui serveGGUI_MCP_URL=http://127.0.0.1:6781/mcpGGUI_MCP_BEARER=devANTHROPIC_API_KEY=sk-ant-...dev is accepted because ggui serve --dev-allow-all accepts any bearer. Under the default
strict-auth posture, use a pair-minted bearer instead — see Pair a client app.
Nothing else moves. The agent wiring reads both values and hands them to the MCP client:
const mcpServers = { ggui: { url: process.env.GGUI_MCP_URL ?? "http://localhost:6781/mcp" },};That is the shape used by every sample in
samples/agents/ — Claude Agent SDK,
OpenAI Agents SDK, and Google ADK all read the same two variables.
@ggui-ai/agent-server resolves the bearer as opts.bearer ?? process.env.GGUI_MCP_BEARER ?? "dev"
and sends it as Authorization: Bearer <token> on every MCP request. Wiring the SDK by hand
instead? The Claude agent example shows the explicit headers form.
Let the CLI write the hosted half
Section titled “Let the CLI write the hosted half”ggui deploy does the whole hosted-side provisioning in one idempotent pass: sign in, create
the cloud app and write its ID into ggui.json, mint a connector key into .env.local as
GGUI_MCP_BEARER, upload your local blueprint pool, push declared gadgets and publicEnv, and
finally write GGUI_MCP_URL. Re-run it after a partial failure and it resumes from where it
stopped.
ggui deployIt writes the endpoint in the correct bare /apps/<appId> shape, which is the practical reason
to prefer it over hand-editing the URL. An existing GGUI_MCP_URL is never clobbered — the first
deploy writes it, later ones leave your override alone. Add --push-keys to also push your
provider key to the app so keySource=own renders run on your own model account.
What actually changes when you cross over
Section titled “What actually changes when you cross over”Who pays for generation. Local ggui serve calls your provider directly with the key in
your environment — no ggui credits involved. Hosted renders draw down your credit balance
unless the app runs on your own provider key, in which case ggui skips the charge entirely.
See Credits, billing & BYOK.
Auth posture. --dev-allow-all is a loopback-only shortcut. Hosted requires a real
ggui_user_* connector key per app, revocable from the CLI.
The blueprint pool. Blueprints you matured locally are worth carrying over — a hosted app
that already has them matches from cache instead of paying to synthesize the same UI again.
ggui push compiles and uploads the local pool to a cloud app (ggui deploy runs it as one of
its steps); ggui export-pool writes the pool to a directory that another ggui serve can load
with --seed-pool <dir>.
Rate limits. Hosted enforces per-key limits that a loopback server does not — see Rate limits.
What does not change: the ggui_* tool surface, the handshake-render-consume sequence,
GGUI_AGENT_SYSTEM_PROMPT, your contracts, and your component code. That is the portability
claim, and it is testable — see Portability.
A loop that works
Section titled “A loop that works”ggui serve --dev-allow-allin one shell, your agent pointed athttp://127.0.0.1:6781/mcp.ggui devin another when you are shaping blueprints; editggui.ui.json, reload the hub, the registry re-indexes on every load.ggui dev --no-servein CI — it loads and validates every manifest and exits non-zero on a malformed one, without binding a socket.- When you need a real user in front of the render,
ggui deploy, then restart the agent with the hosted.env.local. - Need a public URL without leaving the local runtime? Run a tunnel beside it
(
cloudflared tunnel --url http://localhost:6781) and point the client athttps://<tunnel>/mcp.
See also
Section titled “See also”ggui dev— every flag, the dev-hub route table, agent supervisionggui serve— auth postures, persistence bundle, reference deploys- Build an agent on hosted ggui — the hosted path end to end
- Self-hosted quickstart — the self-hosted bootstrap
- Connector keys — what a
ggui_user_*key authorizes