@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.
Files changed (152) hide show
  1. package/dist/admin-blueprints-transport.d.ts.map +1 -1
  2. package/dist/admin-blueprints-transport.js +2 -1
  3. package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
  4. package/dist/admin-oauth-providers-transport.js +7 -5
  5. package/dist/api-renders-routes.d.ts +85 -0
  6. package/dist/api-renders-routes.d.ts.map +1 -0
  7. package/dist/api-renders-routes.js +372 -0
  8. package/dist/build-mcp.d.ts +2 -2
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +64 -5
  11. package/dist/code-routes.d.ts +47 -0
  12. package/dist/code-routes.d.ts.map +1 -0
  13. package/dist/code-routes.js +81 -0
  14. package/dist/code-store-fs.js +2 -2
  15. package/dist/console-auth.d.ts +10 -10
  16. package/dist/console-auth.d.ts.map +1 -1
  17. package/dist/console-auth.js +5 -5
  18. package/dist/console-blueprint-routes.d.ts +71 -0
  19. package/dist/console-blueprint-routes.d.ts.map +1 -0
  20. package/dist/console-blueprint-routes.js +348 -0
  21. package/dist/console-chat-routes.d.ts +80 -0
  22. package/dist/console-chat-routes.d.ts.map +1 -0
  23. package/dist/console-chat-routes.js +204 -0
  24. package/dist/console-config-routes.d.ts +37 -0
  25. package/dist/console-config-routes.d.ts.map +1 -0
  26. package/dist/console-config-routes.js +91 -0
  27. package/dist/console-headers.d.ts +1 -1
  28. package/dist/console-info-routes.d.ts +84 -0
  29. package/dist/console-info-routes.d.ts.map +1 -0
  30. package/dist/console-info-routes.js +135 -0
  31. package/dist/console-keys-routes.d.ts +50 -0
  32. package/dist/console-keys-routes.d.ts.map +1 -0
  33. package/dist/console-keys-routes.js +222 -0
  34. package/dist/console-llm-keys-routes.d.ts +47 -0
  35. package/dist/console-llm-keys-routes.d.ts.map +1 -0
  36. package/dist/console-llm-keys-routes.js +443 -0
  37. package/dist/console-mcp-tools-routes.d.ts +41 -0
  38. package/dist/console-mcp-tools-routes.d.ts.map +1 -0
  39. package/dist/console-mcp-tools-routes.js +60 -0
  40. package/dist/console-registry-routes.d.ts +66 -0
  41. package/dist/console-registry-routes.d.ts.map +1 -0
  42. package/dist/console-registry-routes.js +276 -0
  43. package/dist/console-session-routes.d.ts +89 -0
  44. package/dist/console-session-routes.d.ts.map +1 -0
  45. package/dist/console-session-routes.js +385 -0
  46. package/dist/console-sessions-routes.d.ts +52 -0
  47. package/dist/console-sessions-routes.d.ts.map +1 -0
  48. package/dist/console-sessions-routes.js +106 -0
  49. package/dist/console-static-routes.d.ts +54 -0
  50. package/dist/console-static-routes.d.ts.map +1 -0
  51. package/dist/console-static-routes.js +190 -0
  52. package/dist/console-theme-routes.d.ts +3 -3
  53. package/dist/console-theme-routes.js +1 -1
  54. package/dist/console-timeline.d.ts +5 -5
  55. package/dist/console-timeline.d.ts.map +1 -1
  56. package/dist/console-timeline.js +27 -26
  57. package/dist/console-welcome.js +2 -2
  58. package/dist/email-login.d.ts.map +1 -1
  59. package/dist/email-login.js +2 -3
  60. package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
  61. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
  62. package/dist/ggui-session-channel/action-ingress.js +228 -0
  63. package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
  64. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
  65. package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
  66. package/dist/ggui-session-channel/internal-types.d.ts +102 -0
  67. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
  68. package/dist/ggui-session-channel/internal-types.js +6 -0
  69. package/dist/ggui-session-channel/outbound.d.ts +81 -0
  70. package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
  71. package/dist/ggui-session-channel/outbound.js +174 -0
  72. package/dist/ggui-session-channel/socket-router.d.ts +38 -0
  73. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
  74. package/dist/ggui-session-channel/socket-router.js +213 -0
  75. package/dist/ggui-session-channel/subscribe.d.ts +165 -0
  76. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
  77. package/dist/ggui-session-channel/subscribe.js +370 -0
  78. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
  79. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
  80. package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
  81. package/dist/ggui-session-channel.d.ts +425 -0
  82. package/dist/ggui-session-channel.d.ts.map +1 -0
  83. package/dist/ggui-session-channel.js +262 -0
  84. package/dist/health-routes.d.ts +76 -0
  85. package/dist/health-routes.d.ts.map +1 -0
  86. package/dist/health-routes.js +145 -0
  87. package/dist/index.d.ts +10 -11
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +8 -9
  90. package/dist/instructions-presets.d.ts +3 -3
  91. package/dist/instructions-presets.js +24 -24
  92. package/dist/llm-backed-negotiator.d.ts +68 -67
  93. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  94. package/dist/llm-backed-negotiator.js +82 -221
  95. package/dist/mcp-apps-outbound.d.ts +47 -48
  96. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  97. package/dist/mcp-apps-outbound.js +154 -177
  98. package/dist/mcp-endpoint-routes.d.ts +88 -0
  99. package/dist/mcp-endpoint-routes.d.ts.map +1 -0
  100. package/dist/mcp-endpoint-routes.js +359 -0
  101. package/dist/mcp-mounts.d.ts +2 -76
  102. package/dist/mcp-mounts.d.ts.map +1 -1
  103. package/dist/mcp-mounts.js +0 -76
  104. package/dist/oauth-as-routes.d.ts +60 -0
  105. package/dist/oauth-as-routes.d.ts.map +1 -0
  106. package/dist/oauth-as-routes.js +82 -0
  107. package/dist/oauth-clients-routes.d.ts +39 -0
  108. package/dist/oauth-clients-routes.d.ts.map +1 -0
  109. package/dist/oauth-clients-routes.js +87 -0
  110. package/dist/oauth-login-types.d.ts +1 -20
  111. package/dist/oauth-login-types.d.ts.map +1 -1
  112. package/dist/oauth-login-types.js +30 -7
  113. package/dist/oauth-login.d.ts.map +1 -1
  114. package/dist/oauth-login.js +3 -2
  115. package/dist/oauth-providers-store.d.ts.map +1 -1
  116. package/dist/oauth-providers-store.js +5 -5
  117. package/dist/oauth.d.ts +9 -8
  118. package/dist/oauth.d.ts.map +1 -1
  119. package/dist/oauth.js +41 -19
  120. package/dist/pairing-transport.d.ts.map +1 -1
  121. package/dist/pairing-transport.js +2 -1
  122. package/dist/request-context.d.ts +2 -2
  123. package/dist/request-context.js +2 -2
  124. package/dist/reserved-validators.d.ts.map +1 -1
  125. package/dist/reserved-validators.js +9 -1
  126. package/dist/route-param.d.ts +9 -0
  127. package/dist/route-param.d.ts.map +1 -0
  128. package/dist/route-param.js +10 -0
  129. package/dist/runtime-bundle-route.d.ts +43 -0
  130. package/dist/runtime-bundle-route.d.ts.map +1 -0
  131. package/dist/runtime-bundle-route.js +80 -0
  132. package/dist/schema-compat.d.ts +64 -62
  133. package/dist/schema-compat.d.ts.map +1 -1
  134. package/dist/schema-compat.js +23 -51
  135. package/dist/server.d.ts +179 -193
  136. package/dist/server.d.ts.map +1 -1
  137. package/dist/server.js +644 -3759
  138. package/dist/storage.d.ts +5 -5
  139. package/dist/storage.d.ts.map +1 -1
  140. package/dist/storage.js +5 -5
  141. package/dist/thread-transport.d.ts.map +1 -1
  142. package/dist/thread-transport.js +4 -3
  143. package/dist/user-session-auth.d.ts +7 -21
  144. package/dist/user-session-auth.d.ts.map +1 -1
  145. package/dist/user-session-auth.js +7 -28
  146. package/package.json +16 -15
  147. package/dist/mcp-apps-inbound.d.ts +0 -86
  148. package/dist/mcp-apps-inbound.d.ts.map +0 -1
  149. package/dist/mcp-apps-inbound.js +0 -283
  150. package/dist/render-channel.d.ts +0 -694
  151. package/dist/render-channel.d.ts.map +0 -1
  152. 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 a minimal `ui/initialize` preflight to read the
