@ggui-ai/mcp-server 0.2.0-alpha.4 → 0.4.0-rc.0
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/dist/admin-blueprints-transport.d.ts.map +1 -1
- package/dist/admin-blueprints-transport.js +2 -1
- package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
- package/dist/admin-oauth-providers-transport.js +7 -5
- package/dist/api-renders-routes.d.ts +85 -0
- package/dist/api-renders-routes.d.ts.map +1 -0
- package/dist/api-renders-routes.js +372 -0
- package/dist/build-mcp.d.ts +2 -2
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +64 -5
- package/dist/code-routes.d.ts +47 -0
- package/dist/code-routes.d.ts.map +1 -0
- package/dist/code-routes.js +81 -0
- package/dist/code-store-fs.js +2 -2
- package/dist/console-auth.d.ts +10 -10
- package/dist/console-auth.d.ts.map +1 -1
- package/dist/console-auth.js +5 -5
- package/dist/console-blueprint-routes.d.ts +71 -0
- package/dist/console-blueprint-routes.d.ts.map +1 -0
- package/dist/console-blueprint-routes.js +348 -0
- package/dist/console-chat-routes.d.ts +80 -0
- package/dist/console-chat-routes.d.ts.map +1 -0
- package/dist/console-chat-routes.js +204 -0
- package/dist/console-config-routes.d.ts +37 -0
- package/dist/console-config-routes.d.ts.map +1 -0
- package/dist/console-config-routes.js +91 -0
- package/dist/console-headers.d.ts +1 -1
- package/dist/console-info-routes.d.ts +84 -0
- package/dist/console-info-routes.d.ts.map +1 -0
- package/dist/console-info-routes.js +135 -0
- package/dist/console-keys-routes.d.ts +50 -0
- package/dist/console-keys-routes.d.ts.map +1 -0
- package/dist/console-keys-routes.js +222 -0
- package/dist/console-llm-keys-routes.d.ts +47 -0
- package/dist/console-llm-keys-routes.d.ts.map +1 -0
- package/dist/console-llm-keys-routes.js +443 -0
- package/dist/console-mcp-tools-routes.d.ts +41 -0
- package/dist/console-mcp-tools-routes.d.ts.map +1 -0
- package/dist/console-mcp-tools-routes.js +60 -0
- package/dist/console-registry-routes.d.ts +66 -0
- package/dist/console-registry-routes.d.ts.map +1 -0
- package/dist/console-registry-routes.js +276 -0
- package/dist/console-session-routes.d.ts +89 -0
- package/dist/console-session-routes.d.ts.map +1 -0
- package/dist/console-session-routes.js +385 -0
- package/dist/console-sessions-routes.d.ts +52 -0
- package/dist/console-sessions-routes.d.ts.map +1 -0
- package/dist/console-sessions-routes.js +106 -0
- package/dist/console-static-routes.d.ts +54 -0
- package/dist/console-static-routes.d.ts.map +1 -0
- package/dist/console-static-routes.js +190 -0
- package/dist/console-theme-routes.d.ts +3 -3
- package/dist/console-theme-routes.js +1 -1
- package/dist/console-timeline.d.ts +5 -5
- package/dist/console-timeline.d.ts.map +1 -1
- package/dist/console-timeline.js +27 -26
- package/dist/console-welcome.js +2 -2
- package/dist/email-login.d.ts.map +1 -1
- package/dist/email-login.js +2 -3
- package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
- package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
- package/dist/ggui-session-channel/action-ingress.js +228 -0
- package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
- package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
- package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
- package/dist/ggui-session-channel/internal-types.d.ts +102 -0
- package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
- package/dist/ggui-session-channel/internal-types.js +6 -0
- package/dist/ggui-session-channel/outbound.d.ts +81 -0
- package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
- package/dist/ggui-session-channel/outbound.js +174 -0
- package/dist/ggui-session-channel/socket-router.d.ts +38 -0
- package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
- package/dist/ggui-session-channel/socket-router.js +213 -0
- package/dist/ggui-session-channel/subscribe.d.ts +165 -0
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
- package/dist/ggui-session-channel/subscribe.js +370 -0
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
- package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
- package/dist/ggui-session-channel.d.ts +425 -0
- package/dist/ggui-session-channel.d.ts.map +1 -0
- package/dist/ggui-session-channel.js +262 -0
- package/dist/health-routes.d.ts +76 -0
- package/dist/health-routes.d.ts.map +1 -0
- package/dist/health-routes.js +145 -0
- package/dist/index.d.ts +10 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -9
- package/dist/instructions-presets.d.ts +3 -3
- package/dist/instructions-presets.js +24 -24
- package/dist/llm-backed-negotiator.d.ts +68 -67
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +82 -221
- package/dist/mcp-apps-outbound.d.ts +47 -48
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +154 -177
- package/dist/mcp-endpoint-routes.d.ts +88 -0
- package/dist/mcp-endpoint-routes.d.ts.map +1 -0
- package/dist/mcp-endpoint-routes.js +359 -0
- package/dist/mcp-mounts.d.ts +2 -76
- package/dist/mcp-mounts.d.ts.map +1 -1
- package/dist/mcp-mounts.js +0 -76
- package/dist/oauth-as-routes.d.ts +60 -0
- package/dist/oauth-as-routes.d.ts.map +1 -0
- package/dist/oauth-as-routes.js +82 -0
- package/dist/oauth-clients-routes.d.ts +39 -0
- package/dist/oauth-clients-routes.d.ts.map +1 -0
- package/dist/oauth-clients-routes.js +87 -0
- package/dist/oauth-login-types.d.ts +1 -20
- package/dist/oauth-login-types.d.ts.map +1 -1
- package/dist/oauth-login-types.js +30 -7
- package/dist/oauth-login.d.ts.map +1 -1
- package/dist/oauth-login.js +3 -2
- package/dist/oauth-providers-store.d.ts.map +1 -1
- package/dist/oauth-providers-store.js +5 -5
- package/dist/oauth.d.ts +9 -8
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +41 -19
- package/dist/pairing-transport.d.ts.map +1 -1
- package/dist/pairing-transport.js +2 -1
- package/dist/request-context.d.ts +2 -2
- package/dist/request-context.js +2 -2
- package/dist/reserved-validators.d.ts.map +1 -1
- package/dist/reserved-validators.js +9 -1
- package/dist/route-param.d.ts +9 -0
- package/dist/route-param.d.ts.map +1 -0
- package/dist/route-param.js +10 -0
- package/dist/runtime-bundle-route.d.ts +43 -0
- package/dist/runtime-bundle-route.d.ts.map +1 -0
- package/dist/runtime-bundle-route.js +80 -0
- package/dist/schema-compat.d.ts +64 -62
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +23 -51
- package/dist/server.d.ts +179 -193
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +644 -3759
- package/dist/storage.d.ts +5 -5
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +5 -5
- package/dist/thread-transport.d.ts.map +1 -1
- package/dist/thread-transport.js +4 -3
- package/dist/user-session-auth.d.ts +7 -21
- package/dist/user-session-auth.d.ts.map +1 -1
- package/dist/user-session-auth.js +7 -28
- package/package.json +16 -15
- package/dist/mcp-apps-inbound.d.ts +0 -86
- package/dist/mcp-apps-inbound.d.ts.map +0 -1
- package/dist/mcp-apps-inbound.js +0 -283
- package/dist/render-channel.d.ts +0 -694
- package/dist/render-channel.d.ts.map +0 -1
- package/dist/render-channel.js +0 -1775
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
* same `@ggui-ai/mcp-server` instance that mints the bootstrap.
|
|
33
33
|
*/
|
|
34
34
|
import { deriveBundleOrigins, deriveContractBundle, derivePublicEnvProjection, deriveRenderMeta, findBlueprintExact, } from "@ggui-ai/mcp-server-handlers/renders";
|
|
35
|
-
import { deriveContextDefault } from "@ggui-ai/protocol";
|
|
36
|
-
import { GGUI_RENDER_RESOURCE_MIME, GGUI_RENDER_RESOURCE_URI, MCP_APPS_UI_CAPABILITY, MCP_APP_AI_GGUI_RENDER_META_KEY, deriveContextName, } from "@ggui-ai/protocol/integrations/mcp-apps";
|
|
35
|
+
import { deriveContextDefault, isRecord } from "@ggui-ai/protocol";
|
|
36
|
+
import { GGUI_RENDER_RESOURCE_MIME, GGUI_RENDER_RESOURCE_URI, MCP_APPS_UI_CAPABILITY, MCP_APP_AI_GGUI_RENDER_META_KEY, MCP_APP_BOOTSTRAP_FAILED_TYPE, deriveContextName, } from "@ggui-ai/protocol/integrations/mcp-apps";
|
|
37
37
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
38
38
|
import { registerAppResource } from "@modelcontextprotocol/ext-apps/server";
|
|
39
39
|
import { createHash } from "node:crypto";
|
|
@@ -41,23 +41,25 @@ import { createHash } from "node:crypto";
|
|
|
41
41
|
* Thin-shell body served from `ui://ggui/render` (C8 pivot).
|
|
42
42
|
*
|
|
43
43
|
* **Architectural role.** The shell is a ~30 LOC bootstrap wrapper -
|
|
44
|
-
* it runs
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* it runs the `ui/initialize` + `ui/notifications/initialized`
|
|
45
|
+
* handshake, then waits for the host's spec-canonical
|
|
46
|
+
* `ui/notifications/tool-result` notification carrying the
|
|
47
|
+
* `ai.ggui/render` slice in `_meta`, and dynamic-script-loads the
|
|
48
|
+
* `@ggui-ai/iframe-runtime` bundle from the slice's `runtimeUrl`.
|
|
49
|
+
* Every rendering concern - WS open, subscribe, render mount,
|
|
50
|
+
* component code eval, adapter install - belongs to the renderer
|
|
51
|
+
* bundle (shipped C7a-d), not here.
|
|
50
52
|
*
|
|
51
53
|
* **Why bootstrap-driven URL.** `srcdoc` iframes have `about:srcdoc`
|
|
52
54
|
* as their URL, so relative paths can't resolve to the MCP server's
|
|
53
55
|
* HTTP listener. The server-controlled `runtimeUrl` lands on the
|
|
54
56
|
* `ai.ggui/render.runtimeUrl` slice field and the shell picks it up
|
|
55
|
-
* from the
|
|
57
|
+
* from the tool-result `_meta` slice - origin-agnostic, works
|
|
56
58
|
* under OSS same-origin AND hosted-cloud CDN deployments.
|
|
57
59
|
*
|
|
58
60
|
* **Why `<script type="module">`.** `@ggui-ai/iframe-runtime` is bundled as
|
|
59
61
|
* ESM (its own runtime.ts:5 declares the contract: "the thin-shell
|
|
60
|
-
* HTML loads it via `<script type="module" src=".../
|
|
62
|
+
* HTML loads it via `<script type="module" src=".../iframe-runtime.js">`").
|
|
61
63
|
* Loading the bundle as a classic `<script src=...>` throws
|
|
62
64
|
* `SyntaxError: Unexpected token 'export'` synchronously when the
|
|
63
65
|
* browser parses the bundle - the renderer never executes, the
|
|
@@ -70,31 +72,30 @@ import { createHash } from "node:crypto";
|
|
|
70
72
|
* surface to the parent via
|
|
71
73
|
* `postMessage({type:'ggui:bootstrap-failed', reason, message}, '*')`:
|
|
72
74
|
*
|
|
73
|
-
* - `
|
|
74
|
-
* valid `ai.ggui/render`
|
|
75
|
-
* it.
|
|
75
|
+
* - `MALFORMED_BOOTSTRAP` - the tool-result `_meta` slice arrived
|
|
76
|
+
* without a valid `ai.ggui/render` envelope or `runtimeUrl`.
|
|
76
77
|
* - `BUNDLE_FETCH_FAILED` - `<script src>` errored (network failure,
|
|
77
78
|
* 404, CSP reject with an observable `error` event).
|
|
78
79
|
*
|
|
79
|
-
* Post-renderer failures (WS handshake / auth /
|
|
80
|
+
* Post-renderer failures (WS handshake / auth / render-mismatch) are
|
|
80
81
|
* the renderer bundle's responsibility - `runtime.ts::postBootFailure`
|
|
81
|
-
* emits the same `ggui:bootstrap-failed` envelope
|
|
82
|
-
* failures, a `_ggui:contract-error` envelope on the live channel per
|
|
83
|
-
* `ContractErrorCode` additions (C8 Commit 1/3).
|
|
82
|
+
* emits the same `ggui:bootstrap-failed` envelope.
|
|
84
83
|
*
|
|
85
84
|
* **Adapter boundary unchanged.** The preflight's `message` listener
|
|
86
85
|
* routes ONLY responses to its own pending JSON-RPC ids. MCP Apps
|
|
87
86
|
* lifecycle notifications from the host (`ui/notifications/message`,
|
|
88
87
|
* `ui/update-model-context`) are dropped. This mirrors the pre-C8
|
|
89
|
-
* posture - the shell still MUST NOT mutate
|
|
88
|
+
* posture - the shell still MUST NOT mutate render state from
|
|
90
89
|
* arbitrary host messages. ADAPTER BOUNDARY enforced by the
|
|
91
90
|
* `pending[m.id]` route-by-id check.
|
|
92
91
|
*
|
|
93
92
|
* **Double `ui/initialize` is intentional.** The shell runs a minimal
|
|
94
|
-
* preflight
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
93
|
+
* preflight to complete the MCP Apps handshake (hosts release the
|
|
94
|
+
* tool-result notification only after `ui/notifications/initialized`);
|
|
95
|
+
* the renderer bundle's autostart path runs its own `ui/initialize`
|
|
96
|
+
* for the full bootstrap parse. MCP Apps hosts handle repeats
|
|
97
|
+
* idempotently - the preflight's cost is a single postMessage
|
|
98
|
+
* round-trip.
|
|
98
99
|
*
|
|
99
100
|
* Exported as a string constant for tests; not part of the public
|
|
100
101
|
* package API. Vanilla JS, no build step, no external deps. The shell
|
|
@@ -104,9 +105,9 @@ import { createHash } from "node:crypto";
|
|
|
104
105
|
/**
|
|
105
106
|
* Inline body of the thin shell's bootstrap `<script>` block.
|
|
106
107
|
*
|
|
107
|
-
* Split from {@link
|
|
108
|
+
* Split from {@link GGUI_RENDER_SHELL_HTML} so the exact bytes the
|
|
108
109
|
* browser sees inside the `<script>...</script>` tag are addressable
|
|
109
|
-
* for CSP-hash purposes — see {@link
|
|
110
|
+
* for CSP-hash purposes — see {@link GGUI_RENDER_SHELL_SCRIPT_HASH}.
|
|
110
111
|
*
|
|
111
112
|
* The browser's CSP `'sha256-...'` source-expression is computed over
|
|
112
113
|
* the literal text content of the `<script>` element (everything
|
|
@@ -114,10 +115,11 @@ import { createHash } from "node:crypto";
|
|
|
114
115
|
* whitespace inside). Concatenating this constant unchanged into the
|
|
115
116
|
* shell HTML means the runtime hash and the constant-time hash agree.
|
|
116
117
|
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* `mcp-apps-outbound.test.ts` drift test recomputes
|
|
120
|
-
* loudly if
|
|
118
|
+
* {@link GGUI_RENDER_SHELL_SCRIPT_HASH} is DERIVED from this constant
|
|
119
|
+
* at module load, so edits here propagate to the CSP hash
|
|
120
|
+
* automatically. The `mcp-apps-outbound.test.ts` drift test recomputes
|
|
121
|
+
* the hash from the assembled shell HTML and fails loudly if the body
|
|
122
|
+
* and the HTML assembly ever disagree.
|
|
121
123
|
*/
|
|
122
124
|
const GGUI_RENDER_SHELL_SCRIPT_BODY = `
|
|
123
125
|
(function(){'use strict';
|
|
@@ -127,9 +129,8 @@ const GGUI_RENDER_SHELL_SCRIPT_BODY = `
|
|
|
127
129
|
// 2. iframe -> host: ui/notifications/initialized
|
|
128
130
|
// 3. host -> iframe: ui/notifications/tool-result (per CallToolResult)
|
|
129
131
|
// On tool-result, read the slice envelope from _meta (spec-canonical
|
|
130
|
-
// CallToolResult _meta at the top level,
|
|
131
|
-
//
|
|
132
|
-
// envelope, fetch runtime as blob, inject as script.
|
|
132
|
+
// CallToolResult _meta at the top level), set window.__GGUI_META__ to
|
|
133
|
+
// the envelope, fetch runtime as blob, inject as script.
|
|
133
134
|
// Runtime auto-mounts inline. No nested iframe (claudemcpcontent.com
|
|
134
135
|
// CSP frame-src forbids cross-origin frames). State machine matches
|
|
135
136
|
// buildSelfContainedShell so the same runtime mounts both paths.
|
|
@@ -142,7 +143,7 @@ const GGUI_RENDER_SHELL_SCRIPT_BODY = `
|
|
|
142
143
|
// Phase B (2026-05-27): the historic two-slice envelope
|
|
143
144
|
// (\`ai.ggui/session\` + \`ai.ggui/stack-item\`) collapsed to ONE flat
|
|
144
145
|
// \`ai.ggui/render\` slice. runtimeUrl now lives directly on the
|
|
145
|
-
// render slice;
|
|
146
|
+
// render slice; sessionId is the canonical identity.
|
|
146
147
|
var rpcId=1,pending={};
|
|
147
148
|
var rootEl=document.getElementById('ggui-root');
|
|
148
149
|
rootEl.style.cssText='display:flex;flex-direction:column;height:100%;min-height:300px;margin:0';
|
|
@@ -167,11 +168,11 @@ function postBootstrapFailed(reason,message){
|
|
|
167
168
|
// error pane (McpAppIframe onError, IframeErrorPane) see the
|
|
168
169
|
// failure instead of staring at the inert overlay until the test
|
|
169
170
|
// times out. Reason codes match BootstrapFailureReason.
|
|
170
|
-
try{window.parent.postMessage({type:'
|
|
171
|
+
try{window.parent.postMessage({type:'${MCP_APP_BOOTSTRAP_FAILED_TYPE}',reason:reason,message:message},'*');}catch(e){}
|
|
171
172
|
}
|
|
172
173
|
async function mountFromMeta(envelope){
|
|
173
174
|
if(mounted)return;
|
|
174
|
-
// Slice envelope shape (Phase B): { "ai.ggui/render": {
|
|
175
|
+
// Slice envelope shape (Phase B): { "ai.ggui/render": { sessionId,
|
|
175
176
|
// appId, runtimeUrl, ... } }. runtimeUrl on the render slice is
|
|
176
177
|
// the only load-bearing field at the shell layer — it tells us
|
|
177
178
|
// which iframe-runtime bundle to fetch. Everything else is
|
|
@@ -181,7 +182,7 @@ async function mountFromMeta(envelope){
|
|
|
181
182
|
var runtimeUrl=renderSlice&&renderSlice.runtimeUrl;
|
|
182
183
|
if(!envelope||typeof runtimeUrl!=='string'){
|
|
183
184
|
setOverlay('Bootstrap payload malformed.');
|
|
184
|
-
postBootstrapFailed('
|
|
185
|
+
postBootstrapFailed('MALFORMED_BOOTSTRAP','Bootstrap payload malformed.');
|
|
185
186
|
return;
|
|
186
187
|
}
|
|
187
188
|
setOverlay('Loading UI…');
|
|
@@ -220,42 +221,17 @@ async function mountFromMeta(envelope){
|
|
|
220
221
|
postBootstrapFailed('BUNDLE_FETCH_FAILED',msg);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
|
-
function readMetaFromInitResult(result){
|
|
224
|
-
if(!result||typeof result!=='object')return null;
|
|
225
|
-
var toolOutput=result.toolOutput;
|
|
226
|
-
if(!toolOutput||typeof toolOutput!=='object')return null;
|
|
227
|
-
var meta=toolOutput._meta;
|
|
228
|
-
if(!meta||typeof meta!=='object')return null;
|
|
229
|
-
// Single slice-envelope key (Phase B: ai.ggui/render). Only the
|
|
230
|
-
// render slice's runtimeUrl is load-bearing at the shell layer;
|
|
231
|
-
// the runtime reads everything else off window.__GGUI_META__
|
|
232
|
-
// after we set it.
|
|
233
|
-
var renderSlice=meta['ai.ggui/render'];
|
|
234
|
-
if(!renderSlice||typeof renderSlice!=='object')return null;
|
|
235
|
-
if(typeof renderSlice.runtimeUrl!=='string')return null;
|
|
236
|
-
return meta;
|
|
237
|
-
}
|
|
238
|
-
function hasAiGguiMetaPlaceholder(result){
|
|
239
|
-
// Detect the protocol-violation case where the host signaled
|
|
240
|
-
// "I tried to deliver meta" (toolOutput._meta carries an
|
|
241
|
-
// ai.ggui/render key) but it's malformed. Fail-fast with
|
|
242
|
-
// BOOTSTRAP_META_MISSING rather than waiting forever.
|
|
243
|
-
if(!result||typeof result!=='object')return false;
|
|
244
|
-
var toolOutput=result.toolOutput;
|
|
245
|
-
if(!toolOutput||typeof toolOutput!=='object')return false;
|
|
246
|
-
var meta=toolOutput._meta;
|
|
247
|
-
if(!meta||typeof meta!=='object')return false;
|
|
248
|
-
return 'ai.ggui/render' in meta;
|
|
249
|
-
}
|
|
250
224
|
function readMetaFromCallToolResult(params){
|
|
251
225
|
// MCP Apps spec (specification/2026-01-26/apps.mdx:1145-1155):
|
|
252
226
|
// ui/notifications/tool-result
|
|
253
227
|
// params: CallToolResult // Standard MCP type
|
|
254
228
|
// So params IS the CallToolResult and _meta lives at the top
|
|
255
|
-
// level (
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
// slice-
|
|
229
|
+
// level. Spec-compliant hosts (Claude Desktop, claude.ai
|
|
230
|
+
// Connector, Claude Code) deliver slice-envelope material here.
|
|
231
|
+
// Single slice-envelope key (Phase B: ai.ggui/render). Only the
|
|
232
|
+
// render slice's runtimeUrl is load-bearing at the shell layer;
|
|
233
|
+
// the runtime reads everything else off window.__GGUI_META__
|
|
234
|
+
// after we set it.
|
|
259
235
|
if(!params||typeof params!=='object')return null;
|
|
260
236
|
var meta=params._meta;
|
|
261
237
|
if(!meta||typeof meta!=='object')return null;
|
|
@@ -274,21 +250,14 @@ window.addEventListener('message',function(ev){
|
|
|
274
250
|
}
|
|
275
251
|
if(m.method==='ui/notifications/tool-result'){
|
|
276
252
|
// Spec-compliant hosts: m.params IS the CallToolResult; _meta is
|
|
277
|
-
// at the top level.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if(specB){mountFromMeta(specB);return;}
|
|
281
|
-
// First-party McpAppIframe convention: slice envelope nested under
|
|
282
|
-
// params.toolOutput._meta. First-party hosts (Studio, Portal,
|
|
283
|
-
// console) use this shape for both init-response and post-init
|
|
284
|
-
// notification.
|
|
285
|
-
var bb=readMetaFromInitResult(m.params);
|
|
286
|
-
if(bb){mountFromMeta(bb);return;}
|
|
253
|
+
// at the top level.
|
|
254
|
+
var specMeta=readMetaFromCallToolResult(m.params);
|
|
255
|
+
if(specMeta){mountFromMeta(specMeta);return;}
|
|
287
256
|
// R5 (2026-05-26) -- the /r/<shortCode> HTTP fallback was removed
|
|
288
257
|
// along with the bearer-by-obscurity model. Hosts that strip
|
|
289
258
|
// _meta on the tool-result wire have no fallback path here;
|
|
290
|
-
// spec-canonical hosts deliver meta inline and land in the
|
|
291
|
-
//
|
|
259
|
+
// spec-canonical hosts deliver meta inline and land in the branch
|
|
260
|
+
// above.
|
|
292
261
|
}
|
|
293
262
|
});
|
|
294
263
|
setOverlay('Initializing…');
|
|
@@ -297,29 +266,15 @@ var initTimer=setTimeout(function(){
|
|
|
297
266
|
},3000);
|
|
298
267
|
postRpc('ui/initialize',{
|
|
299
268
|
appCapabilities:{},
|
|
300
|
-
appInfo:{name:'ggui-
|
|
269
|
+
appInfo:{name:'ggui-render',version:'1.0.0'},
|
|
301
270
|
protocolVersion:'2026-01-26'
|
|
302
|
-
}).then(function(
|
|
271
|
+
}).then(function(){
|
|
303
272
|
clearTimeout(initTimer);
|
|
304
273
|
postNotification('ui/notifications/initialized',{});
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
if(b){mountFromMeta(b);return;}
|
|
310
|
-
// Protocol-violation surface: host signalled an attempt
|
|
311
|
-
// (toolOutput._meta carries ai.ggui/render) but it's malformed.
|
|
312
|
-
// Fail fast so hosts pinning a typed error envelope don't sit on
|
|
313
|
-
// the Path-A waiting overlay forever.
|
|
314
|
-
if(hasAiGguiMetaPlaceholder(result)){
|
|
315
|
-
var msg='ui/initialize result missing valid ai.ggui/render.runtimeUrl';
|
|
316
|
-
setOverlay(msg);
|
|
317
|
-
postBootstrapFailed('BOOTSTRAP_META_MISSING',msg);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
// Path A: wait for the host to send ui/notifications/tool-result
|
|
321
|
-
// with structuredContent.{url, shortCode}. MCP Apps hosts that don't
|
|
322
|
-
// implement the reading-B inline-meta convention land here.
|
|
274
|
+
// Wait for the host to send ui/notifications/tool-result carrying
|
|
275
|
+
// the slice envelope in _meta — the spec-canonical delivery channel.
|
|
276
|
+
// The ui/initialize result itself carries no slice meta (the
|
|
277
|
+
// McpUiInitializeResult schema defines no such field).
|
|
323
278
|
setOverlay('Waiting for tool result…');
|
|
324
279
|
}).catch(function(e){
|
|
325
280
|
clearTimeout(initTimer);
|
|
@@ -358,6 +313,16 @@ postRpc('ui/initialize',{
|
|
|
358
313
|
//
|
|
359
314
|
// Value-resolution only — no `--ggui-*` token added or renamed.
|
|
360
315
|
const GGUI_RENDER_SHELL_SURFACE = `var(--ggui-color-surface, #1e293b)`;
|
|
316
|
+
// `#ggui-root` here is LOAD-BEARING for the shell script (NOT a React
|
|
317
|
+
// mount target): the inline script grabs it as `rootEl` for the
|
|
318
|
+
// pre-mount overlays ("Initializing…", "Waiting for tool result…",
|
|
319
|
+
// bootstrap-failure messages) and clears it before injecting the
|
|
320
|
+
// runtime bundle. The runtime itself mounts into its own
|
|
321
|
+
// `<ul data-ggui-session-root>` appended to `document.body`
|
|
322
|
+
// (iframe-runtime `status-dom.ts#ensureStatusDom`), so `#ggui-root`
|
|
323
|
+
// stays empty after a successful mount — that is expected, not a bug.
|
|
324
|
+
// The self-contained shell (`buildSelfContainedShell`) has no overlay
|
|
325
|
+
// script and therefore no anchor div at all.
|
|
361
326
|
export const GGUI_RENDER_SHELL_HTML = `<!doctype html>
|
|
362
327
|
<html lang="en" style="height:100%;background-color:${GGUI_RENDER_SHELL_SURFACE}"><head><meta charset="utf-8"><title>ggui render</title></head>
|
|
363
328
|
<body style="margin:0;height:100%;min-height:480px;background-color:${GGUI_RENDER_SHELL_SURFACE}"><div id="ggui-root" data-ggui-shell="thin" style="height:100%;min-height:480px"></div>
|
|
@@ -390,7 +355,7 @@ export const GGUI_RENDER_SHELL_HTML = `<!doctype html>
|
|
|
390
355
|
* # Where it gets used
|
|
391
356
|
*
|
|
392
357
|
* `console-headers.ts::DEVTOOL_CSP` appends this expression to its
|
|
393
|
-
* `script-src` directive. Hosted closed-runtime
|
|
358
|
+
* `script-src` directive. Hosted closed-runtime render-resource
|
|
394
359
|
* endpoints have their own CSP and serve the same shell — that path
|
|
395
360
|
* needs the same expression added; tracked separately.
|
|
396
361
|
*
|
|
@@ -408,7 +373,7 @@ export const GGUI_RENDER_SHELL_SCRIPT_HASH = `'sha256-${createHash("sha256")
|
|
|
408
373
|
* Register `ui://ggui/render` as a readable resource on an `McpServer`.
|
|
409
374
|
*
|
|
410
375
|
* The resource is STATIC - `resources/read` always returns the same
|
|
411
|
-
* body. Per-
|
|
376
|
+
* body. Per-render state lives on the live channel, not in the resource.
|
|
412
377
|
*
|
|
413
378
|
* When `publicBaseUrl` is supplied, the resource content carries
|
|
414
379
|
* `_meta.ui.csp.{connectDomains,resourceDomains}` per the MCP Apps spec
|
|
@@ -443,10 +408,10 @@ function buildCspMeta(publicBaseUrl,
|
|
|
443
408
|
* same-origin deployments, e.g. `ggui serve` on `127.0.0.1`), derive
|
|
444
409
|
* the CSP block from `runtimeUrl`. The runtime + WS + state endpoints
|
|
445
410
|
* all live on the runtime's origin in same-origin deployments, so
|
|
446
|
-
* declaring it covers every fetch the
|
|
411
|
+
* declaring it covers every fetch the sandboxed iframe makes.
|
|
447
412
|
*
|
|
448
413
|
* Without this fallback, local dev with cross-origin sandbox proxies
|
|
449
|
-
* (sample-agent's `:7790/sandbox.html` writing the
|
|
414
|
+
* (sample-agent's `:7790/sandbox.html` writing the sandbox HTML that
|
|
450
415
|
* references `:6786/_ggui/iframe-runtime.js`) trips a `script-src`
|
|
451
416
|
* violation that blanks the iframe — verified live 2026-05-27.
|
|
452
417
|
*/
|
|
@@ -548,10 +513,10 @@ export function advertiseMcpAppsUiCapability(server) {
|
|
|
548
513
|
});
|
|
549
514
|
}
|
|
550
515
|
/**
|
|
551
|
-
* Build the self-contained shell HTML for a given
|
|
516
|
+
* Build the self-contained shell HTML for a given render.
|
|
552
517
|
*
|
|
553
518
|
* The returned HTML is a complete, standalone document: it inlines the
|
|
554
|
-
* compiled component (base64) +
|
|
519
|
+
* compiled component (base64) + the render id in a `window.__GGUI_META__`
|
|
555
520
|
* global, then loads the iframe-runtime bundle via `<script type="module"
|
|
556
521
|
* src={runtimeUrl}>`. The runtime takes over synchronously on import,
|
|
557
522
|
* mounts the component, and the iframe paints WITHOUT any further server
|
|
@@ -595,14 +560,15 @@ export function buildSelfContainedShell(opts) {
|
|
|
595
560
|
// postMessage paths use. `runtimeUrl` is required across all modes
|
|
596
561
|
// (the shell-bundled script tag fetches the runtime from there).
|
|
597
562
|
const render = {
|
|
598
|
-
|
|
563
|
+
sessionId: opts.sessionId,
|
|
599
564
|
appId: opts.appId,
|
|
600
565
|
runtimeUrl: opts.runtimeUrl,
|
|
601
566
|
...(opts.themeId !== undefined ? { themeId: opts.themeId } : {}),
|
|
602
567
|
...(opts.themeMode !== undefined ? { themeMode: opts.themeMode } : {}),
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
568
|
+
// Per-app theme overlay — same field the MCP-Apps `_meta` slice
|
|
569
|
+
// carries, forwarded onto the inline bootstrap so the self-contained
|
|
570
|
+
// shell applies the operator overlay too.
|
|
571
|
+
...(opts.theme !== undefined ? { theme: opts.theme } : {}),
|
|
606
572
|
...(opts.permissionsPolicy !== undefined && opts.permissionsPolicy.length > 0
|
|
607
573
|
? { permissionsPolicy: opts.permissionsPolicy }
|
|
608
574
|
: {}),
|
|
@@ -646,9 +612,6 @@ export function buildSelfContainedShell(opts) {
|
|
|
646
612
|
}
|
|
647
613
|
: {}),
|
|
648
614
|
...(opts.propsJson !== undefined ? { propsJson: opts.propsJson } : {}),
|
|
649
|
-
...(opts.actionNextSteps !== undefined && Object.keys(opts.actionNextSteps).length > 0
|
|
650
|
-
? { actionNextSteps: opts.actionNextSteps }
|
|
651
|
-
: {}),
|
|
652
615
|
...(opts.contextSlots !== undefined && opts.contextSlots.length > 0
|
|
653
616
|
? { contextSlots: opts.contextSlots }
|
|
654
617
|
: {}),
|
|
@@ -698,10 +661,16 @@ export function buildSelfContainedShell(opts) {
|
|
|
698
661
|
// embedding. `var(--ggui-color-surface, …)` resolves to the active
|
|
699
662
|
// theme's per-mode surface once the iframe-runtime injects the `:root`
|
|
700
663
|
// theme vars; the static dark fallback covers pre-resolve paint.
|
|
664
|
+
// No anchor div: the iframe-runtime never mounts into a server-
|
|
665
|
+
// provided container — it appends its own `<ul data-ggui-session-root>`
|
|
666
|
+
// mount target to `document.body` at boot (iframe-runtime
|
|
667
|
+
// `status-dom.ts#ensureStatusDom`). The thin postMessage shell's
|
|
668
|
+
// `#ggui-root` is different: its inline script uses that div for
|
|
669
|
+
// pre-mount overlays, which this shell has none of (the runtime boots
|
|
670
|
+
// synchronously from the inline `__GGUI_META__` global).
|
|
701
671
|
return `<!doctype html>
|
|
702
672
|
<html lang="en" style="background-color:${GGUI_RENDER_SHELL_SURFACE}"><head><meta charset="utf-8"><title>ggui render</title></head>
|
|
703
673
|
<body style="background-color:${GGUI_RENDER_SHELL_SURFACE}">
|
|
704
|
-
<div id="ggui-root" data-ggui-shell="self-contained"></div>
|
|
705
674
|
<script>window.__GGUI_META__ = ${json};</script>
|
|
706
675
|
<script type="module" crossorigin="anonymous" src="${safeRuntimeUrl}"></script>
|
|
707
676
|
</body></html>`;
|
|
@@ -719,32 +688,27 @@ export function buildSelfContainedShell(opts) {
|
|
|
719
688
|
*
|
|
720
689
|
* @public
|
|
721
690
|
*/
|
|
722
|
-
export function buildSelfContainedLoadingShell(
|
|
691
|
+
export function buildSelfContainedLoadingShell(sessionId) {
|
|
723
692
|
// Standalone served-iframe loading document — same surface-backdrop
|
|
724
693
|
// paint + gating as the thin/self-contained shells so the canvas is
|
|
725
694
|
// dark while the render is still in flight (no Safari white flash).
|
|
726
695
|
return `<!doctype html>
|
|
727
696
|
<html lang="en" style="background-color:${GGUI_RENDER_SHELL_SURFACE}"><head><meta charset="utf-8"><title>ggui render</title></head>
|
|
728
697
|
<body style="background-color:${GGUI_RENDER_SHELL_SURFACE}">
|
|
729
|
-
<div id="ggui-root" data-ggui-shell="loading" data-ggui-
|
|
698
|
+
<div id="ggui-root" data-ggui-shell="loading" data-ggui-session-id="${sessionId
|
|
730
699
|
.replace(/&/g, "&")
|
|
731
700
|
.replace(/"/g, """)
|
|
732
701
|
.replace(/</g, "<")
|
|
733
702
|
.replace(/>/g, ">")}">Generating UI…</div>
|
|
734
703
|
</body></html>`;
|
|
735
704
|
}
|
|
736
|
-
function
|
|
705
|
+
function pickComponentFromGguiSession(render) {
|
|
737
706
|
if (!render)
|
|
738
707
|
return null;
|
|
739
708
|
if (render.type === "mcpApps")
|
|
740
709
|
return null;
|
|
741
710
|
const propsRaw = "props" in render ? render.props : undefined;
|
|
742
|
-
const props = propsRaw
|
|
743
|
-
propsRaw !== null &&
|
|
744
|
-
typeof propsRaw === "object" &&
|
|
745
|
-
!Array.isArray(propsRaw)
|
|
746
|
-
? propsRaw
|
|
747
|
-
: undefined;
|
|
711
|
+
const props = isRecord(propsRaw) ? propsRaw : undefined;
|
|
748
712
|
if (render.type === "system") {
|
|
749
713
|
if (typeof render.kind === "string" && render.kind.length > 0) {
|
|
750
714
|
return {
|
|
@@ -768,22 +732,22 @@ function pickComponentFromRender(render) {
|
|
|
768
732
|
return null;
|
|
769
733
|
}
|
|
770
734
|
/**
|
|
771
|
-
* Register a `ui://ggui/render/{
|
|
735
|
+
* Register a `ui://ggui/render/{sessionId}` resource template. Each
|
|
772
736
|
* `resources/read` request is resolved by looking up the render in the
|
|
773
737
|
* store and returning the self-contained shell with that
|
|
774
738
|
* componentCode inlined.
|
|
775
739
|
*
|
|
776
740
|
* Per-call `_meta.ui.resourceUri` (stamped by `ggui_render.resultMeta`)
|
|
777
|
-
* pins the URI to a specific
|
|
741
|
+
* pins the URI to a specific sessionId; hosts fetch THAT URI rather
|
|
778
742
|
* than the static `ui://ggui/render` one. Both registrations co-exist:
|
|
779
743
|
* legacy postMessage shell at the static URI, self-contained shell at
|
|
780
744
|
* the templated URI.
|
|
781
745
|
*
|
|
782
746
|
* Failure modes:
|
|
783
|
-
* -
|
|
747
|
+
* - GguiSession not found → loading shell (host re-fetches; absent
|
|
784
748
|
* render is a transient state immediately after `ggui_render`).
|
|
785
|
-
* -
|
|
786
|
-
* -
|
|
749
|
+
* - GguiSession found, no componentCode yet → loading shell.
|
|
750
|
+
* - GguiSession found, componentCode present → self-contained shell.
|
|
787
751
|
*
|
|
788
752
|
* Returns nothing; mutates the server in place.
|
|
789
753
|
*
|
|
@@ -792,12 +756,12 @@ function pickComponentFromRender(render) {
|
|
|
792
756
|
export function registerGguiRenderResourceTemplate(server, opts) {
|
|
793
757
|
// TWO templates registered against the same handler core:
|
|
794
758
|
//
|
|
795
|
-
// 1. Single-segment legacy URI — `ui://ggui/render/{
|
|
759
|
+
// 1. Single-segment legacy URI — `ui://ggui/render/{sessionId}`.
|
|
796
760
|
// Pre-resume-contract chats in claude.ai's history persisted
|
|
797
761
|
// this shape; we keep the registration so historical messages
|
|
798
762
|
// still rehydrate (loading shell on render miss).
|
|
799
763
|
//
|
|
800
|
-
// 2. Two-segment resume URI — `ui://ggui/render/{
|
|
764
|
+
// 2. Two-segment resume URI — `ui://ggui/render/{sessionId}/
|
|
801
765
|
// {blueprintKey}`. Stamped by every render since the resume
|
|
802
766
|
// contract landed. Carries enough state for the handler to do:
|
|
803
767
|
// (a) parallel render + blueprint registry lookup (no data
|
|
@@ -805,14 +769,14 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
805
769
|
// the render is gone but the blueprint is still cached
|
|
806
770
|
// (renders the original card with default props/context
|
|
807
771
|
// instead of the dead loading shell).
|
|
808
|
-
const legacyTemplate = new ResourceTemplate(`${GGUI_RENDER_RESOURCE_URI}/{
|
|
772
|
+
const legacyTemplate = new ResourceTemplate(`${GGUI_RENDER_RESOURCE_URI}/{sessionId}`, {
|
|
809
773
|
// No list-callback — the resource set is unbounded per render
|
|
810
774
|
// count, and `resources/list` would leak render ids across
|
|
811
775
|
// tenants. Hosts discover specific URIs via per-call `_meta.ui.
|
|
812
776
|
// resourceUri` instead.
|
|
813
777
|
list: undefined,
|
|
814
778
|
});
|
|
815
|
-
const resumeTemplate = new ResourceTemplate(`${GGUI_RENDER_RESOURCE_URI}/{
|
|
779
|
+
const resumeTemplate = new ResourceTemplate(`${GGUI_RENDER_RESOURCE_URI}/{sessionId}/{blueprintKey}`, { list: undefined });
|
|
816
780
|
// CSP-meta block forwarded on every shell response when the
|
|
817
781
|
// template was wired with `publicBaseUrl`. claude.ai's iframe
|
|
818
782
|
// applies the host's restrictive default (`connect-src 'none'`)
|
|
@@ -826,7 +790,7 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
826
790
|
* Merge gadget-declared origins from
|
|
827
791
|
* {@link deriveBundleOrigins} into the base `templateCspMeta`. The
|
|
828
792
|
* base only carries the publicBaseUrl origin (HTTPS + WSS); without
|
|
829
|
-
* the per-
|
|
793
|
+
* the per-render augmentation, gadget bundle / style / API
|
|
830
794
|
* origins (Leaflet tiles, Mapbox API, Stripe SDK, …) are blocked by
|
|
831
795
|
* claude.ai's iframe CSP and the component fails to render. Returns
|
|
832
796
|
* `undefined` when there's no base CSP at all (publicBaseUrl
|
|
@@ -860,14 +824,14 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
860
824
|
},
|
|
861
825
|
],
|
|
862
826
|
});
|
|
863
|
-
const loadingShell = (uri,
|
|
827
|
+
const loadingShell = (uri, sessionId) => shellContents(uri, buildSelfContainedLoadingShell(sessionId));
|
|
864
828
|
// Single shared handler powers both templates. `blueprintKey` is
|
|
865
829
|
// optional in the variables map — present for the resume URI shape,
|
|
866
830
|
// absent for the legacy single-segment shape.
|
|
867
831
|
async function handle(uri, variables) {
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
if (typeof
|
|
832
|
+
const sessionIdRaw = variables["sessionId"];
|
|
833
|
+
const sessionId = Array.isArray(sessionIdRaw) ? sessionIdRaw[0] : sessionIdRaw;
|
|
834
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
871
835
|
return loadingShell(uri, "unknown");
|
|
872
836
|
}
|
|
873
837
|
const blueprintKeyRaw = variables["blueprintKey"];
|
|
@@ -880,15 +844,18 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
880
844
|
// is still cached (chat-history rehydrate after render TTL or
|
|
881
845
|
// process restart).
|
|
882
846
|
const [stored, blueprint] = await Promise.all([
|
|
883
|
-
opts.renderStore.get(
|
|
884
|
-
hasResumeKey && opts.vectorStore && opts.defaultAppIdFallback
|
|
885
|
-
? findBlueprintExact({ vectorStore: opts.vectorStore }, opts.defaultAppIdFallback, "template",
|
|
847
|
+
opts.renderStore.get(sessionId),
|
|
848
|
+
hasResumeKey && opts.vectorStore && opts.index && opts.defaultAppIdFallback
|
|
849
|
+
? findBlueprintExact({ vectorStore: opts.vectorStore, index: opts.index }, opts.defaultAppIdFallback, "template",
|
|
850
|
+
// Resume URI carries only a contract hash — omit variantKey
|
|
851
|
+
// so the lookup resolves the default variant.
|
|
852
|
+
blueprintKey)
|
|
886
853
|
: Promise.resolve(null),
|
|
887
854
|
]);
|
|
888
855
|
// Happy path: render present and renderable. Mount with the live
|
|
889
856
|
// state (current props, current contextSpec values).
|
|
890
857
|
if (stored) {
|
|
891
|
-
const picked =
|
|
858
|
+
const picked = pickComponentFromGguiSession(stored.render);
|
|
892
859
|
if (picked) {
|
|
893
860
|
// Project the active render to the transport-agnostic bootstrap
|
|
894
861
|
// view — same source of truth the render-mutation handler and
|
|
@@ -934,14 +901,14 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
934
901
|
// validators (server-side gate is authoritative).
|
|
935
902
|
}
|
|
936
903
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
904
|
+
// The codeUrl gate is applied AFTER the live-channel mint below, so
|
|
905
|
+
// a render with no static codeUrl still mounts via live-mode
|
|
906
|
+
// (wsUrl + wsToken) instead of stalling on the loading shell —
|
|
907
|
+
// parity with the `/r/<shortCode>` path. (See the gate after the
|
|
908
|
+
// mint.) This matters for deployments that wire `mintWsToken` but no
|
|
909
|
+
// `codeStore`/`codeBaseUrl` (e.g. the cloud pod): the agent-server
|
|
910
|
+
// inlines THIS resource, so without the fallback every cloud render
|
|
911
|
+
// hung on the dead "Generating UI…" shell.
|
|
945
912
|
// Project the wrapper catalog AND the union-filtered
|
|
946
913
|
// publicEnv onto the inline bootstrap so the resource-served
|
|
947
914
|
// iframe matches the MCP-Apps postMessage path. Without this,
|
|
@@ -968,33 +935,59 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
968
935
|
// state).
|
|
969
936
|
let wsUrl;
|
|
970
937
|
let wsToken;
|
|
938
|
+
let wsExpiresAt;
|
|
971
939
|
if (opts.mintWsToken) {
|
|
972
940
|
try {
|
|
973
|
-
const minted = opts.mintWsToken(
|
|
941
|
+
const minted = opts.mintWsToken(sessionId, stored.appId);
|
|
974
942
|
wsUrl = minted.wsUrl;
|
|
975
943
|
wsToken = minted.token;
|
|
944
|
+
// Forward the token TTL so the iframe-runtime can degrade to
|
|
945
|
+
// static-only mode once it lapses (parity with the render-tool
|
|
946
|
+
// slice projection, render.ts). Dropping it left the live-mode
|
|
947
|
+
// resource shell unable to know when its WS token expired.
|
|
948
|
+
wsExpiresAt = minted.expiresAt;
|
|
976
949
|
}
|
|
977
950
|
catch {
|
|
978
951
|
// Silent — falls back to static-component mode.
|
|
979
952
|
}
|
|
980
953
|
}
|
|
954
|
+
// Mount-mode gate (moved below the live-channel mint): emit the
|
|
955
|
+
// loading shell ONLY when NEITHER a static codeUrl channel NOR a
|
|
956
|
+
// live-channel wsToken is available. A deployment that wires no
|
|
957
|
+
// codeStore (codeUrl === undefined) but DOES wire mintWsToken now
|
|
958
|
+
// mounts via live-mode rather than hanging on "Generating UI…".
|
|
959
|
+
if (!isSystem && codeUrl === undefined && (wsUrl === undefined || wsToken === undefined)) {
|
|
960
|
+
return loadingShell(uri, sessionId);
|
|
961
|
+
}
|
|
981
962
|
const html = buildSelfContainedShell({
|
|
982
|
-
|
|
963
|
+
sessionId,
|
|
983
964
|
appId: stored.appId,
|
|
984
965
|
...(isSystem
|
|
985
966
|
? { systemKind: picked.kind }
|
|
986
|
-
:
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
967
|
+
: codeUrl !== undefined
|
|
968
|
+
? {
|
|
969
|
+
codeUrl,
|
|
970
|
+
...(codeHash !== undefined ? { codeHash } : {}),
|
|
971
|
+
}
|
|
972
|
+
: // No static codeUrl → live-mode (wsUrl + token spread below)
|
|
973
|
+
// carries the render; buildSelfContainedShell accepts
|
|
974
|
+
// live-mode without codeUrl.
|
|
975
|
+
{}),
|
|
990
976
|
runtimeUrl: opts.runtimeUrl,
|
|
991
977
|
...(wsUrl !== undefined && wsToken !== undefined
|
|
992
|
-
? {
|
|
978
|
+
? {
|
|
979
|
+
wsUrl,
|
|
980
|
+
token: wsToken,
|
|
981
|
+
...(wsExpiresAt !== undefined ? { expiresAt: wsExpiresAt } : {}),
|
|
982
|
+
}
|
|
993
983
|
: {}),
|
|
994
984
|
...(opts.themeId !== undefined ? { themeId: opts.themeId } : {}),
|
|
995
985
|
...(opts.themeMode !== undefined ? { themeMode: opts.themeMode } : {}),
|
|
986
|
+
// Per-app theme overlay projected by `deriveRenderMeta` from
|
|
987
|
+
// the render's `theme` sidecar — forwarded so the
|
|
988
|
+
// resource-served iframe matches the postMessage path.
|
|
989
|
+
...(view.theme !== undefined ? { theme: view.theme } : {}),
|
|
996
990
|
...(view.propsJson !== undefined ? { propsJson: view.propsJson } : {}),
|
|
997
|
-
...(view.actionNextSteps !== undefined ? { actionNextSteps: view.actionNextSteps } : {}),
|
|
998
991
|
...(view.contextSlots !== undefined ? { contextSlots: view.contextSlots } : {}),
|
|
999
992
|
...(view.permissionsPolicy !== undefined
|
|
1000
993
|
? { permissionsPolicy: view.permissionsPolicy }
|
|
@@ -1032,7 +1025,7 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
1032
1025
|
// shell.
|
|
1033
1026
|
if (blueprint && opts.defaultAppIdFallback) {
|
|
1034
1027
|
const html = await buildShellFromBlueprint({
|
|
1035
|
-
|
|
1028
|
+
sessionId,
|
|
1036
1029
|
appId: opts.defaultAppIdFallback,
|
|
1037
1030
|
blueprint,
|
|
1038
1031
|
runtimeUrl: opts.runtimeUrl,
|
|
@@ -1046,16 +1039,16 @@ export function registerGguiRenderResourceTemplate(server, opts) {
|
|
|
1046
1039
|
}
|
|
1047
1040
|
// Fallthrough to loading shell when codeStore isn't wired.
|
|
1048
1041
|
}
|
|
1049
|
-
return loadingShell(uri,
|
|
1042
|
+
return loadingShell(uri, sessionId);
|
|
1050
1043
|
}
|
|
1051
1044
|
server.registerResource("ggui-render-self-contained", legacyTemplate, {
|
|
1052
1045
|
title: "ggui render (self-contained, legacy URI)",
|
|
1053
1046
|
description: "Per-render self-contained shell — single-segment URI shape predating the resume contract. Falls back to loading shell when the render is gone (no blueprintKey to do registry-only render).",
|
|
1054
1047
|
mimeType: GGUI_RENDER_RESOURCE_MIME,
|
|
1055
1048
|
}, handle);
|
|
1056
|
-
server.registerResource("ggui-
|
|
1049
|
+
server.registerResource("ggui-render-self-contained-resume", resumeTemplate, {
|
|
1057
1050
|
title: "ggui render (self-contained, resume URI)",
|
|
1058
|
-
description: "Per-render self-contained shell — two-segment URI shape carrying both
|
|
1051
|
+
description: "Per-render self-contained shell — two-segment URI shape carrying both sessionId AND blueprintKey. Resource handler runs Promise.all over render + registry; falls back to registry-only static render when the render has been evicted but the blueprint is still cached.",
|
|
1059
1052
|
mimeType: GGUI_RENDER_RESOURCE_MIME,
|
|
1060
1053
|
}, handle);
|
|
1061
1054
|
}
|
|
@@ -1083,9 +1076,6 @@ async function buildShellFromBlueprint(args) {
|
|
|
1083
1076
|
: undefined;
|
|
1084
1077
|
const propsJson = propsSpec ? JSON.stringify(deriveDefaultPropsValues(propsSpec)) : undefined;
|
|
1085
1078
|
const contextSlots = deriveDefaultContextSlots(contract.contextSpec);
|
|
1086
|
-
const actionNextSteps = "actionSpec" in contract && contract.actionSpec !== undefined
|
|
1087
|
-
? deriveWiredActionToolsFromSpec(contract.actionSpec)
|
|
1088
|
-
: undefined;
|
|
1089
1079
|
let codeUrl;
|
|
1090
1080
|
let codeHash;
|
|
1091
1081
|
try {
|
|
@@ -1098,7 +1088,7 @@ async function buildShellFromBlueprint(args) {
|
|
|
1098
1088
|
return undefined;
|
|
1099
1089
|
}
|
|
1100
1090
|
return buildSelfContainedShell({
|
|
1101
|
-
|
|
1091
|
+
sessionId: args.sessionId,
|
|
1102
1092
|
appId: args.appId,
|
|
1103
1093
|
codeUrl,
|
|
1104
1094
|
codeHash,
|
|
@@ -1107,7 +1097,6 @@ async function buildShellFromBlueprint(args) {
|
|
|
1107
1097
|
...(args.themeMode !== undefined ? { themeMode: args.themeMode } : {}),
|
|
1108
1098
|
...(propsJson !== undefined ? { propsJson } : {}),
|
|
1109
1099
|
...(contextSlots !== undefined ? { contextSlots } : {}),
|
|
1110
|
-
...(actionNextSteps !== undefined ? { actionNextSteps } : {}),
|
|
1111
1100
|
});
|
|
1112
1101
|
}
|
|
1113
1102
|
function deriveDefaultPropsValues(spec) {
|
|
@@ -1144,18 +1133,6 @@ function deriveDefaultContextSlots(spec) {
|
|
|
1144
1133
|
}
|
|
1145
1134
|
return collected.length > 0 ? collected : undefined;
|
|
1146
1135
|
}
|
|
1147
|
-
function deriveWiredActionToolsFromSpec(spec) {
|
|
1148
|
-
const collected = {};
|
|
1149
|
-
for (const [name, entry] of Object.entries(spec)) {
|
|
1150
|
-
if (entry &&
|
|
1151
|
-
typeof entry === "object" &&
|
|
1152
|
-
typeof entry.nextStep === "string" &&
|
|
1153
|
-
entry.nextStep.length > 0) {
|
|
1154
|
-
collected[name] = entry.nextStep;
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
return Object.keys(collected).length > 0 ? collected : undefined;
|
|
1158
|
-
}
|
|
1159
1136
|
/**
|
|
1160
1137
|
* Apply the full MCP Apps outbound wiring to a fresh `McpServer` - both
|
|
1161
1138
|
* the capability advertisement and the `ui://ggui/render` resource. The
|
|
@@ -1163,7 +1140,7 @@ function deriveWiredActionToolsFromSpec(spec) {
|
|
|
1163
1140
|
* one line.
|
|
1164
1141
|
*
|
|
1165
1142
|
* When `selfContained` is supplied, ALSO registers the per-render
|
|
1166
|
-
* `ui://ggui/render/{
|
|
1143
|
+
* `ui://ggui/render/{sessionId}` resource template that serves the
|
|
1167
1144
|
* self-contained shell (the path third-party MCP Apps hosts use). The
|
|
1168
1145
|
* legacy static URI registration is unconditional — first-party hosts
|
|
1169
1146
|
* (Studio, Portal, console) still rely on the postMessage path.
|