@ggui-ai/mcp-server 0.7.0 → 0.9.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 (56) hide show
  1. package/dist/api-renders-routes.d.ts.map +1 -1
  2. package/dist/api-renders-routes.js +32 -23
  3. package/dist/api-renders-stream-route.d.ts +80 -0
  4. package/dist/api-renders-stream-route.d.ts.map +1 -0
  5. package/dist/api-renders-stream-route.js +311 -0
  6. package/dist/build-mcp.d.ts +10 -0
  7. package/dist/build-mcp.d.ts.map +1 -1
  8. package/dist/build-mcp.js +64 -4
  9. package/dist/console-session-routes.d.ts +10 -5
  10. package/dist/console-session-routes.d.ts.map +1 -1
  11. package/dist/console-session-routes.js +21 -5
  12. package/dist/ggui-session-channel/action-ingress.d.ts +2 -2
  13. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
  14. package/dist/ggui-session-channel/channel-subscriptions.d.ts +4 -4
  15. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
  16. package/dist/ggui-session-channel/internal-types.d.ts +63 -9
  17. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
  18. package/dist/ggui-session-channel/outbound.d.ts +15 -5
  19. package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
  20. package/dist/ggui-session-channel/outbound.js +64 -24
  21. package/dist/ggui-session-channel/socket-router.d.ts +8 -3
  22. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -1
  23. package/dist/ggui-session-channel/socket-router.js +6 -1
  24. package/dist/ggui-session-channel/subscribe.d.ts +48 -3
  25. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
  26. package/dist/ggui-session-channel/subscribe.js +97 -36
  27. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +23 -13
  28. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
  29. package/dist/ggui-session-channel/subscriber-lifecycle.js +24 -11
  30. package/dist/ggui-session-channel.d.ts +58 -11
  31. package/dist/ggui-session-channel.d.ts.map +1 -1
  32. package/dist/ggui-session-channel.js +55 -19
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -1
  36. package/dist/instructions-presets.js +10 -10
  37. package/dist/mcp-apps-outbound.d.ts +93 -4
  38. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  39. package/dist/mcp-apps-outbound.js +460 -105
  40. package/dist/mcp-endpoint-routes.d.ts +17 -5
  41. package/dist/mcp-endpoint-routes.d.ts.map +1 -1
  42. package/dist/mcp-endpoint-routes.js +2 -0
  43. package/dist/oauth-as-routes.d.ts.map +1 -1
  44. package/dist/oauth-as-routes.js +36 -0
  45. package/dist/oauth.d.ts.map +1 -1
  46. package/dist/oauth.js +8 -1
  47. package/dist/runtime-bundle-hash.d.ts +43 -0
  48. package/dist/runtime-bundle-hash.d.ts.map +1 -0
  49. package/dist/runtime-bundle-hash.js +66 -0
  50. package/dist/runtime-bundle-route.d.ts +15 -0
  51. package/dist/runtime-bundle-route.d.ts.map +1 -1
  52. package/dist/runtime-bundle-route.js +21 -5
  53. package/dist/server.d.ts +83 -14
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +167 -15
  56. package/package.json +12 -12