45
- * `ai.ggui/render.runtimeUrl` slice field, then dynamic-script-loads
46
- * the `@ggui-ai/iframe-runtime` bundle from that URL. Every rendering
47
- * concern - WS open, subscribe, render mount, component code eval,
48
- * adapter install - belongs to the renderer bundle (shipped C7a-d),
49
- * not here.
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 first `ui/initialize` response - origin-agnostic, works
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=".../renderer.js">`").
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
- * - `BOOTSTRAP_META_MISSING` - `ui/initialize` returned without a
74
- * valid `ai.ggui/render` slice OR without a `runtimeUrl` field on
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 / session-mismatch) are
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 AND, for post-WS-open
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 session state from
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 solely to fetch `runtimeUrl`; the renderer bundle's
95
- * autostart path runs its own `ui/initialize` for the full bootstrap
96
- * parse. MCP Apps hosts handle repeats idempotently - the preflight's
97
- * cost is a single postMessage round-trip.
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 GGUI_SESSION_SHELL_HTML} so the exact bytes the
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 GGUI_SESSION_SHELL_SCRIPT_HASH}.
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
- * NEVER mutate this constant without also bumping
118
- * {@link GGUI_SESSION_SHELL_SCRIPT_HASH} — the
119
- * `mcp-apps-outbound.test.ts` drift test recomputes the hash and fails
120
- * loudly if they diverge.
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, or the first-party
131
- // params.toolOutput._meta shape), set window.__GGUI_META__ to the
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; renderId is the canonical identity.
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:'ggui:bootstrap-failed',reason:reason,message:message},'*');}catch(e){}
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": { renderId,
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('BOOTSTRAP_MALFORMED','Bootstrap payload malformed.');
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 (NOT under params.toolOutput, which is where the
256
- // first-party McpAppIframe convention wraps it). Spec-compliant
257
- // hosts (Claude Desktop, claude.ai Connector, Claude Code) deliver
258
- // slice-envelope material here.
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. Try this FIRST so Claude Desktop / claude.ai
278
- // Connector / Claude Code land here.
279
- var specB=readMetaFromCallToolResult(m.params);
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 two
291
- // branches above.
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-session',version:'1.0.0'},
269
+ appInfo:{name:'ggui-render',version:'1.0.0'},
301
270
  protocolVersion:'2026-01-26'
302
- }).then(function(result){
271
+ }).then(function(){
303
272
  clearTimeout(initTimer);
304
273
  postNotification('ui/notifications/initialized',{});
305
- // Path B: slice envelope inline in ui/initialize result. Hosts
306
- // using <McpAppIframe>'s first-party dispatch deliver meta here
307
- // and never send a separate ui/notifications/tool-result.
308
- var b=readMetaFromInitResult(result);
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 session-resource
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-session state lives on the live channel, not in the resource.
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 canvas iframe makes.
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 canvas HTML that
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 session.
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) + session ids in a `window.__GGUI_META__`
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
- renderId: opts.renderId,
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
- ...(opts.appCallableTools !== undefined && opts.appCallableTools.length > 0
604
- ? { appCallableTools: opts.appCallableTools }
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(renderId) {
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-render-id="${renderId
698
+ <div id="ggui-root" data-ggui-shell="loading" data-ggui-session-id="${sessionId
730
699
  .replace(/&/g, "&amp;")
731
700
  .replace(/"/g, "&quot;")
732
701
  .replace(/</g, "&lt;")
733
702
  .replace(/>/g, "&gt;")}">Generating UI…</div>
734
703
  </body></html>`;
