@ggui-ai/protocol 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/bridge/invoke-agent.d.ts +65 -0
- package/dist/bridge/invoke-agent.d.ts.map +1 -0
- package/dist/bridge/invoke-agent.js +113 -0
- package/dist/envelope-adapters.d.ts +24 -0
- package/dist/envelope-adapters.d.ts.map +1 -0
- package/dist/envelope-adapters.js +14 -0
- package/dist/envelopes/builders.d.ts +145 -0
- package/dist/envelopes/builders.d.ts.map +1 -0
- package/dist/envelopes/builders.js +113 -0
- package/dist/errors/unknown-permission-name.d.ts +12 -0
- package/dist/errors/unknown-permission-name.d.ts.map +1 -0
- package/dist/errors/unknown-permission-name.js +29 -0
- package/dist/errors/version-mismatch.d.ts +55 -0
- package/dist/errors/version-mismatch.d.ts.map +1 -0
- package/dist/errors/version-mismatch.js +52 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
- package/dist/gadgets/resolve-contract-gadgets.js +119 -0
- package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
- package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
- package/dist/gadgets/stdlib-gadgets.js +161 -0
- package/dist/iframe-bridge.d.ts +63 -0
- package/dist/iframe-bridge.d.ts.map +1 -0
- package/dist/iframe-bridge.js +166 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/integrations/mcp-apps.d.ts +1218 -0
- package/dist/integrations/mcp-apps.d.ts.map +1 -0
- package/dist/integrations/mcp-apps.js +427 -0
- package/dist/navigation/index.d.ts +3 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +1 -0
- package/dist/navigation/stack-navigation.d.ts +55 -0
- package/dist/navigation/stack-navigation.d.ts.map +1 -0
- package/dist/navigation/stack-navigation.js +80 -0
- package/dist/recommended-prompts.d.ts +56 -0
- package/dist/recommended-prompts.d.ts.map +1 -0
- package/dist/recommended-prompts.js +55 -0
- package/dist/registry/blueprint-key.d.ts +9 -0
- package/dist/registry/blueprint-key.d.ts.map +1 -0
- package/dist/registry/blueprint-key.js +28 -0
- package/dist/registry/canonicalize-contract.d.ts +35 -0
- package/dist/registry/canonicalize-contract.d.ts.map +1 -0
- package/dist/registry/canonicalize-contract.js +166 -0
- package/dist/registry/summarize-contract.d.ts +46 -0
- package/dist/registry/summarize-contract.d.ts.map +1 -0
- package/dist/registry/summarize-contract.js +63 -0
- package/dist/schema-learning/derive-contract.d.ts +67 -0
- package/dist/schema-learning/derive-contract.d.ts.map +1 -0
- package/dist/schema-learning/derive-contract.js +117 -0
- package/dist/schema-learning/merge.d.ts +32 -0
- package/dist/schema-learning/merge.d.ts.map +1 -0
- package/dist/schema-learning/merge.js +146 -0
- package/dist/schemas/blueprint.d.ts +32 -0
- package/dist/schemas/blueprint.d.ts.map +1 -0
- package/dist/schemas/blueprint.js +92 -0
- package/dist/schemas/data-contract.d.ts +750 -0
- package/dist/schemas/data-contract.d.ts.map +1 -0
- package/dist/schemas/data-contract.js +663 -0
- package/dist/schemas/gadget-name-grammar.d.ts +29 -0
- package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
- package/dist/schemas/gadget-name-grammar.js +28 -0
- package/dist/schemas/handshake-suggestion.d.ts +46 -0
- package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
- package/dist/schemas/handshake-suggestion.js +107 -0
- package/dist/schemas/invoke.d.ts +337 -0
- package/dist/schemas/invoke.d.ts.map +1 -0
- package/dist/schemas/invoke.js +169 -0
- package/dist/schemas/mcp.d.ts +301 -0
- package/dist/schemas/mcp.d.ts.map +1 -0
- package/dist/schemas/mcp.js +373 -0
- package/dist/schemas/ops-blueprint.d.ts +176 -0
- package/dist/schemas/ops-blueprint.d.ts.map +1 -0
- package/dist/schemas/ops-blueprint.js +259 -0
- package/dist/schemas/sync-check.d.ts +11 -0
- package/dist/schemas/sync-check.d.ts.map +1 -0
- package/dist/schemas/sync-check.js +60 -0
- package/dist/screen-blueprints/define.d.ts +22 -0
- package/dist/screen-blueprints/define.d.ts.map +1 -0
- package/dist/screen-blueprints/define.js +3 -0
- package/dist/screen-blueprints/index.d.ts +4 -0
- package/dist/screen-blueprints/index.d.ts.map +1 -0
- package/dist/screen-blueprints/index.js +3 -0
- package/dist/screen-blueprints/match.d.ts +35 -0
- package/dist/screen-blueprints/match.d.ts.map +1 -0
- package/dist/screen-blueprints/match.js +51 -0
- package/dist/screen-blueprints/types.d.ts +164 -0
- package/dist/screen-blueprints/types.d.ts.map +1 -0
- package/dist/screen-blueprints/types.js +1 -0
- package/dist/stream/stream-parser.d.ts +62 -0
- package/dist/stream/stream-parser.d.ts.map +1 -0
- package/dist/stream/stream-parser.js +199 -0
- package/dist/transport/websocket.d.ts +178 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +1 -0
- package/dist/types/app-config.d.ts +61 -0
- package/dist/types/app-config.d.ts.map +1 -0
- package/dist/types/app-config.js +1 -0
- package/dist/types/auth.d.ts +61 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/blueprint.d.ts +206 -0
- package/dist/types/blueprint.d.ts.map +1 -0
- package/dist/types/blueprint.js +1 -0
- package/dist/types/canvas-lifecycle.d.ts +105 -0
- package/dist/types/canvas-lifecycle.d.ts.map +1 -0
- package/dist/types/canvas-lifecycle.js +38 -0
- package/dist/types/capabilities.d.ts +40 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +19 -0
- package/dist/types/contract-inference.d.ts +401 -0
- package/dist/types/contract-inference.d.ts.map +1 -0
- package/dist/types/contract-inference.js +44 -0
- package/dist/types/credential.d.ts +41 -0
- package/dist/types/credential.d.ts.map +1 -0
- package/dist/types/credential.js +32 -0
- package/dist/types/data-bindings.d.ts +322 -0
- package/dist/types/data-bindings.d.ts.map +1 -0
- package/dist/types/data-bindings.js +29 -0
- package/dist/types/data-contract.d.ts +1296 -0
- package/dist/types/data-contract.d.ts.map +1 -0
- package/dist/types/data-contract.js +111 -0
- package/dist/types/events.d.ts +182 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +8 -0
- package/dist/types/feedback.d.ts +24 -0
- package/dist/types/feedback.d.ts.map +1 -0
- package/dist/types/feedback.js +7 -0
- package/dist/types/gadget.d.ts +121 -0
- package/dist/types/gadget.d.ts.map +1 -0
- package/dist/types/gadget.js +24 -0
- package/dist/types/handshake-suggestion.d.ts +264 -0
- package/dist/types/handshake-suggestion.d.ts.map +1 -0
- package/dist/types/handshake-suggestion.js +70 -0
- package/dist/types/host-context.d.ts +163 -0
- package/dist/types/host-context.d.ts.map +1 -0
- package/dist/types/host-context.js +142 -0
- package/dist/types/interface-context.d.ts +105 -0
- package/dist/types/interface-context.d.ts.map +1 -0
- package/dist/types/interface-context.js +115 -0
- package/dist/types/invoke.d.ts +28 -0
- package/dist/types/invoke.d.ts.map +1 -0
- package/dist/types/invoke.js +7 -0
- package/dist/types/live-channel.d.ts +613 -0
- package/dist/types/live-channel.d.ts.map +1 -0
- package/dist/types/live-channel.js +1 -0
- package/dist/types/llm.d.ts +61 -0
- package/dist/types/llm.d.ts.map +1 -0
- package/dist/types/llm.js +186 -0
- package/dist/types/mcp-proxy.d.ts +67 -0
- package/dist/types/mcp-proxy.d.ts.map +1 -0
- package/dist/types/mcp-proxy.js +46 -0
- package/dist/types/mcp.d.ts +637 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +30 -0
- package/dist/types/openrouter-models.d.ts +22 -0
- package/dist/types/openrouter-models.d.ts.map +1 -0
- package/dist/types/openrouter-models.js +4843 -0
- package/dist/types/region.d.ts +26 -0
- package/dist/types/region.d.ts.map +1 -0
- package/dist/types/region.js +36 -0
- package/dist/types/session.d.ts +419 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +1 -0
- package/dist/types/thread.d.ts +207 -0
- package/dist/types/thread.d.ts.map +1 -0
- package/dist/types/thread.js +57 -0
- package/dist/types/ui-generator.d.ts +100 -0
- package/dist/types/ui-generator.d.ts.map +1 -0
- package/dist/types/ui-generator.js +53 -0
- package/dist/validation/ajv-runtime.d.ts +140 -0
- package/dist/validation/ajv-runtime.d.ts.map +1 -0
- package/dist/validation/ajv-runtime.js +452 -0
- package/dist/validation/content-hash.d.ts +3 -0
- package/dist/validation/content-hash.d.ts.map +1 -0
- package/dist/validation/content-hash.js +21 -0
- package/dist/validation/contract-validator.d.ts +244 -0
- package/dist/validation/contract-validator.d.ts.map +1 -0
- package/dist/validation/contract-validator.js +711 -0
- package/dist/validation/cross-references.d.ts +105 -0
- package/dist/validation/cross-references.d.ts.map +1 -0
- package/dist/validation/cross-references.js +164 -0
- package/dist/validation/hygiene-rules.d.ts +250 -0
- package/dist/validation/hygiene-rules.d.ts.map +1 -0
- package/dist/validation/hygiene-rules.js +564 -0
- package/dist/validation/lint-contract.d.ts +130 -0
- package/dist/validation/lint-contract.d.ts.map +1 -0
- package/dist/validation/lint-contract.js +225 -0
- package/dist/validation/name-invariants.d.ts +117 -0
- package/dist/validation/name-invariants.d.ts.map +1 -0
- package/dist/validation/name-invariants.js +172 -0
- package/dist/validation/reserved-channels.d.ts +156 -0
- package/dist/validation/reserved-channels.d.ts.map +1 -0
- package/dist/validation/reserved-channels.js +356 -0
- package/dist/validation/resolve-stream-channel.d.ts +78 -0
- package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
- package/dist/validation/resolve-stream-channel.js +64 -0
- package/dist/validation/sanitize-error.d.ts +46 -0
- package/dist/validation/sanitize-error.d.ts.map +1 -0
- package/dist/validation/sanitize-error.js +88 -0
- package/dist/validation/schema-compat-invariants.d.ts +140 -0
- package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
- package/dist/validation/schema-compat-invariants.js +220 -0
- package/dist/validation/schema-meta-validation.d.ts +60 -0
- package/dist/validation/schema-meta-validation.d.ts.map +1 -0
- package/dist/validation/schema-meta-validation.js +131 -0
- package/dist/validation/schema-subset.d.ts +165 -0
- package/dist/validation/schema-subset.d.ts.map +1 -0
- package/dist/validation/schema-subset.js +295 -0
- package/dist/validation/ui-security.d.ts +54 -0
- package/dist/validation/ui-security.d.ts.map +1 -0
- package/dist/validation/ui-security.js +138 -0
- package/dist/validation/zod-to-json-schema.d.ts +63 -0
- package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
- package/dist/validation/zod-to-json-schema.js +126 -0
- package/dist/version.d.ts +1458 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1459 -0
- package/package.json +113 -0
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Apps integration — outbound delivery types for ggui.
|
|
3
|
+
*
|
|
4
|
+
* This module is the **boundary** for MCP Apps outbound delivery. Anything
|
|
5
|
+
* MCP-Apps-specific that ggui exposes to the rest of the codebase lives
|
|
6
|
+
* here — never in `types/live-channel.ts`, `types/mcp.ts`, `types/session.ts`,
|
|
7
|
+
* or any other core module. Consumers opt in via the subpath import:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import {
|
|
11
|
+
* MCP_APPS_UI_CAPABILITY,
|
|
12
|
+
* GGUI_SESSION_RESOURCE_URI,
|
|
13
|
+
* type GguiBootstrapMeta,
|
|
14
|
+
* } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The root `@ggui-ai/protocol` barrel does NOT re-export this module.
|
|
18
|
+
* That's the isolation rule: core protocol consumers that don't integrate
|
|
19
|
+
* with MCP Apps pay none of its weight, and the blast radius of any spec
|
|
20
|
+
* drift is bounded to callers that explicitly import from here.
|
|
21
|
+
*
|
|
22
|
+
* Core still carries two fields that make the bootstrap flow work —
|
|
23
|
+
* `SubscribePayload.bootstrap?: string` and `AckPayload.sessionToken?:
|
|
24
|
+
* string`. Those are deliberately framed as **general transport bootstrap
|
|
25
|
+
* credentials** (opaque strings), not MCP-Apps-specific. Any future
|
|
26
|
+
* bootstrap mechanism (short-code auto-login, signed-URL bootstrap, etc.)
|
|
27
|
+
* reuses the same slots.
|
|
28
|
+
*/
|
|
29
|
+
import type { JsonObject, JsonSchema, JsonValue } from '../types/data-contract.js';
|
|
30
|
+
/**
|
|
31
|
+
* MCP capability name ggui servers advertise in their MCP `initialize`
|
|
32
|
+
* response capabilities when they implement the MCP Apps outbound path.
|
|
33
|
+
* Spec-canonical; MUST match the string the MCP Apps protocol publishes.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MCP_APPS_UI_CAPABILITY: "io.modelcontextprotocol/ui";
|
|
36
|
+
/**
|
|
37
|
+
* The single MCP Apps resource URI ggui exposes for outbound delivery.
|
|
38
|
+
* `ggui_push` is the sole tool declaration that carries this in its
|
|
39
|
+
* `_meta.ui.resourceUri`. No other ggui tool gets a resource URI —
|
|
40
|
+
* `ggui_push` is the single outbound entry point.
|
|
41
|
+
*/
|
|
42
|
+
export declare const GGUI_SESSION_RESOURCE_URI: "ui://ggui/session";
|
|
43
|
+
/**
|
|
44
|
+
* MIME type for the `ui://ggui/session` resource. Per MCP Apps spec, UI
|
|
45
|
+
* resources carry the `text/html` base type with a `profile=mcp-app`
|
|
46
|
+
* parameter so hosts that don't support MCP Apps don't accidentally
|
|
47
|
+
* render them as plain HTML.
|
|
48
|
+
*/
|
|
49
|
+
export declare const GGUI_SESSION_RESOURCE_MIME: "text/html;profile=mcp-app";
|
|
50
|
+
/**
|
|
51
|
+
* The single `_meta.ui.resourceUri` value ggui uses across every MCP Apps
|
|
52
|
+
* host surface. Exposed as a named constant so tool-declaration code,
|
|
53
|
+
* resource-serving code, and tests all agree on one spelling.
|
|
54
|
+
*/
|
|
55
|
+
export declare const GGUI_PUSH_UI_META: {
|
|
56
|
+
/** Resource URI hosts fetch via `resources/read` on a `ggui_push` tool call. */
|
|
57
|
+
readonly resourceUri: "ui://ggui/session";
|
|
58
|
+
/** Only `"model"` — outer agent can call, iframe views cannot. */
|
|
59
|
+
readonly visibility: readonly ["model"];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Visibility tag carried in `_meta.ui.visibility` on a tool declaration.
|
|
63
|
+
* Per MCP Apps spec, controls who can invoke the tool:
|
|
64
|
+
* - `"model"` — outer agent can call (default in practice)
|
|
65
|
+
* - `"app"` — only an MCP Apps view (iframe) can call, hidden from agent
|
|
66
|
+
*/
|
|
67
|
+
export type McpAppsToolVisibility = 'model' | 'app';
|
|
68
|
+
/**
|
|
69
|
+
* The bootstrap material an MCP Apps view (iframe) needs to mount a
|
|
70
|
+
* ggui-rendered UI. Lives under `_meta.ggui.bootstrap` on the
|
|
71
|
+
* `ggui_push` tool result — **not** on `structuredContent`.
|
|
72
|
+
*
|
|
73
|
+
* **Not model-visible.** Agents SHOULD NOT consume these fields. They are
|
|
74
|
+
* scoped to the view lifecycle and carry credentials the model has no
|
|
75
|
+
* legitimate use for. The split between `structuredContent` (model-facing)
|
|
76
|
+
* and `_meta.ggui.bootstrap` (view-facing) is the protocol-level isolation
|
|
77
|
+
* that keeps WebSocket credentials out of conversation transcripts.
|
|
78
|
+
*
|
|
79
|
+
* **Three boot modes share this single shape**, distinguished by which
|
|
80
|
+
* of the three optional discriminator fields is populated:
|
|
81
|
+
*
|
|
82
|
+
* | Mode | Discriminator | What the view does |
|
|
83
|
+
* | ---------------- | ----------------- | --------------------------------------------------- |
|
|
84
|
+
* | live | `wsUrl` + `token` | Open live-channel WS, subscribe, render agent frames |
|
|
85
|
+
* | static-component | `codeUrl` | Fetch + mount compiled React component, no WS |
|
|
86
|
+
* | system-card | `kind` | Mount built-in system card by registry id, no WS |
|
|
87
|
+
*
|
|
88
|
+
* **Mutual-exclusion contract.** A consumer-acceptable bootstrap MUST
|
|
89
|
+
* carry at least one of `{wsUrl-with-token, codeUrl, kind}`.
|
|
90
|
+
* Multiple may coexist (e.g. a server emitting both `codeUrl`
|
|
91
|
+
* AND `wsUrl+token` lets the runtime pick — current iframe-runtime
|
|
92
|
+
* priority is code-then-system-then-live), but at least one MUST
|
|
93
|
+
* be present. A bootstrap with none is MALFORMED.
|
|
94
|
+
*
|
|
95
|
+
* **Live-mode auth semantics** (only relevant when `wsUrl` + `token`
|
|
96
|
+
* are present):
|
|
97
|
+
* - `token` is session-scoped, short-TTL, single-use on initial
|
|
98
|
+
* `subscribe`. Consumed at the first bootstrap-auth'd subscribe and
|
|
99
|
+
* cannot be reused.
|
|
100
|
+
* - `expiresAt` lets the view skip an obviously-stale bootstrap without
|
|
101
|
+
* a round-trip. Servers reject expired tokens anyway; this is UX
|
|
102
|
+
* sugar, not a security control.
|
|
103
|
+
* - For reconnects, the view should use the longer-lived `sessionToken`
|
|
104
|
+
* the server issues on the successful bootstrap-auth ack — see
|
|
105
|
+
* {@link AckPayload.sessionToken}.
|
|
106
|
+
*
|
|
107
|
+
* **Live-mode example:**
|
|
108
|
+
* ```json
|
|
109
|
+
* {
|
|
110
|
+
* "sessionId": "sess_001", "appId": "app_001",
|
|
111
|
+
* "runtimeUrl": "/_ggui/iframe-runtime.js",
|
|
112
|
+
* "wsUrl": "wss://server.example/ws",
|
|
113
|
+
* "token": "tok_abc", "expiresAt": "2099-01-01T00:00:00.000Z"
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* **Static-component example:**
|
|
118
|
+
* ```json
|
|
119
|
+
* {
|
|
120
|
+
* "sessionId": "sess_001", "appId": "app_001",
|
|
121
|
+
* "runtimeUrl": "/_ggui/iframe-runtime.js",
|
|
122
|
+
* "codeUrl": "https://server.example/code/sha256:abc.js",
|
|
123
|
+
* "codeHash": "sha256:abc...",
|
|
124
|
+
* "themeId": "indigo", "propsJson": "{\"name\":\"Ada\"}"
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* **System-card example:**
|
|
129
|
+
* ```json
|
|
130
|
+
* {
|
|
131
|
+
* "sessionId": "sess_001", "appId": "app_001",
|
|
132
|
+
* "runtimeUrl": "/_ggui/iframe-runtime.js",
|
|
133
|
+
* "kind": "loading"
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export interface GguiBootstrapMeta {
|
|
138
|
+
/**
|
|
139
|
+
* WebSocket URL the view opens for live mode (e.g.
|
|
140
|
+
* `wss://server.example/ws`). REQUIRED in live mode; absent in
|
|
141
|
+
* static-component and system-card modes. When present, MUST be
|
|
142
|
+
* paired with a non-empty `token` — half-live (one without the
|
|
143
|
+
* other) is MALFORMED.
|
|
144
|
+
*/
|
|
145
|
+
readonly wsUrl?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Short-TTL single-use bootstrap token, passed as
|
|
148
|
+
* `SubscribePayload.bootstrap`. REQUIRED in live mode; absent in
|
|
149
|
+
* the other modes.
|
|
150
|
+
*/
|
|
151
|
+
readonly token?: string;
|
|
152
|
+
/**
|
|
153
|
+
* ISO 8601 UTC timestamp after which the token is no longer accepted.
|
|
154
|
+
* Optional; meaningful only in live mode (the token it pairs with
|
|
155
|
+
* may be unbounded TTL when this is absent).
|
|
156
|
+
*/
|
|
157
|
+
readonly expiresAt?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Session id the token binds to. Repeated here (also present on
|
|
160
|
+
* `structuredContent.sessionId`) so the view has everything it needs
|
|
161
|
+
* to subscribe from `_meta.ggui.bootstrap` alone — no need to cross-
|
|
162
|
+
* reference structuredContent. Keeps the view-side boot code small.
|
|
163
|
+
*/
|
|
164
|
+
readonly sessionId: string;
|
|
165
|
+
/**
|
|
166
|
+
* App (tenant) id the token binds to. Required in the subscribe
|
|
167
|
+
* payload; not necessarily appropriate for structuredContent
|
|
168
|
+
* (tenant identity is app-facing bootstrap metadata, not typically
|
|
169
|
+
* part of an agent's typed output surface).
|
|
170
|
+
*/
|
|
171
|
+
readonly appId: string;
|
|
172
|
+
/**
|
|
173
|
+
* URL of the iframe-runtime bundle the iframe should fetch. The
|
|
174
|
+
* thin-shell HTML's inline JS dynamically appends
|
|
175
|
+
* `<script src={runtimeUrl}>` to load the runtime. Server controls
|
|
176
|
+
* this per-session so the shell works in `srcdoc` iframes (which
|
|
177
|
+
* have no origin of their own) and across local / cloud
|
|
178
|
+
* deployments.
|
|
179
|
+
*
|
|
180
|
+
* OSS `ggui serve` resolves this to `/_ggui/iframe-runtime.js` —
|
|
181
|
+
* same-origin as the MCP server's HTTP listener. Hosted cloud
|
|
182
|
+
* serves from a dedicated CDN route. Either way, the server OWNS
|
|
183
|
+
* the string; the shell does not guess, concatenate, or fall back
|
|
184
|
+
* to a bundled default.
|
|
185
|
+
*
|
|
186
|
+
* Named parties: **server** produces; **thin shell** consumes;
|
|
187
|
+
* **iframe runtime bundle** is what the URL resolves to. Failure
|
|
188
|
+
* mode on fetch error: runtime surfaces a `BUNDLE_FETCH_FAILED`
|
|
189
|
+
* bootstrap failure via `postMessage({type:'ggui:bootstrap-failed',
|
|
190
|
+
* reason, message})` to the parent (C8 commit 3). Absent or empty
|
|
191
|
+
* at parse-time is `BOOTSTRAP_META_MISSING` — the shell rejects
|
|
192
|
+
* the bootstrap without attempting a script load.
|
|
193
|
+
*
|
|
194
|
+
* C8 (2026-04-23) made this required. Pre-C8 servers that emit
|
|
195
|
+
* bootstraps without `runtimeUrl` are incompatible with the
|
|
196
|
+
* post-C8 thin-shell HTML — the shell pivot (`~175` → `~30` LOC
|
|
197
|
+
* wrapper) moved all rendering logic out of the shell into the
|
|
198
|
+
* separately-served iframe-runtime bundle, so the URL is now
|
|
199
|
+
* load-bearing.
|
|
200
|
+
* @public
|
|
201
|
+
*/
|
|
202
|
+
readonly runtimeUrl: string;
|
|
203
|
+
/**
|
|
204
|
+
* Optional polling fallback URL the iframe-runtime fetches when its
|
|
205
|
+
* WebSocket transport is unavailable or fails. Points at the same
|
|
206
|
+
* `/api/bootstrap/<shortCode>` endpoint the iframe used to initially
|
|
207
|
+
* load the bootstrap envelope — polling re-fetches it and the
|
|
208
|
+
* iframe-runtime diffs the `propsJson` field to synthesize
|
|
209
|
+
* `props_update` frames for live re-render.
|
|
210
|
+
*
|
|
211
|
+
* Empirically required for MCP-Apps hosts whose iframe sandbox
|
|
212
|
+
* blocks `wss://` at the CSP layer regardless of our
|
|
213
|
+
* `_meta.ui.csp.connectDomains` declaration (Claude Desktop is the
|
|
214
|
+
* known case; claude.ai Connector honors WS). Without this field,
|
|
215
|
+
* polling has no URL to hit and live updates silently no-op when
|
|
216
|
+
* WS is unavailable.
|
|
217
|
+
*
|
|
218
|
+
* Producer: server's `/api/bootstrap/<shortCode>` endpoint stamps
|
|
219
|
+
* this on the response body — same origin as the request, same
|
|
220
|
+
* shortCode in the path. Consumer: iframe-runtime threads this
|
|
221
|
+
* through `createPropsUpdateHandler({pollingUrl})` so
|
|
222
|
+
* `PollingTransport` has a URL to fetch.
|
|
223
|
+
*
|
|
224
|
+
* Absent → no polling fallback. WS-only mode is fine for hosts
|
|
225
|
+
* whose CSP permits `wss://`.
|
|
226
|
+
*
|
|
227
|
+
* @public
|
|
228
|
+
*/
|
|
229
|
+
readonly pollingUrl?: string;
|
|
230
|
+
/**
|
|
231
|
+
* Names of same-server tools whose `_meta.ui.visibility` includes
|
|
232
|
+
* `"app"` and are therefore directly callable from this iframe via
|
|
233
|
+
* `tools/call` (per MCP-Apps spec §2026-01-26 Visibility rules:
|
|
234
|
+
* "app" = callable by the app from the same server connection only;
|
|
235
|
+
* cross-server tool calls are always blocked).
|
|
236
|
+
*
|
|
237
|
+
* Used by the iframe-runtime as a capability fingerprint — telemetry
|
|
238
|
+
* and debug surfaces consult it to know which tool names are reachable
|
|
239
|
+
* over the host's same-server `tools/call` path (e.g. the iframe-
|
|
240
|
+
* internal `ggui_runtime_submit_action` and `ggui_runtime_sync_context`
|
|
241
|
+
* relays). It is NOT consulted to choose a dispatch routing strategy:
|
|
242
|
+
* every user gesture flows through `ggui_runtime_submit_action`, lands
|
|
243
|
+
* on the per-stack-item pending-events pipe, and is drained by the
|
|
244
|
+
* agent's `ggui_consume` long-poll on the next turn. Actions ALWAYS
|
|
245
|
+
* drive turns through consume — there is no synchronous server-side
|
|
246
|
+
* dispatch in agent-mediated deployments.
|
|
247
|
+
*
|
|
248
|
+
* Producers SHOULD include every same-server-app-visible tool;
|
|
249
|
+
* consumers MUST treat an absent field as an empty list (legacy
|
|
250
|
+
* bootstrap envelopes predating this addition).
|
|
251
|
+
*
|
|
252
|
+
* @public
|
|
253
|
+
*/
|
|
254
|
+
readonly appCallableTools?: readonly string[];
|
|
255
|
+
/**
|
|
256
|
+
* Per-action `nextStep` hint mapping for the active stack item's
|
|
257
|
+
* `actionSpec`. Maps `actionName → toolName` where `toolName` is the
|
|
258
|
+
* value of `actionSpec[name].nextStep` — the optional hint naming the
|
|
259
|
+
* tool the agent SHOULD call next when the action fires. Only entries
|
|
260
|
+
* whose `actionSpec[name].nextStep` is declared are projected; actions
|
|
261
|
+
* without a hint are omitted from the map.
|
|
262
|
+
*
|
|
263
|
+
* Producer: server's push handler at push time, sourced from the
|
|
264
|
+
* resolved stack item's `actionSpec`. Consumer: iframe-runtime, which
|
|
265
|
+
* mirrors the hint onto outbound `_meta.ggui.userAction` fall-through
|
|
266
|
+
* envelopes (the inline variant in {@link InlineUserActionMeta.nextStep})
|
|
267
|
+
* so the agent gets a strong tool-choice steer when chat-shortcut
|
|
268
|
+
* fallback fires.
|
|
269
|
+
*
|
|
270
|
+
* Absent / empty mapping ⇒ no per-action hints; the iframe omits
|
|
271
|
+
* `nextStep` from the fall-through envelope and the agent picks the
|
|
272
|
+
* next tool freely.
|
|
273
|
+
*
|
|
274
|
+
* @public
|
|
275
|
+
*/
|
|
276
|
+
readonly actionNextSteps?: Readonly<Record<string, string>>;
|
|
277
|
+
/**
|
|
278
|
+
* Per-slot data for the active stack item's `contextSpec`. Each entry
|
|
279
|
+
* carries the slot name + the JsonSchema for runtime validation +
|
|
280
|
+
* optional debounceMs override + optional default value.
|
|
281
|
+
*
|
|
282
|
+
* Producer: server's push handler at push time, derived from
|
|
283
|
+
* `activeStackItem.contextSpec`. Consumer: iframe-runtime, which (at
|
|
284
|
+
* boot) synthesizes one `React.createContext(default)` per entry and
|
|
285
|
+
* registers it under `globalThis.__ggui__.contexts[contextName]`. The
|
|
286
|
+
* boilerplate destructures the registered Contexts so the LLM has
|
|
287
|
+
* them in scope without any import line.
|
|
288
|
+
*
|
|
289
|
+
* Absent → empty list; the runtime synthesizes no Contexts; the
|
|
290
|
+
* `globalThis.__ggui__.contexts` registry is `{}`.
|
|
291
|
+
*
|
|
292
|
+
* @public
|
|
293
|
+
*/
|
|
294
|
+
readonly contextSlots?: ReadonlyArray<{
|
|
295
|
+
/** Slot key — camelCase JS identifier from `contextSpec`. */
|
|
296
|
+
readonly name: string;
|
|
297
|
+
/** PascalCase Context name auto-derived from `name`. The runtime
|
|
298
|
+
* uses this as the key in `globalThis.__ggui__.contexts`. The
|
|
299
|
+
* boilerplate uses it in destructuring lines. */
|
|
300
|
+
readonly contextName: string;
|
|
301
|
+
/** JsonSchema for the slot value — used by the runtime observer
|
|
302
|
+
* to validate Provider values before posting `ui/update-model-context`. */
|
|
303
|
+
readonly schema: JsonSchema;
|
|
304
|
+
/** Initial value for the slot's React Context Provider. Always
|
|
305
|
+
* populated by the server via {@link deriveContextDefault}: the
|
|
306
|
+
* authored `entry.default` if present, otherwise a schema-typed
|
|
307
|
+
* fallback (`''` / `0` / `false` / `[]` / `{}` / `null`). The
|
|
308
|
+
* runtime owns useState per slot, so the Provider seed is load-
|
|
309
|
+
* bearing — `undefined` here would mean
|
|
310
|
+
* the iframe boots with an indeterminate Provider value. */
|
|
311
|
+
readonly default: JsonValue;
|
|
312
|
+
/** Per-slot debounce override in milliseconds. Omitted → runtime
|
|
313
|
+
* applies `DEFAULT_CONTEXT_DEBOUNCE_MS` (300). `0` = immediate. */
|
|
314
|
+
readonly debounceMs?: number;
|
|
315
|
+
}>;
|
|
316
|
+
/**
|
|
317
|
+
* Optional stack-item pin. When present, the renderer binds to a
|
|
318
|
+
* single `StackItem` (identified by `id`) instead of the full session
|
|
319
|
+
* stack — enables per-card iframes (per-item session-resource
|
|
320
|
+
* endpoint + renderer single-item mode).
|
|
321
|
+
*
|
|
322
|
+
* Absent → renderer renders the whole session stack (default).
|
|
323
|
+
* Present → renderer filters to `session.stack.find(i => i.id === stackItemId)`
|
|
324
|
+
* and ignores the rest. Subsequent live-channel updates for other stack
|
|
325
|
+
* ids are delivered but not rendered.
|
|
326
|
+
*
|
|
327
|
+
* Resource URI convention: per-item shells are served at
|
|
328
|
+
* `ggui://session/<sessionId>/item/<stackItemId>`; whole-session
|
|
329
|
+
* shells remain at `ggui://session/<sessionId>`.
|
|
330
|
+
*
|
|
331
|
+
* The `stackItemId` is the opaque string MCP hosts receive on a
|
|
332
|
+
* `ggui_push` tool result's `stackItemId` field (or equivalently the
|
|
333
|
+
* `StackItem.id` in session state). Renderers that don't recognize
|
|
334
|
+
* the field SHOULD ignore it (falls back to whole-session rendering)
|
|
335
|
+
* — shape-preserving extensibility is the contract.
|
|
336
|
+
*
|
|
337
|
+
* @public
|
|
338
|
+
*/
|
|
339
|
+
readonly stackItemId?: string;
|
|
340
|
+
/**
|
|
341
|
+
* Content-addressable URL the runtime fetches the compiled ES module
|
|
342
|
+
* from. The discriminator for **static-component** boot mode (2026-05-13
|
|
343
|
+
* — the inline base64 `componentCode` channel was retired in favor of
|
|
344
|
+
* always-by-URL delivery).
|
|
345
|
+
*
|
|
346
|
+
* Producer: server. Computes `sha256(componentCode)`, writes the bytes
|
|
347
|
+
* to its `CodeStore`, then emits the URL here. URL shape is
|
|
348
|
+
* `<publicBaseUrl>/code/<hash>.js` for OSS or `<cdn>/code/<hash>.js`
|
|
349
|
+
* for hosted cloud — the renderer doesn't care which origin,
|
|
350
|
+
* `fetch(codeUrl)` works the same.
|
|
351
|
+
*
|
|
352
|
+
* Consumer: iframe runtime. Fetches + dynamic-imports the URL. The
|
|
353
|
+
* response is `Cache-Control: immutable`, so subsequent pushes with
|
|
354
|
+
* identical code hit the browser cache (and any CDN edge).
|
|
355
|
+
*
|
|
356
|
+
* Mutually exclusive at the discriminator level with `wsUrl+token`
|
|
357
|
+
* (live mode) and `kind` (system-card mode). When `codeStore` isn't
|
|
358
|
+
* wired, push falls back to live-mode (wsUrl+token) for delivery via
|
|
359
|
+
* the live-channel stack update.
|
|
360
|
+
*
|
|
361
|
+
* @public
|
|
362
|
+
*/
|
|
363
|
+
readonly codeUrl?: string;
|
|
364
|
+
/**
|
|
365
|
+
* Hex-encoded sha256 of the code bytes the URL serves.
|
|
366
|
+
* Surfaced separately from `codeUrl` so consumers can verify
|
|
367
|
+
* content integrity (the URL already encodes it, but parsing the
|
|
368
|
+
* URL is fragile across CDN configurations) and so the agent can
|
|
369
|
+
* inspect deduplication signals across pushes without parsing the
|
|
370
|
+
* URL.
|
|
371
|
+
*
|
|
372
|
+
* Always paired with `codeUrl`: present together or absent together.
|
|
373
|
+
*
|
|
374
|
+
* @public
|
|
375
|
+
*/
|
|
376
|
+
readonly codeHash?: string;
|
|
377
|
+
/**
|
|
378
|
+
* Discriminator for **system-card** boot mode. Stable identifier the
|
|
379
|
+
* runtime maps via the system-card registry to a built-in component
|
|
380
|
+
* (no ESM source on the wire). When present, the view does NOT open
|
|
381
|
+
* a WebSocket — mounting is purely registry-lookup.
|
|
382
|
+
*
|
|
383
|
+
* Mutually exclusive at the discriminator level with `codeUrl`
|
|
384
|
+
* (static-component mode) and `wsUrl+token` (live mode).
|
|
385
|
+
*
|
|
386
|
+
* @public
|
|
387
|
+
*/
|
|
388
|
+
readonly kind?: string;
|
|
389
|
+
/**
|
|
390
|
+
* Theme preset id forwarded to the renderer (`getTheme(id)`).
|
|
391
|
+
* Optional; absent → renderer uses its baked default theme. Used
|
|
392
|
+
* by the self-contained shell so `ggui.json#theme` takes effect
|
|
393
|
+
* across both the WS-driven and self-contained paths.
|
|
394
|
+
*
|
|
395
|
+
* @public
|
|
396
|
+
*/
|
|
397
|
+
readonly themeId?: string;
|
|
398
|
+
/**
|
|
399
|
+
* Theme color mode (`'light'` | `'dark'`) forwarded to the renderer.
|
|
400
|
+
* The runtime resolves the dark variant of {@link themeId} via
|
|
401
|
+
* `getTheme(id, 'dark')` when set; absent / unknown value falls
|
|
402
|
+
* back to `'light'`.
|
|
403
|
+
*
|
|
404
|
+
* @public
|
|
405
|
+
*/
|
|
406
|
+
readonly themeMode?: 'light' | 'dark';
|
|
407
|
+
/**
|
|
408
|
+
* Pre-serialized props for the rendered component (JSON string).
|
|
409
|
+
* Optional; absent → renderer falls back to empty props. Carried
|
|
410
|
+
* as a string to sidestep XSS-defensive escape concerns when the
|
|
411
|
+
* bootstrap is inlined as a JS literal in the self-contained shell.
|
|
412
|
+
*
|
|
413
|
+
* @public
|
|
414
|
+
*/
|
|
415
|
+
readonly propsJson?: string;
|
|
416
|
+
/**
|
|
417
|
+
* Permissions-Policy directive list derived from the active stack item's
|
|
418
|
+
* `DataContract.clientCapabilities.gadgets[*].permission` field.
|
|
419
|
+
* Browser-capability names (`'camera'`, `'microphone'`,
|
|
420
|
+
* `'geolocation'`, `'clipboard-write'`, `'clipboard-read'`,
|
|
421
|
+
* `'notifications'`, …) or arbitrary identifiers for custom
|
|
422
|
+
* platforms; the host union-deduplicates and emits these as the
|
|
423
|
+
* iframe's `Permissions-Policy` HTTP header (public-render path)
|
|
424
|
+
* or `_meta.ui.permissions` (MCP-Apps embedded path).
|
|
425
|
+
*
|
|
426
|
+
* The bootstrap MIRRORS the same list inline so the iframe-runtime
|
|
427
|
+
* can surface the requested set to in-iframe debug overlays /
|
|
428
|
+
* permission-aware UI. The BROWSER-enforced gate, however, comes
|
|
429
|
+
* from the parent-page transport (HTTP header or iframe attribute)
|
|
430
|
+
* — the iframe-runtime itself cannot change Permissions-Policy
|
|
431
|
+
* post-load; it can only react to what the parent already granted.
|
|
432
|
+
*
|
|
433
|
+
* Absent / empty → no permissions requested (default-deny posture).
|
|
434
|
+
*
|
|
435
|
+
* @public
|
|
436
|
+
*/
|
|
437
|
+
readonly permissionsPolicy?: readonly string[];
|
|
438
|
+
/**
|
|
439
|
+
* Mirror of
|
|
440
|
+
* `handshakeOutput.serverCapabilities.streamWebSocketLocalTools` on
|
|
441
|
+
* the bootstrap envelope, so the iframe-runtime's per-channel
|
|
442
|
+
* transport router can decide WS-subscribe vs iframe-polling for
|
|
443
|
+
* each `streamSpec[ch].source.tool` without re-querying the
|
|
444
|
+
* handshake.
|
|
445
|
+
*
|
|
446
|
+
* Producer: the server's push handler at push time, sourced from
|
|
447
|
+
* `GguiPushHandlerDeps.streamWebSocketLocalTools` (which mirrors the
|
|
448
|
+
* resolver on the handshake handler). Consumer: iframe-runtime's
|
|
449
|
+
* channel-transport module, which:
|
|
450
|
+
*
|
|
451
|
+
* - For each `streamSpec[ch]` with `source.tool` declared AND that
|
|
452
|
+
* tool name is in this list → fire a `channel_subscribe` WS
|
|
453
|
+
* frame; wait for `channel_payload` deliveries.
|
|
454
|
+
* - Otherwise → start a per-channel iframe polling loop (default
|
|
455
|
+
* 10s cadence) that invokes `tools/call` directly through the
|
|
456
|
+
* MCP host proxy.
|
|
457
|
+
*
|
|
458
|
+
* Absent ⇒ universal iframe-polling fallback (no channel uses the
|
|
459
|
+
* WS-subscribe path). Present + empty array ⇒ same behavior; the
|
|
460
|
+
* empty list still says "the WS-subscribe path is supported but no
|
|
461
|
+
* tool is local".
|
|
462
|
+
*
|
|
463
|
+
* @public
|
|
464
|
+
*/
|
|
465
|
+
readonly streamWebSocketLocalTools?: readonly string[];
|
|
466
|
+
/**
|
|
467
|
+
* When `true`, this bootstrap describes a
|
|
468
|
+
* SESSION-SCOPED canvas iframe (one per session) rather than a
|
|
469
|
+
* per-stack-item iframe. The canvas:
|
|
470
|
+
*
|
|
471
|
+
* - Subscribes session-wide on the live channel (no `stackItemId` filter).
|
|
472
|
+
* - Renders a navigable stack of items, not a single pinned entry.
|
|
473
|
+
* - Owns its own chrome — the ggui animator pill + navbar.
|
|
474
|
+
* - Requests `pip` / `fullscreen` display modes from the host based
|
|
475
|
+
* on stack state.
|
|
476
|
+
*
|
|
477
|
+
* Mutually exclusive with `stackItemId`: a canvas iframe never pins
|
|
478
|
+
* to a single item. Defensive parsers SHOULD reject bootstraps with
|
|
479
|
+
* both fields set, but the protocol does not require them to.
|
|
480
|
+
*
|
|
481
|
+
* Absent / false ⇒ existing inline iframe behavior. Required to be
|
|
482
|
+
* explicit (rather than overloading absent-`stackItemId`) because
|
|
483
|
+
* legacy multi-item mode (Studio/Portal/console) ALSO uses
|
|
484
|
+
* absent-`stackItemId`; the explicit flag disambiguates.
|
|
485
|
+
*/
|
|
486
|
+
readonly canvasMode?: boolean;
|
|
487
|
+
/**
|
|
488
|
+
* Resolved gadget catalog the iframe-runtime dynamically
|
|
489
|
+
* imports at boot to populate `globalThis.__ggui__.gadgets`.
|
|
490
|
+
*
|
|
491
|
+
* One entry per registered gadget **package** (GG.8.2 — the channel
|
|
492
|
+
* is per-package, not per-hook: a package's whole module namespace
|
|
493
|
+
* is loaded once and stored under `__ggui__.gadgets[package]`, so
|
|
494
|
+
* every hook AND component export the package ships is reachable).
|
|
495
|
+
* STDLIB exports (the 7 first-party browser-capability hooks shipped
|
|
496
|
+
* by `@ggui-ai/gadgets`) are seeded unconditionally and need NOT
|
|
497
|
+
* appear here — only operator-registered 3rd-party packages (Leaflet,
|
|
498
|
+
* Mapbox, …) do. This list is what makes registered packages
|
|
499
|
+
* reachable inside the iframe.
|
|
500
|
+
*
|
|
501
|
+
* Producer: server's bootstrap builder, sourced from
|
|
502
|
+
* `App.gadgets`. Consumer: iframe-runtime's
|
|
503
|
+
* `loadGadgetRegistry()` which `await import(target)`s each
|
|
504
|
+
* package once and stores the module namespace under the
|
|
505
|
+
* package-name slot.
|
|
506
|
+
*
|
|
507
|
+
* Absent or empty → only STDLIB exports are reachable. Generated
|
|
508
|
+
* components that import an unregistered gadget package fail at the
|
|
509
|
+
* iframe's ESM module-eval (the rewriter has no shim for it).
|
|
510
|
+
*
|
|
511
|
+
* @public
|
|
512
|
+
*/
|
|
513
|
+
readonly gadgets?: ReadonlyArray<{
|
|
514
|
+
/** Bare npm package name (e.g. `@my-org/leaflet`). REQUIRED — it
|
|
515
|
+
* is the registry key the iframe-runtime stores the loaded module
|
|
516
|
+
* namespace under at `globalThis.__ggui__.gadgets[package]`, and
|
|
517
|
+
* the bare-specifier load source when `bundleUrl` is absent. */
|
|
518
|
+
readonly package: string;
|
|
519
|
+
/** ggui-hosted ESM bundle URL — preferred load source when present
|
|
520
|
+
* (same-origin posture, CSP-friendly). The iframe
|
|
521
|
+
* `await import(this)`; absent → the iframe imports the bare
|
|
522
|
+
* `package` specifier. */
|
|
523
|
+
readonly bundleUrl?: string;
|
|
524
|
+
/** SHA-384 SRI hash of the bundle (`sha384-<base64>`).
|
|
525
|
+
* When present alongside `bundleUrl`, iframe-runtime routes the
|
|
526
|
+
* load through a `<link rel="modulepreload" integrity>` gate so
|
|
527
|
+
* the browser refuses execution on hash mismatch. Absent → fall
|
|
528
|
+
* back to integrity-less dynamic `import()` (back-compat for
|
|
529
|
+
* in-tree packages and hand-authored ggui.json refs). */
|
|
530
|
+
readonly bundleSri?: string;
|
|
531
|
+
}>;
|
|
532
|
+
/**
|
|
533
|
+
* Public env values the iframe-runtime installs at
|
|
534
|
+
* `globalThis.__ggui__.publicEnv` for wrapper hooks to read via
|
|
535
|
+
* `getPublicEnv(key)`. Keys MUST match
|
|
536
|
+
* `PUBLIC_ENV_APP_KEY_RE` (`^GGUI_PUBLIC_APP_[A-Z0-9_]+$` — exported
|
|
537
|
+
* from `@ggui-ai/protocol`). The prefix is the security boundary:
|
|
538
|
+
* "public" means visible to anyone with iframe-source access.
|
|
539
|
+
*
|
|
540
|
+
* Filtered by the producer (push handler) to the **union of
|
|
541
|
+
* `wrapper.requires` across declared wrappers** — minimum-disclosure
|
|
542
|
+
* principle. Keys an iframe's wrappers don't ask for never reach
|
|
543
|
+
* the iframe.
|
|
544
|
+
*
|
|
545
|
+
* Producer: server's bootstrap builder, sourced from
|
|
546
|
+
* `App.publicEnv` cross-referenced against the declared wrappers'
|
|
547
|
+
* `requires`. Consumer: iframe-runtime's `installGlobalRegistry`
|
|
548
|
+
* which plants the map verbatim at `__ggui__.publicEnv`.
|
|
549
|
+
*
|
|
550
|
+
* Defensive parse: `parseBootstrap` re-validates every key against
|
|
551
|
+
* the regex; one bad key collapses the whole field to `undefined`
|
|
552
|
+
* (matches the `gadgets` / `contextSlots` parser posture).
|
|
553
|
+
*
|
|
554
|
+
* Absent or empty → no wrapper-readable env values; only wrappers
|
|
555
|
+
* with no `requires` declarations can mount.
|
|
556
|
+
*
|
|
557
|
+
* @public
|
|
558
|
+
*/
|
|
559
|
+
readonly publicEnv?: Readonly<Record<string, string>>;
|
|
560
|
+
/**
|
|
561
|
+
* Precompiled, eval-free runtime validators for the active stack
|
|
562
|
+
* item's contract specs. See {@link CompiledContractValidators}.
|
|
563
|
+
*
|
|
564
|
+
* Why precompiled: the renderer iframe runs under a strict CSP with
|
|
565
|
+
* no `'unsafe-eval'`, so it cannot compile JSON Schemas at runtime
|
|
566
|
+
* (`ajv.compile` builds validators via `new Function`, which the CSP
|
|
567
|
+
* blocks with `EvalError`). Compilation happens server-side at push
|
|
568
|
+
* time — where the contract schema is fixed and `eval` is legal —
|
|
569
|
+
* and the iframe loads each module via a `blob:` dynamic import
|
|
570
|
+
* (governed by `script-src`, not `unsafe-eval`) and only ever RUNS
|
|
571
|
+
* the validator.
|
|
572
|
+
*
|
|
573
|
+
* Producer: server push handler, via `compileValidatorModule` from
|
|
574
|
+
* `@ggui-ai/protocol`. Consumer: iframe-runtime's wire + channel
|
|
575
|
+
* validation layer.
|
|
576
|
+
*
|
|
577
|
+
* Absent → no precompiled validators shipped; the iframe falls back
|
|
578
|
+
* to the server as the sole contract authority (the push-time
|
|
579
|
+
* `assertActionContract` gate still enforces outbound actions).
|
|
580
|
+
*
|
|
581
|
+
* @public
|
|
582
|
+
*/
|
|
583
|
+
readonly compiledValidators?: CompiledContractValidators;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Precompiled, eval-free validators for a contract's runtime-validated
|
|
587
|
+
* specs — see {@link GguiBootstrapMeta.compiledValidators}.
|
|
588
|
+
*
|
|
589
|
+
* Each value is the SOURCE TEXT of an ES module whose `default` export
|
|
590
|
+
* is an Ajv validator function (`(data) => boolean`, carrying
|
|
591
|
+
* `.errors` after a run) — the output of `compileValidatorModule`.
|
|
592
|
+
* The iframe-runtime loads each via a `blob:` dynamic import.
|
|
593
|
+
*
|
|
594
|
+
* @public
|
|
595
|
+
*/
|
|
596
|
+
export interface CompiledContractValidators {
|
|
597
|
+
/** Validator for inbound runtime props (`DataContract.propsSpec`). */
|
|
598
|
+
readonly props?: string;
|
|
599
|
+
/**
|
|
600
|
+
* Validators for outbound action envelopes, keyed by action name
|
|
601
|
+
* (`DataContract.actionSpec`).
|
|
602
|
+
*/
|
|
603
|
+
readonly actions?: Readonly<Record<string, string>>;
|
|
604
|
+
/**
|
|
605
|
+
* Validators for inbound stream payloads, keyed by channel name
|
|
606
|
+
* (`DataContract.streamSpec`).
|
|
607
|
+
*/
|
|
608
|
+
readonly streams?: Readonly<Record<string, string>>;
|
|
609
|
+
/**
|
|
610
|
+
* Validators for inbound context-slot values, keyed by slot name
|
|
611
|
+
* (`DataContract.contextSpec`).
|
|
612
|
+
*/
|
|
613
|
+
readonly context?: Readonly<Record<string, string>>;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Derives the PascalCase Context name from a contextSpec slot key.
|
|
617
|
+
* E.g., `currentStep` → `CurrentStepContext`. Consumed by the server
|
|
618
|
+
* (when populating bootstrap.contextSlots) and the iframe-runtime
|
|
619
|
+
* boilerplate (when generating destructuring lines).
|
|
620
|
+
*
|
|
621
|
+
* Edge cases:
|
|
622
|
+
* - Empty input → `'Context'` (caller-fault path; documented for
|
|
623
|
+
* determinism).
|
|
624
|
+
* - Single-character input → `<UPPER>Context` (e.g. `'a'` → `'AContext'`).
|
|
625
|
+
*
|
|
626
|
+
* @public
|
|
627
|
+
*/
|
|
628
|
+
export declare function deriveContextName(slotKey: string): string;
|
|
629
|
+
/**
|
|
630
|
+
* Shape of the `_meta` field on a `ggui_push` tool result when the server
|
|
631
|
+
* is the MCP Apps outbound host. Wrapping the bootstrap under `_meta.ggui.*`
|
|
632
|
+
* keeps ggui's namespace distinct from MCP Apps' own `_meta.ui.*` on the
|
|
633
|
+
* tool declaration side.
|
|
634
|
+
*
|
|
635
|
+
* Hosts forward `_meta` alongside `structuredContent` when they pass tool
|
|
636
|
+
* output into `ui/initialize`; the view reads `_meta.ggui.bootstrap` from
|
|
637
|
+
* the initialization context.
|
|
638
|
+
*/
|
|
639
|
+
export interface PushResultMeta {
|
|
640
|
+
readonly ggui: {
|
|
641
|
+
readonly bootstrap: GguiBootstrapMeta;
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* CSP metadata copied from an MCP Apps resource declaration.
|
|
646
|
+
* Spec-canonical field names — do NOT rename.
|
|
647
|
+
*/
|
|
648
|
+
export interface McpAppsCsp {
|
|
649
|
+
readonly connectDomains?: string[];
|
|
650
|
+
readonly resourceDomains?: string[];
|
|
651
|
+
readonly frameDomains?: string[];
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Permissions Policy metadata copied from an MCP Apps resource
|
|
655
|
+
* declaration. Spec-canonical field names — do NOT rename.
|
|
656
|
+
*/
|
|
657
|
+
export interface McpAppsPermissions {
|
|
658
|
+
readonly camera?: boolean;
|
|
659
|
+
readonly microphone?: boolean;
|
|
660
|
+
readonly geolocation?: boolean;
|
|
661
|
+
readonly clipboardWrite?: boolean;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Container dimensions hint passed to the embedded iframe via the
|
|
665
|
+
* MCP Apps `ui/initialize` response.
|
|
666
|
+
*/
|
|
667
|
+
export interface McpAppsContainerDimensions {
|
|
668
|
+
readonly height?: number;
|
|
669
|
+
readonly width?: number;
|
|
670
|
+
readonly maxHeight?: number;
|
|
671
|
+
readonly maxWidth?: number;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Locator for the source of an embedded MCP App.
|
|
675
|
+
*
|
|
676
|
+
* Persists STABLE identity (not a raw URL) so session state survives
|
|
677
|
+
* source-server endpoint changes. The runtime `ConnectorRegistry`
|
|
678
|
+
* resolves `connectorId` to the actual endpoint at render time.
|
|
679
|
+
*/
|
|
680
|
+
export interface McpAppsSource {
|
|
681
|
+
/** Stable connector id declared in the app's connector registry. */
|
|
682
|
+
readonly connectorId: string;
|
|
683
|
+
/** Source-server tool whose call produced this UI; scope for
|
|
684
|
+
* `tools/call` proxying. */
|
|
685
|
+
readonly toolName: string;
|
|
686
|
+
/** `ui://` resource URI declared on the source tool's
|
|
687
|
+
* `_meta.ui.resourceUri`. */
|
|
688
|
+
readonly resourceUri: string;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Stack-item variant: an embedded third-party MCP App iframe.
|
|
692
|
+
*
|
|
693
|
+
* **Locator-oriented, not content-oriented.** Persisted state carries
|
|
694
|
+
* `source` (connector identity) + declared CSP/permissions/dimensions
|
|
695
|
+
* metadata; resource BYTES are not stored in session state by default.
|
|
696
|
+
* The `@ggui-ai/mcp-server` resource-proxy route fetches the bytes
|
|
697
|
+
* on-demand via `resources/read` against the source server.
|
|
698
|
+
*
|
|
699
|
+
* **Union safety.** Fields that exist on the {@link StackItem}
|
|
700
|
+
* (generated / native component) variant are declared here as
|
|
701
|
+
* `?: never` so consumers that access them via optional chaining on
|
|
702
|
+
* `SessionStackEntry` still typecheck cleanly. Those fields semantically
|
|
703
|
+
* DO NOT exist on McpAppsStackItem — the `?: never` typing encodes the
|
|
704
|
+
* "structurally absent" guarantee.
|
|
705
|
+
*/
|
|
706
|
+
export interface McpAppsStackItem {
|
|
707
|
+
/** Discriminator — required on this variant. */
|
|
708
|
+
readonly type: 'mcpApps';
|
|
709
|
+
readonly id: string;
|
|
710
|
+
readonly createdAt: string;
|
|
711
|
+
readonly prompt?: string;
|
|
712
|
+
readonly description?: string;
|
|
713
|
+
readonly message?: string;
|
|
714
|
+
readonly source: McpAppsSource;
|
|
715
|
+
readonly csp?: McpAppsCsp;
|
|
716
|
+
readonly permissions?: McpAppsPermissions;
|
|
717
|
+
readonly containerDimensions?: McpAppsContainerDimensions;
|
|
718
|
+
/**
|
|
719
|
+
* Optional integrity pin — sha256 of the resource bytes computed at
|
|
720
|
+
* push time. The resource-proxy route verifies the re-fetched
|
|
721
|
+
* content against this hash; a mismatch breaks the stack item
|
|
722
|
+
* LOUDLY rather than silently serving mutated content.
|
|
723
|
+
*/
|
|
724
|
+
readonly resourceHash?: string;
|
|
725
|
+
/**
|
|
726
|
+
* Bounded dev/cache optimization. When present, the proxy route MAY
|
|
727
|
+
* serve this inline instead of re-fetching via `resources/read`. NOT
|
|
728
|
+
* the canonical carrier — metadata persists, bytes don't. Use only
|
|
729
|
+
* for dev harnesses / offline replay.
|
|
730
|
+
*/
|
|
731
|
+
readonly resourceContent?: string;
|
|
732
|
+
readonly componentCode?: never;
|
|
733
|
+
readonly props?: never;
|
|
734
|
+
readonly contentType?: never;
|
|
735
|
+
readonly schema?: never;
|
|
736
|
+
readonly subscription?: never;
|
|
737
|
+
readonly capabilities?: never;
|
|
738
|
+
readonly actions?: never;
|
|
739
|
+
readonly quality?: never;
|
|
740
|
+
readonly error?: never;
|
|
741
|
+
readonly streamSpec?: never;
|
|
742
|
+
readonly propsSpec?: never;
|
|
743
|
+
readonly actionSpec?: never;
|
|
744
|
+
readonly contextSpec?: never;
|
|
745
|
+
readonly clientCapabilities?: never;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Type guard: narrows a `SessionStackEntry` (or unknown) to
|
|
749
|
+
* {@link McpAppsStackItem}. Uses the discriminator.
|
|
750
|
+
*/
|
|
751
|
+
export declare function isMcpAppsStackItem(entry: unknown): entry is McpAppsStackItem;
|
|
752
|
+
/**
|
|
753
|
+
* Structural validator for an `McpAppsStackItem` — not a Zod schema
|
|
754
|
+
* so we don't force a Zod dependency here. Returns null on failure
|
|
755
|
+
* (caller maps to an appropriate error code). Required when accepting
|
|
756
|
+
* one over the wire from an agent: the discriminator alone isn't
|
|
757
|
+
* enough.
|
|
758
|
+
*/
|
|
759
|
+
export declare function validateMcpAppsStackItem(input: unknown): McpAppsStackItem | null;
|
|
760
|
+
/**
|
|
761
|
+
* Lifecycle states the renderer transitions through inside an MCP Apps
|
|
762
|
+
* iframe. Closed union — adding a new state is a protocol-version-
|
|
763
|
+
* eligible change. Hosts that don't recognise a state MUST treat it as
|
|
764
|
+
* a no-op (don't mirror it, don't crash).
|
|
765
|
+
*
|
|
766
|
+
* State machine:
|
|
767
|
+
*
|
|
768
|
+
* ```
|
|
769
|
+
* ┌────────────┐
|
|
770
|
+
* (iframe boot) │ mounting │
|
|
771
|
+
* └─────┬──────┘
|
|
772
|
+
* │ bundle evaluated +
|
|
773
|
+
* │ React tree mounted +
|
|
774
|
+
* │ WS handshake completed
|
|
775
|
+
* ▼
|
|
776
|
+
* ┌─────────────┐
|
|
777
|
+
* │ code-ready │◀────── (terminal happy state)
|
|
778
|
+
* └──┬─────┬────┘
|
|
779
|
+
* │ │
|
|
780
|
+
* (WS close) │ │ (eval / mount / handshake throw)
|
|
781
|
+
* ▼ ▼
|
|
782
|
+
* ┌──────────┐ ┌───────┐
|
|
783
|
+
* │disconnected│ │ error │
|
|
784
|
+
* └────────────┘ └───────┘
|
|
785
|
+
* ```
|
|
786
|
+
*
|
|
787
|
+
* - `mounting` — emitted ASAP after iframe boot (before bundle eval).
|
|
788
|
+
* A host that observes only `mounting` and never a follow-up state
|
|
789
|
+
* has a renderer that crashed before posting code-ready/error.
|
|
790
|
+
* - `code-ready` — happy-path terminal state. Bundle evaluated, React
|
|
791
|
+
* tree mounted, WS connected, first stack ack folded. Equivalent of
|
|
792
|
+
* the in-iframe `data-ggui-status="connected"`.
|
|
793
|
+
* - `error` — terminal failure. Pairs with the existing
|
|
794
|
+
* `ggui:bootstrap-failed` postMessage envelope which carries the
|
|
795
|
+
* typed reason; this lifecycle state is the COARSE outer-DOM signal
|
|
796
|
+
* ("renderer is not going to come up — give up waiting").
|
|
797
|
+
* - `disconnected` — non-terminal. WebSocket closed after a successful
|
|
798
|
+
* `code-ready`. The renderer MAY transition back to `code-ready` if
|
|
799
|
+
* reconnection succeeds (subscribe.ts owns the reconnect ladder);
|
|
800
|
+
* hosts that pin selectors on `code-ready` will re-resolve when it
|
|
801
|
+
* does.
|
|
802
|
+
*
|
|
803
|
+
* @public
|
|
804
|
+
*/
|
|
805
|
+
export type McpAppLifecycleState = 'mounting' | 'code-ready' | 'error' | 'disconnected';
|
|
806
|
+
/**
|
|
807
|
+
* Lifecycle event payload shape. Carried inside an
|
|
808
|
+
* {@link McpAppLifecycleMessage} envelope (`type: 'ggui:lifecycle'`).
|
|
809
|
+
*
|
|
810
|
+
* Fields:
|
|
811
|
+
* - `state` — required. The lifecycle state being entered.
|
|
812
|
+
* - `stackItemId` — optional. When present, the lifecycle pertains
|
|
813
|
+
* to a specific stack item (per-card iframes via single-item
|
|
814
|
+
* mode). Absent → whole-renderer lifecycle.
|
|
815
|
+
* - `error` — optional, only meaningful when `state === 'error'`.
|
|
816
|
+
* Mirrors the `ggui:bootstrap-failed` postMessage envelope's
|
|
817
|
+
* `reason` + `message` so a single `ggui:lifecycle` listener can
|
|
818
|
+
* surface both the coarse signal AND the typed cause without
|
|
819
|
+
* subscribing to two envelopes. Producers SHOULD set this when
|
|
820
|
+
* `state === 'error'`; it is OPTIONAL because legacy producers
|
|
821
|
+
* emitted no lifecycle event at all and we don't want to require
|
|
822
|
+
* a code change for the coarse signal alone.
|
|
823
|
+
*
|
|
824
|
+
* Producers MUST NOT add fields not enumerated here in this shape;
|
|
825
|
+
* additive evolution requires a new optional key + a doc revision so
|
|
826
|
+
* hosts know what they may observe. Consumers MUST ignore unknown
|
|
827
|
+
* fields (shape-preserving extensibility).
|
|
828
|
+
*
|
|
829
|
+
* @public
|
|
830
|
+
*/
|
|
831
|
+
export interface McpAppLifecycleEvent {
|
|
832
|
+
readonly state: McpAppLifecycleState;
|
|
833
|
+
readonly stackItemId?: string;
|
|
834
|
+
readonly error?: {
|
|
835
|
+
readonly code: string;
|
|
836
|
+
readonly message: string;
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* postMessage envelope the renderer posts to its parent on every
|
|
841
|
+
* lifecycle transition. The string `'ggui:lifecycle'` is the protocol-
|
|
842
|
+
* canonical envelope tag — hosts filter `event.data.type` to subscribe.
|
|
843
|
+
*
|
|
844
|
+
* **Named parties:**
|
|
845
|
+
* - **Renderer** (producer) — running inside the MCP Apps iframe;
|
|
846
|
+
* emits one envelope per state transition.
|
|
847
|
+
* - **Host** (consumer) — running in the parent window (e.g.,
|
|
848
|
+
* `<McpAppIframe>`); listens on `window.message`, narrows
|
|
849
|
+
* `event.source` to the iframe's `contentWindow`, and mirrors
|
|
850
|
+
* `event.state` onto the outer iframe element.
|
|
851
|
+
* - **Observer** (downstream) — tests, accessibility scanners, dev
|
|
852
|
+
* inspectors; read the host-mirrored attribute on the outer DOM
|
|
853
|
+
* element. Observers DO NOT subscribe to postMessage directly —
|
|
854
|
+
* the host is the protocol-defined mirror point.
|
|
855
|
+
*
|
|
856
|
+
* **Obligations:**
|
|
857
|
+
* - Renderer MUST post `mounting` before evaluating the bundle.
|
|
858
|
+
* - Renderer MUST post exactly one terminal state (`code-ready`
|
|
859
|
+
* or `error`) for any successful boot attempt.
|
|
860
|
+
* - Renderer MAY post `disconnected` after a `code-ready` and MAY
|
|
861
|
+
* post `code-ready` again after a successful reconnect.
|
|
862
|
+
* - Host MUST mirror the latest received state onto the outer
|
|
863
|
+
* element via the `data-ggui-mcp-app-iframe-lifecycle="<state>"`
|
|
864
|
+
* attribute. Idempotent re-emission of the same state is a no-op.
|
|
865
|
+
* - Host MUST narrow `event.source` to the iframe's `contentWindow`
|
|
866
|
+
* before trusting the envelope (cross-frame postMessage is the
|
|
867
|
+
* attack surface; envelopes from other windows MUST be dropped).
|
|
868
|
+
*
|
|
869
|
+
* **Defined failure modes:**
|
|
870
|
+
* - Renderer never emits any lifecycle event → host's outer-element
|
|
871
|
+
* attribute is never set, observers timeout waiting for a state.
|
|
872
|
+
* This is the UN-INSTRUMENTED legacy case; not a violation.
|
|
873
|
+
* - Renderer emits `mounting` then no terminal state → host's
|
|
874
|
+
* attribute pins to `'mounting'`. Observers waiting for
|
|
875
|
+
* `'code-ready'` see a stuck attribute and fail their own timeout
|
|
876
|
+
* — the coarse-grained surfacing of "renderer crashed before
|
|
877
|
+
* declaring ready". Hosts MAY layer a watchdog on top to
|
|
878
|
+
* transition the attribute to a synthetic `'timeout'` state, but
|
|
879
|
+
* that is host policy, not protocol obligation.
|
|
880
|
+
* - Renderer emits `code-ready` and the WS later drops without a
|
|
881
|
+
* subsequent `disconnected` → host's attribute remains
|
|
882
|
+
* `'code-ready'`. This is shape-acceptable because reconnect
|
|
883
|
+
* attempts are still in flight; observers that need finer-
|
|
884
|
+
* grained connection state subscribe to `ggui:observe`'s
|
|
885
|
+
* `subscribe-failed` events instead.
|
|
886
|
+
*
|
|
887
|
+
* **Observable violation:**
|
|
888
|
+
* - The outer-element attribute. A renderer that posts envelopes
|
|
889
|
+
* the host can't classify (wrong shape, wrong type tag) does NOT
|
|
890
|
+
* update the attribute; the violation is observable as a stuck
|
|
891
|
+
* attribute relative to the inferred WS / DOM state of the
|
|
892
|
+
* iframe child.
|
|
893
|
+
*
|
|
894
|
+
* @public
|
|
895
|
+
*/
|
|
896
|
+
export interface McpAppLifecycleMessage {
|
|
897
|
+
readonly type: 'ggui:lifecycle';
|
|
898
|
+
readonly event: McpAppLifecycleEvent;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* The closed set of valid lifecycle states. Exposed as a `readonly`
|
|
902
|
+
* tuple so consumers (renderer host filters, conformance tests) can
|
|
903
|
+
* iterate without re-typing the union literally.
|
|
904
|
+
*
|
|
905
|
+
* @public
|
|
906
|
+
*/
|
|
907
|
+
export declare const MCP_APP_LIFECYCLE_STATES: readonly McpAppLifecycleState[];
|
|
908
|
+
/**
|
|
909
|
+
* Type guard for {@link McpAppLifecycleMessage}. Trust-boundary helper
|
|
910
|
+
* — apps consuming raw postMessage data MUST narrow before reading
|
|
911
|
+
* `event.state` to avoid reaching into untyped property bags.
|
|
912
|
+
*
|
|
913
|
+
* Validation rules (all required for `true`):
|
|
914
|
+
* - Outer envelope is an object with `type === 'ggui:lifecycle'`.
|
|
915
|
+
* - `event` is an object with `state` matching {@link
|
|
916
|
+
* MCP_APP_LIFECYCLE_STATES}.
|
|
917
|
+
* - If `stackItemId` is present, it is a non-empty string.
|
|
918
|
+
* - If `error` is present, it is an object with string `code` +
|
|
919
|
+
* `message`.
|
|
920
|
+
*
|
|
921
|
+
* @public
|
|
922
|
+
*/
|
|
923
|
+
export declare function isMcpAppLifecycleMessage(message: unknown): message is McpAppLifecycleMessage;
|
|
924
|
+
/**
|
|
925
|
+
* Type guard for recognizing a `ggui_push` result `_meta` that carries a
|
|
926
|
+
* bootstrap block. Handy for views / tests that receive the raw result.
|
|
927
|
+
*
|
|
928
|
+
* Validates the three-mode discriminator: a well-formed bootstrap MUST
|
|
929
|
+
* carry `runtimeUrl` + `sessionId` + `appId` AND at least one of
|
|
930
|
+
* `{wsUrl, codeUrl, kind}` as a non-empty string. Live mode
|
|
931
|
+
* additionally requires `token` to pair with `wsUrl` — half-live
|
|
932
|
+
* (one without the other) is rejected as MALFORMED.
|
|
933
|
+
*/
|
|
934
|
+
export declare function hasPushBootstrapMeta(meta: unknown): meta is PushResultMeta;
|
|
935
|
+
/**
|
|
936
|
+
* Discriminator for the user-action envelope delivered via
|
|
937
|
+
* `ggui_runtime_submit_action` over the MCP Apps host-relay path
|
|
938
|
+
* (postMessage `tools/call` → host MCP client → server). Every
|
|
939
|
+
* user-driven `WireConfig` method emits this envelope so operators get
|
|
940
|
+
* **uniform server-side observability** across every gesture kind
|
|
941
|
+
* regardless of which user-visible effect the iframe already fired
|
|
942
|
+
* locally (`ui/open-link` / `ui/request-display-mode`) before the audit.
|
|
943
|
+
*
|
|
944
|
+
* **Closed primary set, extensibly-closed forward-compat.** The three
|
|
945
|
+
* primary kinds correspond 1:1 to the `WireConfig` methods that emit
|
|
946
|
+
* gestures today. Forward additions land via the `(string & {})` slot
|
|
947
|
+
* — handlers MUST treat unknown values gracefully (log under an
|
|
948
|
+
* `'unknown'` bucket, never throw or hard-switch). Adding a new kind
|
|
949
|
+
* is additive and does NOT bump the protocol version.
|
|
950
|
+
*
|
|
951
|
+
* | kind | primary host effect | payload shape |
|
|
952
|
+
* | ----------------------- | -------------------------------- | ---------------------------------------------------------------------- |
|
|
953
|
+
* | `dispatch` | pipe append (single source) | `{ intent: string, actionData: JsonValue \| null, uiContext: JsonObject }` |
|
|
954
|
+
* | `openLink` | `ui/open-link` | `{ url: string }` |
|
|
955
|
+
* | `requestDisplayMode` | `ui/request-display-mode` | `{ mode: 'fullscreen' \| 'pip' \| 'inline' }` |
|
|
956
|
+
*
|
|
957
|
+
* Audit is **fail-soft** at the client: if the `tools/call` envelope
|
|
958
|
+
* fails to deliver (host rejects, postMessage on detached parent), the
|
|
959
|
+
* primary host effect MUST still proceed. The audit miss surfaces as a
|
|
960
|
+
* diagnostic on the operator side (gap in the SessionInspector activity
|
|
961
|
+
* row), not as a user-facing failure. This mirrors today's `dispatch`
|
|
962
|
+
* audit-fire posture so semantics stay uniform.
|
|
963
|
+
*
|
|
964
|
+
* Failure-mode note: a malformed envelope (unknown `kind` AND malformed
|
|
965
|
+
* `payload`) lands as `INVALID_ACTION_KIND` on `_ggui:contract-error`.
|
|
966
|
+
* See `ContractErrorCode` for the canonical extensibly-closed code set.
|
|
967
|
+
*/
|
|
968
|
+
export type SubmitActionKind = 'dispatch' | 'openLink' | 'requestDisplayMode' | (string & {});
|
|
969
|
+
/**
|
|
970
|
+
* Per-kind payload schemas for {@link SubmitActionKind}. Keep this discriminated
|
|
971
|
+
* union narrow — adding a new gesture means adding both a kind variant AND
|
|
972
|
+
* its payload shape here, in lockstep, so the `ggui_runtime_submit_action`
|
|
973
|
+
* handler's input parser can validate exhaustively.
|
|
974
|
+
*
|
|
975
|
+
* `payload` for the unknown `(string & {})` extension slot widens to
|
|
976
|
+
* `Record<string, unknown>` — handlers MUST validate shape against their
|
|
977
|
+
* own schema before consuming, since the protocol type can't narrow it.
|
|
978
|
+
*/
|
|
979
|
+
export type SubmitActionEnvelope = {
|
|
980
|
+
readonly kind: 'dispatch';
|
|
981
|
+
readonly payload: {
|
|
982
|
+
/** `actionSpec[*]` key the iframe dispatched against. */
|
|
983
|
+
readonly intent: string;
|
|
984
|
+
/**
|
|
985
|
+
* Typed payload satisfying `actionSpec[intent].schema`.
|
|
986
|
+
* `null` for no-payload gestures (bare button click).
|
|
987
|
+
*/
|
|
988
|
+
readonly actionData: JsonValue | null;
|
|
989
|
+
/**
|
|
990
|
+
* Iframe-local snapshot of the contract's `contextSpec` slot
|
|
991
|
+
* values at the moment the user fired the gesture. Captured at
|
|
992
|
+
* gesture time so the agent can reason about WHAT the user did
|
|
993
|
+
* AND WHAT THEY WERE LOOKING AT atomically — without a second
|
|
994
|
+
* round trip to read state from the rendered UI.
|
|
995
|
+
*
|
|
996
|
+
* Empty object `{}` when the contract has no `contextSpec` or
|
|
997
|
+
* the iframe hasn't yet mirrored any slots.
|
|
998
|
+
*/
|
|
999
|
+
readonly uiContext: JsonObject;
|
|
1000
|
+
};
|
|
1001
|
+
} | {
|
|
1002
|
+
readonly kind: 'openLink';
|
|
1003
|
+
readonly payload: {
|
|
1004
|
+
readonly url: string;
|
|
1005
|
+
};
|
|
1006
|
+
} | {
|
|
1007
|
+
readonly kind: 'requestDisplayMode';
|
|
1008
|
+
readonly payload: {
|
|
1009
|
+
readonly mode: 'fullscreen' | 'pip' | 'inline' | (string & {});
|
|
1010
|
+
};
|
|
1011
|
+
} | {
|
|
1012
|
+
readonly kind: string;
|
|
1013
|
+
readonly payload: Record<string, unknown>;
|
|
1014
|
+
};
|
|
1015
|
+
/**
|
|
1016
|
+
* Canonical input contract for the `ggui_runtime_submit_action` MCP tool.
|
|
1017
|
+
* The iframe-runtime delivers this via the MCP Apps host-relay path
|
|
1018
|
+
* (postMessage `tools/call` → host MCP client → server) — the iframe
|
|
1019
|
+
* has no auth credential of its own, so the host is the protocol-
|
|
1020
|
+
* defined relay party (per `_meta.ui.visibility: ['app']` on the
|
|
1021
|
+
* tool declaration).
|
|
1022
|
+
*
|
|
1023
|
+
* Per-kind semantics:
|
|
1024
|
+
*
|
|
1025
|
+
* - `kind === 'dispatch'`: server appends a consume-entry onto the
|
|
1026
|
+
* stackItem-keyed pending-events pipe (`{type:'action', stackItemId,
|
|
1027
|
+
* intent, actionData, uiContext, actionId, firedAt}`) so the agent's
|
|
1028
|
+
* `ggui_consume` long-poll unblocks in the same chat turn. When the
|
|
1029
|
+
* pipe is closed/missing (popped/closed/never opened), the handler
|
|
1030
|
+
* returns `{ok:false, code:'PIPE_NOT_FOUND'}` and the iframe-runtime
|
|
1031
|
+
* falls through to a `ui/message` envelope carrying
|
|
1032
|
+
* `_meta.ggui.userAction` (see {@link GguiUserActionMeta}) so the
|
|
1033
|
+
* gesture still reaches the agent on its next turn.
|
|
1034
|
+
* - `kind ∈ {'openLink','requestDisplayMode'}`: pure audit — the
|
|
1035
|
+
* user-visible host effect already fired iframe-side via
|
|
1036
|
+
* `ui/open-link` / `ui/request-display-mode`. The server records
|
|
1037
|
+
* the gesture for the SessionInspector feed.
|
|
1038
|
+
*
|
|
1039
|
+
* Required fields:
|
|
1040
|
+
* - `sessionId` / `appId`: bootstrap-issued; server cross-checks.
|
|
1041
|
+
* - `actionId`: 8-hex correlation hash (FNV-1a of intent + data + firedAt
|
|
1042
|
+
* for `dispatch`, kind + payload + firedAt for the host-control kinds).
|
|
1043
|
+
* Lets the host LLM cross-verify a `[ggui:pending-action]` context entry
|
|
1044
|
+
* against a `ui/message` consent prompt by id.
|
|
1045
|
+
* - `firedAt`: ISO-8601 client-monotonic timestamp; useful for ordering
|
|
1046
|
+
* and replay diagnostics. Server uses its own clock for authoritative
|
|
1047
|
+
* log ordering.
|
|
1048
|
+
*
|
|
1049
|
+
* The discriminated `kind` + `payload` pair carries the actual gesture
|
|
1050
|
+
* shape — see {@link SubmitActionEnvelope}.
|
|
1051
|
+
*/
|
|
1052
|
+
export type GguiSubmitActionInput = SubmitActionEnvelope & {
|
|
1053
|
+
readonly sessionId: string;
|
|
1054
|
+
/**
|
|
1055
|
+
* Active stack item id. Optional because the iframe-runtime boots
|
|
1056
|
+
* into a stack-item context only when the host minted one via
|
|
1057
|
+
* `ggui_push` — boot scenarios like system-cards or pre-push
|
|
1058
|
+
* provisional previews don't carry one. Required when
|
|
1059
|
+
* `kind === 'dispatch'` (the kind that needs to land in the
|
|
1060
|
+
* stackItem-keyed pending-event pipe); the server-side handler
|
|
1061
|
+
* rejects dispatch envelopes missing this field.
|
|
1062
|
+
*/
|
|
1063
|
+
readonly stackItemId?: string;
|
|
1064
|
+
readonly appId: string;
|
|
1065
|
+
readonly actionId: string;
|
|
1066
|
+
readonly firedAt: string;
|
|
1067
|
+
};
|
|
1068
|
+
/**
|
|
1069
|
+
* The three canonical gesture kinds — useful for exhaustiveness checks
|
|
1070
|
+
* in `switch (kind) { ... }` blocks. Frozen so consumers can safely use
|
|
1071
|
+
* `as const` against the readonly tuple.
|
|
1072
|
+
*/
|
|
1073
|
+
export declare const SUBMIT_ACTION_KINDS: readonly ["dispatch", "openLink", "requestDisplayMode"];
|
|
1074
|
+
/**
|
|
1075
|
+
* Type guard narrowing an unknown value to {@link GguiSubmitActionInput}.
|
|
1076
|
+
* Validates the `kind` discriminator + the per-kind `payload` shape.
|
|
1077
|
+
* Used by the server-side `ggui_runtime_submit_action` handler to reject
|
|
1078
|
+
* malformed envelopes with `INVALID_ACTION_KIND` instead of silently
|
|
1079
|
+
* coercing.
|
|
1080
|
+
*
|
|
1081
|
+
* Unknown extension kinds are accepted at this guard layer (the
|
|
1082
|
+
* `(string & {})` slot is part of the type) but the per-kind payload
|
|
1083
|
+
* narrowing collapses to `Record<string, unknown>` — extension-handlers
|
|
1084
|
+
* MUST validate shape before consuming.
|
|
1085
|
+
*/
|
|
1086
|
+
export declare function isGguiSubmitActionInput(value: unknown): value is GguiSubmitActionInput;
|
|
1087
|
+
/**
|
|
1088
|
+
* Discriminator the iframe-runtime stamps on a `ui/message` envelope's
|
|
1089
|
+
* `params._meta.ggui.userAction` when a user gesture inside a ggui-
|
|
1090
|
+
* rendered iframe needs to flow to the agent through chat (rather than
|
|
1091
|
+
* direct WS drain via `ggui_consume`).
|
|
1092
|
+
*
|
|
1093
|
+
* Discriminated by `kind`:
|
|
1094
|
+
*
|
|
1095
|
+
* - **`'queued'`** — pipe HAS the event; agent should call
|
|
1096
|
+
* `ggui_consume({stackItemId})` to drain. The prepared call lives
|
|
1097
|
+
* in `nextStep` as `{tool: 'ggui_consume', args: {stackItemId}}`
|
|
1098
|
+
* so the SDK can dispatch verbatim without arg-construction.
|
|
1099
|
+
*
|
|
1100
|
+
* - **`'inline'`** — pipe is GONE (popped / session closed / never
|
|
1101
|
+
* opened). Action data is carried inline in `payload`. The agent
|
|
1102
|
+
* MUST act on this directly; calling `ggui_consume` for this
|
|
1103
|
+
* `stackItemId` would return empty.
|
|
1104
|
+
*
|
|
1105
|
+
* The accompanying `ui/message` text mirrors the structure in human-
|
|
1106
|
+
* readable form so agnostic LLMs without `_meta` awareness still get
|
|
1107
|
+
* the right instruction.
|
|
1108
|
+
*
|
|
1109
|
+
* **Presence is the fingerprint** — agnostic hosts ignore the field;
|
|
1110
|
+
* ggui-aware consumers route via {@link isGguiUserActionMeta}.
|
|
1111
|
+
*
|
|
1112
|
+
* Unified shape with a `kind` discriminator (`queued` | `inline`)
|
|
1113
|
+
* parallels the protocol's other discriminated unions, e.g.
|
|
1114
|
+
* {@link SubmitActionEnvelope}'s `kind`.
|
|
1115
|
+
*
|
|
1116
|
+
* @public
|
|
1117
|
+
*/
|
|
1118
|
+
export type GguiUserActionMeta = QueuedUserActionMeta | InlineUserActionMeta;
|
|
1119
|
+
/**
|
|
1120
|
+
* `_meta.ggui.userAction` variant — pipe has the event; agent dispatches
|
|
1121
|
+
* the prepared `ggui_consume` call to drain. See {@link GguiUserActionMeta}.
|
|
1122
|
+
*
|
|
1123
|
+
* @public
|
|
1124
|
+
*/
|
|
1125
|
+
export interface QueuedUserActionMeta {
|
|
1126
|
+
readonly kind: 'queued';
|
|
1127
|
+
/**
|
|
1128
|
+
* Human-readable one-liner summary for logs / SDK debug surfaces.
|
|
1129
|
+
* Mirrors the chat-visible text but as a structured field so
|
|
1130
|
+
* consumers don't need to parse natural language.
|
|
1131
|
+
*/
|
|
1132
|
+
readonly description: string;
|
|
1133
|
+
/** Stack item the gesture targeted. */
|
|
1134
|
+
readonly stackItemId: string;
|
|
1135
|
+
/** 8-hex FNV-1a correlation id of the gesture. */
|
|
1136
|
+
readonly actionId: string;
|
|
1137
|
+
/** ISO 8601 UTC timestamp of the gesture (iframe local clock). */
|
|
1138
|
+
readonly submittedAt: string;
|
|
1139
|
+
/** Which `actionSpec[*]` entry the iframe dispatched against. */
|
|
1140
|
+
readonly intent: string;
|
|
1141
|
+
/**
|
|
1142
|
+
* Prepared tool call the agent SHOULD dispatch verbatim. Embeds the
|
|
1143
|
+
* `stackItemId` so the SDK doesn't have to thread it manually —
|
|
1144
|
+
* reduces "wrong/missing args" failure modes.
|
|
1145
|
+
*/
|
|
1146
|
+
readonly nextStep: {
|
|
1147
|
+
readonly tool: 'ggui_consume';
|
|
1148
|
+
readonly args: {
|
|
1149
|
+
readonly stackItemId: string;
|
|
1150
|
+
};
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* `_meta.ggui.userAction` variant — pipe is gone; action + ui context
|
|
1155
|
+
* delivered inline. Agent acts on `payload` directly; MUST NOT call
|
|
1156
|
+
* `ggui_consume` for `stackItemId` (no pipe to drain).
|
|
1157
|
+
*
|
|
1158
|
+
* `nextStep` is optional — when the original `actionSpec[intent]` declared
|
|
1159
|
+
* a `nextStep` (the bound agent tool), it's surfaced here as a string
|
|
1160
|
+
* hint so the LLM has a strong steer toward the right tool. When the
|
|
1161
|
+
* contract author left `nextStep` undeclared, the agent is fully free
|
|
1162
|
+
* to choose how to react.
|
|
1163
|
+
*
|
|
1164
|
+
* @public
|
|
1165
|
+
*/
|
|
1166
|
+
export interface InlineUserActionMeta {
|
|
1167
|
+
readonly kind: 'inline';
|
|
1168
|
+
/**
|
|
1169
|
+
* Human-readable one-liner summary for logs / SDK debug surfaces.
|
|
1170
|
+
*/
|
|
1171
|
+
readonly description: string;
|
|
1172
|
+
/** Stack item the gesture targeted. */
|
|
1173
|
+
readonly stackItemId: string;
|
|
1174
|
+
/** 8-hex FNV-1a correlation id of the gesture. */
|
|
1175
|
+
readonly actionId: string;
|
|
1176
|
+
/** ISO 8601 UTC timestamp of the gesture (iframe local clock). */
|
|
1177
|
+
readonly submittedAt: string;
|
|
1178
|
+
/** Which `actionSpec[*]` entry the iframe dispatched against. */
|
|
1179
|
+
readonly intent: string;
|
|
1180
|
+
/**
|
|
1181
|
+
* Both halves of the gesture, captured atomically at gesture time:
|
|
1182
|
+
*
|
|
1183
|
+
* - `actionData` — typed payload satisfying `actionSpec[intent].schema`.
|
|
1184
|
+
* `null` for no-payload gestures (bare button click).
|
|
1185
|
+
* - `uiContext` — snapshot of the iframe's contextSpec values at
|
|
1186
|
+
* the moment the user fired the gesture. Typed by
|
|
1187
|
+
* the contract's `contextSpec`.
|
|
1188
|
+
*
|
|
1189
|
+
* The pair is the SEMANTIC UNIT — what the user did AND what they
|
|
1190
|
+
* were looking at when they did it. Captured at gesture time (not
|
|
1191
|
+
* drain time) for honest history.
|
|
1192
|
+
*/
|
|
1193
|
+
readonly payload: {
|
|
1194
|
+
readonly actionData: JsonValue | null;
|
|
1195
|
+
readonly uiContext: JsonObject;
|
|
1196
|
+
};
|
|
1197
|
+
/**
|
|
1198
|
+
* Optional hint: the agent tool the original `actionSpec[intent].nextStep`
|
|
1199
|
+
* declared. Present when the contract bound this intent to a specific
|
|
1200
|
+
* tool; absent when the author left it free. The agent reads this as
|
|
1201
|
+
* a strong suggestion, not a binding directive (in the inline case
|
|
1202
|
+
* the LLM composes the call, including any context-derived args).
|
|
1203
|
+
*/
|
|
1204
|
+
readonly nextStep?: string;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Type guard for {@link GguiUserActionMeta}. Validates the
|
|
1208
|
+
* discriminated shape on a `ui/message` envelope's
|
|
1209
|
+
* `params._meta.ggui.userAction` field.
|
|
1210
|
+
*
|
|
1211
|
+
* Designed for `_meta.ggui.userAction`-aware consumers (sample agent
|
|
1212
|
+
* dispatcher, e2e assertions, future SDKs) to route deterministically
|
|
1213
|
+
* without speculative shape coercion.
|
|
1214
|
+
*
|
|
1215
|
+
* @public
|
|
1216
|
+
*/
|
|
1217
|
+
export declare function isGguiUserActionMeta(meta: unknown): meta is GguiUserActionMeta;
|
|
1218
|
+
//# sourceMappingURL=mcp-apps.d.ts.map
|