package/dist/server.js CHANGED
@@ -44,7 +44,7 @@ import { createInMemoryBlueprintSearch, createInMemoryGeneratorRegistry, FixedWi
44
44
  import { createGguiListGadgetsHandler, createGguiListThemesHandler, } from "@ggui-ai/mcp-server-handlers/app-discovery";
45
45
  import { createDescribeBlueprintFormatHandler, createDescribeDataContractFormatHandler, createGetBlueprintBoilerplateHandler, createGetExampleBlueprintsHandler, createListAvailablePrimitivesHandler, createListFeaturedBlueprintsHandler, createRenderBlueprintHandler, createSearchBlueprintsHandler, createValidateBlueprintHandler, } from "@ggui-ai/mcp-server-handlers/blueprints";
46
46
  import { createGguiOpsDeleteBlueprintHandler, createGguiOpsGenerateBlueprintHandler, createGguiOpsListBlueprintsHandler, createGguiOpsRegisterBlueprintHandler, createGguiOpsUpdateBlueprintHandler, } from "@ggui-ai/mcp-server-handlers/ops-blueprint";
47
- import { setCacheTraceSink, setPayloadTraceSink } from "@ggui-ai/mcp-server-handlers/renders";
47
+ import { setCacheTraceSink, setPayloadTraceSink, wsOriginToHttpOrigin, } from "@ggui-ai/mcp-server-handlers/renders";
48
48
  import { loadTheme } from "@ggui-ai/project-config/node";
49
49
  import { LIFECYCLE_CHANNEL } from "@ggui-ai/protocol";
50
50
  import { setLlmTraceSink } from "@ggui-ai/ui-gen/harness/llm-trace-sink";
@@ -52,6 +52,7 @@ import { setValidatorTraceSink } from "@ggui-ai/ui-gen/harness/validator-trace-s
52
52
  import express from "express";
53
53
  import { AsyncLocalStorage } from "node:async_hooks";
54
54
  import { randomBytes } from "node:crypto";
55
+ import fs from "node:fs";
55
56
  import path from "node:path";
56
57
  import { BoundedCacheTraceSink, mountConsoleCacheRoutes } from "./console-cache.js";
57
58
  import { applyDevtoolSecurityHeaders } from "./console-headers.js";
@@ -59,6 +60,7 @@ import { BoundedLlmTraceSink, mountConsoleLlmTraceRoutes } from "./console-llm-t
59
60
  import { BoundedPayloadTraceSink, mountConsolePayloadsRoutes } from "./console-payloads.js";
60
61
  import { mountDevtoolThemeRoutes, } from "./console-theme-routes.js";
61
62
  import { mountConsoleTimelineRoutes } from "./console-timeline.js";
63
+ import { buildInlineRenderShellHtml } from "./mcp-apps-outbound.js";
62
64
  import { BoundedValidatorTraceSink, mountConsoleValidatorRoutes } from "./console-validator.js";
63
65
  // Operator-class MCP handlers — twelve `ggui_ops_*` handlers across
64
66
  // four domains (apps / orgs / connector-keys / coupon). Every factory
@@ -69,12 +71,13 @@ import { createCreateAppHandler, createDeleteAppHandler, createListAppsHandler,
69
71
  import { createIssueConnectorKeyHandler, createListConnectorKeysHandler, createRevokeConnectorKeyHandler, } from "@ggui-ai/mcp-server-handlers/ops-connector-keys";
70
72
  import { createRedeemCouponHandler, } from "@ggui-ai/mcp-server-handlers/ops-coupon";
71
73
  import { createCreateOrgHandler, createGetOrgBalanceHandler, createInviteToOrgHandler, createListOrgsHandler, createRemoveOrgMemberHandler, createRenameOrgHandler, createRevokeInviteHandler, } from "@ggui-ai/mcp-server-handlers/ops-orgs";
72
- import { createGguiConsumeHandler, createGguiDeclareToolCatalogHandler, createGguiEmitHandler, createGguiGetSessionHandler, createGguiHandshakeHandler, createGguiListSessionsHandler, createGguiRefreshWsTokenHandler, createGguiRenderHandler, createGguiSubmitActionHandler, createGguiSyncContextHandler, createGguiUpdateHandler, InMemoryToolIdentityCatalogStore, createInMemoryProvisionalPreviewRegistry, } from "@ggui-ai/mcp-server-handlers/renders";
74
+ import { createGguiConsumeHandler, createGguiDeclareToolCatalogHandler, createGguiEmitHandler, createGguiGetSessionHandler, createGguiHandshakeHandler, createGguiListSessionsHandler, createGguiRefreshWsTokenHandler, createGguiRenderHandler, createGguiRuntimePullHandler, createGguiRuntimeTelemetryHandler, createGguiSubmitActionHandler, createGguiSyncContextHandler, createGguiUpdateHandler, createGguiAmendHandler, InMemoryToolIdentityCatalogStore, createInMemoryProvisionalPreviewRegistry, } from "@ggui-ai/mcp-server-handlers/renders";
73
75
  import { DEFAULT_ADMIN_BLUEPRINTS_PATH, mountAdminBlueprintsTransport, } from "./admin-blueprints-transport.js";
74
76
  import { mountAdminOAuthProvidersTransport } from "./admin-oauth-providers-transport.js";
75
77
  import { DEFAULT_BUILDER_APP_ID, defaultAppIdFromIdentity } from "./auth.js";
76
78
  import { mountMcpEndpoints } from "./mcp-endpoint-routes.js";
77
79
  import { mountApiRendersRoutes } from "./api-renders-routes.js";
80
+ import { mountApiRendersStreamRoute } from "./api-renders-stream-route.js";
78
81
  import { mountConsoleBlueprintRoutes } from "./console-blueprint-routes.js";
79
82
  import { mountConsoleChatRoutes } from "./console-chat-routes.js";
80
83
  import { mountConsoleConfigRoutes } from "./console-config-routes.js";
@@ -91,6 +94,7 @@ import { mountHealthRoutes } from "./health-routes.js";
91
94
  import { mountOAuthAuthorizationServerRoutes } from "./oauth-as-routes.js";
92
95
  import { mountOAuthClientsRoutes } from "./oauth-clients-routes.js";
93
96
  import { mountRuntimeBundleRoute } from "./runtime-bundle-route.js";
97
+ import { computeRuntimeBundleHash, insertRuntimeBundleHash, } from "./runtime-bundle-hash.js";
94
98
  import { createCsrfMiddleware, mountCsrfTokenRoute } from "./csrf-middleware.js";
95
99
  import { mountEmailLoginRoutes } from "./email-login.js";
96
100
  import { resolveMcpInstructions } from "./instructions-presets.js";
@@ -108,7 +112,7 @@ import { InMemoryOAuthStorage } from "./oauth.js";
108
112
  import { DEFAULT_PAIRING_ADMIN_INIT_PATH, DEFAULT_PAIRING_PATH, mountPairingTransport, } from "./pairing-transport.js";
109
113
  import { createPairLoginRateLimitMiddleware } from "./rate-limit-middleware.js";
110
114
  import { createGguiSessionChannelServer, } from "./ggui-session-channel.js";
111
- import { buildRequestContextMiddleware, resolveRuntimeUrl } from "./request-context.js";
115
+ import { buildRequestContextMiddleware, resolvePublicBaseUrl, resolveRuntimeUrl, } from "./request-context.js";
112
116
  import { composePreviewReservedValidator, mergeReservedValidators } from "./reserved-validators.js";
113
117
  import { checkRenderSchemaCompat, DEFAULT_SCHEMA_COMPAT_MODE, } from "./schema-compat.js";
114
118
  import { createSecurityHeadersMiddleware } from "./security-headers-middleware.js";
@@ -204,6 +208,7 @@ export function defaultHandlers(deps) {
204
208
  embedding: deps.embedding,
205
209
  vectors: deps.vectors,
206
210
  ...(deps.blueprints ? { blueprints: deps.blueprints } : {}),
211
+ ...(deps.registrySearch ? { registry: deps.registrySearch } : {}),
207
212
  }),
208
213
  createListFeaturedBlueprintsHandler(deps.blueprints ? { blueprints: deps.blueprints } : {}),
209
214
  // Spec / discovery handlers — zero-deps. These may be tagged
@@ -281,6 +286,30 @@ export function defaultHandlers(deps) {
281
286
  ? { renderIdentityStore: deps.render.renderIdentityStore }
282
287
  : {}),
283
288
  }));
