Skip to content

Architecture

read as .md

This page is the protocol’s architecture — what every ggui implementation must do, independent of how it’s deployed. For deployment shapes, see Self-Hosted and Reference deploys.

Agent (LLM-driven)
│ MCP
Server ◀───── WebSocket (live) ──────▶ Renderer (iframe / standalone page)
│ │
│ bootstrap (bundle fetch) │
└────────────────────────────────────────────┘
  • Agent — your code with an LLM in the loop. Speaks to the server over MCP.
  • Server — speaks MCP outward, orchestrates generation, routes events. Hosts the artifact registry the renderer pulls from. Runs at your URL via ggui serve (hosted mcp.ggui.ai coming soon).
  • Renderer — an iframe (or standalone page) hosting the generated component. Sends user actions back to the server through the host’s tools/call relay.

ggui’s wire is split across three orthogonal channels. Each has one job.

ChannelDirectionPurpose
BootstrapServer → RendererOne-shot fetch of the compiled component bundle when the iframe first loads. Any gadgets bound to the session load behind a <link rel="modulepreload" integrity> SRI gate.
MCPAgent ↔ ServerControl plane. ggui_handshake, ggui_render, ggui_update, ggui_consume, ggui_emit, ggui_get_session.
LiveRenderer ↔ ServerWebSocket at ws://127.0.0.1:6781/ws (self-hosted default; hosted wss://mcp.ggui.ai/ws coming soon). Server deliveries outbound (StreamEnvelope, props updates, drain acks); contract violations on an inbound action are answered with a typed error frame (CONTRACT_VIOLATION, code -32020) on this channel — nothing lands on the consume buffer.

The rendered view’s user actions reach the server through the host’s MCP-Apps tools/call relay to ggui_runtime_submit_action — the spec-canonical dispatch path; the server appends the gesture to the pipe ggui_consume drains. The live WebSocket’s job is server → renderer delivery (stream emits, props updates, drain acks).

The channels are independent. The renderer can drop and reconnect the live channel without disturbing an agent’s MCP turn; the agent can ggui_render repeatedly without ever touching the live channel.

→ See Protocol overview for the formal three-channel spec.

ggui has two symmetric capability surfaces — one for what the agent can do, one for what the renderer can render. Both are operator-bounded and declared per-app.

Renderer sideAgent side
Unitgadgettool
CatalogclientCapabilities.gadgetsagentCapabilities.tools
RoleWraps a 3rd-party browser library (Leaflet, Mapbox, Chart.js) into an LLM-callable hookGives the agent a function to invoke (e.g. searchContacts, createInvoice)
Authored asggui.gadget.json manifestMCP tool code
Bound atiframe boot (SRI-verified)session start

Plus a third primitive — blueprints — which aren’t capabilities but cached recipes (pre-composed UIs). A blueprint hit short-circuits fresh generation.

→ See Gadgets SDK, MCP Protocol, Marketplace.

Rendering is a two-call flow — ggui_handshake negotiates, ggui_render commits:

0. NEGOTIATE ggui_handshake({intent, blueprintDraft}) searches the blueprint cache by
contract shape (exact contractHash hit short-circuits; semantic similarity
otherwise) and returns a suggestion
1. COMMIT ggui_render({handshakeId, props}) consumes the handshake; a cache hit
reuses the stored blueprint (~100ms)
2. GENERATE Otherwise, run the server's UI generator (@ggui-ai/ui-gen):
workflow → impl → check → derive
3. COMPILE TSX → JS via esbuild (~20-50ms)
4. DELIVER Renderer fetches the compiled bundle on the bootstrap channel

Published artifacts (gadgets, blueprints) on the marketplace registry pick up an author signature at publish time — Ed25519 (publisher keypair) for private artifacts, sigstore keyless (OIDC) for public ones — see Marketplace. Fresh generations are session-scoped and skip that step.

The generator (step 2) is a bounded harness: pick a workflow (single_pass, staged, staged-concurrent), run the LLM-driven impl phase, run a check leg (typecheck, render-smoke, per-axis assertions), and on failure derive a revised harness and retry up to maxIterations. Output: a TypeScript-typed contract plus a compiled component module.

→ See UI Generator for the harness internals.

Gadgets and blueprints resolve through a four-tier waterfall on every push:

1. App-local ggui.json#app.gadgets, ggui.json#blueprints.include
(plus installed artifacts under .ggui/installed-blueprints/)
2. Per-org private operator's private registry (artifacts with visibility:"private")
3. Public registry.ggui.ai (marketplace)
4. Fall back fresh generation

A blueprint is keyed by a stable blueprintId; its contractHash — the RFC 8785 canonical-JSON hash of its DataContract, scoped per (appId, contractHash) — groups variants and is the cache lookup key. An exact contractHash hit short-circuits to score 1.0; otherwise a multi-axis semantic search (contract embedding, structural fingerprint, variance tags, intent) ranks candidates. Install the same (scope, name, version) on two different servers and the matcher returns the byte-identical UI on both.

→ See Marketplace, Self-Hosted Registry.

The same protocol runs in two shapes:

Self-hostedHosted (coming soon)
MCP endpointyour URL via ggui servemcp.ggui.ai
WS endpointws://127.0.0.1:6781/ws (or your URL)wss://mcp.ggui.ai/ws
Authpluggable AuthAdapter + optional OAuth 2.1 (ggui serve --oauth)OAuth
Registryyour registry (or none)registry.ggui.ai
Generationin-processmanaged
Pick if”I need data residency, custom auth, or air-gapped""I just want to ship an agent UI”

Both speak the same wire. Switching between them is configuration, not code.

→ See OSS Quick Start to run it yourself. A managed hosted path at mcp.ggui.ai is coming soon.