@ggui-ai/mcp-server 0.9.0 → 0.10.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 (37) hide show
  1. package/dist/api-renders-routes.d.ts +21 -0
  2. package/dist/api-renders-routes.d.ts.map +1 -1
  3. package/dist/api-renders-routes.js +22 -5
  4. package/dist/build-mcp.d.ts +38 -7
  5. package/dist/build-mcp.d.ts.map +1 -1
  6. package/dist/build-mcp.js +23 -2
  7. package/dist/code-module-variant.d.ts +150 -0
  8. package/dist/code-module-variant.d.ts.map +1 -0
  9. package/dist/code-module-variant.js +243 -0
  10. package/dist/code-routes.d.ts +12 -2
  11. package/dist/code-routes.d.ts.map +1 -1
  12. package/dist/code-routes.js +12 -2
  13. package/dist/control-service.d.ts +29 -3
  14. package/dist/control-service.d.ts.map +1 -1
  15. package/dist/control-service.js +26 -2
  16. package/dist/health-routes.d.ts +19 -3
  17. package/dist/health-routes.d.ts.map +1 -1
  18. package/dist/health-routes.js +26 -18
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +7 -1
  22. package/dist/mcp-apps-outbound.d.ts +40 -8
  23. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  24. package/dist/mcp-apps-outbound.js +127 -30
  25. package/dist/mcp-endpoint-routes.d.ts +6 -0
  26. package/dist/mcp-endpoint-routes.d.ts.map +1 -1
  27. package/dist/mcp-endpoint-routes.js +67 -1
  28. package/dist/oauth-as-routes.d.ts +11 -0
  29. package/dist/oauth-as-routes.d.ts.map +1 -1
  30. package/dist/oauth-as-routes.js +9 -1
  31. package/dist/runtime-bundle-hash.d.ts +12 -0
  32. package/dist/runtime-bundle-hash.d.ts.map +1 -1
  33. package/dist/runtime-bundle-hash.js +19 -0
  34. package/dist/server.d.ts +223 -56
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +263 -125
  37. package/package.json +13 -12