289
+ // `ggui_runtime_pull` — terminal bridge-pull rung of the live-channel
290
+ // failover ladder (WS → SSE → HTTP polling → bridge-pull). A
291
+ // CSP-jailed MCP Apps iframe pulls the GguiSessionEvent ledger over
292
+ // the host's tools/call postMessage relay — same
293
+ // `_meta.ui.visibility: ['app']` channel as ggui_runtime_submit_action,
294
+ // same ledger + horizon semantics as `GET /api/sessions/:id/events`
295
+ // (both read `renderStore.listEventsSince`). Wired only when a
296
+ // renderStore is bound (render is on) — without render there is no
297
+ // ledger to serve.
298
+ handlers.push(createGguiRuntimePullHandler({
299
+ renderStore: deps.render.renderStore,
300
+ }));
301
+ // `ggui_runtime_telemetry` — the iframe runtime's transport
302
+ // self-report (visibility ['app'], same view-callable channel as
303
+ // submit_action/pull). Sandboxed hosts (claude.ai's
304
+ // claudemcpcontent frames) expose no readable console and no
305
+ // network, so the delivery ladder's behavior there is invisible
306
+ // WITHOUT this channel: the view batches its rung transitions +
307
+ // doorbell events and this handler logs them for operators.
308
+ // Fire-and-forget, store-less — gated on deps.render only because
309
+ // the ladder it reports on exists only when renders do.
310
+ handlers.push(createGguiRuntimeTelemetryHandler({
311
+ ...(deps.logger ? { logger: deps.logger } : {}),
312
+ }));
284
313
  // `ggui_runtime_refresh_ws_token` — G14 (2026-05-23) signed-
