@ggui-ai/mcp-server 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +48 -0
  3. package/dist/admin-blueprints-transport.d.ts +114 -0
  4. package/dist/admin-blueprints-transport.d.ts.map +1 -0
  5. package/dist/admin-blueprints-transport.js +118 -0
  6. package/dist/admin-oauth-providers-transport.d.ts +40 -0
  7. package/dist/admin-oauth-providers-transport.d.ts.map +1 -0
  8. package/dist/admin-oauth-providers-transport.js +263 -0
  9. package/dist/auth.d.ts +39 -0
  10. package/dist/auth.d.ts.map +1 -0
  11. package/dist/auth.js +75 -0
  12. package/dist/build-mcp.d.ts +128 -0
  13. package/dist/build-mcp.d.ts.map +1 -0
  14. package/dist/build-mcp.js +113 -0
  15. package/dist/code-store-fs.d.ts +19 -0
  16. package/dist/code-store-fs.d.ts.map +1 -0
  17. package/dist/code-store-fs.js +98 -0
  18. package/dist/console-auth.d.ts +139 -0
  19. package/dist/console-auth.d.ts.map +1 -0
  20. package/dist/console-auth.js +102 -0
  21. package/dist/console-cache.d.ts +78 -0
  22. package/dist/console-cache.d.ts.map +1 -0
  23. package/dist/console-cache.js +105 -0
  24. package/dist/console-headers.d.ts +124 -0
  25. package/dist/console-headers.d.ts.map +1 -0
  26. package/dist/console-headers.js +49 -0
  27. package/dist/console-llm-trace.d.ts +66 -0
  28. package/dist/console-llm-trace.d.ts.map +1 -0
  29. package/dist/console-llm-trace.js +105 -0
  30. package/dist/console-payloads.d.ts +67 -0
  31. package/dist/console-payloads.d.ts.map +1 -0
  32. package/dist/console-payloads.js +105 -0
  33. package/dist/console-theme-routes.d.ts +111 -0
  34. package/dist/console-theme-routes.d.ts.map +1 -0
  35. package/dist/console-theme-routes.js +202 -0
  36. package/dist/console-timeline.d.ts +45 -0
  37. package/dist/console-timeline.d.ts.map +1 -0
  38. package/dist/console-timeline.js +169 -0
  39. package/dist/console-validator.d.ts +67 -0
  40. package/dist/console-validator.d.ts.map +1 -0
  41. package/dist/console-validator.js +105 -0
  42. package/dist/console-welcome.d.ts +7 -0
  43. package/dist/console-welcome.d.ts.map +1 -0
  44. package/dist/console-welcome.js +221 -0
  45. package/dist/csrf-middleware.d.ts +55 -0
  46. package/dist/csrf-middleware.d.ts.map +1 -0
  47. package/dist/csrf-middleware.js +138 -0
  48. package/dist/email-login.d.ts +174 -0
  49. package/dist/email-login.d.ts.map +1 -0
  50. package/dist/email-login.js +254 -0
  51. package/dist/email-resend.d.ts +29 -0
  52. package/dist/email-resend.d.ts.map +1 -0
  53. package/dist/email-resend.js +71 -0
  54. package/dist/email-sender-from-env.d.ts +34 -0
  55. package/dist/email-sender-from-env.d.ts.map +1 -0
  56. package/dist/email-sender-from-env.js +112 -0
  57. package/dist/email-smtp.d.ts +42 -0
  58. package/dist/email-smtp.d.ts.map +1 -0
  59. package/dist/email-smtp.js +81 -0
  60. package/dist/index.d.ts +102 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +122 -0
  63. package/dist/instructions-presets.d.ts +112 -0
  64. package/dist/instructions-presets.d.ts.map +1 -0
  65. package/dist/instructions-presets.js +195 -0
  66. package/dist/llm-backed-negotiator.d.ts +178 -0
  67. package/dist/llm-backed-negotiator.d.ts.map +1 -0
  68. package/dist/llm-backed-negotiator.js +579 -0
  69. package/dist/logger.d.ts +23 -0
  70. package/dist/logger.d.ts.map +1 -0
  71. package/dist/logger.js +41 -0
  72. package/dist/mcp-apps-inbound.d.ts +86 -0
  73. package/dist/mcp-apps-inbound.d.ts.map +1 -0
  74. package/dist/mcp-apps-inbound.js +278 -0
  75. package/dist/mcp-apps-outbound.d.ts +448 -0
  76. package/dist/mcp-apps-outbound.d.ts.map +1 -0
  77. package/dist/mcp-apps-outbound.js +1163 -0
  78. package/dist/mcp-mounts.d.ts +239 -0
  79. package/dist/mcp-mounts.d.ts.map +1 -0
  80. package/dist/mcp-mounts.js +222 -0
  81. package/dist/oauth-login-types.d.ts +160 -0
  82. package/dist/oauth-login-types.d.ts.map +1 -0
  83. package/dist/oauth-login-types.js +9 -0
  84. package/dist/oauth-login.d.ts +77 -0
  85. package/dist/oauth-login.d.ts.map +1 -0
  86. package/dist/oauth-login.js +455 -0
  87. package/dist/oauth-providers/github.d.ts +17 -0
  88. package/dist/oauth-providers/github.d.ts.map +1 -0
  89. package/dist/oauth-providers/github.js +89 -0
  90. package/dist/oauth-providers/google.d.ts +18 -0
  91. package/dist/oauth-providers/google.d.ts.map +1 -0
  92. package/dist/oauth-providers/google.js +59 -0
  93. package/dist/oauth-providers-store.d.ts +32 -0
  94. package/dist/oauth-providers-store.d.ts.map +1 -0
  95. package/dist/oauth-providers-store.js +291 -0
  96. package/dist/oauth.d.ts +347 -0
  97. package/dist/oauth.d.ts.map +1 -0
  98. package/dist/oauth.js +686 -0
  99. package/dist/pairing-transport.d.ts +99 -0
  100. package/dist/pairing-transport.d.ts.map +1 -0
  101. package/dist/pairing-transport.js +223 -0
  102. package/dist/rate-limit-middleware.d.ts +36 -0
  103. package/dist/rate-limit-middleware.d.ts.map +1 -0
  104. package/dist/rate-limit-middleware.js +57 -0
  105. package/dist/render-gate.d.ts +87 -0
  106. package/dist/render-gate.d.ts.map +1 -0
  107. package/dist/render-gate.js +77 -0
  108. package/dist/render-rate-limit.d.ts +59 -0
  109. package/dist/render-rate-limit.d.ts.map +1 -0
  110. package/dist/render-rate-limit.js +73 -0
  111. package/dist/render-signing.d.ts +98 -0
  112. package/dist/render-signing.d.ts.map +1 -0
  113. package/dist/render-signing.js +113 -0
  114. package/dist/request-context.d.ts +113 -0
  115. package/dist/request-context.d.ts.map +1 -0
  116. package/dist/request-context.js +154 -0
  117. package/dist/reserved-validators.d.ts +22 -0
  118. package/dist/reserved-validators.d.ts.map +1 -0
  119. package/dist/reserved-validators.js +101 -0
  120. package/dist/schema-compat.d.ts +167 -0
  121. package/dist/schema-compat.d.ts.map +1 -0
  122. package/dist/schema-compat.js +187 -0
  123. package/dist/security-headers-middleware.d.ts +38 -0
  124. package/dist/security-headers-middleware.d.ts.map +1 -0
  125. package/dist/security-headers-middleware.js +30 -0
  126. package/dist/server.d.ts +2060 -0
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +6338 -0
  129. package/dist/session-channel.d.ts +651 -0
  130. package/dist/session-channel.d.ts.map +1 -0
  131. package/dist/session-channel.js +1756 -0
  132. package/dist/storage.d.ts +89 -0
  133. package/dist/storage.d.ts.map +1 -0
  134. package/dist/storage.js +171 -0
  135. package/dist/thread-transport.d.ts +118 -0
  136. package/dist/thread-transport.d.ts.map +1 -0
  137. package/dist/thread-transport.js +478 -0
  138. package/dist/user-session-auth.d.ts +167 -0
  139. package/dist/user-session-auth.d.ts.map +1 -0
  140. package/dist/user-session-auth.js +148 -0
  141. package/package.json +76 -0
