---
title: MCP Protocol Reference
description: Wire-level reference for the ggui MCP HTTP API — tools, inputs, return shapes, and a worked curl flow against self-hosted `ggui serve`.
---

The ggui MCP API is [Model Context Protocol](https://modelcontextprotocol.io/) over HTTP with JSON-RPC 2.0. This page is the wire reference: tool names, input shapes, return shapes, error codes, and a complete curl walkthrough.

:::note[One wire — self-hosted today, hosted coming soon]
The agent surface documented below — handshake → render → consume + the rest of the render loop — is served by **self-hosted `ggui serve`** today; snippets target `http://127.0.0.1:6781/mcp`. **Hosted ggui** (`mcp.ggui.ai`) is coming soon and speaks the same wire byte-for-byte — only the URL and the `Authorization` header will change. The hosted deployment will also layer on extra platform-management tools (app + BYOK + billing surfaces); those aren't part of the agent loop.
:::

Three-noun vocab in play on this page: **blueprint** (a cached recipe routed by `BlueprintSearch` over the draft's contract + variance axes), **tool** (an agent-side MCP method the LLM invokes), **gadget** (a renderer-side capability the generated component imports). If those terms are new, skim the [glossary](/glossary/) first.

## Endpoint

**Self-hosted (`ggui serve`):**

```
POST http://127.0.0.1:6781/mcp
```

:::note[Hosted ggui (coming soon)]
The hosted deployment will expose the same surface in two forms: universal `POST https://mcp.ggui.ai/` (the cloud overrides the `/mcp` path to the bare root) and per-app `POST https://mcp.ggui.ai/apps/<appId>` (no `/mcp` suffix — that suffix is local `ggui serve` only).
:::

:::note[This page covers `/mcp` only]
This reference documents the `/mcp` route (audience: agent + runtime). Four `ggui_runtime_*` tools (`submit_action`, `sync_context`, `refresh_ws_token`, `declare_tool_catalog`) also route on `/mcp` but are view/backend-facing and hidden from the model via `_meta.ui.visibility: ["app"]` — agents never call them. Other audiences and standalone services have their own routes and their own references:

- `/protocol` — protocol / conformance discovery. See [Conformance](/protocol/conformance/).
- `/ops` — operator agent tools. See [Ops MCP route](/api/ops-mcp/).
- `/docs` — anonymous docs search. See [Docs MCP route](/api/mcp-docs/).
- `/playground/*` — hosted demo services (coming soon). See [Playground · Todos](/clients/playground-todos/) and [Playground · MDH](/clients/playground-mdh/).
  :::

## Authentication

Local dev: start the server with `ggui serve --dev-allow-all` and any bearer (conventionally `dev`) authenticates as the `builder` identity:

```
Authorization: Bearer dev
Content-Type: application/json
```

Default `ggui serve` is **strict** — only pairing-minted bearers authenticate `/mcp`. Pair a key via the pair code the server prints at boot, or mint one locally with `ggui keys create --keys-file <path>` (the same file `ggui serve --keys-file` reads). The bare `createGguiServer` factory defaults to dev-allow-all until you pass `auth` — swap in a real `AuthAdapter` before exposing the port beyond `127.0.0.1`.

Hosted ggui (coming soon) will use OAuth 2.0 with Dynamic Client Registration — Claude Desktop and other MCP-Apps hosts run the ceremony for you; raw-HTTP callers present the issued bearer token on every call. See [OAuth on mcp.ggui.ai](/api/oauth/) for the ceremony.

## Render Lifecycle

```
1. initialize           → MCP handshake (one-shot per connection)
2. ggui_list_gadgets    → Optional: fetch the gadget catalog
3. ggui_handshake       → Negotiate the wire (handshakeId + suggestion)
4. ggui_render          → Materialize the UI (mints sessionId)
5. ggui_consume         → Long-poll for user events (keyed by sessionId)
6. ggui_update          → Mutate props in place (never re-render)
7. ggui_emit            → Optional: push frames on a streamSpec channel
```

Renders decay implicitly via TTL — there is no explicit close ceremony. `ggui_update` and `ggui_consume` are keyed by `sessionId` (globally unique); the server tenancy-checks via the bearer token.

## Rendering Pipeline

The rendering decision is made during `ggui_handshake`. The negotiator runs `BlueprintSearch` plus contract validation in parallel and returns a routed `suggestion` whose `origin` tag tells the agent which branch fired:

1. **`origin: 'cache'`** — exact or semantic match against a registered blueprint. Free, deterministic reuse on the paired render.
2. **`origin: 'agent'`** — no cache hit, but the agent's draft passed validation. Gen runs against the agent's contract verbatim.
3. **`origin: 'synth'`** — no cache hit AND validation surfaced amendments. The server amends the draft and gen runs against the amended contract.

The handshake response carries `handshakeId`, `action`, and `suggestion` (always with a provisional `blueprintMeta`). The agent then calls `ggui_render` with the `handshakeId` plus `props`: omit `override` to reuse the suggestion's provisional `blueprintId` as-is, or pass `override: {contract?, variance?}` to mint a fresh `blueprintId` against a re-aimed draft.

---

## Agent system prompt

The canonical posture-only system prompt for any agent calling these tools is exported as a string constant:

```ts
import { GGUI_AGENT_SYSTEM_PROMPT } from "@ggui-ai/protocol";
```

Use it as-is. It teaches the wire flow (handshake → render → consume), the three rendering origins, and when to call which tool — without baking in any product-specific persona. Per-tool `description` strings on each `ggui_*` MCP tool reinforce the same flow at the tool layer, so the agent has two consistent signals during planning.

Roll your own system prompt only when you have a domain-specific persona to layer on top. In that case, **concatenate**, don't replace: keep `GGUI_AGENT_SYSTEM_PROMPT` first, then append your additions. Replacing it removes the wire-flow teaching, and the agent will misuse the toolset.

The prompt source lives at `packages/protocol/src/recommended-prompts.ts` and ships with `@ggui-ai/protocol` for every consumer language the protocol package targets.

---

## Tools

| Tool                                                              | Purpose                                                                       |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| [`ggui_handshake`](#ggui_handshake)                               | Negotiate the wire surface before rendering — returns `handshakeId` + a routed `suggestion`. |
| [`ggui_render`](#ggui_render)                                     | Materialize the UI; mints `sessionId`.                                         |
| [`ggui_consume`](#ggui_consume)                                   | Long-poll buffered user events on one GguiSession.                             |
| [`ggui_update`](#ggui_update)                                     | Mutate props on a delivered GguiSession in place.                              |
| [`ggui_emit`](#ggui_emit)                                         | Push a delivery onto a declared `streamSpec` channel.                          |
| [`ggui_get_session`](#ggui_get_session)                           | Read GguiSession state + activity timestamps.                                  |
| [`ggui_list_sessions`](#ggui_list_sessions)                       | Enumerate GguiSessions by host conversation (resume flows).                    |
| [`ggui_list_gadgets`](#ggui_list_gadgets)                         | Fetch the renderer-side gadget catalog before authoring a contract.            |
| [`ggui_list_themes`](#ggui_list_themes)                           | List the theme presets usable via `ggui_render({themeId})`.                    |
| [`ggui_list_featured_blueprints`](#ggui_list_featured_blueprints) | Enumerate builder-curated featured blueprints.                                 |
| [`ggui_search_blueprints`](#ggui_search_blueprints)               | Semantic search across this app's blueprints.                                  |
| [`ggui_render_blueprint`](#ggui_render_blueprint)                 | Resolve a registered blueprint id to its compiled bundle.                      |
| [`ggui_discover`](#ggui_discover)                                 | Platform capability discovery (hosted-only, coming soon).                      |
| [`ggui_request_credential`](#ggui_request_credential)             | OAuth consent proxy (hosted-only, coming soon).                                |

### `ggui_handshake`

Negotiate the wire surface for a UI. Call BEFORE `ggui_render`. The agent posts a draft; the server runs blueprint-search + contract-validation in parallel and returns a routed `suggestion` the agent then accepts or overrides on render.

**Top-level fields:**

| Field            | Type      | Required | Description                                                                                                                                                                                                              |
| ---------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `intent`         | `string`  | Yes      | Concise semantic identity — same intent across calls = same component reused. Example: `"Gmail inbox for email triage"`.                                                                                                 |
| `blueprintDraft` | `object`  | Yes      | Single-field draft wrapping the agent's `contract` (required) plus optional `variance` and optional `generator` slug hint. The contract drives blueprint-search embed/structural axes; variance feeds the variance axis. |
| `forceCreate`    | `boolean` | No       | Skip blueprint-search and route straight to validation + agent-mode suggestion against the draft. Use after a prior handshake returned an unwanted cache suggestion.                                                     |

**Returns:** `{ handshakeId, action, suggestion, nextStep? }`

| Field         | Type     | Description                                                                                                                                                                                                                |
| ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `handshakeId` | `string` | Stable id — pass to `ggui_render`. Records are SINGLE-USE and expire after 10 minutes.                                                                                                                                     |
| `action`      | `enum`   | One of `create` / `reuse` / `update` / `replace` / `declined`.                                                                                                                                                             |
| `suggestion`  | `object` | Routed suggestion. Carries `origin: 'cache' \| 'agent' \| 'synth'`, an always-present provisional `blueprintMeta` (incl. `blueprintId`), and conditional `amendments` (synth-only) / `validationFindings` (soft on cache). |
| `nextStep`    | `object` | Wire-shape recovery hint — `{tool: 'ggui_render', example}` worked-literal of the next call.                                                                                                                               |

The agent branches the paired `ggui_render` on `suggestion.origin`: any origin can be accepted (reuse the provisional `blueprintId` verbatim) or overridden (mint fresh against a new draft).

### `ggui_render`

Materialize the UI. Step 3 of the three-step handshake protocol. `handshakeId` and `props` are REQUIRED. Commit relative to the handshake's suggestion by PRESENCE of `override`: omit it to ACCEPT the suggestion as-is, or provide `override: {contract?, variance?}` to re-aim (PATCH semantics).

```json
// ACCEPT the suggestion as-is
{ "handshakeId": "h_…", "props": {…} }

// re-draft the contract (cold-gen)
{ "handshakeId": "h_…", "props": {…}, "override": { "contract": {…} } }

// re-aim the variant axis
{ "handshakeId": "h_…", "props": {…}, "override": { "variance": {…} } }
```

| Field         | Type     | Required | Description                                                                                                                                                                                                              |
| ------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `handshakeId` | `string` | Yes      | From a prior `ggui_handshake` response.                                                                                                                                                                                  |
| `props`       | `object` | Yes      | Runtime prop values for THIS render. Validated against the effective contract's `propsSpec`; failures fail the render with a recoverable `ContractViolationError`. Pass `{}` when the contract declares no `propsSpec`.  |
| `themeId`     | `string` | No       | Per-render theme preset override — wins over `App.defaultThemeId` for THIS render. Discover ids via `ggui_list_themes`. Omit to inherit the app theme.                                                                   |
| `infra`       | `object` | No       | `{model?}` — provider-prefixed per-render model override (e.g. `anthropic/claude-haiku-4-5`). Strict — unknown keys are rejected.                                                                                        |
| `override`    | `object` | No       | Omit to ACCEPT the suggestion as-is. Provide `{contract?, variance?}` to re-aim: `override.contract` re-drafts the contract (STRICT — must already conform) and cold-gens; `override.variance` re-aims the variant axis. |

**Returns:** `{ sessionId, resourceUri, action, contractHash, blueprintId, variantKey, cache, nextStep? }`

| Field          | Type     | Description                                                                                                                                                                                     |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sessionId`    | `string` | Globally-unique id (UUID) for the delivered render. Use for `ggui_consume` / `ggui_update`.                                                                                                     |
| `resourceUri`  | `string` | Spec-canonical MCP-Apps entry point — `ui://ggui/render/<id>`, mirrored on the tool result's `_meta.ui.resourceUri`. A host mounts the render from this; there is no clickable URL on the wire. |
| `action`       | `enum`   | One of `create` / `reuse` / `update` / `replace` / `declined`.                                                                                                                                  |
| `contractHash` | `string` | Canonical hash of the rendered data contract (shape only — fields, types, specs). Same hash ⟺ same data flow.                                                                                   |
| `blueprintId`  | `string` | Opaque id of the materialised component. Equal across two renders ⟺ the same cached component was served (a fresh gen mints a new id).                                                          |
| `variantKey`   | `string` | Canonical hash of the design-time variance. With `contractHash` it forms the reuse key.                                                                                                         |
| `cache`        | `object` | Reuse outcome — `{ hit, similarity?, cachedBlueprintId?, llmCallsAvoided, kind?, reason? }`.                                                                                                    |
| `nextStep`     | `object` | Emitted ONLY when the rendered contract has a non-empty `actionSpec`. Points at `ggui_consume({sessionId})` for the inbound action loop. Pure-display renders get no `nextStep`.                |

The render consumes the handshake record. Bootstrap credentials (`wsUrl`, `wsToken`, `expiresAt`) reach the iframe via the `_meta["ai.ggui/render"]` slice, not via this response.

### `ggui_consume`

Long-poll for buffered user events on one render. Events drain on read (consume-once semantics). Call this right after every `ggui_render` whose response carries `nextStep.tool === 'ggui_consume'`.

| Field       | Type     | Required | Description                                                                                                                                                                                                  |
| ----------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sessionId` | `string` | Yes      | Render to consume from. Globally unique.                                                                                                                                                                     |
| `timeout`   | `number` | No       | Long-poll seconds — integer in `[0, 25]`; `0` = immediate (default). Values outside `[0, 25]` reject `INVALID_PARAMS`. Returns on the first event or at timeout; re-call on empty to keep waiting — a longer wait is your loop, not a bigger timeout (pick 5–15s typical, 25 max). |

**Returns:** `{ events: ConsumeEventEntry[], status: "active" | "expired", client? }`

Keyed by `sessionId`. THE LOOP: when `events` is non-empty, react (commonly via `ggui_update` to refresh the iframe), then re-call `ggui_consume`. The render stays `active` until its TTL elapses — `status: "expired"` means no more events will arrive, so the long-poll loop terminates. Exit when you have the events you need, or when `status` is `expired`. The optional `client` field echoes mid-render host observations (window resize, fullscreen toggle, etc.) without forcing a fresh handshake.

### `ggui_update`

Mutate props on a delivered render in place. Targets `sessionId` directly. Discriminated on `kind`.

```json
// FULL replacement
{ "sessionId": "…", "kind": "replace", "props": { … } }

// RFC 7396 JSON Merge Patch
{ "sessionId": "…", "kind": "merge",   "patch": { … } }
```

| Field       | Type     | Required   | Description                                                                                                                                               |
| ----------- | -------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sessionId` | `string` | Yes        | The render to mutate (UUID from `ggui_render` response).                                                                                                  |
| `kind`      | `enum`   | Yes        | `'replace'` — the `props` map IS the new state. `'merge'` — apply RFC 7396 JSON Merge Patch (null deletes a key; arrays fully replace, NOT element-wise). |
| `props`     | `object` | If replace | Full replacement props map. Required when `kind: 'replace'`.                                                                                              |
| `patch`     | `object` | If merge   | RFC 7396 patch. Required when `kind: 'merge'`.                                                                                                            |

**Returns:** `{ sessionId, updated, resourceUri }`

`resourceUri` is unchanged from the initial render — the same `ui://ggui/render/{sessionId}` the mount stamped. Both modes validate the final props state (post-merge for `merge`) against the render's `propsSpec` and reject on violation. Use for partial UI state changes; for a structurally different surface, handshake + render a fresh one. Post-update the iframe receives the new props via the live-channel `props_update` WS frame.

### `ggui_emit`

Emit a new delivery on a declared `streamSpec` channel of the render. The agent describes WHAT new data exists; the server stamps the canonical `StreamEnvelope` (mode derived from `streamSpec[channel].mode`, `seq` + `timestamp` server-assigned). Validates `payload` against the channel's declared `schema` and rejects undeclared channels at call time.

| Field       | Type      | Required | Description                                                                                                                                                  |
| ----------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sessionId` | `string`  | Yes      | Render to stream to. Server enforces app-ownership.                                                                                                          |
| `channel`   | `string`  | Yes      | Channel name declared on the render's `streamSpec`. Undeclared channels reject.                                                                              |
| `payload`   | `unknown` | Yes      | Delivery payload. Validated against `streamSpec[channel].schema`.                                                                                            |
| `complete`  | `boolean` | No       | Terminal-delivery marker. Only valid when the channel was declared with `complete: true` on the streamSpec; setting it on a non-completable channel rejects. |

**Returns:** `{ accepted }`

`accepted: true` means the server validated and enqueued the envelope at the boundary. No-subscriber is NOT an error — buffered retention and live fan-out happen independently. The server-assigned `seq` is observable on the delivered `StreamEnvelope` (the live-channel `data` WS frame), not on this tool result.

### `ggui_get_session`

Retrieve GguiSession state — id, appId, event sequence, activity timestamps. Bumps the activity heartbeat on every successful read. Omits `componentCode` + `sourceCode` (those live on the renderable surface, not the agent-visible one).

| Field       | Type     | Required | Description             |
| ----------- | -------- | -------- | ----------------------- |
| `sessionId` | `string` | Yes      | GguiSession to inspect. |

**Returns:** `{ id, appId, eventSequence, createdAt, lastActivityAt, expiresAt }`

`createdAt` / `lastActivityAt` / `expiresAt` are epoch milliseconds (numbers).

### `ggui_list_sessions`

Enumerate this app's GguiSessions by host conversation — the lookup behind resume flows. Matches on the `_meta["ai.ggui/host-session"]` pair (`hostName` + `hostSessionId`) captured at render creation; sessions created without that slice never match host-scoped queries.

| Field           | Type     | Required | Description                                                                                                    |
| --------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `hostName`      | `string` | No       | Filter by host identifier (`claude.ai`, `sample`, …). Pair with `hostSessionId` to target one conversation.    |
| `hostSessionId` | `string` | No       | The host's opaque conversation-grouping key (e.g. a claude.ai thread id). Typically paired with `hostName`.    |
| `limit`         | `number` | No       | Max rows, 1–200. Default 50. Newest-last ordering matches the conversation timeline.                           |

**Returns:** `{ sessions: [{ sessionId, hostName?, hostSessionId?, createdAt, lastActivityAt, status, wsToken?, wsTokenExpiresAt? }] }`

`createdAt` / `lastActivityAt` are ISO 8601 strings here. The `wsToken` pair is present only when the deployment wires a `mintWsToken` seam — resume flows use it to remount each iframe without a fresh handshake.

:::note[Removed in `draft-2026-05-27`]
The legacy session-lifecycle and stack-manipulation tools were deleted in the Phase B render-identity collapse. The session vessel and stack-of-N concept they served no longer exist; renders are single-mount and decay implicitly via TTL.
:::

### `ggui_list_gadgets`

Return the catalog of renderer-side **gadgets** the UI may import via the package-keyed `clientCapabilities.gadgets` map of a `DataContract`. Call this BEFORE authoring a contract so the catalog you seed only references gadgets the renderer will actually serve. Returns the per-app catalog: the 7-hook stdlib package (`@ggui-ai/gadgets` 0.3.0) is the structural floor; gadgets declared in `ggui.json#app.gadgets` layer on top (declared wins on a package collision).

| Field   | Type     | Required | Description                                                                                                                                    |
| ------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `appId` | `string` | No       | The app whose catalog to fetch. Defaults to the caller-resolved appId from the auth header. Explicit mismatch surfaces as `app_access_denied`. |

**Returns:** `{ gadgets: GadgetDescriptor[] }`

Each `GadgetDescriptor` is a gadget PACKAGE: `{ package, version, exports: GadgetExport[], … }` — package-level identity plus transport metadata (`bundleUrl` / `bundleHost` / `bundleSri` / `styleUrl` / `connect` / `requires` / `typesUrl` / `typesSri` for non-stdlib packages). Each `GadgetExport` is a field-presence-discriminated union — a hook export `{ hook, description?, usage?, example?, gotchas?, permission?, required? }` or a component export `{ component, description?, usage?, example?, gotchas?, permission?, required? }`. Full entry shape: [SDK gadgets guide](/sdk/gadgets/).

### `ggui_list_themes`

Return the theme presets an agent may apply per render via `ggui_render({ themeId })`. When the app configures an `availableThemeIds` allowlist, the catalog is filtered to it (catalog order preserved; unregistered ids silently dropped).

| Field   | Type     | Required | Description                                                                                                                                    |
| ------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `appId` | `string` | No       | The app whose theme catalog to fetch. Defaults to the caller-resolved appId from the auth header. Explicit mismatch surfaces as `app_access_denied`. |

**Returns:** `{ themes: [{ id, name, description, modes }] }`

`modes` lists the variants each preset ships (`light` / `dark`).

### `ggui_list_featured_blueprints`

Enumerate the builder-curated featured blueprints declared via the server's blueprint catalog (typically `ggui.json#blueprints.include` for OSS deployments). Returns an empty list when no catalog is wired.

**Inputs:** none.

**Returns:** `{ blueprints: BlueprintEntry[], total }`

Pair with `ggui_search_blueprints` for semantic lookup or `ggui_render_blueprint` to materialize one directly.

### `ggui_search_blueprints`

Semantic search across this app's blueprints — both manifest-declared UIs (`ggui.json#blueprints.include`) and previously cached generations. Matches by name/description against the manifest source and by cosine similarity against the semantic vector index; results merge + dedupe by id (manifest wins on collision) and sort by score descending.

| Field   | Type     | Required | Description                                                |
| ------- | -------- | -------- | ---------------------------------------------------------- |
| `query` | `string` | Yes      | Natural-language description of the UI you're looking for. |
| `limit` | `number` | No       | Max results. Default 10. Maximum 100.                      |

**Returns:** `{ results, total, query }`

Each result row carries `{ id, name, description, category, props, callbacks, featured, relevance: 'match', score }`. `score` is 0–1 (cosine similarity for semantic hits; `1.0` for exact manifest-name matches, `0.7` for manifest substring matches). Agents use `score` to decide whether to reuse a blueprint or generate from scratch.

### `ggui_discover`

:::note[Hosted-only (coming soon)]
Will be registered on hosted `mcp.ggui.ai`. Self-hosted `ggui serve` omits this tool — the same info is available statically from the OSS package metadata.
:::

Return platform capabilities (protocol version, supported content types, shell types, adapter types, component-capability catalog) and — when the bearer token resolves to a known app — that app's enabled adapters / granted capabilities / auth mode / rate limit. Call BEFORE the first handshake when you need to branch on what this deployment supports.

**Inputs:** none.

**Returns:** `{ protocolVersion, contentTypes, shellTypes, adapterTypes, componentCapabilities, app? }`

| Field                   | Type       | Description                                                                                                                                                                                 |
| ----------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocolVersion`       | `string`   | ggui protocol revision (prelaunch drafts use `draft-YYYY-MM-DD`; first frozen release will be `1.0.0`).                                                                                     |
| `contentTypes`          | `string[]` | Bundle content types this deployment serves (e.g. `application/javascript+react`).                                                                                                          |
| `shellTypes`            | `string[]` | Available shell flavors (`chat`, `fullscreen`, `spatial`).                                                                                                                                  |
| `adapterTypes`          | `string[]` | Adapter families wired on this deployment (`voice`, `camera`, `location`, `bluetooth`).                                                                                                     |
| `componentCapabilities` | `string[]` | Informational capability vocabulary. The load-bearing per-app grant lives on the operator-registered `GadgetExport.permission` in `App.gadgets` (registry side) — not on the contract wire. |
| `app`                   | `object?`  | Present when the bearer token resolves to a known app. `{ enabledAdapters?, grantedCapabilities?, defaultShellType?, authMode?, rateLimitPerMinute? }`.                                     |

### `ggui_request_credential`

:::note[Hosted-only (coming soon)]
Will be registered on hosted `mcp.ggui.ai`. Self-hosted deployments don't ship the credential proxy — drive your own OAuth ceremonies in agent code.
:::

Request OAuth consent from the end user via the Portal's consent overlay. Blocks up to 25 seconds polling for the user's choice (Allow once / Always allow / Deny). Short-circuits with `granted: true` when a prior grant already exists for this user + app + service.

| Field       | Type     | Required | Description                                                                                   |
| ----------- | -------- | -------- | --------------------------------------------------------------------------------------------- |
| `serviceId` | `string` | Yes      | OAuth service identifier (matches an `McpServiceConfig` entry — e.g. `"bashdoor"`, `"ubot"`). |
| `reason`    | `string` | No       | One-line rationale shown to the user inside the consent overlay.                              |
| `sessionId` | `string` | No       | Existing render to surface the consent UI into. Required to actually surface the overlay.     |

**Returns:** `{ granted, mode?, service?, reason? }`

| Field     | Type                 | Description                                                             |
| --------- | -------------------- | ----------------------------------------------------------------------- |
| `granted` | `boolean`            | Whether the user (or a prior grant) approved.                           |
| `mode`    | `'once' \| 'always'` | Grant mode when `granted: true`. Absent on denial / timeout.            |
| `service` | `{ name, icon }`     | Display info pulled from `McpServiceConfig`. Echoed back for UI parity. |
| `reason`  | `string`             | Denial / timeout / error rationale when `granted: false`.               |

### `ggui_render_blueprint`

Resolve a registered blueprint id to its compiled JS bundle, inline. The OSS handler reads the manifest entry via the server's `UiRegistry`, compiles on demand from the colocated TSX (`@ggui-ai/dev-stack::LocalUiRegistry` is the reference impl), and returns the bundle as a single JSON field. Fails with a clear error when the id is unknown or no bundle is available. Only registered when the server boots with a `UiRegistry` seam — otherwise the tool is omitted from `tools/list` entirely.

| Field         | Type     | Required | Description                                                                                                             |
| ------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `blueprintId` | `string` | Yes      | Stable blueprint id declared via `ggui.ui.json#id`. Must match an entry in this server's UI registry. |

**Returns:** `{ blueprintId, blueprintName, code, contentType }`

`code` is the compiled JS bundle as a string (ESM `export default` producing the component to mount). `contentType` is typically `'application/javascript+react'` — pinned by the server's compile pipeline. The caller mounts `code` directly; no second round-trip is required.

---

## Events

Two distinct shapes are in play. Don't conflate them:

- **`ActionEnvelope`** — live-channel inbound on the WebSocket subscribe seam. Used by browser/SDK consumers that listen to live events (e.g. `@ggui-ai/wire`'s `useRender`). See [WebSocket Protocol](/api/websocket-protocol/).
- **`ConsumeEventEntry`** — per-gesture row on the render-keyed consume pipe, returned by `ggui_consume`. This is what agents read.

### `ActionEnvelope` (live-channel inbound)

```typescript
interface ActionEnvelope<TPayload = JsonValue> {
  sessionId: string;
  type: EventType;
  payload?: TPayload; // For `data:submit`: { action, data?, tool? }
  clientSeq?: number; // client-monotonic, for at-least-once dedup
}
```

### `ConsumeEventEntry` (consume pipe)

```typescript
interface ConsumeEventEntry {
  readonly type: "action";
  readonly sessionId: string;
  readonly intent: string; // which actionSpec[*] fired
  readonly actionData: JsonValue | null; // matches actionSpec[intent].schema
  readonly uiContext: JsonObject; // contextSpec slot snapshot at gesture time
  readonly actionId: string; // 8-hex FNV-1a correlation id
  readonly firedAt: string; // ISO 8601 UTC
}
```

Both envelopes are flat — no nested `event` / `context` / `meta` blocks. Diagnostic render metadata (device info, interface context) lives on the render at subscribe time, not per-delivery.

### Event Types

`EventType` has exactly one member, `data:submit`.

| Type          | Category | Description                              |
| ------------- | -------- | ---------------------------------------- |
| `data:submit` | Data     | User gesture surfaced as a consume event |

The pre-actionSpec multi-event vocabulary (`data:change`, `lifecycle:*`, `interaction:*`, `error:*`) was deleted in draft-2026-06-12 — it never had a first-party producer. Today's actionSpec-driven flow surfaces every user gesture as a `data:submit` `ConsumeEventEntry`. There is no other event vocabulary — agent code reads from `ggui_consume` and only needs to recognize the `data:submit` shape.

---

## Error Codes

Names mirror the `MCP_ERROR_CODES` / `PLATFORM_ERROR_CODES` constants exported from `@ggui-ai/protocol`. The `-32010` range is the ggui platform-extension block, not part of the core protocol.

| Code     | Name                        | Description                            |
| -------- | --------------------------- | --------------------------------------- |
| `-32700` | `PARSE_ERROR`               | Invalid JSON in request                 |
| `-32600` | `INVALID_REQUEST`           | Not a valid JSON-RPC object             |
| `-32601` | `METHOD_NOT_FOUND`          | Unknown method name                     |
| `-32602` | `INVALID_PARAMS`            | Missing or invalid tool arguments       |
| `-32603` | `INTERNAL_ERROR`            | Server-side failure                     |
| `-32001` | `UNAUTHORIZED`              | Invalid token or app ID                 |
| `-32002` | `SESSION_NOT_FOUND`         | Session expired or deleted              |
| `-32003` | `APP_NOT_FOUND`             | App ID does not exist                   |
| `-32004` | `PRODUCTION_FAILED`         | UI production failed                    |
| `-32005` | `CAPABILITY_DENIED`         | Requested capability not granted        |
| `-32010` | `GENERATION_QUOTA_EXCEEDED` | Platform: generation quota exhausted    |
| `-32011` | `APP_LIMIT_EXCEEDED`        | Platform: app-count ceiling reached     |
| `-32012` | `CONCURRENT_SESSION_LIMIT`  | Platform: too many live sessions        |
| `-32013` | `RATE_LIMIT_EXCEEDED`       | Platform: reserved rate-limit code      |
| `-32020` | `CONTRACT_VIOLATION`        | Platform: contract validation failed    |

---

## Supported Models

There is no fixed model menu. The operator sets the per-app default via `ggui.json#generation.model` — any provider-prefixed route, written `provider:model` (canonical) or LiteLLM-style `provider/model`, e.g. `anthropic:claude-haiku-4-5-20251001`. Providers on the self-hosted BYOK path: `anthropic`, `openai`, `google`, `openrouter` (`bedrock` routes are hosted-runtime-only and rejected by `ggui serve`).

Agents can override the model per render via `ggui_render({ infra: { model } })` with a provider-prefixed id (e.g. `anthropic/claude-haiku-4-5`).

---

## Example: Full Render Flow (curl)

This walkthrough runs against self-hosted `ggui serve` (started with `--dev-allow-all`). Hosted `mcp.ggui.ai` (coming soon) will speak the same wire — only the URL and bearer change.

```bash
# 1. Initialize
curl -X POST http://127.0.0.1:6781/mcp \
  -H "Authorization: Bearer dev" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "clientInfo": { "name": "curl", "version": "1.0" },
      "capabilities": {}
    }
  }'

# 2. Handshake — negotiate the wire surface
#    (blueprintDraft carries the agent's contract)
curl -X POST http://127.0.0.1:6781/mcp \
  -H "Authorization: Bearer dev" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 2, "method": "tools/call",
    "params": {
      "name": "ggui_handshake",
      "arguments": {
        "intent": "Contact form",
        "blueprintDraft": {
          "contract": { "propsSpec": {}, "actionSpec": {} }
        }
      }
    }
  }'

# 3. Render — accept the handshake suggestion verbatim (mints sessionId)
curl -X POST http://127.0.0.1:6781/mcp \
  -H "Authorization: Bearer dev" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 3, "method": "tools/call",
    "params": {
      "name": "ggui_render",
      "arguments": { "handshakeId": "hs_…", "props": {} }
    }
  }'

# 4. Poll for events (keyed by sessionId from step 3)
curl -X POST http://127.0.0.1:6781/mcp \
  -H "Authorization: Bearer dev" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 4, "method": "tools/call",
    "params": {
      "name": "ggui_consume",
      "arguments": { "sessionId": "<sessionId from step 3>", "timeout": 15 }
    }
  }'

# Renders decay implicitly via TTL — no explicit close.
```

---

## See Also

- [Protocol overview](/protocol/overview/) — the three channels at a glance
- [WebSocket Protocol](/api/websocket-protocol/) — live-channel live events
- [MCP Apps](/api/mcp-apps/) — inline rendering inside MCP hosts
- [OAuth on mcp.ggui.ai](/api/oauth/) — hosted auth ceremony (coming soon)
- [Ops MCP route](/api/ops-mcp/) — operator agent tools at `/ops`
- [Docs MCP route](/api/mcp-docs/) — anonymous docs search at `/docs`
- [Playground · Todos](/clients/playground-todos/) — hosted demo service at `/playground/todos` (coming soon)
- [Playground · MDH](/clients/playground-mdh/) — hosted demo service at `/playground/mdh` (coming soon)
- [MCP services](/architecture/mcp-services/) — mounting standalone services on one server
- [Glossary](/glossary/) — gadget / tool / blueprint, plus every other term