@@ -64,6 +64,27 @@ interface MountOptions {
64
64
  readonly codeStore?: CodeStore;
65
65
  /** Operator-configured public origin for absolute URL composition. */
66
66
  readonly publicBaseUrl?: string;
67
+ /**
68
+ * Origin the content-addressable routes (`/code/*`, `/contract/*`)
69
+ * are reached at — an edge-cached asset host when the deployment
70
+ * fronts static paths separately (ggui#522). Defaults to
71
+ * {@link publicBaseUrl}, then the request host. Session-API URLs
72
+ * (`/events`, `/stream`) never use it: those are dynamic and stay on
73
+ * the public origin.
74
+ */
75
+ readonly codeBaseUrl?: string;
76
+ /**
77
+ * Strict-CSP module-variant minter (ggui#522 slice 2) — the /state
78
+ * read re-mints the `codeModuleUrl` twin alongside `codeUrl`. Only
79
+ * consulted when an explicit {@link codeBaseUrl}/{@link publicBaseUrl}
80
+ * base resolved: the variant embeds absolute shim URLs on the SAME
81
+ * origin, and a request-derived base has no shim family behind it.
82
+ */
83
+ readonly mintCodeModuleUrl?: (args: {
84
+ readonly code: string;
85
+ readonly hash: string;
86
+ readonly base: string;
87
+ }) => string | undefined;
67
88
  /** Live-mode credential minter — fresh trio on every /state read. */
68
89
  readonly mintBootstrap?: (sessionId: string, appId: string) => {
69
90
  wsUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"api-renders-routes.d.ts","sourceRoot":"","sources":["../src/api-renders-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAQ9F,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,qEAAqE;IACrE,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,KACV;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IACzC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA0V9D"}
1
+ {"version":3,"file":"api-renders-routes.d.ts","sourceRoot":"","sources":["../src/api-renders-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAQ9F,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE;QAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,KAAK,MAAM,GAAG,SAAS,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,KACV;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IACzC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA8W9D"}
@@ -52,7 +52,11 @@ import { composeSessionApiUrls, MCP_APP_AI_GGUI_RENDER_META_KEY, } from "@ggui-a
52
52
  * Returns nothing — the routes self-register.
53
53
  */
54
54
  export function mountApiRendersRoutes(opts) {
55
- const { app, renderStore, secret, appMetadataStore, themeId, themeMode, codeStore, publicBaseUrl, mintBootstrap, resolveRuntimeUrl, logger, } = opts;
55
+ const { app, renderStore, secret, appMetadataStore, themeId, themeMode, codeStore, publicBaseUrl, codeBaseUrl, mintBootstrap, resolveRuntimeUrl, logger, } = opts;
56
+ // The origin the content-addressable routes are reached at (asset
57
+ // host if fronted separately, else the public origin); the request
58
+ // host is the last resort for local/tunnel deployments.
59
+ const staticBase = codeBaseUrl ?? publicBaseUrl;
56
60
  app.get("/api/sessions/:sessionId/state", async (req, res) => {
57
61
  // CORS on EVERY response, gates included: cross-origin frames can
58
62
  // only read a status when the response carries ACAO. Without it a
@@ -173,6 +177,7 @@ export function mountApiRendersRoutes(opts) {
173
177
  // and need the URL to mount/refresh the static-component variant.
174
178
  let renderCodeUrl;
175
179
  let renderCodeHash;
180
+ let renderCodeModuleUrl;
176
181
  let renderContractHash;
177
182
  let renderValidatorsUrl;
178
183
  if (!isSystem && !isMcpApps && codeStore) {
@@ -183,10 +188,19 @@ export function mountApiRendersRoutes(opts) {
183
188
  await codeStore.put(hash, code);
184
189
  renderCodeHash = hash;
185
190
  const requestHost = req.get("host") ?? "";
186
- const base = publicBaseUrl
187
- ? publicBaseUrl.replace(/\/$/, "")
191
+ const base = staticBase !== undefined
192
+ ? staticBase.replace(/\/$/, "")
188
193
  : `${req.protocol}://${requestHost}`;
189
194
  renderCodeUrl = `${base}/code/${hash}.js`;
195
+ // Strict-CSP module-variant twin — only on an explicit base
196
+ // (see MountOptions.mintCodeModuleUrl for why).
197
+ if (staticBase !== undefined) {
198
+ renderCodeModuleUrl = opts.mintCodeModuleUrl?.({
199
+ code,
200
+ hash,
201
+ base,
202
+ });
203
+ }
190
204
  }
191
205
  catch {
192
206
  // Silent — the caller falls back to live-mode delivery, and
@@ -205,8 +219,8 @@ export function mountApiRendersRoutes(opts) {
205
219
  await codeStore.put(bundle.contractHash, bundle.bundleSource);
206
220
  renderContractHash = bundle.contractHash;
207
221
  const requestHost = req.get("host") ?? "";
208
- const base = publicBaseUrl
209
- ? publicBaseUrl.replace(/\/$/, "")
222
+ const base = staticBase !== undefined
223
+ ? staticBase.replace(/\/$/, "")
210
224
  : `${req.protocol}://${requestHost}`;
211
225
  renderValidatorsUrl = `${base}/contract/${bundle.contractHash}.js`;
212
226
  }
@@ -251,6 +265,9 @@ export function mountApiRendersRoutes(opts) {
251
265
  ? {
252
266
  codeUrl: renderCodeUrl,
253
267
  ...(renderCodeHash !== undefined ? { codeHash: renderCodeHash } : {}),
268
+ ...(renderCodeModuleUrl !== undefined
269
+ ? { codeModuleUrl: renderCodeModuleUrl }
270
+ : {}),
254
271
  }
255
272
  : {}),
256
273
  ...(renderContractHash !== undefined && renderValidatorsUrl !== undefined
@@ -126,13 +126,44 @@ export interface BuildMcpServerOptions {
126
126
  * `resources/read` later.
127
127
  */
128
128
  readonly extraResources?: ReadonlyArray<(server: McpServer) => void>;
129
+ /**
130
+ * Withhold every handler's per-result bootstrap MATERIAL from tool
131
+ * results — the read-plane-only posture.
132
+ *
133
+ * By default a successful tool result carries the handler's
134
+ * `resultMeta` — for `ggui_render` / `ggui_update` that is the
135
+ * `ai.ggui/render` bootstrap a host may mount DIRECTLY without any
136
+ * further round-trip, plus the spec-canonical pointer `_meta.ui.
137
+ * resourceUri`. Setting this makes the server publish only the
138
+ * durable IDENTITY: `structuredContent.resourceUri` (the `ui://`
139
+ * locator) and — the same value, on the wire slot MCP Apps hosts
140
+ * read — `_meta.ui.resourceUri` (+ the legacy flat `ui/resourceUri`).
141
+ * `resultMeta` is never invoked (no bootstrap token is minted for a
142
+ * slice nobody receives); the pointer is derived from the validated
143
+ * OUTPUT itself, the single source of truth `resultMeta` reuses.
144
+ * A host MUST resolve every view by an authenticated `resources/read`
145
+ * — the persisted-locator path — before it can mount anything.
146
+ *
147
+ * Why the pointer stays (ggui#537): the identity IS the pointer.
148
+ * Spec-canonical hosts (claude.ai, Claude Desktop, `@ggui-ai/
149
+ * mcp-apps-react`'s chat-helpers, the OSS samples) mount the
150
+ * per-render self-contained shell that `_meta.ui.resourceUri` names —
151
+ * a `resources/read` the HOST performs, exactly the read-plane path
152
+ * this posture wants. The first arm (f8c93405d) stripped `_meta`
153
+ * wholesale, which took the pointer with it and left every such host
154
+ * with the declaration-level static shell and a result it could not
155
+ * mount from ("Waiting for tool result…", prod 2026-08-16→17).
156
+ *
157
+ * That is a deployment posture, not a debug switch: a hosted
158
+ * deployment that wants "views mount only through the read plane"
159
+ * (thread-scoped ownership checks, fresh per-read credentials, no
160
+ * inlined bootstrap material crossing a chat transcript) states it
161
+ * here by construction, and any host that still expects the inlined
162
+ * bootstrap fails loudly (a locator it cannot resolve) instead of
163
+ * silently mounting stale material. `structuredContent` and `content`
164
+ * are untouched; only `_meta` is withheld.
165
+ */
166
+ readonly withholdResultMeta?: boolean;
129
167
  }
130
- /**
131
- * Build a fresh MCP server with every handler registered.
132
- *
133
- * `getContext` is a late-binding accessor so the HTTP layer can thread
134
- * per-request context (via AsyncLocalStorage or a closure) without
135
- * leaking the shape into this module.
136
- */
137
168
  export declare function buildMcpServer(info: ServerInfo, handlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>, getContext: () => HandlerContext, logger: Logger, opts?: BuildMcpServerOptions): McpServer;
138
169
  //# sourceMappingURL=build-mcp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-mcp.d.ts","sourceRoot":"","sources":["../src/build-mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAAK,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAEL,KAAK,iCAAiC,EACvC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iCAAiC,CAAC;IAC3D;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC5D;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IAElE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC;CACtE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,EAChE,UAAU,EAAE,MAAM,cAAc,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,qBAA0B,GAC/B,SAAS,CAmPX"}
1
+ {"version":3,"file":"build-mcp.d.ts","sourceRoot":"","sources":["../src/build-mcp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,EAAK,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAEL,KAAK,iCAAiC,EACvC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iCAAiC,CAAC;IAC3D;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC5D;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IAElE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC;IAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CACvC;AAyBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,EAChE,UAAU,EAAE,MAAM,cAAc,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,qBAA0B,GAC/B,SAAS,CAwPX"}
package/dist/build-mcp.js CHANGED
@@ -22,6 +22,23 @@ import { installMcpAppsOutbound, } from './mcp-apps-outbound.js';
22
22
  * per-request context (via AsyncLocalStorage or a closure) without
23
23
  * leaking the shape into this module.
24
24
  */
25
+ /**
26
+ * The identity-only `_meta` a withholding server publishes (ggui#537):
27
+ * when the validated output carries a `ui://` `resourceUri` (the durable
28
+ * locator `ggui_render` / `ggui_update` surface on structuredContent),
29
+ * mirror it onto the spec-canonical `_meta.ui.resourceUri` slot MCP
30
+ * Apps hosts read (+ the legacy flat key), and nothing else. Same value
31
+ * `resultMeta` would have stamped, without invoking it — no bootstrap
32
+ * material, no minted token. `undefined` for outputs without a locator.
33
+ */
34
+ function identityPointerMeta(validated) {
35
+ if (!isRecord(validated))
36
+ return undefined;
37
+ const uri = validated['resourceUri'];
38
+ if (typeof uri !== 'string' || !uri.startsWith('ui://'))
39
+ return undefined;
40
+ return { ui: { resourceUri: uri }, 'ui/resourceUri': uri };
41
+ }
25
42
  export function buildMcpServer(info, handlers, getContext, logger, opts = {}) {
26
43
  const server = new McpServer({
27
44
  name: info.name,
@@ -128,8 +145,12 @@ export function buildMcpServer(info, handlers, getContext, logger, opts = {}) {
128
145
  const validated = z.object(handler.outputSchema).parse(data);
129
146
  // Per-result `_meta` — NOT merged into structuredContent, so
130
147
  // agents that typecheck against the tool signature never see
131
- // it. This is where view-only bootstrap material lives.
132
- const meta = await handler.resultMeta?.(data, input, ctx);
148
+ // it. This is where view-only bootstrap material lives. Under
149
+ // the withhold posture only the identity pointer is published,
150
+ // derived from the output (see `withholdResultMeta`).
151
+ const meta = opts.withholdResultMeta === true
152
+ ? identityPointerMeta(validated)
153
+ : await handler.resultMeta?.(data, input, ctx);
133
154
  logger.info('tool_invoked', {
134
155
  tool: handler.name,
135
156
  appId: ctx.appId,
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Strict-CSP module-variant delivery (ggui#522 slice 2).
3
+ *
4
+ * The `/code/<hash>.js` route serves STORED component bytes — compiled
5
+ * ESM whose imports are still BARE specifiers (`react`,
6
+ * `@ggui-ai/design/primitives`, …). A browser cannot import that
7
+ * standalone; the renderer historically bridged the gap in-frame by
8
+ * rewriting bare specifiers to `data:` shims and instantiating the
9
+ * module from a `blob:` URL — both of which a strict host CSP refuses.
10
+ *
11
+ * This module adds the server-side twin of that bridge: a SECOND,
12
+ * immutable route `/code/<hash>.m<rt>.js` serving the SAME stored bytes
13
+ * with the rewrite already applied — every bare specifier resolved to a
14
+ * static shim asset under `/_ggui/shims/<rt>/<name>.js` on the same
15
+ * origin. A frame whose `script-src` allows only the asset origin can
16
+ * `import()` the variant URL directly: no `blob:`, no `data:`, no
17
+ * `'unsafe-eval'`.
18
+ *
19
+ * `<rt>` — the 12-hex content hash of the runtime bundle — is the
20
+ * variant KEY, and it lives in the PATH deliberately: the asset CDN
21
+ * (CloudFront `CACHING_OPTIMIZED`) ignores query strings, so a
22
+ * query-keyed variant would collapse to one cached body across
23
+ * runtime versions. The hash versions the whole family: the shim
24
+ * files ship in the runtime bundle's dist, so any shim-affecting
25
+ * change also changes the bundle bytes, which changes `<rt>`, which
26
+ * changes every variant URL — immutable caching stays sound.
27
+ *
28
+ * The route serves only the CURRENT `<rt>`: after a redeploy, a
29
+ * CDN-evicted old-variant URL 404s (`no-store`) and the renderer falls
30
+ * back to its raw-bytes ladder — honest degradation, never stale shims.
31
+ *
32
+ * 3rd-party gadget packages (GG.8.2) have no static shim (their
33
+ * exports are derived per-render from the generated code); a module
34
+ * importing one is NOT asset-deliverable. Both ends respect that: the
35
+ * minter declines to stamp `codeModuleUrl`, and the route declines
36
+ * (404) any body whose rewrite leaves a bare specifier standing.
37
+ */
38
+ import type { Express } from "express";
39
+ import type { Logger } from "./logger.js";
40
+ /**
41
+ * Mint the `codeModuleUrl` for one render's component code, or decline.
42
+ *
43
+ * `undefined` ⇒ the code is not asset-deliverable (it imports a
44
+ * package with no static shim) — the slice then carries only the raw
45
+ * `codeUrl`/`codeB64` carriers and the renderer uses its blob ladder.
46
+ */
47
+ export type MintCodeModuleUrl = (args: {
48
+ /** RAW stored component code (markers included — stripped here). */
49
+ readonly code: string;
50
+ /** `sha256(code)` — the same hash `codeUrl` is composed from. */
51
+ readonly hash: string;
52
+ /** Code base URL, no trailing slash (the emitter already trims). */
53
+ readonly base: string;
54
+ }) => string | undefined;
55
+ /** The variant URL for (hash, rt) under `base` — ONE composition site. */
56
+ export declare function composeCodeModuleUrl(args: {
57
+ readonly base: string;
58
+ readonly hash: string;
59
+ readonly runtimeHash: string;
60
+ }): string;
61
+ /**
62
+ * Build the minter every `codeUrl` emitter injects. Coverability is
63
+ * decided from the code's own import list: every bare specifier must
64
+ * have a static shim ({@link ASSET_SHIM_FOR_SPECIFIER}) or the mint
65
+ * declines. Markers are stripped first — their embedded JSON can
66
+ * contain strings the import scanner would misread.
67
+ */
68
+ export declare function createCodeModuleUrlMinter(args: {
69
+ readonly runtimeHash: string;
70
+ }): MintCodeModuleUrl;
71
+ /**
72
+ * Rewrite stored component bytes into the servable asset-module
73
+ * variant — the same preprocessing the renderer's blob ladder applies
74
+ * (strip markers, hoist imports) plus the `asset-url` specifier
75
+ * rewrite. Declines when any bare specifier survives (no static shim
76
+ * exists for it) instead of serving a module that fails at eval.
77
+ */
78
+ export declare function rewriteToAssetModule(args: {
79
+ readonly code: string;
80
+ readonly shimBaseUrl: string;
81
+ }): {
82
+ readonly ok: true;
83
+ readonly code: string;
84
+ } | {
85
+ readonly ok: false;
86
+ readonly leftover: readonly string[];
87
+ };
88
+ /**
89
+ * Read the static shim files emitted by the iframe-runtime build
90
+ * (`dist/shims/<name>.js`) into memory, keyed by file name. Captured
91
+ * ONCE at composition — the immutable shim route must keep serving the
92
+ * exact bytes its URL family was stamped for, even across an in-place
93
+ * rebuild of the dist (same guarantee the hashed bundle route makes by
94
+ * serving captured bytes).
95
+ *
96
+ * `undefined` when the directory is missing/empty (bundle built by an
97
+ * older toolchain) — callers then skip the whole variant family.
98
+ */
99
+ export declare function captureShimSources(shimsDir: string): ReadonlyMap<string, Buffer> | undefined;
100
+ /**
101
+ * Mount `GET <urlPrefix>/:rt/:file` serving the captured shim sources
102
+ * with the immutable posture of the hashed-bundle route. Only the
103
+ * CURRENT `runtimeHash` is served; any other `rt` 404s `no-store` so a
104
+ * stale URL can never lock wrong bytes into a cache.
105
+ */
106
+ export declare function mountShimRoutes(opts: {
107
+ readonly app: Express;
108
+ readonly urlPrefix: string;
109
+ readonly runtimeHash: string;
110
+ readonly shims: ReadonlyMap<string, Buffer>;
111
+ }): void;
112
+ /**
113
+ * Options for the `/code/<hash>.m<rt>.js` variant route
114
+ * (mounted by `mountCodeRoutes` BEFORE the plain routes).
115
+ */
116
+ export interface CodeModuleVariantOptions {
117
+ /** 12-hex content hash of the runtime bundle currently served. */
118
+ readonly runtimeHash: string;
119
+ /**
120
+ * Absolute base of THIS build's shim directory
121
+ * (`<codeBaseUrl><urlPrefix>/<runtimeHash>`), no trailing slash —
122
+ * the rewrite embeds it into every import.
123
+ */
124
+ readonly shimBaseUrl: string;
125
+ }
126
+ /**
127
+ * RegExp route for the variant path. A RegExp (not a string pattern)
128
+ * on purpose: two params separated by literal dots inside one path
129
+ * segment sit exactly on path-to-regexp's least-stable parsing ground,
130
+ * and a silent non-match here would read as "variant never works".
131
+ */
132
+ export declare const CODE_MODULE_VARIANT_ROUTE: RegExp;
133
+ /**
134
+ * Mount the module-variant route. Serving posture mirrors the plain
135
+ * code route (immutable on success, `no-store` on every failure); the
136
+ * two decline arms are:
137
+ *
138
+ * - `rt` ≠ the current runtime hash → 404. The shims for another
139
+ * build are unknowable here; the renderer falls back to raw bytes.
140
+ * - leftover bare specifier after rewrite → 404. The module imports
141
+ * a package with no static shim (3rd-party gadget); serving it
142
+ * would fail at eval inside the frame instead of falling back.
143
+ */
144
+ export declare function mountCodeModuleVariantRoute(opts: {
145
+ readonly app: Express;
146
+ readonly codeStore: import("@ggui-ai/mcp-server-core").CodeStore;
147
+ readonly variant: CodeModuleVariantOptions;
148
+ readonly logger: Logger;
149
+ }): void;
150
+ //# sourceMappingURL=code-module-variant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-module-variant.d.ts","sourceRoot":"","sources":["../src/code-module-variant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAU1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,IAAI,EAAE;IACrC,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,KAAK,MAAM,GAAG,SAAS,CAAC;AAEzB,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,MAAM,CAET;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG,iBAAiB,CASpB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAQ9G;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,GACf,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAkBzC;AAQD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C,GAAG,IAAI,CAyCP;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,QAAkD,CAAC;AAEzF;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,0BAA0B,EAAE,SAAS,CAAC;IACjE,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,IAAI,CA8DP"}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Strict-CSP module-variant delivery (ggui#522 slice 2).
3
+ *
4
+ * The `/code/<hash>.js` route serves STORED component bytes — compiled
5
+ * ESM whose imports are still BARE specifiers (`react`,
6
+ * `@ggui-ai/design/primitives`, …). A browser cannot import that
7
+ * standalone; the renderer historically bridged the gap in-frame by
8
+ * rewriting bare specifiers to `data:` shims and instantiating the
9
+ * module from a `blob:` URL — both of which a strict host CSP refuses.
10
+ *
11
+ * This module adds the server-side twin of that bridge: a SECOND,
12
+ * immutable route `/code/<hash>.m<rt>.js` serving the SAME stored bytes
13
+ * with the rewrite already applied — every bare specifier resolved to a
14
+ * static shim asset under `/_ggui/shims/<rt>/<name>.js` on the same
15
+ * origin. A frame whose `script-src` allows only the asset origin can
16
+ * `import()` the variant URL directly: no `blob:`, no `data:`, no
17
+ * `'unsafe-eval'`.
18
+ *
19
+ * `<rt>` — the 12-hex content hash of the runtime bundle — is the
20
+ * variant KEY, and it lives in the PATH deliberately: the asset CDN
21
+ * (CloudFront `CACHING_OPTIMIZED`) ignores query strings, so a
22
+ * query-keyed variant would collapse to one cached body across
23
+ * runtime versions. The hash versions the whole family: the shim
24
+ * files ship in the runtime bundle's dist, so any shim-affecting
25
+ * change also changes the bundle bytes, which changes `<rt>`, which
26
+ * changes every variant URL — immutable caching stays sound.
27
+ *
28
+ * The route serves only the CURRENT `<rt>`: after a redeploy, a
29
+ * CDN-evicted old-variant URL 404s (`no-store`) and the renderer falls
30
+ * back to its raw-bytes ladder — honest degradation, never stale shims.
31
+ *
32
+ * 3rd-party gadget packages (GG.8.2) have no static shim (their
33
+ * exports are derived per-render from the generated code); a module
34
+ * importing one is NOT asset-deliverable. Both ends respect that: the
35
+ * minter declines to stamp `codeModuleUrl`, and the route declines
36
+ * (404) any body whose rewrite leaves a bare specifier standing.
37
+ */
38
+ import fs from "node:fs";
39
+ import path from "node:path";
40
+ import { rewriteImports, findBareImportSpecifiers, stripMarkers, ASSET_SHIM_FOR_SPECIFIER, } from "@ggui-ai/design/rendering";
41
+ import { hoistImports } from "@ggui-ai/design/module-loader";
42
+ /** The variant URL for (hash, rt) under `base` — ONE composition site. */
43
+ export function composeCodeModuleUrl(args) {
44
+ return `${args.base}/code/${args.hash}.m${args.runtimeHash}.js`;
45
+ }
46
+ /**
47
+ * Build the minter every `codeUrl` emitter injects. Coverability is
48
+ * decided from the code's own import list: every bare specifier must
49
+ * have a static shim ({@link ASSET_SHIM_FOR_SPECIFIER}) or the mint
50
+ * declines. Markers are stripped first — their embedded JSON can
51
+ * contain strings the import scanner would misread.
52
+ */
53
+ export function createCodeModuleUrlMinter(args) {
54
+ const { runtimeHash } = args;
55
+ return ({ code, hash, base }) => {
56
+ const bare = findBareImportSpecifiers(stripMarkers(code));
57
+ if (!bare.every((spec) => spec in ASSET_SHIM_FOR_SPECIFIER)) {
58
+ return undefined;
59
+ }
60
+ return composeCodeModuleUrl({ base, hash, runtimeHash });
61
+ };
62
+ }
63
+ /**
64
+ * Rewrite stored component bytes into the servable asset-module
65
+ * variant — the same preprocessing the renderer's blob ladder applies
66
+ * (strip markers, hoist imports) plus the `asset-url` specifier
67
+ * rewrite. Declines when any bare specifier survives (no static shim
68
+ * exists for it) instead of serving a module that fails at eval.
69
+ */
70
+ export function rewriteToAssetModule(args) {
71
+ const rewritten = rewriteImports(hoistImports(stripMarkers(args.code)), {
72
+ mode: "asset-url",
73
+ shimBaseUrl: args.shimBaseUrl,
74
+ });
75
+ const leftover = findBareImportSpecifiers(rewritten);
76
+ if (leftover.length > 0)
77
+ return { ok: false, leftover };
78
+ return { ok: true, code: rewritten };
79
+ }
80
+ /**
81
+ * Read the static shim files emitted by the iframe-runtime build
82
+ * (`dist/shims/<name>.js`) into memory, keyed by file name. Captured
83
+ * ONCE at composition — the immutable shim route must keep serving the
84
+ * exact bytes its URL family was stamped for, even across an in-place
85
+ * rebuild of the dist (same guarantee the hashed bundle route makes by
86
+ * serving captured bytes).
87
+ *
88
+ * `undefined` when the directory is missing/empty (bundle built by an
89
+ * older toolchain) — callers then skip the whole variant family.
90
+ */
91
+ export function captureShimSources(shimsDir) {
92
+ let entries;
93
+ try {
94
+ entries = fs.readdirSync(shimsDir);
95
+ }
96
+ catch {
97
+ return undefined;
98
+ }
99
+ const out = new Map();
100
+ for (const entry of entries) {
101
+ if (!entry.endsWith(".js"))
102
+ continue;
103
+ try {
104
+ out.set(entry, fs.readFileSync(path.join(shimsDir, entry)));
105
+ }
106
+ catch {
107
+ // A vanishing file mid-capture degrades to "that shim 404s";
108
+ // the renderer's fallback ladder covers it.
109
+ }
110
+ }
111
+ return out.size > 0 ? out : undefined;
112
+ }
113
+ /** File-segment shape of a shim asset (`react.js`, `jsx-runtime.js`). */
114
+ const SHIM_FILE_REGEX = /^[a-z][a-z-]*\.js$/;
115
+ /** 12-hex runtime-hash path segment. */
116
+ const RUNTIME_HASH_REGEX = /^[a-f0-9]{12}$/;
117
+ /**
118
+ * Mount `GET <urlPrefix>/:rt/:file` serving the captured shim sources
119
+ * with the immutable posture of the hashed-bundle route. Only the
120
+ * CURRENT `runtimeHash` is served; any other `rt` 404s `no-store` so a
121
+ * stale URL can never lock wrong bytes into a cache.
122
+ */
123
+ export function mountShimRoutes(opts) {
124
+ const { app, urlPrefix, runtimeHash, shims } = opts;
125
+ app.get(`${urlPrefix}/:rt/:file`, (req, res) => {
126
+ // Same CORS rationale as the runtime bundle: module fetches from a
127
+ // sandboxed `srcdoc` iframe (`null` origin) always run CORS mode.
128
+ res.setHeader("Access-Control-Allow-Origin", "*");
129
+ const rt = req.params["rt"];
130
+ const file = req.params["file"];
131
+ if (typeof rt !== "string" ||
132
+ !RUNTIME_HASH_REGEX.test(rt) ||
133
+ typeof file !== "string" ||
134
+ !SHIM_FILE_REGEX.test(file)) {
135
+ res.setHeader("Cache-Control", "no-store");
136
+ res.status(400).json({
137
+ error: {
138
+ code: "invalid_request",
139
+ message: "shim path must be /<12-hex runtime hash>/<name>.js",
140
+ },
141
+ });
142
+ return;
143
+ }
144
+ const source = rt === runtimeHash ? shims.get(file) : undefined;
145
+ if (source === undefined) {
146
+ res.setHeader("Cache-Control", "no-store");
147
+ res.status(404).json({
148
+ error: {
149
+ code: "not_found",
150
+ message: rt === runtimeHash
151
+ ? "unknown shim module"
152
+ : "shim family not served by this runtime build",
153
+ },
154
+ });
155
+ return;
156
+ }
157
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
158
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
159
+ res.status(200).send(source);
160
+ });
161
+ }
162
+ /**
163
+ * RegExp route for the variant path. A RegExp (not a string pattern)
164
+ * on purpose: two params separated by literal dots inside one path
165
+ * segment sit exactly on path-to-regexp's least-stable parsing ground,
166
+ * and a silent non-match here would read as "variant never works".
167
+ */
168
+ export const CODE_MODULE_VARIANT_ROUTE = /^\/code\/([a-f0-9]{64})\.m([a-f0-9]{12})\.js$/;
169
+ /**
170
+ * Mount the module-variant route. Serving posture mirrors the plain
171
+ * code route (immutable on success, `no-store` on every failure); the
172
+ * two decline arms are:
173
+ *
174
+ * - `rt` ≠ the current runtime hash → 404. The shims for another
175
+ * build are unknowable here; the renderer falls back to raw bytes.
176
+ * - leftover bare specifier after rewrite → 404. The module imports
177
+ * a package with no static shim (3rd-party gadget); serving it
178
+ * would fail at eval inside the frame instead of falling back.
179
+ */
180
+ export function mountCodeModuleVariantRoute(opts) {
181
+ const { app, codeStore, variant, logger } = opts;
182
+ app.get(CODE_MODULE_VARIANT_ROUTE, async (req, res) => {
183
+ res.setHeader("Access-Control-Allow-Origin", "*");
184
+ // RegExp routes surface capture groups as positional params.
185
+ const hash = req.params["0"];
186
+ const rt = req.params["1"];
187
+ if (typeof hash !== "string" || typeof rt !== "string") {
188
+ res.setHeader("Cache-Control", "no-store");
189
+ res.status(400).json({
190
+ error: { code: "invalid_request", message: "malformed variant path" },
191
+ });
192
+ return;
193
+ }
194
+ if (rt !== variant.runtimeHash) {
195
+ res.setHeader("Cache-Control", "no-store");
196
+ res.status(404).json({
197
+ error: {
198
+ code: "not_found",
199
+ message: "variant family not served by this runtime build",
200
+ },
201
+ });
202
+ return;
203
+ }
204
+ try {
205
+ const code = await codeStore.get(hash);
206
+ if (code === null) {
207
+ res.setHeader("Cache-Control", "no-store");
208
+ res.status(404).json({
209
+ error: { code: "not_found", message: "unknown code hash" },
210
+ });
211
+ return;
212
+ }
213
+ const rewritten = rewriteToAssetModule({
214
+ code,
215
+ shimBaseUrl: variant.shimBaseUrl,
216
+ });
217
+ if (!rewritten.ok) {
218
+ logger.warn("code_module_variant_declined", {
219
+ hash,
220
+ leftover: [...rewritten.leftover],
221
+ });
222
+ res.setHeader("Cache-Control", "no-store");
223
+ res.status(404).json({
224
+ error: {
225
+ code: "not_found",
226
+ message: "module not asset-deliverable (bare imports without static shims)",
227
+ },
228
+ });
229
+ return;
230
+ }
231
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
232
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
233
+ res.status(200).send(rewritten.code);
234
+ }
235
+ catch (err) {
236
+ logger.warn("code_module_variant_failed", { hash, error: String(err) });
237
+ res.setHeader("Cache-Control", "no-store");
238
+ res.status(500).json({
239
+ error: { code: "internal", message: "variant fetch failed" },
240
+ });
241
+ }
242
+ });
243
+ }
@@ -30,6 +30,7 @@
30
30
  import type { CodeStore } from "@ggui-ai/mcp-server-core";
31
31
  import type { Express } from "express";
32
32
  import type { Logger } from "./logger.js";
33
+ import { type CodeModuleVariantOptions } from "./code-module-variant.js";
33
34
  interface MountOptions {
34
35
  /** Express app to mount onto. */
35
36
  readonly app: Express;
@@ -37,10 +38,19 @@ interface MountOptions {
37
38
  readonly codeStore: CodeStore;
38
39
  /** Structured logger for fetch-failure warnings. */
39
40
  readonly logger: Logger;
41
+ /**
42
+ * Strict-CSP module-variant family (`/code/<hash>.m<rt>.js`,
43
+ * ggui#522 slice 2). Present ⇒ the variant route mounts BEFORE the
44
+ * plain routes — ordering is load-bearing: `/code/:hash.js`'s param
45
+ * would otherwise swallow the variant path (`:hash` = `<hash>.m<rt>`)
46
+ * and 400 it at the hash gate.
47
+ */
48
+ readonly moduleVariant?: CodeModuleVariantOptions;
40
49
  }
41
50
  /**
42
- * Mount `GET /code/:hash.js` + `GET /contract/:hash.js` onto the
43
- * express app. Returns nothing — the routes self-register.
51
+ * Mount `GET /code/:hash.js` + `GET /contract/:hash.js` (+ the
52
+ * module-variant route when configured) onto the express app. Returns
53
+ * nothing — the routes self-register.
44
54
  */
45
55
  export declare function mountCodeRoutes(opts: MountOptions): void;
46
56
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"code-routes.d.ts","sourceRoot":"","sources":["../src/code-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA6CxD"}
1
+ {"version":3,"file":"code-routes.d.ts","sourceRoot":"","sources":["../src/code-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;AAElC,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC;CACnD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAqDxD"}