285
314
  // envelope refresh tool. Registered only when a refresh seam is
286
315
  // wired (typically `channelBootstrap.refresh` from the
@@ -364,12 +393,29 @@ export function defaultHandlers(deps) {
364
393
  // re-apply patched props on the live mount without a WS round-trip.
365
394
  ...(deps.update.mintBootstrap ? { mintWsToken: deps.update.mintBootstrap } : {}),
366
395
  ...(deps.update.runtimeUrl !== undefined ? { runtimeUrl: deps.update.runtimeUrl } : {}),
396
+ ...(deps.update.sessionApiBaseUrl !== undefined
397
+ ? { sessionApiBaseUrl: deps.update.sessionApiBaseUrl }
398
+ : {}),
367
399
  ...(deps.update.themeId !== undefined ? { themeId: deps.update.themeId } : {}),
368
400
  ...(deps.update.themeMode !== undefined ? { themeMode: deps.update.themeMode } : {}),
369
401
  ...(deps.update.themeProvider !== undefined
370
402
  ? { themeProvider: deps.update.themeProvider }
371
403
  : {}),
372
404
  }));
405
+ // ggui_amend rides the SAME deps slot (#483 tool split): one
406
+ // mutation core, one wiring. It reads only the mutation-flow deps
407
+ // (renderStore / identity / notifier) — the bootstrap-emission
408
+ // options above are meaningless to a tool that emits no result
409
+ // meta by design.
410
+ handlers.push(createGguiAmendHandler({
411
+ renderStore: deps.update.renderStore,
412
+ ...(deps.update.renderIdentityStore
413
+ ? { renderIdentityStore: deps.update.renderIdentityStore }
414
+ : {}),
415
+ ...(deps.update.propsUpdateNotifier
416
+ ? { propsUpdateNotifier: deps.update.propsUpdateNotifier }
417
+ : {}),
418
+ }));
373
419
  }
374
420
  // ggui_consume registers whenever render is bound (it shares the
375
421
  // GguiSessionStore for sessionId resolution + tenancy checks).
@@ -512,6 +558,9 @@ export function defaultHandlers(deps) {
512
558
  : {}),
513
559
  ...(deps.render.mintBootstrap ? { mintWsToken: deps.render.mintBootstrap } : {}),
514
560
  ...(deps.render.runtimeUrl !== undefined ? { runtimeUrl: deps.render.runtimeUrl } : {}),
