@ggui-ai/mcp-server 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-renders-routes.d.ts +21 -0
- package/dist/api-renders-routes.d.ts.map +1 -1
- package/dist/api-renders-routes.js +22 -5
- package/dist/build-mcp.d.ts +38 -7
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +23 -2
- package/dist/code-module-variant.d.ts +150 -0
- package/dist/code-module-variant.d.ts.map +1 -0
- package/dist/code-module-variant.js +243 -0
- package/dist/code-routes.d.ts +12 -2
- package/dist/code-routes.d.ts.map +1 -1
- package/dist/code-routes.js +12 -2
- package/dist/control-service.d.ts +29 -3
- package/dist/control-service.d.ts.map +1 -1
- package/dist/control-service.js +26 -2
- package/dist/health-routes.d.ts +19 -3
- package/dist/health-routes.d.ts.map +1 -1
- package/dist/health-routes.js +26 -18
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/mcp-apps-outbound.d.ts +40 -8
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +127 -30
- package/dist/mcp-endpoint-routes.d.ts +6 -0
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +67 -1
- package/dist/oauth-as-routes.d.ts +11 -0
- package/dist/oauth-as-routes.d.ts.map +1 -1
- package/dist/oauth-as-routes.js +9 -1
- package/dist/runtime-bundle-hash.d.ts +12 -0
- package/dist/runtime-bundle-hash.d.ts.map +1 -1
- package/dist/runtime-bundle-hash.js +19 -0
- package/dist/server.d.ts +223 -56
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +263 -125
- package/package.json +13 -12
package/dist/server.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* once at boot so operators see the shape they're running.
|
|
39
39
|
*/
|
|
40
40
|
import { CONSOLE_DIST_DIR } from "@ggui-ai/console/server";
|
|
41
|
-
import { RUNTIME_BUNDLE_FILE, RUNTIME_BUNDLE_URL_PATH } from "@ggui-ai/iframe-runtime/server";
|
|
41
|
+
import { RUNTIME_BUNDLE_FILE, RUNTIME_BUNDLE_URL_PATH, RUNTIME_SHIMS_DIR, RUNTIME_SHIMS_URL_PREFIX, } from "@ggui-ai/iframe-runtime/server";
|
|
42
42
|
import { createDeterministicBlueprintSelector, isTokenRegisteringAuthAdapter, mintSessionToken, mintWsToken, refreshWsToken, verifyToken, } from "@ggui-ai/mcp-server-core";
|
|
43
43
|
import { createInMemoryBlueprintSearch, createInMemoryGeneratorRegistry, FixedWindowRateLimiter, InMemoryActiveConsumerRegistry, InMemoryAppMetadataStore, InMemoryAuthAdapter, InMemoryBlueprintIndex, InMemoryBlueprintStore, InMemoryKeyValueStore, InMemoryPairingService, InMemoryPendingEventConsumer, InMemoryQuotaStore, InMemoryGguiSessionStore, InMemoryGguiSessionStreamBuffer, InMemoryVectorStore, MockEmbeddingProvider, NoopAuditSink, NoopRateLimiter, NoopTelemetrySink, } from "@ggui-ai/mcp-server-core/in-memory";
|
|
44
44
|
import { createGguiListGadgetsHandler, createGguiListThemesHandler, } from "@ggui-ai/mcp-server-handlers/app-discovery";
|
|
@@ -71,7 +71,7 @@ import { createCreateAppHandler, createDeleteAppHandler, createListAppsHandler,
|
|
|
71
71
|
import { createIssueConnectorKeyHandler, createListConnectorKeysHandler, createRevokeConnectorKeyHandler, } from "@ggui-ai/mcp-server-handlers/ops-connector-keys";
|
|
72
72
|
import { createRedeemCouponHandler, } from "@ggui-ai/mcp-server-handlers/ops-coupon";
|
|
73
73
|
import { createCreateOrgHandler, createGetOrgBalanceHandler, createInviteToOrgHandler, createListOrgsHandler, createRemoveOrgMemberHandler, createRenameOrgHandler, createRevokeInviteHandler, } from "@ggui-ai/mcp-server-handlers/ops-orgs";
|
|
74
|
-
import { createGguiConsumeHandler, createGguiDeclareToolCatalogHandler, createGguiEmitHandler, createGguiGetSessionHandler, createGguiHandshakeHandler, createGguiListSessionsHandler, createGguiRefreshWsTokenHandler, createGguiRenderHandler, createGguiRuntimePullHandler, createGguiRuntimeTelemetryHandler, createGguiSubmitActionHandler, createGguiSyncContextHandler, createGguiUpdateHandler, createGguiAmendHandler, InMemoryToolIdentityCatalogStore, createInMemoryProvisionalPreviewRegistry, } from "@ggui-ai/mcp-server-handlers/renders";
|
|
74
|
+
import { createGguiConsumeHandler, createGguiDeclareToolCatalogHandler, createGguiEmitHandler, createGguiGetRenderSourceHandler, createGguiGetSessionHandler, createGguiHandshakeHandler, createGguiListSessionsHandler, createGguiRefreshWsTokenHandler, createGguiRenderHandler, createGguiRuntimePullHandler, createGguiRuntimeTelemetryHandler, createGguiSubmitActionHandler, createGguiSyncContextHandler, createGguiUpdateHandler, createGguiAmendHandler, InMemoryToolIdentityCatalogStore, createInMemoryProvisionalPreviewRegistry, } from "@ggui-ai/mcp-server-handlers/renders";
|
|
75
75
|
import { DEFAULT_ADMIN_BLUEPRINTS_PATH, mountAdminBlueprintsTransport, } from "./admin-blueprints-transport.js";
|
|
76
76
|
import { mountAdminOAuthProvidersTransport } from "./admin-oauth-providers-transport.js";
|
|
77
77
|
import { DEFAULT_BUILDER_APP_ID, defaultAppIdFromIdentity } from "./auth.js";
|
|
@@ -90,6 +90,7 @@ import { mountConsoleSessionRoutes } from "./console-session-routes.js";
|
|
|
90
90
|
import { mountConsoleStaticRoutes } from "./console-static-routes.js";
|
|
91
91
|
import { mountConsoleSessionsRoutes } from "./console-sessions-routes.js";
|
|
92
92
|
import { mountCodeRoutes } from "./code-routes.js";
|
|
93
|
+
import { captureShimSources, createCodeModuleUrlMinter, mountShimRoutes, } from "./code-module-variant.js";
|
|
93
94
|
import { mountHealthRoutes } from "./health-routes.js";
|
|
94
95
|
import { mountOAuthAuthorizationServerRoutes } from "./oauth-as-routes.js";
|
|
95
96
|
import { mountOAuthClientsRoutes } from "./oauth-clients-routes.js";
|
|
@@ -126,30 +127,6 @@ const DEFAULT_INFO = {
|
|
|
126
127
|
version: "0.0.1",
|
|
127
128
|
description: "Open self-hosted MCP server for the ggui protocol. Powered by @ggui-ai/mcp-server-handlers.",
|
|
128
129
|
};
|
|
129
|
-
/**
|
|
130
|
-
* Canonical default handler set. Every `@ggui-ai/mcp-server-handlers`
|
|
131
|
-
* family the OSS server ships with lands here, bound to the caller-
|
|
132
|
-
* supplied deps. Use this when you want to EXTEND the defaults rather
|
|
133
|
-
* than replace them wholesale:
|
|
134
|
-
*
|
|
135
|
-
* ```ts
|
|
136
|
-
* const server = createGguiServer({
|
|
137
|
-
* vectors, embedding,
|
|
138
|
-
* handlers: [
|
|
139
|
-
* ...defaultHandlers({ vectors, embedding }),
|
|
140
|
-
* myCustomHandler,
|
|
141
|
-
* ],
|
|
142
|
-
* });
|
|
143
|
-
* ```
|
|
144
|
-
*
|
|
145
|
-
* Without this helper, `handlers:` replaces the full list — callers
|
|
146
|
-
* lose the defaults unless they copy-paste them. Keeping `defaultHandlers`
|
|
147
|
-
* named means the default set stays discoverable + testable in one place.
|
|
148
|
-
*
|
|
149
|
-
* `render` is opt-in via `deps.render` — it's only useful when the server
|
|
150
|
-
* was booted with `mcpApps: true` (so `ui://ggui/render` is served)
|
|
151
|
-
* and pairs a real GguiSessionStore. Callers get the choice explicitly.
|
|
152
|
-
*/
|
|
153
130
|
/**
|
|
154
131
|
* Assemble the `opsBlueprint` dep bundle for `defaultHandlers`.
|
|
155
132
|
*
|
|
@@ -179,9 +156,82 @@ function buildOpsBlueprintDeps(input) {
|
|
|
179
156
|
...(input.resolveLlm ? { resolveLlm: input.resolveLlm } : {}),
|
|
180
157
|
...(input.blueprints ? { blueprints: input.blueprints } : {}),
|
|
181
158
|
...(input.cacheRegistry ? { cacheRegistry: input.cacheRegistry } : {}),
|
|
159
|
+
authorizeAppAccess: input.authorizeAppAccess ?? (async () => ({ allowed: true })),
|
|
182
160
|
},
|
|
183
161
|
};
|
|
184
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Materialize the operator-class blueprint family from a bound
|
|
165
|
+
* {@link OpsBlueprintBundle}. Registered on the control plane via
|
|
166
|
+
* `audience: ['ops']`.
|
|
167
|
+
*
|
|
168
|
+
* Four of the five land whenever the bundle is present; the `generate`
|
|
169
|
+
* tool additionally requires `resolveLlm` + `blueprints` (the same deps
|
|
170
|
+
* the render generation path reads) because without them it has no
|
|
171
|
+
* dispatch target.
|
|
172
|
+
*
|
|
173
|
+
* Lives here — not inline in {@link defaultHandlers} — so BOTH
|
|
174
|
+
* composition paths build the family from ONE body:
|
|
175
|
+
* `defaultHandlers` for the default handler set, and
|
|
176
|
+
* {@link buildOpsBundleHandlers} for deployments that supply their own
|
|
177
|
+
* `CreateGguiServerOptions.handlers` array. The construction used to sit
|
|
178
|
+
* only inside `defaultHandlers`, so an explicit handler list silently
|
|
179
|
+
* dropped all five tools even with the bundle bound.
|
|
180
|
+
*/
|
|
181
|
+
export function buildOpsBlueprintHandlers(input) {
|
|
182
|
+
const { bundle } = input;
|
|
183
|
+
const handlers = [];
|
|
184
|
+
// Bundle-explicit `appMetadataStore` wins; falls back to the
|
|
185
|
+
// server-level one — same precedence
|
|
186
|
+
// `deps.handshake.appMetadataStore ?? deps.appMetadataStore` uses.
|
|
187
|
+
// Gates `assertGadgetsRegistered` inside both generate + register
|
|
188
|
+
// (see each handler's own no-op-when-unbound posture).
|
|
189
|
+
const opsBlueprintAppMetadataStore = bundle.appMetadataStore ?? input.appMetadataStore;
|
|
190
|
+
if (bundle.resolveLlm && bundle.blueprints) {
|
|
191
|
+
handlers.push(createGguiOpsGenerateBlueprintHandler({
|
|
192
|
+
registry: bundle.registry,
|
|
193
|
+
blueprintStore: bundle.blueprintStore,
|
|
194
|
+
resolveLlm: bundle.resolveLlm,
|
|
195
|
+
blueprints: bundle.blueprints,
|
|
196
|
+
...(bundle.putCode ? { putCode: bundle.putCode } : {}),
|
|
197
|
+
...(bundle.listAllForApp ? { listAllForApp: bundle.listAllForApp } : {}),
|
|
198
|
+
...(opsBlueprintAppMetadataStore
|
|
199
|
+
? { appMetadataStore: opsBlueprintAppMetadataStore }
|
|
200
|
+
: {}),
|
|
201
|
+
...(bundle.cacheRegistry ? { cacheRegistry: bundle.cacheRegistry } : {}),
|
|
202
|
+
...(input.telemetry ? { telemetry: input.telemetry } : {}),
|
|
203
|
+
authorizeAppAccess: bundle.authorizeAppAccess,
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
// `ggui_ops_register_blueprint` — sibling of `_generate_*` that
|
|
207
|
+
// accepts pre-built componentCode bytes. No LLM dispatch, so it
|
|
208
|
+
// registers whenever the ops dep bundle is bound (no resolveLlm
|
|
209
|
+
// / blueprints gate). Operator UX entry point for fixture
|
|
210
|
+
// seeding + export/reimport round-trips.
|
|
211
|
+
handlers.push(createGguiOpsRegisterBlueprintHandler({
|
|
212
|
+
blueprintStore: bundle.blueprintStore,
|
|
213
|
+
...(bundle.putCode ? { putCode: bundle.putCode } : {}),
|
|
214
|
+
...(bundle.listAllForApp ? { listAllForApp: bundle.listAllForApp } : {}),
|
|
215
|
+
...(opsBlueprintAppMetadataStore ? { appMetadataStore: opsBlueprintAppMetadataStore } : {}),
|
|
216
|
+
...(bundle.cacheRegistry ? { cacheRegistry: bundle.cacheRegistry } : {}),
|
|
217
|
+
...(input.telemetry ? { telemetry: input.telemetry } : {}),
|
|
218
|
+
authorizeAppAccess: bundle.authorizeAppAccess,
|
|
219
|
+
}));
|
|
220
|
+
handlers.push(createGguiOpsListBlueprintsHandler({
|
|
221
|
+
blueprintStore: bundle.blueprintStore,
|
|
222
|
+
blueprintSearch: bundle.blueprintSearch,
|
|
223
|
+
authorizeAppAccess: bundle.authorizeAppAccess,
|
|
224
|
+
}));
|
|
225
|
+
handlers.push(createGguiOpsUpdateBlueprintHandler({
|
|
226
|
+
blueprintStore: bundle.blueprintStore,
|
|
227
|
+
authorizeAppAccess: bundle.authorizeAppAccess,
|
|
228
|
+
}));
|
|
229
|
+
handlers.push(createGguiOpsDeleteBlueprintHandler({
|
|
230
|
+
blueprintStore: bundle.blueprintStore,
|
|
231
|
+
authorizeAppAccess: bundle.authorizeAppAccess,
|
|
232
|
+
}));
|
|
233
|
+
return handlers;
|
|
234
|
+
}
|
|
185
235
|
export function defaultHandlers(deps) {
|
|
186
236
|
// Single shared pending-events pipe (Model C, sessionId-keyed).
|
|
187
237
|
// render opens (`markCreated`), submit_action appends, consume drains,
|
|
@@ -377,6 +427,11 @@ export function defaultHandlers(deps) {
|
|
|
377
427
|
? { serverCapabilities: deps.handshake.serverCapabilities }
|
|
378
428
|
: {}),
|
|
379
429
|
...(lifecycleEmitter ? { lifecycleEmitter } : {}),
|
|
430
|
+
// Server-level TelemetrySink → the handler's `handshake.decided`
|
|
431
|
+
// emission. Without this thread the composed emission never
|
|
432
|
+
// fired (the handler dep predates the wiring — P2 of
|
|
433
|
+
// docs/plans/2026-08-19-schema-precise-render.md pinned it).
|
|
434
|
+
...(deps.telemetry ? { telemetrySink: deps.telemetry } : {}),
|
|
380
435
|
}));
|
|
381
436
|
}
|
|
382
437
|
if (deps.update) {
|
|
@@ -401,6 +456,10 @@ export function defaultHandlers(deps) {
|
|
|
401
456
|
...(deps.update.themeProvider !== undefined
|
|
402
457
|
? { themeProvider: deps.update.themeProvider }
|
|
403
458
|
: {}),
|
|
459
|
+
// Mutation-time `render.contract_violation` events (P2
|
|
460
|
+
// measurement — update-time violations are baselined with
|
|
461
|
+
// render-time ones, not hidden).
|
|
462
|
+
...(deps.telemetry ? { telemetrySink: deps.telemetry } : {}),
|
|
404
463
|
}));
|
|
405
464
|
// ggui_amend rides the SAME deps slot (#483 tool split): one
|
|
406
465
|
// mutation core, one wiring. It reads only the mutation-flow deps
|
|
@@ -415,6 +474,8 @@ export function defaultHandlers(deps) {
|
|
|
415
474
|
...(deps.update.propsUpdateNotifier
|
|
416
475
|
? { propsUpdateNotifier: deps.update.propsUpdateNotifier }
|
|
417
476
|
: {}),
|
|
477
|
+
// Same mutation core as ggui_update — same violation events.
|
|
478
|
+
...(deps.telemetry ? { telemetrySink: deps.telemetry } : {}),
|
|
418
479
|
}));
|
|
419
480
|
}
|
|
420
481
|
// ggui_consume registers whenever render is bound (it shares the
|
|
@@ -480,6 +541,12 @@ export function defaultHandlers(deps) {
|
|
|
480
541
|
handlers.push(createGguiGetSessionHandler({
|
|
481
542
|
renderStore: deps.render.renderStore,
|
|
482
543
|
}));
|
|
544
|
+
// ggui_get_render_source (#282 data-plane rider) — the calling
|
|
545
|
+
// app reads its OWN render's generated source. No heartbeat: a
|
|
546
|
+
// one-shot source read is not an activity signal.
|
|
547
|
+
handlers.push(createGguiGetRenderSourceHandler({
|
|
548
|
+
renderStore: deps.render.renderStore,
|
|
549
|
+
}));
|
|
483
550
|
// ggui_list_sessions — host-scoped render enumeration for resume.
|
|
484
551
|
// Folds the ws-token mint into the same call so the host doesn't
|
|
485
552
|
// round-trip twice (list, then mint-per-render). Reuses the
|
|
@@ -567,6 +634,11 @@ export function defaultHandlers(deps) {
|
|
|
567
634
|
? { themeProvider: deps.render.themeProvider }
|
|
568
635
|
: {}),
|
|
569
636
|
...(deps.render.rateLimiter ? { rateLimiter: deps.render.rateLimiter } : {}),
|
|
637
|
+
// Render measurement events (`render.attempted` /
|
|
638
|
+
// `render.contract_violation` / `render.committed`) — P2 of
|
|
639
|
+
// docs/plans/2026-08-19-schema-precise-render.md. One
|
|
640
|
+
// server-level sink, every consumer.
|
|
641
|
+
...(deps.telemetry ? { telemetrySink: deps.telemetry } : {}),
|
|
570
642
|
...(deps.render.shortCodeIndex ? { shortCodeIndex: deps.render.shortCodeIndex } : {}),
|
|
571
643
|
...(deps.render.renderIdentityStore
|
|
572
644
|
? { renderIdentityStore: deps.render.renderIdentityStore }
|
|
@@ -599,6 +671,9 @@ export function defaultHandlers(deps) {
|
|
|
599
671
|
? {
|
|
600
672
|
codeStore: deps.render.codeStore,
|
|
601
673
|
codeBaseUrl: deps.render.codeBaseUrl,
|
|
674
|
+
...(deps.render.mintCodeModuleUrl
|
|
675
|
+
? { mintCodeModuleUrl: deps.render.mintCodeModuleUrl }
|
|
676
|
+
: {}),
|
|
602
677
|
}
|
|
603
678
|
: {}),
|
|
604
679
|
// Share the handshake KV store between the two handlers so
|
|
@@ -611,61 +686,24 @@ export function defaultHandlers(deps) {
|
|
|
611
686
|
...(lifecycleEmitter ? { lifecycleEmitter } : {}),
|
|
612
687
|
}));
|
|
613
688
|
}
|
|
614
|
-
// Operator-class blueprint tools
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
//
|
|
689
|
+
// Operator-class blueprint tools — see `buildOpsBlueprintHandlers`
|
|
690
|
+
// for the family's registration rules. The server-level
|
|
691
|
+
// `deps.appMetadataStore` + `deps.telemetry` are offered as the
|
|
692
|
+
// bundle's fallbacks; render already gets the server-level store at
|
|
693
|
+
// :1398. Cloud pods that pass their own handler list pick the same
|
|
694
|
+
// family up through `buildOpsBundleHandlers`.
|
|
620
695
|
if (deps.opsBlueprint) {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
blueprintStore: deps.opsBlueprint.blueprintStore,
|
|
625
|
-
resolveLlm: deps.opsBlueprint.resolveLlm,
|
|
626
|
-
blueprints: deps.opsBlueprint.blueprints,
|
|
627
|
-
...(deps.opsBlueprint.putCode ? { putCode: deps.opsBlueprint.putCode } : {}),
|
|
628
|
-
...(deps.opsBlueprint.listAllForApp
|
|
629
|
-
? { listAllForApp: deps.opsBlueprint.listAllForApp }
|
|
630
|
-
: {}),
|
|
631
|
-
...(deps.opsBlueprint.cacheRegistry
|
|
632
|
-
? { cacheRegistry: deps.opsBlueprint.cacheRegistry }
|
|
633
|
-
: {}),
|
|
634
|
-
...(deps.telemetry ? { telemetry: deps.telemetry } : {}),
|
|
635
|
-
}));
|
|
636
|
-
}
|
|
637
|
-
// `ggui_ops_register_blueprint` — sibling of `_generate_*` that
|
|
638
|
-
// accepts pre-built componentCode bytes. No LLM dispatch, so it
|
|
639
|
-
// registers whenever the ops dep bundle is bound (no resolveLlm
|
|
640
|
-
// / blueprints gate). Operator UX entry point for fixture
|
|
641
|
-
// seeding + export/reimport round-trips.
|
|
642
|
-
handlers.push(createGguiOpsRegisterBlueprintHandler({
|
|
643
|
-
blueprintStore: deps.opsBlueprint.blueprintStore,
|
|
644
|
-
...(deps.opsBlueprint.putCode ? { putCode: deps.opsBlueprint.putCode } : {}),
|
|
645
|
-
...(deps.opsBlueprint.listAllForApp
|
|
646
|
-
? { listAllForApp: deps.opsBlueprint.listAllForApp }
|
|
647
|
-
: {}),
|
|
648
|
-
...(deps.opsBlueprint.cacheRegistry
|
|
649
|
-
? { cacheRegistry: deps.opsBlueprint.cacheRegistry }
|
|
650
|
-
: {}),
|
|
696
|
+
handlers.push(...buildOpsBlueprintHandlers({
|
|
697
|
+
bundle: deps.opsBlueprint,
|
|
698
|
+
...(deps.appMetadataStore ? { appMetadataStore: deps.appMetadataStore } : {}),
|
|
651
699
|
...(deps.telemetry ? { telemetry: deps.telemetry } : {}),
|
|
652
700
|
}));
|
|
653
|
-
handlers.push(createGguiOpsListBlueprintsHandler({
|
|
654
|
-
blueprintStore: deps.opsBlueprint.blueprintStore,
|
|
655
|
-
blueprintSearch: deps.opsBlueprint.blueprintSearch,
|
|
656
|
-
}));
|
|
657
|
-
handlers.push(createGguiOpsUpdateBlueprintHandler({
|
|
658
|
-
blueprintStore: deps.opsBlueprint.blueprintStore,
|
|
659
|
-
}));
|
|
660
|
-
handlers.push(createGguiOpsDeleteBlueprintHandler({
|
|
661
|
-
blueprintStore: deps.opsBlueprint.blueprintStore,
|
|
662
|
-
}));
|
|
663
701
|
}
|
|
664
702
|
return handlers;
|
|
665
703
|
}
|
|
666
704
|
/**
|
|
667
705
|
* Build the operator-class handlers for the apps / orgs /
|
|
668
|
-
* connector-keys / coupon domains. Each domain materializes
|
|
706
|
+
* connector-keys / coupon / blueprint domains. Each domain materializes
|
|
669
707
|
* independently when its deps seam is bound; deployments that wired
|
|
670
708
|
* none get an empty list.
|
|
671
709
|
*
|
|
@@ -696,6 +734,15 @@ export function buildOpsBundleHandlers(deps) {
|
|
|
696
734
|
coupons: deps.opsCoupon.coupons,
|
|
697
735
|
}));
|
|
698
736
|
}
|
|
737
|
+
if (deps.opsBlueprint) {
|
|
738
|
+
// No server-level `appMetadataStore` fallback on this path — the
|
|
739
|
+
// bundle's own field governs, since a deployment that binds its own
|
|
740
|
+
// bundle here binds the store with it.
|
|
741
|
+
handlers.push(...buildOpsBlueprintHandlers({
|
|
742
|
+
bundle: deps.opsBlueprint,
|
|
743
|
+
...(deps.telemetry ? { telemetry: deps.telemetry } : {}),
|
|
744
|
+
}));
|
|
745
|
+
}
|
|
699
746
|
return handlers;
|
|
700
747
|
}
|
|
701
748
|
/**
|
|
@@ -707,6 +754,9 @@ export function createGguiServer(opts = {}) {
|
|
|
707
754
|
const info = { ...DEFAULT_INFO, ...opts.info };
|
|
708
755
|
const logger = opts.logger ?? createConsoleLogger({ server: info.name });
|
|
709
756
|
const bodyLimit = opts.bodyLimit ?? "4mb";
|
|
757
|
+
// Where the content-addressable routes are reached from the iframe:
|
|
758
|
+
// an explicit asset host, else the public origin (see `codeBaseUrl`).
|
|
759
|
+
const codeBaseUrl = opts.codeBaseUrl ?? opts.publicBaseUrl;
|
|
710
760
|
// Operator mode: gates the `/devtools/*` namespace. Explicit option
|
|
711
761
|
// wins; otherwise read GGUI_MODE env (`'dev'` opts in, anything else
|
|
712
762
|
// including unset is `'prod'`). Surfaced via `/info` so the SPA
|
|
@@ -852,6 +902,33 @@ export function createGguiServer(opts = {}) {
|
|
|
852
902
|
: insertRuntimeBundleHash(urlOrPath, runtimeBundleHash, runtimePath.slice(runtimePath.lastIndexOf("/") + 1));
|
|
853
903
|
const hashedRuntimePath = runtimeBundleHash !== undefined ? insertHash(runtimePath) : undefined;
|
|
854
904
|
const runtimeBootstrapUrl = insertHash(runtimeConfig.url ?? runtimePath);
|
|
905
|
+
// Strict-CSP module-variant family (ggui#522 slice 2). Every
|
|
906
|
+
// ingredient must exist for any of it to mount: the shim files the
|
|
907
|
+
// runtime build emitted next to the bundle (the fetchable twins of
|
|
908
|
+
// the data-url shims), the bundle content hash (the variant KEY —
|
|
909
|
+
// shims ship in the bundle's dist, so one hash versions both), the
|
|
910
|
+
// code store (the variant is a `/code` twin), and an absolute code
|
|
911
|
+
// base URL (the rewrite embeds absolute shim URLs — a srcdoc frame
|
|
912
|
+
// has no base to resolve relative ones). Missing any ⇒ no variant
|
|
913
|
+
// URLs are minted and no variant routes mount; renders keep the raw
|
|
914
|
+
// codeUrl/codeB64 carriers and the renderer's blob ladder.
|
|
915
|
+
const shimSources = runtimeEnabled && runtimeBundleHash !== undefined
|
|
916
|
+
? captureShimSources(runtimeConfig.distDir !== undefined
|
|
917
|
+
? path.join(runtimeConfig.distDir, "shims")
|
|
918
|
+
: RUNTIME_SHIMS_DIR)
|
|
919
|
+
: undefined;
|
|
920
|
+
const codeModuleVariant = shimSources !== undefined &&
|
|
921
|
+
runtimeBundleHash !== undefined &&
|
|
922
|
+
opts.codeStore !== undefined &&
|
|
923
|
+
codeBaseUrl !== undefined
|
|
924
|
+
? {
|
|
925
|
+
runtimeHash: runtimeBundleHash,
|
|
926
|
+
shimBaseUrl: `${codeBaseUrl.replace(/\/$/, "")}${RUNTIME_SHIMS_URL_PREFIX}/${runtimeBundleHash}`,
|
|
927
|
+
}
|
|
928
|
+
: undefined;
|
|
929
|
+
const mintCodeModuleUrl = codeModuleVariant !== undefined
|
|
930
|
+
? createCodeModuleUrlMinter({ runtimeHash: codeModuleVariant.runtimeHash })
|
|
931
|
+
: undefined;
|
|
855
932
|
// Lazy resolver: each render/update handler invocation looks up the
|
|
856
933
|
// request-context-derived absolute base inside the request scope
|
|
857
934
|
// (via AsyncLocalStorage). Static `publicBaseUrl` wins when set;
|
|
@@ -1463,18 +1540,22 @@ export function createGguiServer(opts = {}) {
|
|
|
1463
1540
|
//
|
|
1464
1541
|
// Content-addressable code delivery. When the operator
|
|
1465
1542
|
// wired `opts.codeStore`, forward it to the render
|
|
1466
|
-
// handler along with the base
|
|
1467
|
-
//
|
|
1468
|
-
//
|
|
1469
|
-
//
|
|
1470
|
-
//
|
|
1543
|
+
// handler along with the base URL the code-blob route
|
|
1544
|
+
// resolves to: the explicit `codeBaseUrl` (an edge-cached
|
|
1545
|
+
// asset host, ggui#522) or, absent that, the
|
|
1546
|
+
// `--public-base-url` (so the URL is reachable from a
|
|
1547
|
+
// remote host's iframe sandbox); with neither we fall
|
|
1548
|
+
// back to "no codeUrl emission" — the iframe then
|
|
1471
1549
|
// mounts through the live trio and the WS subscribe
|
|
1472
1550
|
// carries the render body, which is the delivery path a
|
|
1473
1551
|
// render-channel deployment already has.
|
|
1474
|
-
...(opts.codeStore &&
|
|
1552
|
+
...(opts.codeStore && codeBaseUrl !== undefined
|
|
1475
1553
|
? {
|
|
1476
1554
|
codeStore: opts.codeStore,
|
|
1477
|
-
codeBaseUrl
|
|
1555
|
+
codeBaseUrl,
|
|
1556
|
+
...(mintCodeModuleUrl !== undefined
|
|
1557
|
+
? { mintCodeModuleUrl }
|
|
1558
|
+
: {}),
|
|
1478
1559
|
}
|
|
1479
1560
|
: {}),
|
|
1480
1561
|
// Bootstrap-side mirror of the handshake's
|
|
@@ -1597,42 +1678,48 @@ export function createGguiServer(opts = {}) {
|
|
|
1597
1678
|
// `appMetadataStore` to register `ggui_list_themes`; absent ⇒
|
|
1598
1679
|
// tool omitted from `tools/list`.
|
|
1599
1680
|
...(opts.themes ? { themes: opts.themes } : {}),
|
|
1600
|
-
// Operator-class blueprint tool wiring.
|
|
1601
|
-
//
|
|
1602
|
-
//
|
|
1603
|
-
//
|
|
1604
|
-
//
|
|
1605
|
-
//
|
|
1606
|
-
//
|
|
1607
|
-
// the
|
|
1608
|
-
//
|
|
1609
|
-
//
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
//
|
|
1614
|
-
//
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1681
|
+
// Operator-class blueprint tool wiring. Explicit-wins: a
|
|
1682
|
+
// deployment that supplies `opts.opsBlueprint` gets that bundle
|
|
1683
|
+
// threaded through AS-IS (its own store/search/registry/
|
|
1684
|
+
// authorizer — the factory does not assemble or override it).
|
|
1685
|
+
// Otherwise falls back to the OSS default assembly below, which
|
|
1686
|
+
// threads the resolved blueprint store + search + generator
|
|
1687
|
+
// registry into defaultHandlers; the five `ggui_ops_*` tools
|
|
1688
|
+
// land on the control plane via their `audience: ['ops']` tag.
|
|
1689
|
+
// The `resolveLlm` + `blueprints` deps come from the same
|
|
1690
|
+
// source render reads, so generate dispatches through the same
|
|
1691
|
+
// credential + catalog path as live agent traffic.
|
|
1692
|
+
// listAllForApp wires only when the resolved store is the
|
|
1693
|
+
// in-memory adapter (which exposes it); cloud adapters bind
|
|
1694
|
+
// their own listAllForApp via the search seam.
|
|
1695
|
+
// The fallback is gated on `generators`: without it, the ops
|
|
1696
|
+
// `generate` path has no dispatch target; the
|
|
1697
|
+
// register/list/update/delete quartet could technically run
|
|
1698
|
+
// without it but the operator UX expects all five together, so
|
|
1699
|
+
// we gate the whole block on the registry.
|
|
1700
|
+
...(opts.opsBlueprint
|
|
1701
|
+
? { opsBlueprint: opts.opsBlueprint }
|
|
1702
|
+
: generators
|
|
1703
|
+
? buildOpsBlueprintDeps({
|
|
1704
|
+
registry: generators,
|
|
1705
|
+
blueprintStore,
|
|
1706
|
+
blueprintSearch,
|
|
1707
|
+
...(generationWithCache?.resolveLlm
|
|
1708
|
+
? { resolveLlm: generationWithCache.resolveLlm }
|
|
1627
1709
|
: {}),
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1710
|
+
...(generationWithCache?.blueprints
|
|
1711
|
+
? { blueprints: generationWithCache.blueprints }
|
|
1712
|
+
: opts.blueprintProvider
|
|
1713
|
+
? { blueprints: opts.blueprintProvider }
|
|
1714
|
+
: {}),
|
|
1715
|
+
// Mirror operator-authored blueprints into the cache
|
|
1716
|
+
// vectorStore so the agent-facing matchBlueprint exact-
|
|
1717
|
+
// key probe (handshake + render) finds them. Same bundle
|
|
1718
|
+
// the render handler + handshake negotiator already
|
|
1719
|
+
// consume.
|
|
1720
|
+
...(generationWithCache?.cache ? { cacheRegistry: generationWithCache.cache } : {}),
|
|
1721
|
+
})
|
|
1722
|
+
: {}),
|
|
1636
1723
|
// ggui_emit resolves the channel via
|
|
1637
1724
|
// a lazy getter so the handler captures whatever
|
|
1638
1725
|
// `channelForHealth` ends up pointing at after
|
|
@@ -1648,11 +1735,13 @@ export function createGguiServer(opts = {}) {
|
|
|
1648
1735
|
logger,
|
|
1649
1736
|
});
|
|
1650
1737
|
// Operator-class domain handlers (apps / orgs / connector-keys /
|
|
1651
|
-
// coupon). Built on EVERY path — they hang off their own
|
|
1652
|
-
// options, so a deployment that supplies a custom base
|
|
1653
|
-
// still gets the domains it wired. A name already claimed
|
|
1654
|
-
// list wins: that's how the cloud pod ships its own
|
|
1655
|
-
// `ggui_ops_create_app` while still picking up the rest of the family
|
|
1738
|
+
// coupon / blueprints). Built on EVERY path — they hang off their own
|
|
1739
|
+
// explicit options, so a deployment that supplies a custom base
|
|
1740
|
+
// handler list still gets the domains it wired. A name already claimed
|
|
1741
|
+
// by the base list wins: that's how the cloud pod ships its own
|
|
1742
|
+
// `ggui_ops_create_app` while still picking up the rest of the family,
|
|
1743
|
+
// and it is what keeps the blueprint family single-registered when the
|
|
1744
|
+
// default handler set already built it from the same bundle.
|
|
1656
1745
|
const baseHandlerNames = new Set(baseHandlers.map((h) => h.name));
|
|
1657
1746
|
const opsBundleHandlers = buildOpsBundleHandlers(opts).filter((h) => !baseHandlerNames.has(h.name));
|
|
1658
1747
|
const handlers = composeHandlersWithMounts(opsBundleHandlers.length > 0 ? [...baseHandlers, ...opsBundleHandlers] : baseHandlers, opts.mcpMounts);
|
|
@@ -1820,6 +1909,7 @@ export function createGguiServer(opts = {}) {
|
|
|
1820
1909
|
buildResourceValidator({
|
|
1821
1910
|
universalMcpPath: opts.universalMcpPath ?? "/mcp",
|
|
1822
1911
|
perAppRouting: opts.perAppRouting,
|
|
1912
|
+
controlPath: CONTROL_PATH,
|
|
1823
1913
|
}),
|
|
1824
1914
|
};
|
|
1825
1915
|
const oauthStorage = oauthConfig.storage ?? new InMemoryOAuthStorage();
|
|
@@ -1833,6 +1923,7 @@ export function createGguiServer(opts = {}) {
|
|
|
1833
1923
|
oauthConfig,
|
|
1834
1924
|
oauthStorage,
|
|
1835
1925
|
universalMcpPath: opts.universalMcpPath ?? "/mcp",
|
|
1926
|
+
controlPath: CONTROL_PATH,
|
|
1836
1927
|
...(opts.perAppRouting !== undefined
|
|
1837
1928
|
? {
|
|
1838
1929
|
perAppRouting: {
|
|
@@ -1859,6 +1950,7 @@ export function createGguiServer(opts = {}) {
|
|
|
1859
1950
|
info,
|
|
1860
1951
|
toolCount: handlers.length,
|
|
1861
1952
|
readinessChecks: opts.readinessChecks ?? [],
|
|
1953
|
+
advisoryChecks: opts.advisoryChecks ?? [],
|
|
1862
1954
|
getChannel: () => channelForHealth,
|
|
1863
1955
|
...(opts.threads !== undefined
|
|
1864
1956
|
? {
|
|
@@ -1952,6 +2044,13 @@ export function createGguiServer(opts = {}) {
|
|
|
1952
2044
|
...(opts.theme !== undefined && opts.theme.source !== "default"
|
|
1953
2045
|
? { themeMode: opts.theme.mode }
|
|
1954
2046
|
: {}),
|
|
2047
|
+
// The live pick reaches the read door too (ggui#539) — same
|
|
2048
|
+
// getter the render handler's deps carry, so the FIRST
|
|
2049
|
+
// resolution layer is identical across the tool-result
|
|
2050
|
+
// slice and the served shell.
|
|
2051
|
+
...(opts.themeProvider !== undefined
|
|
2052
|
+
? { themeProvider: opts.themeProvider }
|
|
2053
|
+
: {}),
|
|
1955
2054
|
// Resume contract — registry-only fallback. Wired
|
|
1956
2055
|
// when the blueprint vector store is available so the
|
|
1957
2056
|
// resource handler can rehydrate a render-evicted
|
|
@@ -1990,10 +2089,13 @@ export function createGguiServer(opts = {}) {
|
|
|
1990
2089
|
// shell that would never paint. Wiring both (the shape
|
|
1991
2090
|
// this factory produces) also means a fault on one
|
|
1992
2091
|
// degrades to the other instead of failing the read.
|
|
1993
|
-
...(opts.codeStore &&
|
|
2092
|
+
...(opts.codeStore && codeBaseUrl !== undefined
|
|
1994
2093
|
? {
|
|
1995
2094
|
codeStore: opts.codeStore,
|
|
1996
|
-
codeBaseUrl
|
|
2095
|
+
codeBaseUrl,
|
|
2096
|
+
...(mintCodeModuleUrl !== undefined
|
|
2097
|
+
? { mintCodeModuleUrl }
|
|
2098
|
+
: {}),
|
|
1997
2099
|
}
|
|
1998
2100
|
: {}),
|
|
1999
2101
|
// Bind the app-metadata store so the resource
|
|
@@ -2060,6 +2162,7 @@ export function createGguiServer(opts = {}) {
|
|
|
2060
2162
|
...(opts.allowedKinds !== undefined ? { allowedKinds: opts.allowedKinds } : {}),
|
|
2061
2163
|
...(resolvedInstructions !== undefined ? { instructions: resolvedInstructions } : {}),
|
|
2062
2164
|
...(opts.extraResources !== undefined ? { extraResources: opts.extraResources } : {}),
|
|
2165
|
+
...(opts.withholdResultMeta === true ? { withholdResultMeta: true } : {}),
|
|
2063
2166
|
};
|
|
2064
2167
|
// MCP wire endpoints (data plane: universal / per-app; control
|
|
2065
2168
|
// plane: /control; plus any isolated services) — see
|
|
@@ -2074,6 +2177,7 @@ export function createGguiServer(opts = {}) {
|
|
|
2074
2177
|
info,
|
|
2075
2178
|
handlers,
|
|
2076
2179
|
controlHandlers: controlService.handlers,
|
|
2180
|
+
controlOpsToolNames: controlService.opsToolNames,
|
|
2077
2181
|
mcpServices,
|
|
2078
2182
|
als,
|
|
2079
2183
|
appIdFromIdentity,
|
|
@@ -2103,6 +2207,20 @@ export function createGguiServer(opts = {}) {
|
|
|
2103
2207
|
? { hashed: { path: hashedRuntimePath, source: runtimeBundleBytes } }
|
|
2104
2208
|
: {}),
|
|
2105
2209
|
});
|
|
2210
|
+
// Static shim assets (ggui#522 slice 2) — the fetchable twins of
|
|
2211
|
+
// the data-url import shims, served immutable under the runtime
|
|
2212
|
+
// bundle's content hash. Mounted whenever the build shipped them
|
|
2213
|
+
// (independent of the code store: the `/code` variant route is the
|
|
2214
|
+
// usual consumer, but a foreign composition may rewrite against
|
|
2215
|
+
// these shims itself).
|
|
2216
|
+
if (shimSources !== undefined && runtimeBundleHash !== undefined) {
|
|
2217
|
+
mountShimRoutes({
|
|
2218
|
+
app,
|
|
2219
|
+
urlPrefix: RUNTIME_SHIMS_URL_PREFIX,
|
|
2220
|
+
runtimeHash: runtimeBundleHash,
|
|
2221
|
+
shims: shimSources,
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2106
2224
|
}
|
|
2107
2225
|
// R6 /state snapshot + R7 /events cursor-replay reads — see
|
|
2108
2226
|
// `./api-renders-routes.ts` for the wsToken auth posture, tenancy
|
|
@@ -2123,6 +2241,10 @@ export function createGguiServer(opts = {}) {
|
|
|
2123
2241
|
: {}),
|
|
2124
2242
|
...(opts.codeStore ? { codeStore: opts.codeStore } : {}),
|
|
2125
2243
|
...(opts.publicBaseUrl !== undefined ? { publicBaseUrl: opts.publicBaseUrl } : {}),
|
|
2244
|
+
// Asset host for the content-addressable URLs the /state read
|
|
2245
|
+
// composes (ggui#522) — session-API URLs keep the public origin.
|
|
2246
|
+
...(opts.codeBaseUrl !== undefined ? { codeBaseUrl: opts.codeBaseUrl } : {}),
|
|
2247
|
+
...(mintCodeModuleUrl !== undefined ? { mintCodeModuleUrl } : {}),
|
|
2126
2248
|
...(mintBootstrap ? { mintBootstrap } : {}),
|
|
2127
2249
|
resolveRuntimeUrl: resolveRuntimeUrlForResultMeta,
|
|
2128
2250
|
logger,
|
|
@@ -2146,7 +2268,14 @@ export function createGguiServer(opts = {}) {
|
|
|
2146
2268
|
// `./code-routes.ts` for the route contract (cache posture, CORS,
|
|
2147
2269
|
// hash validation).
|
|
2148
2270
|
if (opts.codeStore) {
|
|
2149
|
-
mountCodeRoutes({
|
|
2271
|
+
mountCodeRoutes({
|
|
2272
|
+
app,
|
|
2273
|
+
codeStore: opts.codeStore,
|
|
2274
|
+
logger,
|
|
2275
|
+
...(codeModuleVariant !== undefined
|
|
2276
|
+
? { moduleVariant: codeModuleVariant }
|
|
2277
|
+
: {}),
|
|
2278
|
+
});
|
|
2150
2279
|
}
|
|
2151
2280
|
// Pairing transport + auth bridge. Opt-in via `opts.pairing`. When
|
|
2152
2281
|
// enabled with defaults, we mint an `InMemoryPairingService` and wire
|
|
@@ -3049,7 +3178,7 @@ export function createGguiServer(opts = {}) {
|
|
|
3049
3178
|
* Build the {@link OAuthConfig.validateResource} callback from the
|
|
3050
3179
|
* deployment shape (RFC 8707).
|
|
3051
3180
|
*
|
|
3052
|
-
*
|
|
3181
|
+
* Three valid resource shapes are recognized:
|
|
3053
3182
|
* - **Universal** — exactly `${issuer}` when `universalMcpPath` is
|
|
3054
3183
|
* `/`, otherwise `${issuer}${universalMcpPath}`. Cloud
|
|
3055
3184
|
* `mcp.ggui.ai` collapses the bare-root case (the domain already
|
|
@@ -3057,6 +3186,11 @@ export function createGguiServer(opts = {}) {
|
|
|
3057
3186
|
* - **Per-app** — `${issuer}${perAppRouting.pathPrefix}/<appId>`
|
|
3058
3187
|
* where `<appId>` matches `perAppRouting.paramPattern`. Cloud
|
|
3059
3188
|
* uses `/apps` prefix + `[A-Za-z0-9]{8}`.
|
|
3189
|
+
* - **Control plane** — `${issuer}${controlPath}` (ggui#505). A
|
|
3190
|
+
* host naming `/control` consents through the same ceremony and
|
|
3191
|
+
* receives a UNIVERSAL key: the consent page extracts an appId
|
|
3192
|
+
* only from the per-app shape, and control-plane ops are
|
|
3193
|
+
* account-level by design.
|
|
3060
3194
|
*
|
|
3061
3195
|
* Anything else returns `false` → /authorize emits `invalid_target`
|
|
3062
3196
|
* per RFC 8707 §2 before showing consent. Defense-in-depth — the
|
|
@@ -3064,7 +3198,7 @@ export function createGguiServer(opts = {}) {
|
|
|
3064
3198
|
* can trust the value because it's already been validated.
|
|
3065
3199
|
*/
|
|
3066
3200
|
function buildResourceValidator(opts) {
|
|
3067
|
-
const { universalMcpPath, perAppRouting } = opts;
|
|
3201
|
+
const { universalMcpPath, perAppRouting, controlPath } = opts;
|
|
3068
3202
|
// Normalize a single trailing slash on the path-only-root form. RFC
|
|
3069
3203
|
// 3986 §6.2.3 says `https://host` and `https://host/` are equivalent
|
|
3070
3204
|
// when no other path segments follow. Some clients (claude.ai 2026-05)
|
|
@@ -3077,6 +3211,10 @@ function buildResourceValidator(opts) {
|
|
|
3077
3211
|
const universalResource = universalMcpPath === "/" ? issuer : `${issuer}${universalMcpPath}`;
|
|
3078
3212
|
if (stripTrailingSlash(resource) === stripTrailingSlash(universalResource))
|
|
3079
3213
|
return true;
|
|
3214
|
+
if (controlPath !== undefined &&
|
|
3215
|
+
stripTrailingSlash(resource) === stripTrailingSlash(`${issuer}${controlPath}`)) {
|
|
3216
|
+
return true;
|
|
3217
|
+
}
|
|
3080
3218
|
if (!perAppRouting)
|
|
3081
3219
|
return false;
|
|
3082
3220
|
const { paramPattern, pathPrefix = "" } = perAppRouting;
|