@@ -0,0 +1,2060 @@
1
+ /**
2
+ * createGguiServer — build a runnable open MCP server.
3
+ *
4
+ * Composition:
5
+ *
6
+ * - `@ggui-ai/mcp-server-handlers/blueprints` — the three blueprint-read
7
+ * handlers (search / list_featured / render), shared with hosted
8
+ * closed-runtime servers. If you want more tools, extract the next
9
+ * family into `@ggui-ai/mcp-server-handlers` and pass it via
10
+ * `handlers:`.
11
+ *
12
+ * - `@ggui-ai/mcp-server-core/in-memory` — default backing adapters
13
+ * (vectors, embedding, auth). Real persistence bindings ship in
14
+ * later packages (sqlite / postgres / redis) and plug into the
15
+ * same interfaces.
16
+ *
17
+ * - `@modelcontextprotocol/sdk` — `McpServer` + `StreamableHTTPServerTransport`
18
+ * matching the MCP wire spec. Fresh transport + fresh server per
19
+ * request (stateless); response close tears both down.
20
+ *
21
+ * Transport:
22
+ *
23
+ * POST /mcp — MCP Streamable HTTP wire protocol (JSON-RPC).
24
+ * GET /ggui/health — unauthenticated liveness, returns
25
+ * `{status, server, version, tools, ...}`.
26
+ * GET /ggui/auth-check — authenticated liveness. 204 when the bearer
27
+ * token resolves via the configured AuthAdapter,
28
+ * 401 otherwise. Pairs with `/ggui/health` so
29
+ * clients (e.g. Portal settings) can distinguish
30
+ * `reachable` from `token-invalid` without
31
+ * opening a full MCP session just to probe.
32
+ * GET/DELETE /mcp — 405 (stateless server doesn't support the
33
+ * streaming continuation / session-terminate verbs).
34
+ *
35
+ * Zero-config boot: omit every option and you get an in-memory server
36
+ * accepting any bearer token (dev mode) with the blueprint-read
37
+ * handlers wired up. The `Logger.warn('dev_mode_auth_enabled')` fires
38
+ * once at boot so operators see the shape they're running.
39
+ */
40
+ import { type Express, type Request } from 'express';
41
+ import { Server as NodeHttpServer } from 'node:http';
42
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
43
+ import { type ThemeWriter, type ThemeFileUploader } from './console-theme-routes.js';
44
+ import { type ZodRawShape } from 'zod';
45
+ import type { AppMetadataStore, AuditSink, AuthAdapter, AuthResult, BlueprintProvider, BlueprintSearch, BlueprintSelector, BlueprintStore, ConnectorRegistry, CodeStore, EmbeddingProvider, GeneratorRegistry, KeyValueStore, PairingService, PendingEventConsumer, ProviderKeyStore, RateLimiter, SessionStore, SessionStreamBuffer, ShortCodeIndex, TelemetrySink, ThreadStore, VectorStore } from '@ggui-ai/mcp-server-core';
46
+ import type { Blueprint } from '@ggui-ai/protocol';
47
+ import type { DiscoveredPrimitiveCatalog, LoadedTheme } from '@ggui-ai/project-config/node';
48
+ import type { OperatorConfig } from '@ggui-ai/project-config';
49
+ import type { SharedHandler } from '@ggui-ai/mcp-server-handlers';
50
+ import { type ThemeCatalogEntry } from '@ggui-ai/mcp-server-handlers/app-discovery';
51
+ import { type AppsSource, type UserDefaultAppSource } from '@ggui-ai/mcp-server-handlers/ops-apps';
52
+ import { type OrgsSource, type OrgInvitesSource } from '@ggui-ai/mcp-server-handlers/ops-orgs';
53
+ import { type ConnectorKeysSource } from '@ggui-ai/mcp-server-handlers/ops-connector-keys';
54
+ import { type CouponRedeemSource } from '@ggui-ai/mcp-server-handlers/ops-coupon';
55
+ import type { UiRegistry } from '@ggui-ai/ui-registry';
56
+ import { type ChannelNotifier, type GenerationCredentials, type GenerationDeps, type HandshakeNegotiator, type PropsUpdateNotifier, type ProvisionalPreviewDeps, type ProvisionalPreviewEmitter, type ProvisionalPreviewConfig, type ProvisionalPreviewOutcome } from '@ggui-ai/mcp-server-handlers/session-mutations';
57
+ import { type ServerInfo } from './build-mcp.js';
58
+ import { type OAuthConfig } from './oauth.js';
59
+ import { type Logger } from './logger.js';
60
+ import { type EmailSender, type MagicLinkStore } from './email-login.js';
61
+ import { type McpInstructionsValue } from './instructions-presets.js';
62
+ import { type ThreadOwnerResolver } from './thread-transport.js';
63
+ import { type SessionChannelServer, type WiredActionRouter } from './session-channel.js';
64
+ import { type McpServerMount, type McpService } from './mcp-mounts.js';
65
+ import { type SchemaCompatMode } from './schema-compat.js';
66
+ export declare function defaultHandlers(deps: {
67
+ readonly embedding: EmbeddingProvider;
68
+ readonly vectors: VectorStore;
69
+ /**
70
+ * Optional blueprint catalog source. When bound,
71
+ * `ggui_list_featured_blueprints` enumerates the provider's
72
+ * catalog; absent = the handler returns an empty list (the
73
+ * zero-config OSS default).
74
+ *
75
+ * `createGguiServer` constructs a `ManifestBlueprintProvider`
76
+ * from `ggui.json#blueprints.include` at boot and threads it in
77
+ * here — that's how manifest-declared UIs surface through the MCP
78
+ * tool.
79
+ */
80
+ readonly blueprints?: BlueprintProvider;
81
+ /**
82
+ * UI registry consulted by `ggui_render_blueprint`. When bound,
83
+ * the render handler is registered and resolves every call through
84
+ * this registry's `get(id)` + `getBundle(id)` pair. Absent = the
85
+ * render handler is omitted from the handler array (no deprecation
86
+ * shim, no throwing stub — operator sees "tool unavailable" only
87
+ * if a caller tries to invoke it).
88
+ *
89
+ * `createGguiServer` threads `opts.uiRegistry` through when
90
+ * present. OSS `ggui serve` binds
91
+ * `@ggui-ai/dev-stack::LocalUiRegistry`.
92
+ */
93
+ readonly uiRegistry?: UiRegistry;
94
+ /**
95
+ * Handshake-state KV store. When bound, `ggui_handshake` is
96
+ * registered and the paired `ggui_push({handshakeId})` consume
97
+ * path is enabled. Both handlers share the same instance so the
98
+ * write + read sit on one source of truth.
99
+ *
100
+ * Absent = handshake handler is NOT registered AND
101
+ * `ggui_push({handshakeId})` falls back to a rejection shape.
102
+ */
103
+ readonly handshake?: {
104
+ readonly kvStore: KeyValueStore;
105
+ /**
106
+ * Optional session store. When bound, `ggui_handshake` validates
107
+ * the wire `sessionId` against this store (existence + tenant
108
+ * ownership) before negotiating. OSS sets this to the same store
109
+ * push uses so the handshake catches unknown / cross-tenant ids
110
+ * at the earliest boundary; cloud pods omit and validate at push
111
+ * time via their own DDB-backed path.
112
+ */
113
+ readonly sessionStore?: SessionStore;
114
+ /**
115
+ * Optional negotiator binding. Absent = `ggui_handshake` stamps
116
+ * `action: 'create'` + honest no-negotiator reason on the record
117
+ * (the seam is still real; persistence + consumption still
118
+ * anchor the round-trip).
119
+ */
120
+ readonly negotiator?: HandshakeNegotiator;
121
+ /**
122
+ * Optional per-app metadata resolver. When bound, the handshake
123
+ * handler reads `app.gadgets` and threads the catalog to
124
+ * the negotiator so synth knows which gadget bindings the app
125
+ * exposes. Defaults to the same `appMetadataStore` the
126
+ * `ggui_list_gadgets` tool uses.
127
+ */
128
+ readonly appMetadataStore?: AppMetadataStore;
129
+ /**
130
+ * Optional resolver for the `serverCapabilities` field on
131
+ * every handshake response. Composition wires this so iframes
132
+ * learn which `streamSpec[ch].source.tool` channels they can WS-
133
+ * subscribe-for vs. must iframe-poll directly. Returning
134
+ * `undefined` omits the field (universal iframe-polling fallback).
135
+ */
136
+ readonly serverCapabilities?: () => import('@ggui-ai/protocol').ServerCapabilities | undefined;
137
+ };
138
+ readonly push?: {
139
+ readonly sessionStore: SessionStore;
140
+ readonly renderBaseUrl: string;
141
+ /**
142
+ * Optional render-URL signer. Returns the query suffix to append
143
+ * to a minted URL (`sig=...&exp=...`, no leading `?` or `&`).
144
+ * Absent when `renderSigning: false` is set on boot.
145
+ * Mirrors the handler-side dep with the same name.
146
+ */
147
+ readonly signRenderUrl?: (shortCode: string) => string;
148
+ /**
149
+ * Optional bootstrap-credential minter. When present, `ggui_push`
150
+ * results carry `_meta.ggui.bootstrap`. When absent, they don't —
151
+ * non-MCP-Apps hosts still get `structuredContent.url` as the
152
+ * fallback link.
153
+ */
154
+ readonly mintBootstrap?: (sessionId: string, appId: string) => {
155
+ wsUrl: string;
156
+ token: string;
157
+ expiresAt: string;
158
+ };
159
+ /**
160
+ * URL of the renderer bundle the thin-shell HTML should fetch
161
+ * (C8 — plan §C8). Padded onto
162
+ * {@link GguiBootstrapMeta.runtimeUrl} at `resultMeta` time.
163
+ * Same-origin default is `/_ggui/iframe-runtime.js`; hosted cloud
164
+ * operators override to a CDN URL. Required when `mintBootstrap`
165
+ * is set (the thin shell depends on it); otherwise ignored.
166
+ *
167
+ * Function form: callers passing a getter let the handler
168
+ * resolve the URL per request — auto-derive from
169
+ * `X-Forwarded-Host` when the TCP peer is loopback so tunnel/
170
+ * reverse-proxy setups produce absolute URLs that work under
171
+ * srcdoc iframes (claude.ai). Static `publicBaseUrl` config
172
+ * still wins.
173
+ */
174
+ readonly runtimeUrl?: string | (() => string | undefined);
175
+ /**
176
+ * Theme preset id resolved from `ggui.json#theme`. Forwarded onto
177
+ * `_meta.ggui.bootstrap.themeId` in the `ggui_push` resultMeta so
178
+ * MCP Apps hosts (claude.ai, Claude Desktop) propagate the
179
+ * operator's theme into the iframe.
180
+ */
181
+ readonly themeId?: string;
182
+ /** Theme color mode resolved from `ggui.json#theme.mode`. */
183
+ readonly themeMode?: 'light' | 'dark';
184
+ /**
185
+ * Live theme getter — resolved per-push. When set, supersedes
186
+ * the static `themeId` / `themeMode` for every push's bootstrap.
187
+ * Pair with the same getter passed into `createGguiServer({
188
+ * themeProvider })` and a closure that reads from the shared
189
+ * mutable cell `mountDevtoolThemeRoutes`'s POST handler updates.
190
+ * Forwarded onto `deps.push.themeProvider` so the handler reads
191
+ * the live theme each call.
192
+ */
193
+ readonly themeProvider?: () => {
194
+ readonly id?: string;
195
+ readonly mode?: 'light' | 'dark';
196
+ } | undefined;
197
+ /**
198
+ * Optional connector registry — required for accepting
199
+ * `shortcuts.mcpApps` push payloads (inbound MCP Apps hosting).
200
+ * Omitted = inbound path is rejected with a clear error.
201
+ */
202
+ readonly connectors?: ConnectorRegistry;
203
+ /**
204
+ * Optional admission-control limiter. When present, `ggui_push`
205
+ * gates every call through `rateLimiter.check({key:
206
+ * 'ggui_push:<appId>', cost:1})` before doing any work; denial
207
+ * surfaces as a `RateLimitedError`. Omitted = unlimited (the
208
+ * `NoopRateLimiter` server default).
209
+ */
210
+ readonly rateLimiter?: RateLimiter;
211
+ /**
212
+ * Optional shortCode → session binding index. When present,
213
+ * `ggui_push` records every minted `shortCode` so console's
214
+ * `/s/<shortCode>` viewer (via the session-cookie endpoint) can
215
+ * resolve it back to the right session. Absent = hosted cloud
216
+ * flow (DynamoDB side-table owns lookups), or console not
217
+ * enabled.
218
+ */
219
+ readonly shortCodeIndex?: ShortCodeIndex;
220
+ /**
221
+ * Optional provisional-preview wiring. When present, `ggui_push`
222
+ * kicks off the configured emitter on every qualifying push (the
223
+ * `evaluateProvisionalPreviewGate` predicate filters MCP Apps
224
+ * pushes + storyless calls automatically). Absent = no preview
225
+ * channel traffic.
226
+ *
227
+ * Constructed by `createGguiServer` from `opts.provisionalPreview`
228
+ * plus the late-bound `SessionChannelServer.sendToSession`
229
+ * closure; callers threading their own handler set can build
230
+ * `ProvisionalPreviewDeps` directly.
231
+ */
232
+ readonly provisionalPreview?: ProvisionalPreviewDeps;
233
+ /**
234
+ * Optional generation wiring. When present, `ggui_push` invokes
235
+ * the supplied {@link UiGenerator} on every story-path call and
236
+ * appends the generated `StackItem` to the session before
237
+ * returning `codeReady: true`. Absent = push stays in
238
+ * placeholder mode (session + shortCode + preview still work,
239
+ * but no componentCode is produced).
240
+ *
241
+ * Callers compose the `GenerationDeps` directly via the
242
+ * `@ggui-ai/mcp-server-handlers` export — `defaultHandlers`
243
+ * simply threads the bundle through to `createGguiPushHandler`.
244
+ */
245
+ readonly generation?: GenerationDeps;
246
+ /**
247
+ * Optional live-subscriber stack-push notifier.
248
+ * When present, every successful `appendStackItem` inside `ggui_push`
249
+ * fan-outs a `{type:'push', payload:{stackItem}}` live-channel frame to
250
+ * every live subscriber on the affected session. Forwarded as-is
251
+ * to `createGguiPushHandler`.
252
+ *
253
+ * Hosts without a session channel (programmatic embedding, Lambda
254
+ * one-shot) leave this absent — there are no live subscribers to
255
+ * notify, and the push handler's own no-op-on-absent posture
256
+ * keeps the path intact.
257
+ */
258
+ readonly channelNotifier?: ChannelNotifier;
259
+ /**
260
+ * Optional F4 schema compat check hook. When present,
261
+ * `ggui_push` invokes it immediately
262
+ * before every `appendStackItem` — if the pending StackItem's
263
+ * `actionSpec` / `streamSpec` references a tool whose schemas
264
+ * disagree, the hook throws `SchemaCompatError` and the handler
265
+ * converts the rejection into an error stack-item + `codeReady:
266
+ * false`. Forwarded as-is to `createGguiPushHandler`.
267
+ *
268
+ * `createGguiServer` binds this closure against the composed
269
+ * `handlers` list + `opts.schemaCompatCheck` (default `'reject'`)
270
+ * automatically; callers composing their own push handler via
271
+ * `defaultHandlers` wire the hook themselves.
272
+ */
273
+ readonly checkStackItemContracts?: (shape: {
274
+ readonly actionSpec?: import('@ggui-ai/protocol').ActionSpec;
275
+ readonly streamSpec?: import('@ggui-ai/protocol').StreamSpec;
276
+ }) => void;
277
+ /**
278
+ * Optional content-addressable code store. When present together
279
+ * with {@link codeBaseUrl}, `ggui_push` writes generated
280
+ * componentCode to the store and surfaces `codeUrl` + `codeHash`
281
+ * on the response — the sole static-component delivery channel
282
+ * post-T3-1 (2026-05-13).
283
+ *
284
+ * Absent: `ggui_push.resultMeta` omits `codeUrl`. The iframe boots
285
+ * via live-mode (wsUrl+token) and receives the stack item via the
286
+ * live-channel WS subscribe. `/r/<shortCode>` + `/api/bootstrap/<shortCode>`
287
+ * routes ALSO mint `codeUrl` when `codeStore` is set — they derive
288
+ * the base URL from `req.protocol + req.host` when `codeBaseUrl`
289
+ * isn't explicit (works for local dev + tunnel deployments).
290
+ * Forwarded as-is to `createGguiPushHandler`.
291
+ */
292
+ readonly codeStore?: CodeStore;
293
+ /**
294
+ * Base URL the code-blob route resolves to. Required when
295
+ * `codeStore` is present so the handler can compose
296
+ * `<base>/code/<hash>.js`. Forwarded as-is to
297
+ * `createGguiPushHandler`.
298
+ */
299
+ readonly codeBaseUrl?: string;
300
+ /**
301
+ * Resolver for the bootstrap field
302
+ * `streamWebSocketLocalTools`. Mirrors the handshake's
303
+ * `serverCapabilities.streamWebSocketLocalTools` so iframe-runtime
304
+ * can pick WS-subscribe vs iframe-poll per channel. Composing
305
+ * `createGguiServer` wires both from the SAME
306
+ * `streamWebSocketLocalTools` option — so a server that advertises
307
+ * a tool on the handshake also surfaces it on the bootstrap.
308
+ *
309
+ * Returns undefined ⇒ field omitted from bootstrap ⇒ legacy
310
+ * "iframe polls everything" path. Returns an empty array ⇒
311
+ * "WS transport supported but no tool is local" (still useful —
312
+ * lets the iframe know the server is transport-aware).
313
+ */
314
+ readonly streamWebSocketLocalTools?: () => readonly string[] | undefined;
315
+ /**
316
+ * Optional bootstrap-refresh seam for the
317
+ * `ggui_runtime_refresh_bootstrap` tool (G14, 2026-05-23). When
318
+ * supplied, the tool registers and validates each refresh request
319
+ * via this seam's HMAC check + refresh-window arithmetic. Typically
320
+ * wired against the SAME `channelBootstrap.refresh` the
321
+ * session-channel server uses for WS upgrade validation, so both
322
+ * paths share one HMAC secret and one refresh-window policy.
323
+ *
324
+ * Absent: the tool is NOT registered on this deployment. iframes
325
+ * fall back to the historical "fresh handshake on every reconnect"
326
+ * posture — fast via the matcher cache, but more wire traffic than
327
+ * a stateless refresh.
328
+ *
329
+ * `createGguiServer` wires this from the `mcpAppsEnabled` branch's
330
+ * `channelBootstrap.refresh` so the OSS factory's behavior matches
331
+ * the cloud pod's tool-side composition.
332
+ */
333
+ readonly bootstrapRefresh?: import('@ggui-ai/mcp-server-handlers/session-mutations').BootstrapRefreshSeam;
334
+ };
335
+ /**
336
+ * `ggui_update` wiring. When present, register the OSS update
337
+ * handler against the supplied SessionStore + optional live-channel
338
+ * props_update notifier. The handler reads sessionId / stackItemId
339
+ * from wire input today, but a future in-process dispatcher can
340
+ * populate them on the canonical context.
341
+ *
342
+ * Absent = `ggui_update` is NOT registered on this server. Hosts
343
+ * that don't expose props mutation keep the smaller surface (e.g.,
344
+ * static-blueprint demos, MCP-Apps-only deployments).
345
+ */
346
+ readonly update?: {
347
+ readonly sessionStore: SessionStore;
348
+ /**
349
+ * Optional live-subscriber `props_update` notifier — typically a
350
+ * thin closure over `SessionChannelServer.sendPropsUpdate`.
351
+ * Forwarded as-is to `createGguiUpdateHandler`. Hosts without a
352
+ * session channel leave this absent; the handler still persists
353
+ * via `sessionStore.appendStackItem` on every successful patch.
354
+ */
355
+ readonly propsUpdateNotifier?: PropsUpdateNotifier;
356
+ /**
357
+ * Bootstrap-credential minter (live trio). When wired, the
358
+ * `ggui_update` resultMeta emits `_meta.ggui.bootstrap` so MCP Apps
359
+ * hosts that re-post `ui/notifications/tool-result` via postMessage
360
+ * can re-apply patched props on the live mount without re-subscribing.
361
+ * Mirrors the same field on `push` deps; composing hosts wire both
362
+ * from the same minter.
363
+ */
364
+ readonly mintBootstrap?: (sessionId: string, appId: string) => {
365
+ wsUrl: string;
366
+ token: string;
367
+ expiresAt: string;
368
+ };
369
+ /** Iframe-runtime bundle URL forwarded onto bootstrap.runtimeUrl.
370
+ * Function form mirrors push deps — see {@link BuildMcpDeps.push}. */
371
+ readonly runtimeUrl?: string | (() => string | undefined);
372
+ /** Theme preset id forwarded onto bootstrap.themeId. */
373
+ readonly themeId?: string;
374
+ /** Theme color mode forwarded onto bootstrap.themeMode. */
375
+ readonly themeMode?: 'light' | 'dark';
376
+ /** Live theme getter — overrides static themeId/themeMode per-update. */
377
+ readonly themeProvider?: () => {
378
+ readonly id?: string;
379
+ readonly mode?: 'light' | 'dark';
380
+ } | undefined;
381
+ /** Returns names of app-visible tools for bootstrap.appCallableTools. */
382
+ readonly appCallableTools?: () => readonly string[];
383
+ /** Resolver for bootstrap.streamWebSocketLocalTools. */
384
+ readonly streamWebSocketLocalTools?: () => readonly string[] | undefined;
385
+ };
386
+ /**
387
+ * Pending-events consumer wiring for `ggui_consume`. When `push`
388
+ * is bound, the handler registers automatically with an in-memory
389
+ * default; pass `consume.pendingEventConsumer` to override (e.g.,
390
+ * SQLite-backed for persistent dev or a Dynamo adapter on cloud).
391
+ *
392
+ * `defaultSessionTtlSeconds` controls the activity-bump TTL the
393
+ * handler forwards to `consumeAndClear` on every read. Falls back
394
+ * to 1 day when omitted.
395
+ */
396
+ readonly consume?: {
397
+ readonly pendingEventConsumer?: PendingEventConsumer;
398
+ readonly defaultSessionTtlSeconds?: number;
399
+ };
400
+ /**
401
+ * Stream channel wiring for `ggui_emit`. When `push` is bound, the
402
+ * handler registers automatically; its `sendEnvelope` closes over
403
+ * `stream.channelProvider`, a lazy getter that resolves the
404
+ * `SessionChannelServer` at emit time (the channel is constructed
405
+ * AFTER `defaultHandlers` runs, so a static reference would always
406
+ * be null on the OSS in-process boot).
407
+ *
408
+ * Absent / returns null = no live receiver. Emit still succeeds at
409
+ * the protocol level; the envelope just isn't fanned out. Matches
410
+ * cloud's `ggui_emit_accepted_no_receiver` posture.
411
+ *
412
+ * The getter pattern lets the OSS server bind once at boot, then
413
+ * mutate the cell after `createSessionChannelServer` runs.
414
+ */
415
+ readonly stream?: {
416
+ readonly channelProvider?: () => SessionChannelServer | null;
417
+ };
418
+ /**
419
+ * Structured-event logger threaded into handlers that emit
420
+ * protocol-adherence telemetry — `ggui_consume` fires the yellow-
421
+ * flag `action_consume_slow` info-event when an event sat in the
422
+ * pipe past the latency threshold. Absent = silent; the handler's
423
+ * drain semantics are unaffected.
424
+ */
425
+ readonly logger?: Logger;
426
+ /**
427
+ * Operational-signal sink. Threaded into handlers that emit named
428
+ * events (`ggui_new_session` emits `session.created` /
429
+ * `session.create_failed`). Absent = NoopTelemetrySink semantic.
430
+ * Lossy + non-throwing per the {@link TelemetrySink} contract.
431
+ */
432
+ readonly telemetry?: TelemetrySink;
433
+ /**
434
+ * Per-app metadata store backing `ggui_list_gadgets`.
435
+ * Absent = `createGguiServer` constructs a fresh
436
+ * `InMemoryAppMetadataStore` seeded with `STDLIB_GADGETS` per app
437
+ * on first access (sandbox-app permitted-error path inside the
438
+ * handler also falls back to stdlib, so omitting the store still
439
+ * yields a working tool).
440
+ *
441
+ * Hosted deployments inject an `AppMetadataStore` backed by their per-app
442
+ * metadata table (cloud's DDB-backed adapter applies the
443
+ * default-on-read pattern inside `getApp` directly).
444
+ */
445
+ readonly appMetadataStore?: AppMetadataStore;
446
+ /**
447
+ * Global theme-catalog resolver consumed by `ggui_list_themes` AND by
448
+ * `ggui_new_session`'s opt-in `requestThemeList` projection. Returns
449
+ * the full registry every call (kept as a function so additions to
450
+ * the catalog at runtime — e.g. operator-defined themes in a future
451
+ * slice — surface without a server restart). When BOTH this and
452
+ * `appMetadataStore` are bound, `ggui_list_themes` registers; either
453
+ * absent ⇒ the tool is omitted from the handler array (zero-config
454
+ * OSS behavior: a deployment that hasn't wired themes simply doesn't
455
+ * advertise theme picking).
456
+ *
457
+ * The CLI binds this to `@ggui-ai/design`'s `listThemes()`; hosted
458
+ * deployments may project a different shape so this stays
459
+ * design-package-agnostic at the handler layer.
460
+ */
461
+ readonly themes?: () => readonly ThemeCatalogEntry[];
462
+ /**
463
+ * Operator-class blueprint tool wiring. When
464
+ * `generators` + `blueprintStore` + `blueprintSearch` are all
465
+ * bound, `defaultHandlers` registers the four `ggui_ops_*`
466
+ * blueprint tools on `/ops`:
467
+ *
468
+ * - `ggui_ops_generate_blueprint` (requires `resolveLlm` +
469
+ * `blueprints` too — same deps the push generation path
470
+ * reads).
471
+ * - `ggui_ops_list_blueprints`
472
+ * - `ggui_ops_update_blueprint`
473
+ * - `ggui_ops_delete_blueprint`
474
+ *
475
+ * Absent = the ops tools are not registered (operator UX falls
476
+ * back to whatever surface the cloud pod exposes, or the deployment
477
+ * runs without operator authorship). The list/update/delete trio
478
+ * registers even when `generate` deps are absent — read-only
479
+ * operations on an existing store can be useful for inspection.
480
+ */
481
+ readonly opsBlueprint?: {
482
+ readonly registry: GeneratorRegistry;
483
+ readonly blueprintStore: BlueprintStore;
484
+ readonly blueprintSearch: BlueprintSearch;
485
+ /**
486
+ * Hook into the store's code-body path. When the bound
487
+ * `blueprintStore` is an `InMemoryBlueprintStore`, pass
488
+ * `(codeHash, body) => store.putCode(codeHash, body)` so the
489
+ * generated body is reachable via `getCode(codeHash)`. Cloud
490
+ * adapters that persist code inside `BlueprintStore.put` omit
491
+ * this.
492
+ */
493
+ readonly putCode?: (codeHash: string, body: string) => void | Promise<void>;
494
+ /**
495
+ * Per-app blueprint enumerator — used for the persona near-dup
496
+ * warning on the generate path. Optional; when omitted the
497
+ * check is skipped.
498
+ */
499
+ readonly listAllForApp?: (appId: string) => Promise<readonly Blueprint[]>;
500
+ /**
501
+ * Resolver for LLM credentials on the generate path. Same shape
502
+ * as `push.generation.resolveLlm` — typically wired to the same
503
+ * closure. When absent, the generate handler is NOT registered
504
+ * (list/update/delete still register).
505
+ */
506
+ readonly resolveLlm?: (ctx: import('@ggui-ai/mcp-server-handlers').HandlerContext) => Promise<GenerationCredentials | null> | GenerationCredentials | null;
507
+ /**
508
+ * BlueprintProvider passed to the generator (same instance
509
+ * `defaultHandlers`'s blueprint search reads). Required when
510
+ * `resolveLlm` is set — generate needs this on its
511
+ * UiGenerateInput.
512
+ */
513
+ readonly blueprints?: BlueprintProvider;
514
+ /**
515
+ * Cache-registry mirror for `ggui_ops_generate_blueprint`. When
516
+ * bound, operator-authored blueprints are dual-written to the
517
+ * cache vectorStore via `registerBlueprint` so the agent-facing
518
+ * matchBlueprint exact-key probe (handshake + push) finds them.
519
+ * Same bundle the push handler reads/writes.
520
+ */
521
+ readonly cacheRegistry?: {
522
+ readonly embedding: EmbeddingProvider;
523
+ readonly vectorStore: VectorStore;
524
+ };
525
+ };
526
+ /**
527
+ * Per-domain dep bundles for the twelve operator-class `ggui_ops_*`
528
+ * handlers covering apps + orgs + connector-keys + coupons. Each
529
+ * domain is independently optional:
530
+ * deployments that don't wire the seam simply don't register that
531
+ * domain's tools (matching `ggui_ops_get_credit_balance`'s pattern).
532
+ *
533
+ * OSS deployments (no AppSync, no Cognito) leave these all
534
+ * undefined and the surface stays narrow. Cloud pods bind
535
+ * AppSync-backed adapters in a follow-up slice; the handlers ship
536
+ * here with deps seams only.
537
+ */
538
+ readonly opsApps?: {
539
+ readonly apps: AppsSource;
540
+ readonly userDefaultApp: UserDefaultAppSource;
541
+ };
542
+ readonly opsOrgs?: {
543
+ readonly orgs: OrgsSource;
544
+ readonly invites: OrgInvitesSource;
545
+ };
546
+ readonly opsConnectorKeys?: {
547
+ readonly connectorKeys: ConnectorKeysSource;
548
+ };
549
+ readonly opsCoupon?: {
550
+ readonly coupons: CouponRedeemSource;
551
+ };
552
+ }): ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
553
+ export interface CreateGguiServerOptions {
554
+ /**
555
+ * Server identity broadcast to MCP clients. Defaults to
556
+ * `{name: 'ggui-mcp-server', version: '0.0.1', ...}`.
557
+ */
558
+ readonly info?: Partial<ServerInfo>;
559
+ /**
560
+ * Operator-mode hint that gates the `/devtools/*` namespace.
561
+ *
562
+ * - `'prod'` (default for `ggui serve`): only `/admin/*` mounts.
563
+ * - `'dev'` (default for `ggui dev`): `/devtools/*` mounts in
564
+ * addition to `/admin/*` and the SPA shows the dev-mode link
565
+ * in the TopNav. The dev surfaces are admin-cookie gated, same
566
+ * as `/admin/*` — `mode: 'dev'` only changes WHAT mounts, not
567
+ * who can reach it.
568
+ *
569
+ * When omitted, resolves from `process.env.GGUI_MODE` (`'dev'` →
570
+ * dev, anything else including unset → prod). Pass an explicit
571
+ * value to override the env in test fixtures and embedders.
572
+ */
573
+ readonly mode?: 'dev' | 'prod';
574
+ /**
575
+ * Shared handler set to expose. Defaults to the blueprint-read family
576
+ * (search + list_featured + render). Pass your own list to add / remove
577
+ * tools — handlers MUST be `SharedHandler` instances from
578
+ * `@ggui-ai/mcp-server-handlers` (or shape-compatible custom ones).
579
+ */
580
+ readonly handlers?: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
581
+ /**
582
+ * Identity-kind allowlist for tool registration. When set, handlers
583
+ * whose `allowedFor` field declares a non-overlapping audience are
584
+ * skipped at registration time. Handlers without `allowedFor` register
585
+ * unconditionally.
586
+ *
587
+ * - agent-builder posture sets `['app']` — skips hypothetical
588
+ * `['user']`-only handlers without affecting the existing toolset
589
+ * (which is all `['app', 'builder']`).
590
+ * - end-user / Connector posture sets `['user']` — skips all
591
+ * agent-builder writes (push / handshake / update) while keeping
592
+ * the read-only blueprint surface visible.
593
+ * - OSS local omits this option — every handler registers; OSS
594
+ * callers resolve to `kind: 'builder'` and the filter never fires.
595
+ *
596
+ * See `packages/mcp-server-handlers/src/types.ts`
597
+ * (`SharedHandler.allowedFor`).
598
+ */
599
+ readonly allowedKinds?: ReadonlyArray<'app' | 'user' | 'builder'>;
600
+ /**
601
+ * Auth adapter. Defaults to `InMemoryAuthAdapter({devAllowAll: true})`
602
+ * — accepts any non-empty bearer token as the `builder` identity.
603
+ * Every real deployment SHOULD override this (e.g. with a
604
+ * `PairingService`-backed adapter).
605
+ */
606
+ readonly auth?: AuthAdapter;
607
+ /**
608
+ * Vector store for blueprint search. Defaults to `InMemoryVectorStore`.
609
+ */
610
+ readonly vectors?: VectorStore;
611
+ /**
612
+ * Embedding provider for blueprint search. Defaults to `MockEmbeddingProvider`
613
+ * — produces deterministic but NOT semantically meaningful vectors.
614
+ * Swap for a real provider (OpenAI / Bedrock / Voyage / local) in
615
+ * production.
616
+ */
617
+ readonly embedding?: EmbeddingProvider;
618
+ /**
619
+ * Per-app metadata source. When bound, threaded into
620
+ * `defaultHandlers` so `ggui_list_gadgets`,
621
+ * `ggui_list_themes`, `ggui_new_session({themeId?})`, and the
622
+ * handshake's `app.gadgets` lookup all read from the same
623
+ * store. The CLI binds an `InMemoryAppMetadataStore` seeded from
624
+ * `ggui.json#theme.preset` (so every appId picks up the operator's
625
+ * chosen default theme without an explicit `register()`); hosted
626
+ * deployments bind a multi-tenant adapter.
627
+ *
628
+ * Absent ⇒ `defaultHandlers` constructs a fresh
629
+ * `InMemoryAppMetadataStore` per request site that needs one (no
630
+ * cross-handler sharing) and `ggui_list_themes` is NOT registered.
631
+ */
632
+ readonly appMetadataStore?: AppMetadataStore;
633
+ /**
634
+ * Global theme-catalog resolver. When bound alongside
635
+ * `appMetadataStore`, registers `ggui_list_themes` and projects the
636
+ * same catalog into `ggui_new_session({requestThemeList: true})`
637
+ * outputs. Read each call so additions to the registry at runtime
638
+ * (operator-defined themes in a future slice) surface without a
639
+ * restart.
640
+ *
641
+ * The OSS CLI binds this to `@ggui-ai/design`'s `listThemes()`;
642
+ * hosted deployments may project a different shape. Kept as a
643
+ * resolver function so this surface stays design-package-agnostic.
644
+ */
645
+ readonly themes?: () => readonly ThemeCatalogEntry[];
646
+ /**
647
+ * Blueprint catalog source consulted by
648
+ * `ggui_list_featured_blueprints`. Omitted = the handler returns
649
+ * an empty list (zero-config default). The `ggui-cli` binding
650
+ * constructs a `ManifestBlueprintProvider` from the declared
651
+ * `ggui.json#blueprints.include` manifests and passes it here,
652
+ * so `ggui serve` surfaces every authored UI through the MCP tool
653
+ * without any code change per deployment.
654
+ *
655
+ * Passing `blueprintProvider:` is what makes the `ggui.json`
656
+ * manifest's blueprint declarations take effect — the manifest
657
+ * shape stops being inert once a provider is wired.
658
+ */
659
+ readonly blueprintProvider?: BlueprintProvider;
660
+ /**
661
+ * Admin-blueprints transport (`POST /admin/blueprints`) — runtime
662
+ * manifest registration into the active {@link blueprintProvider}.
663
+ *
664
+ * - `undefined` (default) or an object `{path?}` — mount the
665
+ * route at `/admin/blueprints` (override via `{path: '/x'}`).
666
+ * Only actually mounted when {@link blueprintProvider} is also
667
+ * wired; without a caller-supplied provider the default
668
+ * provider built inside `defaultHandlers` is unreachable from
669
+ * this scope and silently mounting would surprise operators.
670
+ * - `{path: null}` — disable the route explicitly while leaving
671
+ * the provider wired (operators who want the provider but not
672
+ * the HTTP admin surface).
673
+ * - `false` — same as `{path: null}`, but shorter.
674
+ *
675
+ * In-memory only: runtime-registered manifests survive until the
676
+ * server process exits. Plan explicitly permits this choice; a
677
+ * follow-up slice may add disk persistence if operators ask.
678
+ *
679
+ * Auth: builder-bearer, same gate as `/admin/pair/init`.
680
+ */
681
+ readonly adminBlueprints?: false | {
682
+ readonly path?: string | null;
683
+ };
684
+ /**
685
+ * UI registry consulted by `ggui_render_blueprint`. When present, the
686
+ * render handler is registered on the MCP wire and resolves every
687
+ * call through this registry's `get(id)` + `getBundle(id)` pair.
688
+ * When absent, `ggui_render_blueprint` is NOT registered at all —
689
+ * callers that attempted to invoke it get a clean "tool not
690
+ * available" MCP response instead of a throwing shim.
691
+ *
692
+ * OSS `ggui serve` binds `@ggui-ai/dev-stack::LocalUiRegistry` here
693
+ * (manifest-backed, compile-on-demand via esbuild). Hosted or
694
+ * programmatic embedders plug in their own implementation (cloud
695
+ * origin, S3-backed, etc.) — the handler is pure over the
696
+ * `UiRegistry` interface.
697
+ */
698
+ readonly uiRegistry?: UiRegistry;
699
+ /**
700
+ * Primitive catalogs declared in `ggui.json#primitives.{packages,local}`
701
+ * and resolved at boot by `discoverPrimitives()` in
702
+ * `@ggui-ai/project-config/node`. Threaded through here so future
703
+ * consumers (generator wiring, capability introspection) can
704
+ * enumerate every declared primitive source without re-reading the
705
+ * manifest.
706
+ *
707
+ * The server itself does NOT yet thread these into the generation
708
+ * pipeline. Making the catalogs visible at boot is the minimum
709
+ * honest "capability is consumed" signal: the declaration is no
710
+ * longer inert; any in-process host can read
711
+ * `server.primitiveCatalogs`.
712
+ *
713
+ * Omitted = the zero-config default (the CLI passes the single
714
+ * shipped `@ggui-ai/design/primitives` catalog; programmatic hosts
715
+ * may pass whatever they discovered themselves).
716
+ */
717
+ readonly primitiveCatalogs?: readonly DiscoveredPrimitiveCatalog[];
718
+ /**
719
+ * Theme resolved at boot from `ggui.json#theme` by
720
+ * `loadTheme()` in `@ggui-ai/project-config/node`. Threaded through
721
+ * here so future consumers (console bootstrap, render endpoint,
722
+ * MCP apps iframe) can pull the token tree + pre-rendered CSS block
723
+ * without re-reading the manifest.
724
+ *
725
+ * The server itself does NOT yet inject theme CSS into console
726
+ * or bake it into MCP responses. Making the theme visible at boot
727
+ * is the minimum honest "capability is consumed" signal: the
728
+ * declaration stops being inert; any in-process host can read
729
+ * `server.theme`.
730
+ *
731
+ * Omitted = the zero-config default (the server builds a
732
+ * `LoadedTheme` backed by `@ggui-ai/design`'s shipped `lightTheme`
733
+ * internally; `server.theme.source === 'default'`).
734
+ */
735
+ readonly theme?: LoadedTheme;
736
+ /**
737
+ * Optional callback that persists a theme selection back to disk.
738
+ *
739
+ * The console `/theme` route mutates `ggui.json#theme` via this
740
+ * callback. The server never touches the filesystem itself — the
741
+ * caller (typically `@ggui-ai/cli`'s serve command) provides a
742
+ * writer that knows where `ggui.json` lives and how to rewrite
743
+ * just the `theme` field while preserving formatting + comments.
744
+ *
745
+ * Signature: receives the new {@link ThemeConfig} (or `null` to
746
+ * clear the field and fall back to defaults). Returns a promise
747
+ * that resolves once the write is durable. Throwing surfaces as
748
+ * a 500 from `POST /ggui/console/theme` with the error message
749
+ * forwarded to the operator.
750
+ *
751
+ * Omitted = `POST /ggui/console/theme` returns 501
752
+ * (`writer_not_configured`). The picker stays browsable; clicking
753
+ * "save" surfaces the missing-writer error instead of silently
754
+ * writing nowhere.
755
+ */
756
+ readonly themeWriter?: ThemeWriter;
757
+ /**
758
+ * Optional callback that writes an uploaded DTCG theme document to a
759
+ * file alongside `ggui.json`. Pairs with {@link themeWriter}: the
760
+ * `POST /ggui/console/theme/upload` route invokes this first, then
761
+ * calls `themeWriter` with `{ file: './<filename>', mode }`.
762
+ *
763
+ * Omitted = the upload route returns 501 and the picker hides its
764
+ * "Upload theme.json" button. Provided alone (without a writer) is
765
+ * also treated as "not configured" — both side-effects must land for
766
+ * an upload to be meaningful.
767
+ */
768
+ readonly themeFileUploader?: ThemeFileUploader;
769
+ /**
770
+ * Live-theme getter. When set, the `ggui_push` handler reads this
771
+ * on every result-meta computation and embeds the returned `id` /
772
+ * `mode` into `_meta.ggui.bootstrap`. Pair with
773
+ * {@link onThemeConfigChange} so a console save updates the
774
+ * shared state cell the closure reads from. The cell pattern
775
+ * closes the parallel-state-stores bug where the push handler
776
+ * captured `themeId` at boot from the static `theme` opt and
777
+ * silently ignored every subsequent ggui.json edit until restart.
778
+ *
779
+ * Returning `undefined` means "no theme override" — the static
780
+ * `theme` opt's resolved id/mode (if any) take effect. Returning
781
+ * `{ id, mode? }` always wins over the static path.
782
+ */
783
+ readonly themeProvider?: () => {
784
+ readonly id?: string;
785
+ readonly mode?: 'light' | 'dark';
786
+ } | undefined;
787
+ /**
788
+ * Optional change notifier — fires when the operator's theme
789
+ * selection changes via `POST /ggui/console/theme` (or the
790
+ * `/upload` variant). Forwarded onto
791
+ * `mountDevtoolThemeRoutes({onConfigChange})`. Pair with a
792
+ * `themeProvider` closure that reads from the same shared cell
793
+ * the callback writes to so a console save reaches the next push
794
+ * without restarting the server.
795
+ *
796
+ * `next` matches `ThemeConfig` from `@ggui-ai/project-config` —
797
+ * one of: a string shorthand (`'indigo'`), a preset object
798
+ * (`{ preset, mode?, overrides? }`), a file object
799
+ * (`{ file, mode? }`), or `null` (cleared).
800
+ */
801
+ readonly onThemeConfigChange?: (next: string | {
802
+ preset: string;
803
+ mode?: 'light' | 'dark';
804
+ overrides?: Record<string, string>;
805
+ } | {
806
+ file: string;
807
+ mode?: 'light' | 'dark';
808
+ } | null) => void;
809
+ /**
810
+ * Map a resolved identity to the `appId` used by handlers for tenant
811
+ * scoping. Defaults to `defaultAppIdFromIdentity` — single-user
812
+ * `'builder'` for builder-kind identities, `userId`/`workspaceId`
813
+ * for user-kind.
814
+ */
815
+ readonly appIdFromIdentity?: (result: AuthResult) => string;
816
+ /**
817
+ * Path the universal MCP endpoint mounts at. Defaults to `/mcp` per
818
+ * Streamable HTTP convention. Cloud `mcp.ggui.ai` overrides to `/`
819
+ * (bare root) so URLs are short — the domain already says "mcp",
820
+ * no need to repeat it in the path.
821
+ *
822
+ * Threaded into the well-known protected-resource metadata so OAuth
823
+ * clients discover the right resource URL, and into the route table
824
+ * so `app.post(${path})` / `app.get/delete(${path})` mount on it.
825
+ */
826
+ readonly universalMcpPath?: string;
827
+ /**
828
+ * Per-tenant URL routing. When set, the factory
829
+ * additionally mounts `${pathPrefix}/:${paramName}(${paramPattern})`
830
+ * alongside the universal path. The shared handler reads
831
+ * `req.params[paramName]` and uses it as `ctx.appId`, overriding
832
+ * `appIdFromIdentity` for that request.
833
+ *
834
+ * Cloud `mcp.ggui.ai` deployments pass `{paramName: 'appId',
835
+ * paramPattern: '[A-Za-z0-9]{8}', pathPrefix: '/apps'}` so URLs
836
+ * like `mcp.ggui.ai/apps/aB3kP9xY` route to a session scoped to
837
+ * that specific GguiApp. The `/apps/` prefix segments the
838
+ * namespace cleanly — no risk of an 8-char appId colliding with a
839
+ * bare-root system route like `/health` or `/settings`.
840
+ *
841
+ * Without `pathPrefix`, the route mounts at the bare
842
+ * `/:${paramName}(${paramPattern})` — useful only when the
843
+ * deployment owns the entire URL space and the pattern guarantees
844
+ * no collision (e.g. UUIDs).
845
+ *
846
+ * Pattern is an Express-compatible regex (no slashes, no flags).
847
+ * Express only matches the URL when it satisfies the pattern; a
848
+ * malformed appId 404s instead of reaching the handler.
849
+ *
850
+ * `authorize` is the deployment-specific access check. After auth
851
+ * resolves but before session work begins, the handler invokes it
852
+ * with the URL-supplied appId + identity. Throw to deny — the
853
+ * handler converts to a 403 response and skips MCP processing.
854
+ * Cloud uses this to verify `GguiApp.userId === identity.userId`
855
+ * (raw-DDB readers in pod tools bypass AppSync owner-auth, so
856
+ * this is the boundary that prevents cross-user blueprint reads).
857
+ * OSS deployments that opt in to per-app routing without an
858
+ * authorize callback are TRUSTED — every authenticated caller can
859
+ * scope to any URL appId.
860
+ */
861
+ readonly perAppRouting?: {
862
+ readonly paramName: string;
863
+ readonly paramPattern: string;
864
+ /**
865
+ * Optional path prefix prepended to the per-app route. Cloud
866
+ * `mcp.ggui.ai` uses `'/apps'` so URLs are
867
+ * `mcp.ggui.ai/apps/<appId>` — leaves the bare root for system
868
+ * routes (`/health`, `/oauth/*`, `/.well-known/*`) without
869
+ * collision concerns. Omit when the pattern alone guarantees
870
+ * non-collision (e.g. UUIDs, opaque hex of fixed length).
871
+ */
872
+ readonly pathPrefix?: string;
873
+ readonly authorize?: (urlAppId: string, identity: AuthResult) => Promise<void>;
874
+ };
875
+ /** Structured logger. Defaults to `createConsoleLogger()`. */
876
+ readonly logger?: Logger;
877
+ /**
878
+ * Optional error-to-HTTP mapper invoked on any handler / transport
879
+ * exception that surfaces past the MCP SDK. When the mapper returns
880
+ * a `{status, code, message}` triple the factory writes that JSON-RPC
881
+ * error response instead of the default `500 / -32603 'Internal
882
+ * server error'`. Returning `undefined` (or omitting the option)
883
+ * preserves the default.
884
+ *
885
+ * Use case: hosted closed-runtime deployments throw domain errors from
886
+ * tool handlers (e.g. `SessionAccessError` "this session doesn't belong
887
+ * to you") that should map to HTTP 404 so callers can distinguish
888
+ * tenancy violations from real server bugs. OSS deployments don't
889
+ * need this seam — every domain error is a 500 unless they say
890
+ * otherwise.
891
+ *
892
+ * The mapper MUST NOT throw. It runs inside the factory's outer
893
+ * `catch (err)` block; any throw from the mapper itself is treated
894
+ * as if it returned `undefined` (default 500).
895
+ */
896
+ readonly errorMapper?: (err: unknown) => {
897
+ readonly status: number;
898
+ readonly code: number;
899
+ readonly message: string;
900
+ } | undefined;
901
+ /**
902
+ * BYOK provider-key store consumed by the operator-facing
903
+ * `/ggui/console/llm-keys` admin API and (today, transparently
904
+ * via `ggui-cli`'s `ByokResolver`) by the generation pipeline.
905
+ *
906
+ * When set, the gated route block mounts:
907
+ * - `GET /ggui/console/llm-keys` — list providers + presence
908
+ * - `POST /ggui/console/llm-keys` — set a provider's key
909
+ * - `DELETE /ggui/console/llm-keys/:provider` — clear (idempotent)
910
+ *
911
+ * The store is the SAME instance backing the CLI's `ByokResolver`
912
+ * second-step lookup (`~/.ggui/credentials.json` by default) — writing
913
+ * via this API has immediate effect on subsequent generations because
914
+ * `PlaintextFileProviderKeyStore` re-reads the file on every `get()`.
915
+ *
916
+ * Omitted (default): the route block is NOT mounted. Operators
917
+ * who want the /settings UI to work pass a store explicitly. The
918
+ * CLI binding does so for personal-mode `ggui serve`; programmatic
919
+ * embedders (test suites, custom hosts) can omit it.
920
+ */
921
+ readonly providerKeys?: ProviderKeyStore;
922
+ /**
923
+ * Map an authenticated request to the BYOK scope key the
924
+ * `/ggui/console/llm-keys` endpoints write under (and that the
925
+ * generation pipeline reads via `ProviderKeyStore.get(scope, provider)`).
926
+ *
927
+ * Default depends on {@link providerKeysGate}:
928
+ * - `'admin-token'` (default): scope is always `'global'` — the
929
+ * OSS-personal posture: every caller who clears the admin gate
930
+ * operates on the single global keyset stored in
931
+ * `~/.ggui/credentials.json`.
932
+ * - `'auth-adapter'`: scope is derived from the authenticated
933
+ * identity — `userId` for `kind: 'user'`, `appId` for `kind: 'app'`,
934
+ * and `'global'` for `kind: 'builder'` (which under multi-tenant
935
+ * is rejected at the gate before this fires anyway).
936
+ *
937
+ * Operators with composite scopes (`${appId}:${userId}` for per-app-
938
+ * per-user keysets) override this; the underlying store treats the
939
+ * value as opaque. The `identity` arg is `null` under the
940
+ * `'admin-token'` gate (no auth-adapter call happens) and the
941
+ * resolved `AuthResult` under the `'auth-adapter'` gate.
942
+ */
943
+ readonly providerKeyScope?: (req: Request, identity: AuthResult | null) => string;
944
+ /**
945
+ * Which gate guards the `/ggui/console/llm-keys` plane.
946
+ *
947
+ * - `'admin-token'` (default — OSS-personal posture): the gate
948
+ * accepts the admin bearer (Authorization header or
949
+ * `ggui_console_admin` cookie). Single global keyset; the
950
+ * /settings UI lets the operator paste keys that everyone uses.
951
+ * - `'auth-adapter'` (multi-tenant posture): the gate calls the
952
+ * server's configured `AuthAdapter` (same path as `/mcp`). Each
953
+ * authenticated end-user manages their OWN keys, scoped by
954
+ * {@link providerKeyScope} (default: `userId` / `appId`).
955
+ * `kind: 'builder'` identities are rejected at the gate — the
956
+ * posture is meaningless without a real per-caller identifier.
957
+ *
958
+ * Both gates require `providerKeys` to be set; route block is
959
+ * unmounted otherwise. Under `'admin-token'`, the route block is
960
+ * additionally unmounted when the server has no admin token wired
961
+ * (e.g. embedding hosts that don't surface console routes). Under
962
+ * `'auth-adapter'`, the admin token is irrelevant — the route is
963
+ * mounted whenever `providerKeys` is set.
964
+ */
965
+ readonly providerKeysGate?: 'admin-token' | 'auth-adapter';
966
+ /** Express body size limit. Defaults to `'4mb'`. */
967
+ readonly bodyLimit?: string;
968
+ /**
969
+ * Session store — backing plane for the live-channel session endpoint
970
+ * (and future OSS session-reading MCP tools). Defaults to
971
+ * `InMemorySessionStore`, which is fine for OSS zero-config / dev.
972
+ * SQLite / Postgres / Redis adapters bind via the same interface
973
+ * when they land.
974
+ */
975
+ readonly sessionStore?: SessionStore;
976
+ /**
977
+ * Outbound stream replay buffer for the live-channel endpoint. Defaults
978
+ * to a fresh `InMemorySessionStreamBuffer` — fine for OSS zero-config
979
+ * / dev. Operators who need durability layer a different
980
+ * `SessionStreamBuffer` implementation behind this seam.
981
+ *
982
+ * Only used when `sessionChannel` is enabled. Ignored otherwise.
983
+ */
984
+ readonly streamBuffer?: SessionStreamBuffer;
985
+ /**
986
+ * Enable the OSS live-channel session endpoint at `/ws` (configurable).
987
+ *
988
+ * - `false` (default): no session channel. `/mcp` is the only
989
+ * HTTP surface. Callers who only need the tool plane get the
990
+ * smallest shape.
991
+ * - `true`: mount the channel at the default path (`/ws`) with
992
+ * the default session store.
993
+ * - `{ path?: string }`: override the mount path.
994
+ *
995
+ * The live channel is where the live-contract enforcement point
996
+ * lives. Enabling this makes the OSS server a second real consumer
997
+ * of the shared `@ggui-ai/mcp-server-handlers/session-mutations`
998
+ * helpers.
999
+ */
1000
+ readonly sessionChannel?: boolean | {
1001
+ readonly path?: string;
1002
+ };
1003
+ /**
1004
+ * Opt-in WS-direct action dispatcher for agent-less deployments.
1005
+ * When present AND `sessionChannel: true`, the channel server
1006
+ * fires the tool named by an incoming action's `payload.tool` hint
1007
+ * (falling back to `actionSpec[name].nextStep` when the client
1008
+ * omitted the hint) in-process after inbound validation, and emits
1009
+ * every declared `streamSpec[name].source.tool` refresh on the
1010
+ * session. See {@link WiredActionRouter} + `session-channel.ts` for
1011
+ * the full router contract.
1012
+ *
1013
+ * Absent = agent-mediated behavior (canonical for MCP Apps hosts and
1014
+ * Claude Agent SDK consumers). Inbound actions land on the
1015
+ * stackItemId-keyed pending-events pipe via `ggui_runtime_submit_action`
1016
+ * and the agent's `ggui_consume` long-poll drains them. CLI
1017
+ * composition in `ggui serve` wires a router by default over the
1018
+ * same handler bundle `/mcp` uses, since that command runs WITHOUT
1019
+ * an agent (raw WS clients hitting the OSS server directly). Library
1020
+ * consumers MAY pass their own router; library consumers running
1021
+ * behind an agent typically pass `undefined` so actions flow through
1022
+ * the agent's reasoning loop.
1023
+ */
1024
+ readonly wiredActionRouter?: WiredActionRouter;
1025
+ /**
1026
+ * Per-call timeout for wired-tool invocations, in ms. Defaults to
1027
+ * `DEFAULT_WIRED_TOOL_TIMEOUT_MS` (30 s) when omitted. Forwarded
1028
+ * verbatim to `createSessionChannelServer`.
1029
+ */
1030
+ readonly wiredActionTimeoutMs?: number;
1031
+ /**
1032
+ * Opt-in plumbing for `channel_subscribe` polling — the WS fan-out
1033
+ * path for `streamSpec[*].source.tool`. When present, the
1034
+ * session channel accepts `channel_subscribe` frames whose
1035
+ * `source.tool` is in `allowlist` and begins polling. When absent
1036
+ * (the OSS first-run zero-config posture), every `channel_subscribe`
1037
+ * rejects with `CHANNEL_NOT_LOCAL` so the iframe falls back to
1038
+ * direct polling via the MCP host proxy.
1039
+ *
1040
+ * The `allowlist` is also advertised on every successful
1041
+ * `ggui_handshake` response as
1042
+ * `serverCapabilities.streamWebSocketLocalTools` so `@ggui-ai/wire`
1043
+ * agrees with the server on which channels use WS fan-out.
1044
+ *
1045
+ * Only consulted when `sessionChannel` is enabled. Forwarded
1046
+ * verbatim to `createSessionChannelServer` (see
1047
+ * `SessionChannelOptions.streamWebSocketLocalTools`).
1048
+ */
1049
+ readonly streamWebSocketLocalTools?: import('./session-channel.js').SessionChannelLocalToolsOptions;
1050
+ /**
1051
+ * Override the sanitizer applied to the stringified original error
1052
+ * written into `ContractErrorPayload.error.causedBy`. Defaults to
1053
+ * `@ggui-ai/protocol::sanitizeCausedBy` when omitted — redacts
1054
+ * Bearer tokens, query-param secrets, common env-var dumps, and
1055
+ * truncates at 2 KB. Forwarded verbatim to
1056
+ * `createSessionChannelServer`. See `SessionChannelOptions
1057
+ * .sanitizeCausedBy` for the contract.
1058
+ */
1059
+ readonly sanitizeCausedBy?: import('@ggui-ai/protocol').SanitizeCausedBy;
1060
+ /**
1061
+ * Extra reserved-channel payload validators merged with the
1062
+ * server's default A2UI preview validator before being passed to
1063
+ * the session channel (Item 4 injection pattern). Caller-provided
1064
+ * entries WIN on key conflict — the pattern is "server supplies
1065
+ * defaults, operator may replace by key".
1066
+ *
1067
+ * Absent = the server binds only the A2UI validator for
1068
+ * `_ggui:preview` by default. `_ggui:contract-error` is validated
1069
+ * via the protocol-shipped builtin regardless of this option.
1070
+ *
1071
+ * Pass `new Map()` (explicitly empty) to DISABLE the A2UI default —
1072
+ * useful in tests that want to assert `validateStreamData`'s
1073
+ * fall-through behavior on `_ggui:preview` without the adapter
1074
+ * running.
1075
+ */
1076
+ readonly extraReservedValidators?: ReadonlyMap<string, import('@ggui-ai/protocol').ReservedChannelValidator>;
1077
+ /**
1078
+ * Protocol-version handshake policy for the session channel. Forwarded
1079
+ * verbatim to `createSessionChannelServer` (see
1080
+ * `SessionChannelOptions.versionPolicy`). Defaults to `'reject'` —
1081
+ * mismatched `SubscribePayload.supportedVersions` emits
1082
+ * UPGRADE_REQUIRED and closes the connection. Legacy opt-out
1083
+ * `'advisory'` keeps the connection open after the error frame for
1084
+ * controlled migration windows.
1085
+ *
1086
+ * Only consulted when `sessionChannel` is enabled.
1087
+ */
1088
+ readonly versionPolicy?: 'advisory' | 'reject';
1089
+ /**
1090
+ * Policy for the schema compat check. Checks that every
1091
+ * `actionSpec[name]` tool ref points at a tool whose
1092
+ * `inputSchema` is a superset of the
1093
+ * action's declared `schema`, and that every
1094
+ * `streamSpec[channel].tool` ref points at a tool whose return
1095
+ * schema fits inside the channel's declared `schema`.
1096
+ *
1097
+ * - `'reject'` (default) — violations throw before the stack
1098
+ * item commits (or before blueprint registration completes).
1099
+ * Canonical enforcement posture for launch.
1100
+ * - `'warn'` — violations log through the server's structured
1101
+ * logger (`schema_compat_warn` event with the full report
1102
+ * attached). Caller's flow continues. Used for controlled
1103
+ * migration windows.
1104
+ * - `'off'` — check is skipped entirely. Test / opt-out
1105
+ * convenience.
1106
+ *
1107
+ * Applies to both check points wired by this server:
1108
+ *
1109
+ * 1. The console `POST /ggui/console/blueprint/:id/try`
1110
+ * endpoint (blueprint registration — fires when a manifest
1111
+ * blueprint's pre-declared `actionSpec` / `streamSpec`
1112
+ * references a tool mounted on this server).
1113
+ * 2. The `ggui_push` generation path (push-time — defensive
1114
+ * wiring for when the generator starts emitting
1115
+ * actionSpec / streamSpec on its `UIGenerationResponse`;
1116
+ * current generators emit only componentCode, but the
1117
+ * hook is in place so the check fires automatically when
1118
+ * generator outputs widen).
1119
+ *
1120
+ * See `./schema-compat.ts` for the check helper contract, and
1121
+ * `@ggui-ai/protocol/validation/{schema-subset,zod-to-json-schema}`
1122
+ * for the underlying primitives.
1123
+ */
1124
+ readonly schemaCompatCheck?: SchemaCompatMode;
1125
+ /**
1126
+ * Enable OAuth 2.1 + PKCE + Dynamic Client Registration on this
1127
+ * server (per MCP spec 2025-06-18+).
1128
+ *
1129
+ * - `false` / omitted (default): OAuth routes NOT mounted;
1130
+ * `WWW-Authenticate` header NOT set on 401 responses. Pure-bearer
1131
+ * clients (CLI tools shipping `Authorization: Bearer ggui_user_*`)
1132
+ * still work; OAuth-discovery clients (Claude Desktop, claude.ai,
1133
+ * Goose, etc.) bail with "couldn't reach" / "couldn't authenticate".
1134
+ * - `true`: enable with defaults. Mounts:
1135
+ * GET /.well-known/oauth-protected-resource (RFC 9728)
1136
+ * GET /.well-known/oauth-authorization-server (RFC 8414)
1137
+ * POST /oauth/register (RFC 7591 DCR)
1138
+ * GET /oauth/authorize (paste-key form)
1139
+ * POST /oauth/authorize (form submit)
1140
+ * POST /oauth/token (code → access_token)
1141
+ * Adds `WWW-Authenticate: Bearer realm=mcp, resource_metadata=…`
1142
+ * header to 401 responses on `/mcp`. Storage defaults to
1143
+ * {@link InMemoryOAuthStorage}.
1144
+ * - `{ issuerUrl?, storage? }`: explicit config. `issuerUrl` is the
1145
+ * public origin the server advertises in metadata + redirects
1146
+ * (defaults to derivation from `X-Forwarded-Proto`/`Host`);
1147
+ * `storage` swaps the in-memory map for a Redis/DDB-backed
1148
+ * implementation when multi-replica deployments need stateless
1149
+ * token exchange.
1150
+ *
1151
+ * The OAuth flow is a one-time ceremony per Claude Desktop install:
1152
+ * the user pastes their `ggui_user_*` API key once, the server hands
1153
+ * it back as the `access_token`. Subsequent `/mcp` calls hit the
1154
+ * existing {@link AuthAdapter} (e.g. `ApiKeyAuthAdapter`) unchanged.
1155
+ * See `./oauth.ts` for the full flow.
1156
+ */
1157
+ readonly oauth?: boolean | OAuthConfig;
1158
+ /**
1159
+ * Enable the MCP Apps outbound delivery path on this server.
1160
+ *
1161
+ * - `false` / omitted (default): `ggui_push` is NOT registered;
1162
+ * `ui://ggui/session` is NOT served; `io.modelcontextprotocol/ui`
1163
+ * is NOT advertised. Server looks identical to the pre-MCP-Apps
1164
+ * surface.
1165
+ * - `true`: enable with sensible defaults. Requires
1166
+ * `sessionChannel: true` so the iframe has a WebSocket to open;
1167
+ * throws at construction otherwise. `renderBaseUrl` defaults to
1168
+ * `"http://localhost/r/"` (dev-sensible; production operators
1169
+ * override).
1170
+ * - `{ renderBaseUrl?, shellHtml?, wsUrl? }`: explicit config.
1171
+ *
1172
+ * When enabled, FOUR things happen on every fresh per-request
1173
+ * `McpServer`:
1174
+ * 1. `ggui_push` tool is registered, carrying `_meta.ui.resourceUri:
1175
+ * "ui://ggui/session"` and `_meta.ui.visibility: ["model"]` on
1176
+ * its declaration.
1177
+ * 2. `ui://ggui/session` is served via `resources/read`.
1178
+ * 3. `io.modelcontextprotocol/ui` is advertised in the server's
1179
+ * `initialize` capabilities (under `experimental`).
1180
+ * 4. Each `ggui_push` result carries `_meta.ggui.bootstrap` (wsUrl
1181
+ * + short-TTL token + expiresAt). The session-channel server
1182
+ * accepts that token on `subscribe` and issues a longer-TTL
1183
+ * `sessionToken` in the ack for iframe reconnects.
1184
+ */
1185
+ readonly mcpApps?: boolean | {
1186
+ readonly renderBaseUrl?: string;
1187
+ readonly shellHtml?: string;
1188
+ /**
1189
+ * External WebSocket URL the iframe should open, visible to
1190
+ * MCP Apps hosts. Defaults to `"ws://localhost:<port>/ws"`
1191
+ * — only sensible for local dev. Production operators pass
1192
+ * their public URL (`wss://mcp.example.com/ws`).
1193
+ */
1194
+ readonly wsUrl?: string;
1195
+ };
1196
+ /**
1197
+ * Iframe-runtime bundle mount (C8 — plan §C8).
1198
+ *
1199
+ * The thin-shell HTML served from `ui://ggui/session` dynamic-
1200
+ * script-loads the renderer bundle from this URL. The server needs
1201
+ * to either (a) serve the bundle itself (default), or (b) publish
1202
+ * the operator-owned URL on `_meta.ggui.bootstrap.runtimeUrl`
1203
+ * so the shell knows where to look.
1204
+ *
1205
+ * - `true` / omitted (default when `mcpApps` is on): serve the
1206
+ * bundle via `express.static` at `/_ggui/iframe-runtime.js` from the
1207
+ * `@ggui-ai/iframe-runtime` package's built `dist/iframe-runtime.js`.
1208
+ * `runtimeUrl` on the bootstrap becomes `/_ggui/iframe-runtime.js`.
1209
+ * - `false`: no static mount. Operator MUST supply
1210
+ * `runtime.url` so the bootstrap still carries a valid URL
1211
+ * (or the shell will fail `MALFORMED_BOOTSTRAP`).
1212
+ * - `{ path?, distDir?, url? }`: explicit config. `path` is the
1213
+ * HTTP route under which the bundle is served (default
1214
+ * `/_ggui/iframe-runtime.js`); `distDir` overrides the
1215
+ * {@link @ggui-ai/iframe-runtime/server!RUNTIME_BUNDLE_FILE} auto-
1216
+ * resolution for advanced embeddings; `url` overrides the URL
1217
+ * written onto the bootstrap (useful when a CDN / proxy fronts
1218
+ * the bundle — mount here for local verification, publish the
1219
+ * external URL to clients).
1220
+ *
1221
+ * Ignored entirely when `mcpApps` is disabled.
1222
+ */
1223
+ readonly runtime?: boolean | {
1224
+ readonly path?: string;
1225
+ readonly distDir?: string;
1226
+ readonly url?: string;
1227
+ };
1228
+ /**
1229
+ * Connector registry for external MCP servers. Required to accept
1230
+ * inbound MCP Apps push payloads (`shortcuts.mcpApps`) and for the
1231
+ * `/mcp-apps/resource` proxy route to resolve source-server
1232
+ * endpoints. Absent = inbound MCP Apps hosting disabled.
1233
+ */
1234
+ readonly connectors?: ConnectorRegistry;
1235
+ /**
1236
+ * HMAC secret used to sign bootstrap + session tokens. When the MCP
1237
+ * Apps outbound path is enabled and no secret is passed, the server
1238
+ * mints a random 32-byte secret at boot — fine for dev + a single
1239
+ * long-running process, wrong for multi-host deployments (each host
1240
+ * would reject the others' tokens). Production operators MUST pass
1241
+ * a deterministic secret (typically from env / secrets manager).
1242
+ *
1243
+ * Ignored entirely when MCP Apps is disabled.
1244
+ */
1245
+ readonly bootstrapSecret?: string;
1246
+ /**
1247
+ * Enable the pairing transport. Adds `POST /pair` (public, completes a
1248
+ * pairing handshake) and by default `POST /admin/pair/init` (builder-
1249
+ * authenticated, mints a one-shot code).
1250
+ *
1251
+ * - `false` / omitted (default): pairing routes are NOT mounted.
1252
+ * `GguiServer.pairingService` is `null`. The server is still a
1253
+ * valid MCP server; pairing is simply not part of its surface.
1254
+ * - `true`: enable with defaults. Constructs an
1255
+ * `InMemoryPairingService` and bridges its `onTokenIssued` /
1256
+ * `onTokenRevoked` callbacks into the configured {@link auth}
1257
+ * adapter. Requires the adapter to implement
1258
+ * `registerToken`/`unregisterToken` (see
1259
+ * `isTokenRegisteringAuthAdapter`); throws at construction
1260
+ * otherwise.
1261
+ * - `{ service?, serverName?, path?, adminInitPath? }`: explicit
1262
+ * config. When `service` is omitted, the default
1263
+ * `InMemoryPairingService` + auth-adapter bridge is constructed
1264
+ * as above. When `service` is provided, the caller owns the
1265
+ * service's lifecycle AND the auth bridge — `createGguiServer`
1266
+ * mounts the HTTP routes only.
1267
+ *
1268
+ * Pairing-minted tokens authenticate subsequent `/mcp` and live-channel
1269
+ * requests through the normal bearer path — the bridge registers them
1270
+ * into the active AuthAdapter, NOT a parallel pairing-only store.
1271
+ */
1272
+ /**
1273
+ * Enable the persistent-chat HTTP transport. Mounts six routes under
1274
+ * the configured path (defaults to `/threads`):
1275
+ *
1276
+ * POST /threads — createThread
1277
+ * GET /threads — listThreads
1278
+ * GET /threads/:id — getThread
1279
+ * PATCH /threads/:id — applyThreadAction
1280
+ * GET /threads/:id/messages — listMessages
1281
+ * POST /threads/:id/messages — appendMessage
1282
+ *
1283
+ * - Omitted / undefined (default): no thread routes. The server is
1284
+ * still a valid MCP server + optional live-channel host; persistent
1285
+ * chat simply isn't part of its surface.
1286
+ * - `{ store: ThreadStore }`: enable with the supplied store. OSS
1287
+ * dev callers pass `new InMemoryThreadStore()`. SQLite binding
1288
+ * (Step 6 of the slice) plugs in the same way.
1289
+ * - Extra fields (`path`, `ownerFromIdentity`) are power-user
1290
+ * overrides — sensible defaults otherwise.
1291
+ *
1292
+ * SSE observe endpoint (`GET /threads/:id/stream`) is Step 5 of the
1293
+ * same slice; it lands behind this option but in a separate route.
1294
+ */
1295
+ readonly threads?: {
1296
+ readonly store: ThreadStore;
1297
+ /**
1298
+ * URL prefix. Defaults to `/threads`. Operators who already have
1299
+ * another server mounted under `/threads` override here.
1300
+ */
1301
+ readonly path?: string;
1302
+ /**
1303
+ * Identity → ownerId mapping override. Defaults to
1304
+ * `defaultThreadOwnerFromIdentity` from `thread-transport.ts`:
1305
+ * pairing metadata → `paired_<pairingId>`; cognito → `cognito_<sub>`;
1306
+ * kind=user → `user_<workspaceId ?? userId>`; everything else →
1307
+ * `DEFAULT_BUILDER_OWNER_ID` ("builder").
1308
+ */
1309
+ readonly ownerFromIdentity?: ThreadOwnerResolver;
1310
+ /**
1311
+ * Durability advertisement for the thread store. Surfaced on
1312
+ * `GET /ggui/health` under `threads.durability` so Portal + other
1313
+ * clients can decide whether to display a non-durable caveat.
1314
+ *
1315
+ * - `'durable'`: data survives server restart. `ggui serve`
1316
+ * resolves this automatically when `storage.threads.driver ===
1317
+ * 'sqlite'` is declared in `ggui.json`.
1318
+ * - `'ephemeral'` (default): in-memory or otherwise lost on
1319
+ * restart. Safe default — overclaiming durability would mislead
1320
+ * Portal into hiding its caveat.
1321
+ *
1322
+ * Embedded hosts that supply a custom `store` also supply the
1323
+ * right durability claim; the server doesn't inspect the store
1324
+ * instance to guess.
1325
+ */
1326
+ readonly durability?: 'durable' | 'ephemeral';
1327
+ };
1328
+ readonly pairing?: boolean | {
1329
+ /**
1330
+ * Custom PairingService implementation. When present, the
1331
+ * caller owns the service's full lifecycle including any
1332
+ * `onTokenIssued` / `onTokenRevoked` bridging into their auth
1333
+ * adapter. When absent, a default `InMemoryPairingService`
1334
+ * is constructed and bridged automatically.
1335
+ */
1336
+ readonly service?: PairingService;
1337
+ /**
1338
+ * Server display name the default `InMemoryPairingService`
1339
+ * surfaces in `PairingInit.serverName` and `PairingCompletion
1340
+ * .serverName`. Defaults to `info.name`. Ignored when
1341
+ * {@link service} is provided.
1342
+ */
1343
+ readonly serverName?: string;
1344
+ /**
1345
+ * When set, the default `InMemoryPairingService` persists
1346
+ * its pairings + idCounter to this JSON file (atomic write,
1347
+ * `0600` perms) and restores them on subsequent boots —
1348
+ * tokens survive a `ggui serve` restart. Tokens are stored in
1349
+ * **plaintext**: assume the file lives on operator-controlled
1350
+ * disk (e.g. `~/.ggui/keys.json`). For multi-operator or
1351
+ * untrusted-host deployments swap to a hashed adapter.
1352
+ * Ignored when {@link service} is provided.
1353
+ */
1354
+ readonly persistencePath?: string;
1355
+ /**
1356
+ * URL path the `POST /pair` route is mounted at. Defaults to
1357
+ * `/pair`.
1358
+ */
1359
+ readonly path?: string;
1360
+ /**
1361
+ * URL path the `POST /admin/pair/init` route is mounted at.
1362
+ * Defaults to `/admin/pair/init`. Pass `null` to disable the
1363
+ * HTTP-triggered mint path — embedded hosts that call
1364
+ * `GguiServer.pairingService.initPairing()` programmatically
1365
+ * may not want the route.
1366
+ */
1367
+ readonly adminInitPath?: string | null;
1368
+ /**
1369
+ * URL-template the `POST /admin/pair/:pairingId/revoke` route
1370
+ * is mounted at. Defaults to `/admin/pair/:pairingId/revoke`.
1371
+ * Pass `null` to disable the HTTP-triggered revoke path —
1372
+ * embedded hosts that call `GguiServer.pairingService
1373
+ * .revokePairing()` programmatically may not want the route.
1374
+ */
1375
+ readonly adminRevokePath?: string | null;
1376
+ };
1377
+ /**
1378
+ * Operational / product-signal sink. Bound once at composition;
1379
+ * transports + handlers call `emit` for lossy counts / durations.
1380
+ * Defaults to {@link NoopTelemetrySink} — an OSS deployment that
1381
+ * doesn't care about metrics sees zero-cost no-op. Real adapters
1382
+ * (OTLP, CloudWatch, Datadog) plug in here.
1383
+ *
1384
+ * Sync, fire-and-forget, MUST NOT throw — see `TelemetrySink`.
1385
+ */
1386
+ readonly telemetry?: TelemetrySink;
1387
+ /**
1388
+ * Durable audit-log sink for privileged actions (pairing-token
1389
+ * lifecycle today; API-key lifecycle + admin mutations follow in
1390
+ * later slices). Bound once at composition; ingress points await
1391
+ * `record` and surface failure.
1392
+ *
1393
+ * Defaults to {@link NoopAuditSink} with a boot-time `warn` log —
1394
+ * same pattern as the missing-auth-adapter warning. Production
1395
+ * deployments MUST bind a durable implementation (DynamoDB /
1396
+ * Postgres journal / Kafka topic) because privileged actions
1397
+ * leaving no record is a compliance breach.
1398
+ */
1399
+ readonly audit?: AuditSink;
1400
+ /**
1401
+ * Admission-control limiter applied at the highest-cost handler
1402
+ * ingress — today just `ggui_push`. Defaults to
1403
+ * {@link NoopRateLimiter} (always allows). Per-handler wiring maps
1404
+ * denials from a `RateLimitedError` to HTTP 429 + `Retry-After` /
1405
+ * `X-RateLimit-*` headers at the transport boundary.
1406
+ *
1407
+ * For real policy (per-app or per-identity windows), bind a
1408
+ * `FixedWindowRateLimiter` over a durable
1409
+ * {@link import('@ggui-ai/mcp-server-core').QuotaStore} — or any
1410
+ * adapter that implements the same contract. Handlers never see the
1411
+ * policy shape; they just call `check` and honor the decision.
1412
+ *
1413
+ * Wiring only the highest-cost handler is intentional. Other
1414
+ * handlers (blueprint search, thread reads, pairing) follow as
1415
+ * individual slices when real policy signal demands it.
1416
+ */
1417
+ readonly rateLimiter?: RateLimiter;
1418
+ /**
1419
+ * CSRF secret used to HMAC-sign double-submit tokens for browser
1420
+ * POST/PUT/DELETE/PATCH endpoints. Production deployments pass a
1421
+ * stable value (so tokens survive a deploy); OSS dev defaults to a
1422
+ * fresh per-process random — pre-restart tokens won't validate
1423
+ * after restart, which is acceptable for dev where sessions don't
1424
+ * survive a restart anyway.
1425
+ */
1426
+ readonly csrfSecret?: string;
1427
+ /**
1428
+ * Trust the `X-Forwarded-For` header for per-IP rate limiting on
1429
+ * `/pair`. Operators behind a reverse proxy / load
1430
+ * balancer that strips and re-attaches a trusted client-IP header
1431
+ * pass `true`; localhost-only dev paths leave it falsy so requests
1432
+ * key on `req.socket.remoteAddress` instead.
1433
+ */
1434
+ readonly trustProxy?: boolean;
1435
+ /**
1436
+ * Public base URL the server is reachable at — REQUIRED for OAuth
1437
+ * login routes. Composes the `redirect_uri` registered
1438
+ * with each OAuth provider's console as
1439
+ * `${publicBaseUrl}/ggui/oauth-login/<providerId>/callback`. If
1440
+ * absent, OAuth login routes are NOT mounted (admin transport
1441
+ * still mounts so operators can paste credentials in advance).
1442
+ */
1443
+ readonly publicBaseUrl?: string;
1444
+ /**
1445
+ * Render-URL signing config — capability-URL hardening.
1446
+ *
1447
+ * When set (default), every minted `/r/<code>` URL carries
1448
+ * `?sig=<hmac>&exp=<unix>`. The gate on `/r/` + `/api/bootstrap/`
1449
+ * verifies the sig + freshness before lookup; tampered or expired
1450
+ * URLs return 410.
1451
+ *
1452
+ * - `secret`: 32-byte hex key. When omitted, a fresh random key
1453
+ * is minted at boot — restart = every outstanding URL dies
1454
+ * (good for incident response, bad if you don't want it).
1455
+ * Pin a stable secret to survive restarts.
1456
+ * - `ttlSeconds`: URL lifetime. Defaults to 24h (86400s).
1457
+ * - Set `renderSigning: false` to disable the layer entirely
1458
+ * (`--no-render-signing`). URLs revert to the plain unsigned
1459
+ * form. Use for legacy hosts that strip query strings or
1460
+ * tooling that pre-records URLs.
1461
+ */
1462
+ readonly renderSigning?: false | {
1463
+ readonly secret?: string;
1464
+ readonly ttlSeconds?: number;
1465
+ };
1466
+ /**
1467
+ * Per-shortCode rate-limit config. Defends `/r/` and
1468
+ * `/api/bootstrap/` against brute-force scans.
1469
+ *
1470
+ * - `windowSeconds` + `limit`: bucket size. Default 60s / 30 hits.
1471
+ * - `false`: disable rate limiting entirely (CI/test convenience).
1472
+ *
1473
+ * Rate-limit decision is per-shortCode (not per-peer) — cross-origin
1474
+ * iframe hosts NAT every user through one proxy; per-peer limits
1475
+ * would either rate-limit the whole host or accept every peer.
1476
+ * See render-rate-limit.ts for the rationale.
1477
+ */
1478
+ readonly renderRateLimit?: false | {
1479
+ readonly windowSeconds?: number;
1480
+ readonly limit?: number;
1481
+ };
1482
+ /**
1483
+ * Override path for `~/.ggui/oauth-providers.json`. Mainly for
1484
+ * tests + per-deployment isolation. Defaults to the home-relative
1485
+ * path resolved by `createOAuthProvidersStore`.
1486
+ */
1487
+ readonly oauthProvidersPath?: string;
1488
+ /**
1489
+ * Server-level instructions surfaced on the MCP `InitializeResult.
1490
+ * instructions` field. MCP hosts (Claude.ai web, Claude Desktop,
1491
+ * the MCP Inspector) inject this into the LLM's system prompt as a
1492
+ * top-level block, ABOVE per-tool descriptions — influencing
1493
+ * "how should I behave with this server's tools generally?"
1494
+ *
1495
+ * - Omit (`undefined`): use the package default (`'default'`
1496
+ * preset — sensible "ggui first when UI fits" nudge).
1497
+ * - Preset name: `'default' | 'aggressive' | 'minimal' | 'off'`.
1498
+ * `'aggressive'` matches a manual "always use ggui_*" custom
1499
+ * instruction. `'off'` omits the field entirely.
1500
+ * - Arbitrary string: used verbatim. Lets operators write
1501
+ * deployment-specific copy without forking the package.
1502
+ *
1503
+ * Since OSS forks can edit `instructions-presets.ts` directly, the
1504
+ * preset enum is a convenience dial, not a contract — devs are
1505
+ * welcome to ship custom strings or tweak the presets to match
1506
+ * their fleet's voice.
1507
+ */
1508
+ readonly mcpInstructions?: McpInstructionsValue;
1509
+ /**
1510
+ * Email magic-link login config. When set, mounts
1511
+ * `POST /ggui/email-login/start`, `GET /ggui/email-login/verify`,
1512
+ * and `GET /ggui/email-login/config` so the `/login` UI can offer
1513
+ * a passwordless email path. Requires `publicBaseUrl` so the magic
1514
+ * link the user clicks resolves back to this server.
1515
+ *
1516
+ * - `false` / omitted: no email login routes mounted. `/login`
1517
+ * fetches `/ggui/email-login/config` → 404 → hides the form.
1518
+ * - `{ sender, fromAddress, ... }`: opt-in. The `sender` is the
1519
+ * transport (use `ConsoleEmailSender` for dev; SMTP / Resend /
1520
+ * SES adapters for production). `fromAddress` is stamped on
1521
+ * every outgoing message.
1522
+ *
1523
+ * Authentication: callbacks mint
1524
+ * `{ kind: 'user', userId: 'email:<lowercased-email>', roles: [] }`
1525
+ * via `auth.registerToken`. The configured `auth` adapter MUST
1526
+ * support `registerToken` — pairing-incompatible adapters
1527
+ * (Cognito/OIDC) can't accept email login.
1528
+ */
1529
+ readonly emailLogin?: {
1530
+ readonly sender: EmailSender;
1531
+ readonly fromAddress: string;
1532
+ readonly store?: MagicLinkStore;
1533
+ readonly subject?: string;
1534
+ readonly bodyText?: string;
1535
+ readonly bodyHtml?: string;
1536
+ };
1537
+ /**
1538
+ * Enable the `@ggui-ai/console` operator landing page. When
1539
+ * enabled, the server:
1540
+ *
1541
+ * - Mounts `GET /ggui/console/info` — returns
1542
+ * `{ server, version, description?, pairing: { enabled, pending } }`
1543
+ * as JSON. Consumed by the landing-page SPA on first load.
1544
+ * - Mounts `express.static` at the configured `path` (default `/`)
1545
+ * pointing at the console's built `dist/`.
1546
+ *
1547
+ * Boundary lock:
1548
+ *
1549
+ * - Same-origin ONLY — not a Portal replacement, not an MCP Apps
1550
+ * iframe shell.
1551
+ * - No same-origin cookie, no session viewer, no WebSocket
1552
+ * wiring yet.
1553
+ *
1554
+ * Options:
1555
+ *
1556
+ * - `false` / omitted (default): no console mount. The server
1557
+ * is identical to the pre-console surface.
1558
+ * - `true`: mount at `/` with the package's built-in `dist/`.
1559
+ * - `{ path?, distDir? }`: override the URL path (`path`) and/or
1560
+ * the filesystem dir that Express serves (`distDir`). `distDir`
1561
+ * is primarily a test-fixture seam — production should leave it
1562
+ * unset so the package-shipped bundle is served.
1563
+ *
1564
+ * If the resolved `distDir` does not exist on disk when the route is
1565
+ * hit, the server responds with 503 + a clear hint pointing operators
1566
+ * at `pnpm --filter @ggui-ai/console build`. Silent 404 would be
1567
+ * a worse failure mode — operators would think console was
1568
+ * broken rather than unbuilt.
1569
+ */
1570
+ readonly console?: boolean | {
1571
+ /** URL path to mount at. Defaults to `/`. */
1572
+ readonly path?: string;
1573
+ /**
1574
+ * Override the filesystem dir Express serves. Defaults to the
1575
+ * package-shipped `dist/` (`CONSOLE_DIST_DIR`). Primarily a
1576
+ * test-fixture seam.
1577
+ */
1578
+ readonly distDir?: string;
1579
+ /**
1580
+ * Enable the Slice-2 same-origin session-cookie flow
1581
+ * (`POST /ggui/console/session-cookie` + session-channel
1582
+ * cookie-auth wiring). Defaults to OFF — the landing-page
1583
+ * static surface is useful on its own (pair-code display,
1584
+ * server identity); turning on the cookie flow is an
1585
+ * explicit step that pulls in additional deps.
1586
+ *
1587
+ * Enabling REQUIRES `sessionChannel: true` — the cookie only
1588
+ * authenticates the live-channel WebSocket upgrade, so a cookie
1589
+ * flow without a channel to use it on would be pointless +
1590
+ * confusing. Throws at construction if that invariant fails.
1591
+ *
1592
+ * Enabling REQUIRES a configured {@link shortCodeIndex} — the
1593
+ * cookie endpoint resolves shortCode → sessionId by reading
1594
+ * it. Throws at construction if the index is absent.
1595
+ *
1596
+ * The cookie signing secret is the same {@link bootstrapSecret}
1597
+ * used by the MCP Apps bootstrap/session tokens — different
1598
+ * token `kind` claims make cross-kind confusion impossible
1599
+ * (see `console-auth.ts` isolation comment).
1600
+ */
1601
+ readonly sessionCookie?: boolean | {
1602
+ /**
1603
+ * Cookie TTL in seconds. Defaults to 8 hours
1604
+ * (`DEFAULT_DEVTOOL_SESSION_TTL_SEC`).
1605
+ */
1606
+ readonly ttlSec?: number;
1607
+ /**
1608
+ * Add `Secure` to the Set-Cookie attributes. Explicit
1609
+ * because auto-detecting TLS through a reverse proxy
1610
+ * is unreliable — operators passing `true` when their
1611
+ * public URL is HTTPS is the safe contract.
1612
+ */
1613
+ readonly secure?: boolean;
1614
+ };
1615
+ /**
1616
+ * Admin bearer that gates the operator-only console routes
1617
+ * (`/ggui/console/keys*`, `/ggui/console/admin-login`). When
1618
+ * absent, `createGguiServer` mints `ggui_admin_<base64url(9)>`
1619
+ * at boot — surfaced on {@link GguiServer.adminToken} so the
1620
+ * CLI banner can print it. Operator passes `--admin-token <t>`
1621
+ * to pin a stable value across restarts.
1622
+ *
1623
+ * The gate accepts either an `Authorization: Bearer <token>`
1624
+ * header OR the `ggui_console_admin` cookie set by the
1625
+ * admin-login route. Other console routes (registry, sessions,
1626
+ * cached blueprints, …) are NOT gated by this token — that's
1627
+ * a separate audit slice. The keys plane is the immediate
1628
+ * threat: plaintext bearer rendering + mint + revoke must not
1629
+ * be reachable to anyone who finds the URL over a tunnel.
1630
+ */
1631
+ readonly adminToken?: string;
1632
+ /**
1633
+ * Onboarding-redirect probe. Called per `GET /` request; if
1634
+ * it returns a non-null path, the server responds 302 to that
1635
+ * path instead of the SPA index. Use this to send first-run
1636
+ * operators (no LLM credentials configured) to the assistant-
1637
+ * connection flow before the chat playground is meaningful.
1638
+ *
1639
+ * The probe is recomputed every request — once the operator
1640
+ * sets a key, the next visit serves the SPA normally. Scoped
1641
+ * to the root path only; deep links bypass the redirect so
1642
+ * `/preview/<id>` / `/blueprints` / etc. continue to work.
1643
+ *
1644
+ * Return `null` to fall through to the SPA. Returning the
1645
+ * same path the request is already on is a no-op (the server
1646
+ * compares before redirecting to avoid loops).
1647
+ */
1648
+ readonly landingRedirect?: () => string | null;
1649
+ };
1650
+ /**
1651
+ * Public welcome page served at the console root (`/`) when the
1652
+ * console is mounted there.
1653
+ *
1654
+ * Resolved from `ggui.json#operator` + `ggui.json#app.name` by the
1655
+ * `ggui serve` CLI; programmatic embedders pass whatever values
1656
+ * fit their context. The page identifies who runs the server
1657
+ * (operator block — hidden entirely when nothing is configured)
1658
+ * and links to the public deep-link surfaces (`/preview/<id>`,
1659
+ * `/s/<shortCode>`) plus an "Operator login →" affordance pointing
1660
+ * at `/admin-login`.
1661
+ *
1662
+ * Posture: this page is the ONLY unauthenticated SPA-mount HTML
1663
+ * surface alongside `/admin-login`. Every other client-side route
1664
+ * (`/admin/*`, `/devtools/*`) requires the admin cookie/bearer.
1665
+ *
1666
+ * Omitted = the legacy SPA index handler runs at `/` (no welcome
1667
+ * page; the SPA handles its own root-route render).
1668
+ *
1669
+ * Ignored when `console.path !== '/'` — operators mounting console
1670
+ * on a non-root prefix already opted out of the welcome page surface.
1671
+ */
1672
+ readonly welcomePage?: {
1673
+ /** Operator-block input. Hidden entirely when omitted/empty. */
1674
+ readonly operator?: OperatorConfig;
1675
+ /**
1676
+ * Display name for the running app (typically `ggui.json#app.name`).
1677
+ * Falls back to the server identity name when omitted.
1678
+ */
1679
+ readonly appName?: string;
1680
+ };
1681
+ /**
1682
+ * Index for resolving `shortCode → { sessionId, appId }`. Required
1683
+ * when `console.sessionCookie` is enabled (the cookie endpoint
1684
+ * looks up the posted shortCode to find the session to bind).
1685
+ *
1686
+ * Pair this with a `push` handler so the agent's `ggui_push` writes
1687
+ * the shortCode into the same index that console later reads.
1688
+ * See `defaultHandlers` for the wiring seam.
1689
+ */
1690
+ readonly shortCodeIndex?: ShortCodeIndex;
1691
+ /**
1692
+ * Content-addressable code blob storage. When wired, this server
1693
+ * mounts `GET /code/<hash>.js` for the iframe runtime to fetch
1694
+ * compiled componentCode by content hash. The push handler writes
1695
+ * to the store before emitting `_meta.ggui.bootstrap.codeUrl`.
1696
+ *
1697
+ * Defaults: when omitted the route is NOT mounted; the push
1698
+ * handler falls back to inline base64 `componentCode` on
1699
+ * `_meta.ggui.bootstrap` (legacy delivery channel).
1700
+ *
1701
+ * OSS dev wires `FileSystemCodeStore` (rooted at `~/.ggui/code-cache/`)
1702
+ * via `ggui-cli/buildMcpServerBackend`. Tests wire
1703
+ * `InMemoryCodeStore` from `@ggui-ai/mcp-server-core/in-memory`.
1704
+ * A hosted closed runtime wires a durable adapter (e.g. S3-backed)
1705
+ * from its own closed-source package. The wire format is identical
1706
+ * across deployments — only the storage adapter changes.
1707
+ */
1708
+ readonly codeStore?: CodeStore;
1709
+ /**
1710
+ * Provisional A2UI preview wiring for `ggui_push`. When the config
1711
+ * flag is on, every qualifying component push kicks off the
1712
+ * supplied emitter; frames land on the reserved `_ggui:preview`
1713
+ * channel of the push's session.
1714
+ *
1715
+ * The server owns the `sendEnvelope` + registry plumbing — only
1716
+ * the emitter + flag + optional observers are caller-facing.
1717
+ *
1718
+ * Requires `sessionChannel: true` + `mcpApps` enabled (preview
1719
+ * needs a channel to emit on AND a push handler to attach to).
1720
+ * When the flag is on without those, `createGguiServer` throws —
1721
+ * silent drop would make "I enabled preview and nothing fires"
1722
+ * look like a generation bug instead of a wiring bug.
1723
+ *
1724
+ * `ggui-cli`'s `buildMcpServerBackend` passes the deterministic
1725
+ * emitter from `@ggui-ai/preview-a2ui/emitters` as the OSS
1726
+ * default; hosted + programmatic hosts inject their own.
1727
+ */
1728
+ readonly provisionalPreview?: {
1729
+ /** Global kill-switch. Default `false` (no preview fan-out). */
1730
+ readonly enabled: boolean;
1731
+ /**
1732
+ * Caller-supplied producer. Absent = no preview even when
1733
+ * `enabled` is true (guardrail: hosts opting in must be
1734
+ * explicit about the producer).
1735
+ */
1736
+ readonly emitter: ProvisionalPreviewEmitter;
1737
+ /** Per-push predicate. See {@link ProvisionalPreviewConfig}. */
1738
+ readonly isEnabledFor?: ProvisionalPreviewConfig['isEnabledFor'];
1739
+ /** Lifecycle observer. Fires sync — must not throw. */
1740
+ readonly onOutcome?: (outcome: ProvisionalPreviewOutcome) => void;
1741
+ /** Clock override for tests. Defaults to `Date.now`. */
1742
+ readonly now?: () => number;
1743
+ };
1744
+ /**
1745
+ * Handshake preflight wiring. When `mcpApps` is enabled, the
1746
+ * server defaults to an `InMemoryKeyValueStore` + no negotiator:
1747
+ * `ggui_handshake` is registered, `ggui_push({handshakeId})` is
1748
+ * consumable, and handshake records persist for 10 minutes before
1749
+ * single-use consumption.
1750
+ *
1751
+ * Explicit overrides:
1752
+ *
1753
+ * - `{kvStore}` — swap the persistence backend (e.g., SQLite
1754
+ * when it lands) while keeping the default "no negotiator"
1755
+ * shape.
1756
+ * - `{kvStore, negotiator}` — wire a real negotiator (e.g. RAG
1757
+ * in a hosted closed runtime) so handshake records carry a
1758
+ * decision the paired push echoes as `structuredContent.decision`.
1759
+ * - `false` — explicitly disable: `ggui_handshake` is NOT
1760
+ * registered and `ggui_push({handshakeId})` falls back to
1761
+ * the rejection shape.
1762
+ *
1763
+ * Omitted entirely means "use the default in-memory store when
1764
+ * `mcpApps` is on". That matches how `sessionStore` and
1765
+ * `streamBuffer` default — no opt-in required for the OSS
1766
+ * first-run path.
1767
+ *
1768
+ * Requires `mcpApps` to be enabled — handshake is paired with
1769
+ * `ggui_push`, which is only registered under MCP Apps. Throws
1770
+ * at construction when handshake is explicitly enabled without
1771
+ * MCP Apps.
1772
+ */
1773
+ readonly handshake?: false | {
1774
+ /**
1775
+ * Persistence plane. Omit to accept the default
1776
+ * `InMemoryKeyValueStore`.
1777
+ */
1778
+ readonly kvStore?: KeyValueStore;
1779
+ /**
1780
+ * Optional negotiator. Omit = handshake records stamp
1781
+ * `action: 'create'` + no-negotiator-bound reason.
1782
+ */
1783
+ readonly negotiator?: HandshakeNegotiator;
1784
+ };
1785
+ /**
1786
+ * Generation wiring for the `ggui_push` story path. When present,
1787
+ * every component push invokes the bound `UiGenerator` and
1788
+ * appends the result as a real `StackItem` on the session.
1789
+ * Absent = placeholder mode: `ggui_push` on the story path
1790
+ * returns `codeReady: false` without writing componentCode.
1791
+ *
1792
+ * Requires `mcpApps` to be enabled — generation attaches to
1793
+ * `ggui_push`, which is only registered when MCP Apps is on.
1794
+ * Throws at construction otherwise so a misconfigured server
1795
+ * doesn't silently drop the generator binding.
1796
+ *
1797
+ * The `@ggui-ai/ui-gen` package ships the OSS default
1798
+ * implementation (`createUiGenerator({adapter})`); a hosted closed
1799
+ * runtime supplies its own generator binding through the same seam.
1800
+ * BYOK resolution (env → credentials file) is the CLI layer's
1801
+ * concern — at this boundary the caller hands in a closure that
1802
+ * returns resolved credentials per push.
1803
+ */
1804
+ readonly generation?: GenerationDeps;
1805
+ /**
1806
+ * Optional multi-generator registry. When present, exposes named
1807
+ * generators (e.g. `ui-gen-default-haiku-4-5`,
1808
+ * `ui-gen-advanced-opus-4-7`) for consumers such as the blueprint
1809
+ * matcher, `ggui_ops_generate_blueprint`, the LLM-driven variant
1810
+ * selector, the console blueprint UI, and the benchmark framework.
1811
+ *
1812
+ * When omitted, `createGguiServer` auto-seeds a registry containing
1813
+ * `generation.uiGenerator` (when `generation` is supplied) so
1814
+ * consumers observe a non-empty registry by default. When supplied,
1815
+ * the caller's registry is used as-is; the caller is responsible
1816
+ * for including their `generation.uiGenerator` if they want it
1817
+ * discoverable.
1818
+ */
1819
+ readonly generators?: GeneratorRegistry;
1820
+ /**
1821
+ * Optional multi-variant blueprint store. When present, `Blueprint`
1822
+ * rows persist via this seam so `ggui_ops_generate_blueprint` and
1823
+ * push-on-cache-miss can read + write through it. When omitted,
1824
+ * `createGguiServer` auto-seeds an {@link InMemoryBlueprintStore}.
1825
+ */
1826
+ readonly blueprintStore?: BlueprintStore;
1827
+ /**
1828
+ * Optional variant selector. When present, the handshake handler
1829
+ * calls `selectVariant(candidates)` against the candidate list
1830
+ * returned by `blueprintStore.list((appId, contractHash))`. When
1831
+ * omitted, `createGguiServer` defaults to
1832
+ * {@link createDeterministicBlueprintSelector} — a deterministic
1833
+ * fallback ladder.
1834
+ *
1835
+ * Operators MAY swap in an LLM-driven selector without touching the
1836
+ * handler composition.
1837
+ */
1838
+ readonly blueprintSelector?: BlueprintSelector;
1839
+ /**
1840
+ * Optional multi-axis blueprint search. When present, the
1841
+ * three-step handshake reads through this seam for the
1842
+ * parallel-search half of step 2 (cache vs agent vs synth
1843
+ * routing). When omitted, `createGguiServer` auto-seeds an
1844
+ * `createInMemoryBlueprintSearch` against the resolved
1845
+ * `blueprintStore`. The auto-seeded search wires the optional
1846
+ * `embedding` provider when set, so cached `contractEmbedding`
1847
+ * fields on Blueprint rows surface in the embed axis without
1848
+ * additional caller wiring.
1849
+ *
1850
+ * Operators MAY swap in a vector-DB-backed search (Pinecone,
1851
+ * pgvector, OpenSearch) without touching downstream handlers —
1852
+ * the seam is the contract; the implementation is fungible.
1853
+ */
1854
+ readonly blueprintSearch?: BlueprintSearch;
1855
+ /**
1856
+ * External tool-handler bundles aggregated onto this server's
1857
+ * `/mcp` surface. Every mount's handlers register alongside
1858
+ * ggui's native tools, so one MCP session sees both — `tools/list`
1859
+ * enumerates ggui-native tools plus every mount's tools, and
1860
+ * `tools/call` dispatches uniformly.
1861
+ *
1862
+ * Each mount is a `{ name, handlers }` bundle where `handlers` is
1863
+ * `SharedHandler[]` — the exact shape ggui-native handlers use.
1864
+ * A fixture, hosted adapter, or programmatic host builds handlers
1865
+ * against the same `@ggui-ai/mcp-server-handlers` seams + zod
1866
+ * shapes they'd use for any ggui-native tool, then passes them
1867
+ * here.
1868
+ *
1869
+ * Collision rules: mount tool names MUST NOT collide with a
1870
+ * ggui-native tool name OR with any other mount's tool name.
1871
+ * Composition throws on collision so misconfiguration surfaces
1872
+ * at server-construction time rather than as a surprising
1873
+ * "tools/call dispatched to the wrong handler" at runtime.
1874
+ *
1875
+ * Ignored when {@link handlers} is set — callers who pass a
1876
+ * custom handler list compose the final list themselves.
1877
+ */
1878
+ readonly mcpMounts?: ReadonlyArray<McpServerMount>;
1879
+ /**
1880
+ * Isolated MCP services — each mounted at its own HTTP path with
1881
+ * its own tool namespace. Unlike {@link mcpMounts} (which aggregates
1882
+ * tools onto the shared audience-filtered routes), every entry here
1883
+ * becomes a self-contained MCP server reachable at `app.post(path)`.
1884
+ *
1885
+ * Use a service when the handler set is conceptually a distinct MCP
1886
+ * server (`mcp.ggui.ai/docs`, `mcp.ggui.ai/playground/todos`). Use a
1887
+ * mount when the handlers should appear alongside ggui-native tools
1888
+ * on the shared `/mcp` surface.
1889
+ *
1890
+ * Compose-time invariants are enforced by `validateMcpServices`:
1891
+ * unique non-reserved paths, non-empty handler `outputSchema`, no
1892
+ * `audience` tags on service handlers, no within-service tool-name
1893
+ * collisions. Cross-service tool-name collisions ARE allowed.
1894
+ *
1895
+ * Empty / absent → no service routes mounted, no behavior change.
1896
+ */
1897
+ readonly mcpServices?: ReadonlyArray<McpService>;
1898
+ /**
1899
+ * Per-request resource registrars run against every fresh
1900
+ * `McpServer` instance, after the MCP-Apps outbound install (when
1901
+ * enabled) and before tool registration. The hook is the canonical
1902
+ * extension seam for hosts that mount cross-cutting MCP App UI
1903
+ * bundles (e.g. a `ui://`-scheme resource for system-level cards)
1904
+ * without baking the bundle's wiring into this OSS factory.
1905
+ *
1906
+ * Each registrar receives the per-request `McpServer`; misuse
1907
+ * (duplicate URI, malformed declaration) throws synchronously and
1908
+ * fails the request before tool dispatch, surfacing the
1909
+ * misconfiguration immediately. Idempotent in spirit — the
1910
+ * underlying SDK rejects duplicate registrations.
1911
+ */
1912
+ readonly extraResources?: ReadonlyArray<(server: McpServer) => void>;
1913
+ /**
1914
+ * Per-domain dep seams for the twelve operator-class `ggui_ops_*`
1915
+ * handlers covering the console's apps + orgs + connector-keys +
1916
+ * coupon surfaces. Each domain is independently optional —
1917
+ * `defaultHandlers` registers a domain's tools only when its seam
1918
+ * is bound here. OSS deployments leave these undefined (the smaller
1919
+ * surface); cloud pods bind AppSync-backed adapters.
1920
+ *
1921
+ * Mirrors `creditBalance` + `creditTransactions`'s pattern — the
1922
+ * shared-handler layer is the same code path everywhere, and the
1923
+ * deps interface is the boundary between the open handler and the
1924
+ * deployment-specific implementation.
1925
+ */
1926
+ readonly opsApps?: {
1927
+ readonly apps: AppsSource;
1928
+ readonly userDefaultApp: UserDefaultAppSource;
1929
+ };
1930
+ readonly opsOrgs?: {
1931
+ readonly orgs: OrgsSource;
1932
+ readonly invites: OrgInvitesSource;
1933
+ };
1934
+ readonly opsConnectorKeys?: {
1935
+ readonly connectorKeys: ConnectorKeysSource;
1936
+ };
1937
+ readonly opsCoupon?: {
1938
+ readonly coupons: CouponRedeemSource;
1939
+ };
1940
+ }
1941
+ export interface GguiServer {
1942
+ /**
1943
+ * The Express app. Mount it under your own parent router if you want
1944
+ * to add middleware, or call {@link listen} for the zero-config path.
1945
+ */
1946
+ readonly app: Express;
1947
+ /**
1948
+ * Bind the app to a port and return the underlying `node:http` server.
1949
+ * Resolves once the listener is accepting connections.
1950
+ */
1951
+ listen(port?: number, host?: string): Promise<NodeHttpServer>;
1952
+ /** Close every outstanding HTTP connection. Idempotent. */
1953
+ close(): Promise<void>;
1954
+ /**
1955
+ * Number of MCP tools registered on this server. Same value the
1956
+ * `GET /ggui/health` endpoint echoes. Useful for hosts (CLIs,
1957
+ * dashboards, tests) that want to surface a real count without
1958
+ * round-tripping over HTTP.
1959
+ */
1960
+ readonly toolCount: number;
1961
+ /**
1962
+ * The OSS live-channel session endpoint, when `sessionChannel` was
1963
+ * enabled. `null` when disabled. Hosts can use this for
1964
+ * introspection (`.sessionCount`, `.subscriberCount`) or for
1965
+ * composition with future mutation handlers that want to fan out
1966
+ * via `sessionChannel.sendToSession(sessionId, data)`.
1967
+ */
1968
+ readonly sessionChannel: SessionChannelServer | null;
1969
+ /**
1970
+ * The pairing service bound to this server, when the `pairing` option
1971
+ * was enabled. `null` when pairing is disabled. In-process hosts
1972
+ * (CLIs, embedded viewers) use this to call `initPairing()` directly
1973
+ * instead of POSTing to `/admin/pair/init`, and to list / revoke
1974
+ * pairings over a programmatic path.
1975
+ */
1976
+ readonly pairingService: PairingService | null;
1977
+ /**
1978
+ * Primitive catalogs resolved at boot from
1979
+ * `ggui.json#primitives.{packages,local}`. Empty array when the
1980
+ * operator passed nothing (programmatic hosts) or when the
1981
+ * declaration resolved to an empty set. Read-only; callers that
1982
+ * need to mutate the catalog should rebuild the server.
1983
+ *
1984
+ * After the CLI `discoverPrimitives()` walk, every declared source
1985
+ * surfaces here in boot-time order (packages first, locals after).
1986
+ * Generator integration (threading the catalog into
1987
+ * `buildSystemPrompt`) consumes this field.
1988
+ */
1989
+ readonly primitiveCatalogs: readonly DiscoveredPrimitiveCatalog[];
1990
+ /**
1991
+ * Theme resolved at boot from `ggui.json#theme`. Always populated
1992
+ * — the server falls back to `@ggui-ai/design`'s shipped
1993
+ * `lightTheme` when the caller omits `opts.theme`, so consumers
1994
+ * never have to null-check.
1995
+ *
1996
+ * Carries the parsed DTCG document + a pre-rendered
1997
+ * `:root { --ggui-*: value; }` CSS block. The server does not yet
1998
+ * inject this CSS into any HTTP response or console bootstrap;
1999
+ * downstream consumers read `server.theme.document` or
2000
+ * `server.theme.cssVariables` as they layer on.
2001
+ */
2002
+ readonly theme: LoadedTheme;
2003
+ /**
2004
+ * Admin bearer that gates the operator-only console routes
2005
+ * (`/ggui/console/keys*` + `/ggui/console/admin-login`). Either the
2006
+ * value the caller supplied via {@link CreateGguiServerOptions.console}
2007
+ * `.adminToken`, or a freshly minted `ggui_admin_*` token when the
2008
+ * caller didn't pass one. `null` when console is disabled (the gate
2009
+ * has no consumer).
2010
+ *
2011
+ * The CLI banner reads this and prints it next to PAIR_CODE so the
2012
+ * operator can paste it into the admin-login page on their first
2013
+ * visit to `/keys`.
2014
+ */
2015
+ readonly adminToken: string | null;
2016
+ /**
2017
+ * The composed generator registry. When the caller passed
2018
+ * `opts.generators`, this is that exact registry. Otherwise, when
2019
+ * `opts.generation` was supplied, this is an auto-seeded registry
2020
+ * containing `generation.uiGenerator` under its declared slug.
2021
+ * `null` when neither was supplied (no generators to expose).
2022
+ *
2023
+ * This field is a seam — consumers read it for blueprint matcher
2024
+ * dispatch, `ggui_ops_generate_blueprint`, the LLM-driven variant
2025
+ * selector, the console blueprint UI, and the benchmark framework.
2026
+ */
2027
+ readonly generators: GeneratorRegistry | null;
2028
+ /**
2029
+ * The composed multi-variant blueprint store. When the caller
2030
+ * passed `opts.blueprintStore`, this is that exact instance.
2031
+ * Otherwise an auto-seeded {@link InMemoryBlueprintStore}.
2032
+ */
2033
+ readonly blueprintStore: BlueprintStore;
2034
+ /**
2035
+ * The composed variant selector. When the caller passed
2036
+ * `opts.blueprintSelector`, this is that exact instance. Otherwise
2037
+ * {@link createDeterministicBlueprintSelector} — a deterministic
2038
+ * fallback ladder. Operators MAY swap in an LLM-driven selector.
2039
+ */
2040
+ readonly blueprintSelector: BlueprintSelector;
2041
+ /**
2042
+ * The composed multi-axis blueprint search. When the caller passed
2043
+ * `opts.blueprintSearch`, this is that exact instance. Otherwise
2044
+ * `createInMemoryBlueprintSearch` against the resolved
2045
+ * `blueprintStore`, wiring the optional `embedding` provider if one
2046
+ * was supplied.
2047
+ *
2048
+ * The three-step handshake reads this for its parallel search +
2049
+ * validate step. Hosts MAY introspect it for cache-warm runbooks
2050
+ * or observability.
2051
+ */
2052
+ readonly blueprintSearch: BlueprintSearch;
2053
+ }
2054
+ /**
2055
+ * Build a runnable OSS MCP server. Every option has a sensible default
2056
+ * so `createGguiServer()` with no arguments boots a working in-memory
2057
+ * server on demand.
2058
+ */
2059
+ export declare function createGguiServer(opts?: CreateGguiServerOptions): GguiServer;
2060
+ //# sourceMappingURL=server.d.ts.map