561
+ ...(deps.render.sessionApiBaseUrl !== undefined
562
+ ? { sessionApiBaseUrl: deps.render.sessionApiBaseUrl }
563
+ : {}),
515
564
  ...(deps.render.themeId !== undefined ? { themeId: deps.render.themeId } : {}),
516
565
  ...(deps.render.themeMode !== undefined ? { themeMode: deps.render.themeMode } : {}),
517
566
  ...(deps.render.themeProvider !== undefined
@@ -541,10 +590,11 @@ export function defaultHandlers(deps) {
541
590
  ? { checkRenderContracts: deps.render.checkRenderContracts }
542
591
  : {}),
543
592
  // Content-addressable code store. Both fields are forwarded
544
- // together; the render handler
545
- // requires both to emit `codeUrl`. Absent or partial = no
546
- // static delivery channel, so the bootstrap mounts through the
547
- // live trio instead (see render.ts handler body).
593
+ // together; the render handler requires both to emit
594
+ // `codeUrl`. Absent or partial = no cache-addressable static
595
+ // channel; the bootstrap still carries the inline `codeB64`
596
+ // twin for under-cap renders and mounts through the live trio
597
+ // where minted (see render.ts handler body).
548
598
  ...(deps.render.codeStore && deps.render.codeBaseUrl
549
599
  ? {
550
600
  codeStore: deps.render.codeStore,
@@ -770,7 +820,38 @@ export function createGguiServer(opts = {}) {
770
820
  const runtimeBundleFile = runtimeConfig.distDir !== undefined
771
821
  ? path.join(runtimeConfig.distDir, "iframe-runtime.js")
772
822
  : RUNTIME_BUNDLE_FILE;
773
- const runtimeBootstrapUrl = runtimeConfig.url ?? runtimePath;
823
+ // Bundle bytes, captured ONCE at composition. Feed three consumers:
824
+ // the content hash below, the immutable hashed route (which must
825
+ // serve exactly the bytes its name hashes — see runtime-bundle-
826
+ // route.ts), and the opt-in inline-runtime shell. Unreadable file ⇒
827
+ // undefined ⇒ every consumer falls back to its pre-#472 behavior.
828
+ let runtimeBundleBytes;
829
+ if (runtimeEnabled) {
830
+ try {
831
+ runtimeBundleBytes = fs.readFileSync(runtimeBundleFile);
832
+ }
833
+ catch {
834
+ // The plain route's own existsSync handles the missing-bundle
835
+ // warning; nothing extra to say here.
836
+ }
837
+ }
838
+ // Content-hashed runtime URL (#472). The stamped `runtimeUrl` (and
839
+ // the hashed route it points at) embeds sha256[0..12) of the bytes:
840
+ // clients cache one download per content version with zero
841
+ // revalidations, and a deploy rolls over by changing the URL. The
842
+ // rewrite applies to a configured absolute `runtime.url` ONLY when
843
+ // its filename matches the served bundle's route (a CDN fronting
844
+ // THIS server still resolves the hashed name at its origin); a URL
845
+ // pointing at a foreign copy is left untouched, and
846
+ // `runtime.hashedUrl: false` opts out entirely.
847
+ const runtimeBundleHash = runtimeBundleBytes !== undefined && runtimeConfig.hashedUrl !== false
848
+ ? computeRuntimeBundleHash(runtimeBundleBytes)
849
+ : undefined;
850
+ const insertHash = (urlOrPath) => runtimeBundleHash === undefined
851
+ ? urlOrPath
852
+ : insertRuntimeBundleHash(urlOrPath, runtimeBundleHash, runtimePath.slice(runtimePath.lastIndexOf("/") + 1));
853
+ const hashedRuntimePath = runtimeBundleHash !== undefined ? insertHash(runtimePath) : undefined;
854
+ const runtimeBootstrapUrl = insertHash(runtimeConfig.url ?? runtimePath);
774
855
  // Lazy resolver: each render/update handler invocation looks up the
775
856
  // request-context-derived absolute base inside the request scope
776
857
  // (via AsyncLocalStorage). Static `publicBaseUrl` wins when set;
@@ -1185,6 +1266,7 @@ export function createGguiServer(opts = {}) {
1185
1266
  // Registers `ggui_runtime_declare_tool_catalog`.
1186
1267
  toolIdentityCatalogStore,
1187
1268
  ...(opts.blueprintProvider ? { blueprints: opts.blueprintProvider } : {}),
1269
+ ...(opts.registrySearch ? { registrySearch: opts.registrySearch } : {}),
1188
1270
  // UI registry for `ggui_render_blueprint`. Absent = render tool
1189
1271
  // is NOT registered on this server (defaultHandlers' own opt-in
1190
1272
  // rule). OSS CLI wires a `LocalUiRegistry` here so manifest
@@ -1270,6 +1352,14 @@ export function createGguiServer(opts = {}) {
1270
1352
  // an absolute URL instead of the relative default that
1271
1353
  // breaks under srcdoc iframes (claude.ai).
1272
1354
  runtimeUrl: resolveRuntimeUrlForResultMeta,
1355
+ // Session-API base for `pollingUrl` + `sseUrl` stamping.
1356
+ // Same lazy request-scope resolution as runtimeUrl:
1357
+ // static publicBaseUrl wins, X-Forwarded-Host honored
1358
+ // for loopback peers, `undefined` outside any request —
1359
+ // the handler then falls back to the minted trio's
1360
+ // wsUrl origin flip (OSS dev: `ws://localhost/ws` →
1361
+ // `http://localhost`).
1362
+ sessionApiBaseUrl: () => resolvePublicBaseUrl(opts.publicBaseUrl),
1273
1363
  // Forward operator-picked theme onto every
1274
1364
  // `ai.ggui/render.themeId` slice field so MCP Apps hosts
1275
1365
  // (claude.ai, Claude Desktop) that mount via the
@@ -1458,10 +1548,10 @@ export function createGguiServer(opts = {}) {
1458
1548
  ? { renderIdentityStore: opts.renderIdentityStore }
1459
1549
  : {}),
1460
1550
  propsUpdateNotifier: {
1461
- sendPropsUpdate: async (sessionId, props) => {
1551
+ sendPropsUpdate: async (sessionId, props, epoch) => {
1462
1552
  if (!channelForHealth)
1463
1553
  return;
1464
- await channelForHealth.sendPropsUpdate(sessionId, props);
1554
+ await channelForHealth.sendPropsUpdate(sessionId, props, epoch);
1465
1555
  },
1466
1556
  },
1467
1557
  // Bootstrap-emission deps. Mirror render so MCP Apps hosts
@@ -1475,6 +1565,9 @@ export function createGguiServer(opts = {}) {
1475
1565
  // same absolute URL when the server sits behind a tunnel
1476
1566
  // / reverse proxy.
1477
1567
  runtimeUrl: resolveRuntimeUrlForResultMeta,
1568
+ // Session-API base — matches render so both tools stamp
1569
+ // identical `pollingUrl` + `sseUrl` values.
1570
+ sessionApiBaseUrl: () => resolvePublicBaseUrl(opts.publicBaseUrl),
1478
1571
  ...(opts.theme !== undefined && opts.theme.source === "preset"
1479
1572
  ? { themeId: opts.theme.preset }
1480
1573
  : {}),
@@ -1782,15 +1875,40 @@ export function createGguiServer(opts = {}) {
1782
1875
  logger,
1783
1876
  corsOrigins: originHostPolicy.allowedOrigins,
1784
1877
  });
1878
+ // Inline-runtime shell (opt-in): embed the runtime bundle's source
1879
+ // into the static-resource shell so fetch-blocked hosts (iframe CSP
1880
+ // without external script-src) can still boot. Resolved ONCE at
1881
+ // composition time from the same bundle file the runtime mount
1882
+ // serves. An explicit `shellHtml` wins; a read failure warns and
1883
+ // falls back to the thin shell (the server still works everywhere
1884
+ // the thin shell worked).
1885
+ let inlineShellHtml;
1886
+ if (mcpAppsEnabled && mcpAppsConfig.inlineRuntimeShell === true) {
1887
+ if (runtimeBundleBytes !== undefined) {
1888
+ inlineShellHtml = buildInlineRenderShellHtml(runtimeBundleBytes.toString("utf8"));
1889
+ }
1890
+ else {
1891
+ // inlineRuntimeShell is on but the runtime bundle could not be
1892
+ // read at composition — serve the thin postMessage shell instead.
1893
+ logger.warn("mcp_apps_inline_shell_bundle_unreadable", {
1894
+ runtimeBundleFile,
1895
+ });
1896
+ }
1897
+ }
1785
1898
  // Per-boot `buildMcpServer` options — every input below is fixed at
1786
1899
  // composition time, so the bundle is assembled once and the MCP
1787
1900
  // endpoint family spreads a fresh copy per request.
1788
1901
  const buildMcpOptions = {
1789
1902
  mcpAppsOutbound: mcpAppsEnabled,
1790
- // Caller-provided `shellHtml` overrides the default;
1791
- // `installMcpAppsOutbound` falls back to its baked
1792
- // `GGUI_RENDER_SHELL_HTML` constant when absent.
1793
- ...(mcpAppsConfig.shellHtml !== undefined ? { shellHtml: mcpAppsConfig.shellHtml } : {}),
1903
+ // Caller-provided `shellHtml` overrides the default (and the
1904
+ // inline-runtime shell); `installMcpAppsOutbound` falls back to
1905
+ // its baked `GGUI_RENDER_SHELL_HTML` constant when both are
1906
+ // absent.
1907
+ ...(mcpAppsConfig.shellHtml !== undefined
1908
+ ? { shellHtml: mcpAppsConfig.shellHtml }
1909
+ : inlineShellHtml !== undefined
1910
+ ? { shellHtml: inlineShellHtml }
1911
+ : {}),
1794
1912
  // Forward the operator-supplied public origin so the static
1795
1913
  // `ui://ggui/render` resource declares `_meta.ui.csp` for
1796
1914
  // spec-compliant hosts (Claude Desktop / claude.ai Connector /
@@ -1798,6 +1916,18 @@ export function createGguiServer(opts = {}) {
1798
1916
  // (`connect-src 'none'`) blocks the iframe from fetching the
1799
1917
  // runtime bundle and opening the WebSocket.
1800
1918
  ...(opts.publicBaseUrl !== undefined ? { publicBaseUrl: opts.publicBaseUrl } : {}),
1919
+ // Live-channel origins for the static shell's CSP declaration:
1920
+ // the WS endpoint + its http-origin flip (the session-API base the
1921
+ // sseUrl/pollingUrl stamps derive from when `publicBaseUrl` is
1922
+ // unset — the cloud pod's posture, see compose.ts's Origin/Host
1923
+ // ruling). Cross-origin hosts (claude.ai) build the frame CSP from
1924
+ // the STATIC shell resource they mount; per-render declarations
1925
+ // never reach it, so without these entries every network rung of
1926
+ // the failover ladder (WS, SSE, HTTP polling) is `connect-src`-
1927
+ // blocked in the frame — observed live in #471 round 11.
1928
+ ...(mcpAppsEnabled
1929
+ ? { extraConnectUrls: [wsUrl, wsOriginToHttpOrigin(wsUrl)] }
1930
+ : {}),
1801
1931
  // Per-render self-contained shell registration. Only wired
1802
1932
  // when MCP Apps is on AND the render store is resolved —
1803
1933
  // both preconditions for `ggui_render.resultMeta` stamping a
@@ -1964,7 +2094,15 @@ export function createGguiServer(opts = {}) {
1964
2094
  // `runtime.url`. The bootstrap still carries a `runtimeUrl` so
1965
2095
  // the shell knows where to look — just not our HTTP listener.
1966
2096
  if (runtimeEnabled) {
1967
- mountRuntimeBundleRoute({ app, runtimePath, runtimeBundleFile, logger });
2097
+ mountRuntimeBundleRoute({
2098
+ app,
2099
+ runtimePath,
2100
+ runtimeBundleFile,
2101
+ logger,
2102
+ ...(hashedRuntimePath !== undefined && runtimeBundleBytes !== undefined
2103
+ ? { hashed: { path: hashedRuntimePath, source: runtimeBundleBytes } }
2104
+ : {}),
2105
+ });
1968
2106
  }
1969
2107
  // R6 /state snapshot + R7 /events cursor-replay reads — see
1970
2108
  // `./api-renders-routes.ts` for the wsToken auth posture, tenancy
@@ -1989,6 +2127,20 @@ export function createGguiServer(opts = {}) {
1989
2127
  resolveRuntimeUrl: resolveRuntimeUrlForResultMeta,
1990
2128
  logger,
1991
2129
  });
2130
+ // SSE live stream (the ladder's middle rung) — same wsToken auth
2131
+ // posture as /state and /events; see `./api-renders-stream-route.ts`
2132
+ // for the framing contract. The channel is late-bound via the same
2133
+ // `() => channelForHealth` pattern as `stream.channelProvider`:
2134
+ // route mounting runs before `createGguiSessionChannelServer`, and
2135
+ // the mcpApps ⇒ renderChannel throw above guarantees a channel
2136
+ // exists by listen time.
2137
+ mountApiRendersStreamRoute({
2138
+ app,
2139
+ renderStore,
2140
+ secret: sharedTokenSecret,
2141
+ channelProvider: () => channelForHealth,
2142
+ logger,
2143
+ });
1992
2144
  }
1993
2145
  // Content-addressable code + contract-validator delivery — see
1994
2146
  // `./code-routes.ts` for the route contract (cache posture, CORS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/mcp-server",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Self-hosted MCP server for the ggui protocol. An HTTP/MCP binding layer over @ggui-ai/mcp-server-handlers and the reference adapters from @ggui-ai/mcp-server-core — run it via `ggui serve`.",
5
5
  "keywords": [
6
6
  "ggui",
@@ -32,16 +32,16 @@
32
32
  "resend": "^6.12.2",
33
33
  "ws": "^8.20.1",
34
34
  "zod": "^4.3.6",
35
- "@ggui-ai/console": "0.7.0",
36
- "@ggui-ai/iframe-runtime": "0.7.0",
37
- "@ggui-ai/mcp-server-handlers": "0.7.0",
38
- "@ggui-ai/negotiator": "0.7.0",
39
- "@ggui-ai/mcp-server-core": "0.7.0",
40
- "@ggui-ai/preview-a2ui": "0.7.0",
41
- "@ggui-ai/project-config": "0.7.0",
42
- "@ggui-ai/protocol": "0.7.0",
43
- "@ggui-ai/ui-gen": "0.7.0",
44
- "@ggui-ai/ui-registry": "0.7.0"
35
+ "@ggui-ai/iframe-runtime": "0.9.0",
36
+ "@ggui-ai/console": "0.9.0",
37
+ "@ggui-ai/mcp-server-handlers": "0.9.0",
38
+ "@ggui-ai/negotiator": "0.9.0",
39
+ "@ggui-ai/mcp-server-core": "0.9.0",
40
+ "@ggui-ai/preview-a2ui": "0.9.0",
41
+ "@ggui-ai/project-config": "0.9.0",
42
+ "@ggui-ai/ui-gen": "0.9.0",
43
+ "@ggui-ai/ui-registry": "0.9.0",
44
+ "@ggui-ai/protocol": "0.9.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/express": "^5.0.0",
@@ -50,7 +50,7 @@
50
50
  "@types/ws": "^8.5.10",
51
51
  "typescript": "^5.0.0",
52
52
  "vitest": "^3.2.6",
53
- "@ggui-ai/protocol-conformance": "0.7.0"
53
+ "@ggui-ai/protocol-conformance": "0.9.0"
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",