735
704
  }
736
- function pickComponentFromRender(render) {
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 !== undefined &&
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/{renderId}` resource template. Each
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 renderId; hosts fetch THAT URI rather
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
- * - Render not found → loading shell (host re-fetches; absent
747
+ * - GguiSession not found → loading shell (host re-fetches; absent
784
748
  * render is a transient state immediately after `ggui_render`).
785
- * - Render found, no componentCode yet → loading shell.
786
- * - Render found, componentCode present → self-contained shell.
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/{renderId}`.
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/{renderId}/
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}/{renderId}`, {
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}/{renderId}/{blueprintKey}`, { list: undefined });
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-stack-item augmentation, gadget bundle / style / API
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, renderId) => shellContents(uri, buildSelfContainedLoadingShell(renderId));
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 renderIdRaw = variables["renderId"];
869
- const renderId = Array.isArray(renderIdRaw) ? renderIdRaw[0] : renderIdRaw;
870
- if (typeof renderId !== "string" || renderId.length === 0) {
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(renderId),
884
- hasResumeKey && opts.vectorStore && opts.defaultAppIdFallback
885
- ? findBlueprintExact({ vectorStore: opts.vectorStore }, opts.defaultAppIdFallback, "template", blueprintKey)
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 = pickComponentFromRender(stored.render);
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
- if (!isSystem && codeUrl === undefined) {
938
- // Compiled-component render but no codeUrl channel available
939
- // — emit the loading shell so the operator can refresh once
940
- // codeStore is wired. Direct-render `/r/<shortCode>` falls
941
- // through to live-mode instead; this MCP-resource path has
942
- // no WS-mode fallback (resources/read is one-shot).
943
- return loadingShell(uri, renderId);
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(renderId, stored.appId);
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
- renderId,
963
+ sessionId,
983
964
  appId: stored.appId,
984
965
  ...(isSystem
985
966
  ? { systemKind: picked.kind }
986
- : {
987
- codeUrl: codeUrl,
988
- ...(codeHash !== undefined ? { codeHash } : {}),
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
- ? { wsUrl, token: wsToken }
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
- renderId,
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, renderId);
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-session-self-contained-resume", resumeTemplate, {
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 renderId 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.",
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
- renderId: args.renderId,
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/{renderId}` resource template that serves the
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.