@ggui-ai/mcp-server 0.2.0-alpha.4 → 0.4.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin-blueprints-transport.d.ts.map +1 -1
- package/dist/admin-blueprints-transport.js +2 -1
- package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
- package/dist/admin-oauth-providers-transport.js +7 -5
- package/dist/api-renders-routes.d.ts +85 -0
- package/dist/api-renders-routes.d.ts.map +1 -0
- package/dist/api-renders-routes.js +372 -0
- package/dist/build-mcp.d.ts +2 -2
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +64 -5
- package/dist/code-routes.d.ts +47 -0
- package/dist/code-routes.d.ts.map +1 -0
- package/dist/code-routes.js +81 -0
- package/dist/code-store-fs.js +2 -2
- package/dist/console-auth.d.ts +10 -10
- package/dist/console-auth.d.ts.map +1 -1
- package/dist/console-auth.js +5 -5
- package/dist/console-blueprint-routes.d.ts +71 -0
- package/dist/console-blueprint-routes.d.ts.map +1 -0
- package/dist/console-blueprint-routes.js +348 -0
- package/dist/console-chat-routes.d.ts +80 -0
- package/dist/console-chat-routes.d.ts.map +1 -0
- package/dist/console-chat-routes.js +204 -0
- package/dist/console-config-routes.d.ts +37 -0
- package/dist/console-config-routes.d.ts.map +1 -0
- package/dist/console-config-routes.js +91 -0
- package/dist/console-headers.d.ts +1 -1
- package/dist/console-info-routes.d.ts +84 -0
- package/dist/console-info-routes.d.ts.map +1 -0
- package/dist/console-info-routes.js +135 -0
- package/dist/console-keys-routes.d.ts +50 -0
- package/dist/console-keys-routes.d.ts.map +1 -0
- package/dist/console-keys-routes.js +222 -0
- package/dist/console-llm-keys-routes.d.ts +47 -0
- package/dist/console-llm-keys-routes.d.ts.map +1 -0
- package/dist/console-llm-keys-routes.js +443 -0
- package/dist/console-mcp-tools-routes.d.ts +41 -0
- package/dist/console-mcp-tools-routes.d.ts.map +1 -0
- package/dist/console-mcp-tools-routes.js +60 -0
- package/dist/console-registry-routes.d.ts +66 -0
- package/dist/console-registry-routes.d.ts.map +1 -0
- package/dist/console-registry-routes.js +276 -0
- package/dist/console-session-routes.d.ts +89 -0
- package/dist/console-session-routes.d.ts.map +1 -0
- package/dist/console-session-routes.js +385 -0
- package/dist/console-sessions-routes.d.ts +52 -0
- package/dist/console-sessions-routes.d.ts.map +1 -0
- package/dist/console-sessions-routes.js +106 -0
- package/dist/console-static-routes.d.ts +54 -0
- package/dist/console-static-routes.d.ts.map +1 -0
- package/dist/console-static-routes.js +190 -0
- package/dist/console-theme-routes.d.ts +3 -3
- package/dist/console-theme-routes.js +1 -1
- package/dist/console-timeline.d.ts +5 -5
- package/dist/console-timeline.d.ts.map +1 -1
- package/dist/console-timeline.js +27 -26
- package/dist/console-welcome.js +2 -2
- package/dist/email-login.d.ts.map +1 -1
- package/dist/email-login.js +2 -3
- package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
- package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
- package/dist/ggui-session-channel/action-ingress.js +228 -0
- package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
- package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
- package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
- package/dist/ggui-session-channel/internal-types.d.ts +102 -0
- package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
- package/dist/ggui-session-channel/internal-types.js +6 -0
- package/dist/ggui-session-channel/outbound.d.ts +81 -0
- package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
- package/dist/ggui-session-channel/outbound.js +174 -0
- package/dist/ggui-session-channel/socket-router.d.ts +38 -0
- package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
- package/dist/ggui-session-channel/socket-router.js +213 -0
- package/dist/ggui-session-channel/subscribe.d.ts +165 -0
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
- package/dist/ggui-session-channel/subscribe.js +370 -0
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
- package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
- package/dist/ggui-session-channel.d.ts +425 -0
- package/dist/ggui-session-channel.d.ts.map +1 -0
- package/dist/ggui-session-channel.js +262 -0
- package/dist/health-routes.d.ts +76 -0
- package/dist/health-routes.d.ts.map +1 -0
- package/dist/health-routes.js +145 -0
- package/dist/index.d.ts +10 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -9
- package/dist/instructions-presets.d.ts +3 -3
- package/dist/instructions-presets.js +24 -24
- package/dist/llm-backed-negotiator.d.ts +68 -67
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +82 -221
- package/dist/mcp-apps-outbound.d.ts +47 -48
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +154 -177
- package/dist/mcp-endpoint-routes.d.ts +88 -0
- package/dist/mcp-endpoint-routes.d.ts.map +1 -0
- package/dist/mcp-endpoint-routes.js +359 -0
- package/dist/mcp-mounts.d.ts +2 -76
- package/dist/mcp-mounts.d.ts.map +1 -1
- package/dist/mcp-mounts.js +0 -76
- package/dist/oauth-as-routes.d.ts +60 -0
- package/dist/oauth-as-routes.d.ts.map +1 -0
- package/dist/oauth-as-routes.js +82 -0
- package/dist/oauth-clients-routes.d.ts +39 -0
- package/dist/oauth-clients-routes.d.ts.map +1 -0
- package/dist/oauth-clients-routes.js +87 -0
- package/dist/oauth-login-types.d.ts +1 -20
- package/dist/oauth-login-types.d.ts.map +1 -1
- package/dist/oauth-login-types.js +30 -7
- package/dist/oauth-login.d.ts.map +1 -1
- package/dist/oauth-login.js +3 -2
- package/dist/oauth-providers-store.d.ts.map +1 -1
- package/dist/oauth-providers-store.js +5 -5
- package/dist/oauth.d.ts +9 -8
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +41 -19
- package/dist/pairing-transport.d.ts.map +1 -1
- package/dist/pairing-transport.js +2 -1
- package/dist/request-context.d.ts +2 -2
- package/dist/request-context.js +2 -2
- package/dist/reserved-validators.d.ts.map +1 -1
- package/dist/reserved-validators.js +9 -1
- package/dist/route-param.d.ts +9 -0
- package/dist/route-param.d.ts.map +1 -0
- package/dist/route-param.js +10 -0
- package/dist/runtime-bundle-route.d.ts +43 -0
- package/dist/runtime-bundle-route.d.ts.map +1 -0
- package/dist/runtime-bundle-route.js +80 -0
- package/dist/schema-compat.d.ts +64 -62
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +23 -51
- package/dist/server.d.ts +179 -193
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +644 -3759
- package/dist/storage.d.ts +5 -5
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +5 -5
- package/dist/thread-transport.d.ts.map +1 -1
- package/dist/thread-transport.js +4 -3
- package/dist/user-session-auth.d.ts +7 -21
- package/dist/user-session-auth.d.ts.map +1 -1
- package/dist/user-session-auth.js +7 -28
- package/package.json +16 -15
- package/dist/mcp-apps-inbound.d.ts +0 -86
- package/dist/mcp-apps-inbound.d.ts.map +0 -1
- package/dist/mcp-apps-inbound.js +0 -283
- package/dist/render-channel.d.ts +0 -694
- package/dist/render-channel.d.ts.map +0 -1
- package/dist/render-channel.js +0 -1775
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing-transport.d.ts","sourceRoot":"","sources":["../src/pairing-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"pairing-transport.d.ts","sourceRoot":"","sources":["../src/pairing-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,qBAAqB,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CAmNN"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '@ggui-ai/protocol';
|
|
1
2
|
import { resolveIdentity, UnauthenticatedError } from './auth.js';
|
|
2
3
|
/** Default URL path the pairing-completion route is mounted at. */
|
|
3
4
|
export const DEFAULT_PAIRING_PATH = '/pair';
|
|
@@ -36,7 +37,7 @@ export function mountPairingTransport(app, opts) {
|
|
|
36
37
|
// stores verbatim.
|
|
37
38
|
app.post(path, async (req, res) => {
|
|
38
39
|
const reqLogger = opts.logger.child({ route: 'POST ' + path });
|
|
39
|
-
const body = (req.body
|
|
40
|
+
const body = isRecord(req.body) ? req.body : {};
|
|
40
41
|
const code = typeof body['code'] === 'string' ? body['code'] : undefined;
|
|
41
42
|
const deviceName = typeof body['deviceName'] === 'string' ? body['deviceName'] : undefined;
|
|
42
43
|
const remoteAddress = req.socket.remoteAddress ?? undefined;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-request context plumbing — AsyncLocalStorage backed.
|
|
3
3
|
*
|
|
4
4
|
* The capability-URL routes (`/r/<code>`, `/api/bootstrap/<code>`) and
|
|
5
|
-
* the
|
|
5
|
+
* the render/update tool result-meta builders all want to know the
|
|
6
6
|
* absolute public base URL of THIS server as seen by THIS client.
|
|
7
7
|
* That can't come from a static config in two common dev/OSS scenarios:
|
|
8
8
|
*
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Auto-derive applies ONLY to data the relevant route really should
|
|
32
32
|
* derive from the request:
|
|
33
|
-
* - runtimeUrl on
|
|
33
|
+
* - runtimeUrl on render/update bootstrap meta (this slice).
|
|
34
34
|
*
|
|
35
35
|
* Auto-derive MUST NOT apply to:
|
|
36
36
|
* - OAuth callback URLs (operator-config'd one-time per provider;
|
package/dist/request-context.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-request context plumbing — AsyncLocalStorage backed.
|
|
3
3
|
*
|
|
4
4
|
* The capability-URL routes (`/r/<code>`, `/api/bootstrap/<code>`) and
|
|
5
|
-
* the
|
|
5
|
+
* the render/update tool result-meta builders all want to know the
|
|
6
6
|
* absolute public base URL of THIS server as seen by THIS client.
|
|
7
7
|
* That can't come from a static config in two common dev/OSS scenarios:
|
|
8
8
|
*
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Auto-derive applies ONLY to data the relevant route really should
|
|
32
32
|
* derive from the request:
|
|
33
|
-
* - runtimeUrl on
|
|
33
|
+
* - runtimeUrl on render/update bootstrap meta (this slice).
|
|
34
34
|
*
|
|
35
35
|
* Auto-derive MUST NOT apply to:
|
|
36
36
|
* - OAuth callback URLs (operator-config'd one-time per provider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reserved-validators.d.ts","sourceRoot":"","sources":["../src/reserved-validators.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reserved-validators.d.ts","sourceRoot":"","sources":["../src/reserved-validators.ts"],"names":[],"mappings":"AAqCA,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,mBAAmB,CAAC;AA0C3B;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,IAAI,WAAW,CAC5D,MAAM,EACN,wBAAwB,CACzB,CAEA;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,EAC/D,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,GAClE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,CAS3D"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - {@link composePreviewReservedValidator} — returns a single-entry
|
|
14
14
|
* map binding `_ggui:preview` to the A2UI adapter. Call at server
|
|
15
15
|
* construction and feed into `extraReservedValidators` on
|
|
16
|
-
* `
|
|
16
|
+
* `GguiSessionChannelOptions` / `CreateGguiServerOptions`.
|
|
17
17
|
* - {@link mergeReservedValidators} — combine multiple validator maps
|
|
18
18
|
* when a caller provides their own extras AND the server wants to
|
|
19
19
|
* layer A2UI on top. Caller-provided entries win on key conflict.
|
|
@@ -22,6 +22,14 @@
|
|
|
22
22
|
* catalog filtering, no surface-id checking — those live in the A2UI
|
|
23
23
|
* runtime consumer. The adapter enforces only what `parseServerMessage`
|
|
24
24
|
* enforces: message-shape conformance to the V1 write-path union.
|
|
25
|
+
*
|
|
26
|
+
* The three copies (mcp-server / react / react-native) are documented
|
|
27
|
+
* structural mirrors: docstrings may differ per package, but the
|
|
28
|
+
* EXECUTABLE CODE must stay identical — guarded by the
|
|
29
|
+
* comment-stripped mirror gate in the SDK twin-parity suites
|
|
30
|
+
* (`ggui-react{,-native}/src/twin-parity.test.ts`,
|
|
31
|
+
* `CODE_IDENTICAL_MIRRORS`). Apply code changes to all three copies
|
|
32
|
+
* in the same slice.
|
|
25
33
|
*/
|
|
26
34
|
import { parseServerMessage, } from '@ggui-ai/preview-a2ui';
|
|
27
35
|
import { PREVIEW_CHANNEL, } from '@ggui-ai/protocol';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single (non-splat) route param / query value is a string at runtime, but
|
|
3
|
+
* @types/express@5 widens `req.params[name]` to `string | string[]` (path-to-
|
|
4
|
+
* regexp v8 supports repeatable params). Narrow to the single-string case;
|
|
5
|
+
* an array (multi-value) collapses to undefined so callers' existing
|
|
6
|
+
* missing-param guards handle it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function singleParam(v: string | string[] | undefined): string | undefined;
|
|
9
|
+
//# sourceMappingURL=route-param.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-param.d.ts","sourceRoot":"","sources":["../src/route-param.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEhF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single (non-splat) route param / query value is a string at runtime, but
|
|
3
|
+
* @types/express@5 widens `req.params[name]` to `string | string[]` (path-to-
|
|
4
|
+
* regexp v8 supports repeatable params). Narrow to the single-string case;
|
|
5
|
+
* an array (multi-value) collapses to undefined so callers' existing
|
|
6
|
+
* missing-param guards handle it.
|
|
7
|
+
*/
|
|
8
|
+
export function singleParam(v) {
|
|
9
|
+
return typeof v === 'string' ? v : undefined;
|
|
10
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iframe-runtime bundle static mount (C8 — plan §C8 Deliverable 2).
|
|
3
|
+
*
|
|
4
|
+
* Serves the `@ggui-ai/iframe-runtime` iframe runtime bundle from
|
|
5
|
+
* `runtimePath` (default `/_ggui/iframe-runtime.js`). The thin-shell
|
|
6
|
+
* HTML served from `ui://ggui/render` dynamic-script-loads this
|
|
7
|
+
* URL on boot — the rendering runtime is OUT of the shell and IN
|
|
8
|
+
* this separately-served file (C8 pivot, shrinking the shell from
|
|
9
|
+
* ~175 LOC inline JS to ~30 LOC wrapper).
|
|
10
|
+
*
|
|
11
|
+
* Routing discipline: registered BEFORE the console block because
|
|
12
|
+
* console's default `path` is `/` and its `express.static`
|
|
13
|
+
* + SPA-fallback would otherwise match `/_ggui/iframe-runtime.js` first
|
|
14
|
+
* (Express route table is order-sensitive). Registering early keeps
|
|
15
|
+
* the runtime path from leaking into the console's `index.html`
|
|
16
|
+
* fallback on a missing-bundle day.
|
|
17
|
+
*
|
|
18
|
+
* Missing-bundle posture mirrors the console mount: 503 with a
|
|
19
|
+
* `pnpm --filter @ggui-ai/iframe-runtime build` remediation hint. Silent
|
|
20
|
+
* 404 would be mistaken for "renderer is broken" instead of
|
|
21
|
+
* "renderer bundle wasn't built" — same debugging trap console
|
|
22
|
+
* avoids.
|
|
23
|
+
*/
|
|
24
|
+
import type { Express } from "express";
|
|
25
|
+
import type { Logger } from "./logger.js";
|
|
26
|
+
interface MountOptions {
|
|
27
|
+
/** Express app to mount onto. */
|
|
28
|
+
readonly app: Express;
|
|
29
|
+
/** HTTP route under which the bundle is mounted. */
|
|
30
|
+
readonly runtimePath: string;
|
|
31
|
+
/** Absolute path of the built bundle file on disk. */
|
|
32
|
+
readonly runtimeBundleFile: string;
|
|
33
|
+
/** Structured logger for the missing-bundle boot warning. */
|
|
34
|
+
readonly logger: Logger;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Mount `GET <runtimePath>` onto the express app. Returns nothing —
|
|
38
|
+
* the route self-registers. When the bundle file is missing on disk
|
|
39
|
+
* the mount degrades to a 503 with a build hint.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mountRuntimeBundleRoute(opts: MountOptions): void;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=runtime-bundle-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-bundle-route.d.ts","sourceRoot":"","sources":["../src/runtime-bundle-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAiDhE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iframe-runtime bundle static mount (C8 — plan §C8 Deliverable 2).
|
|
3
|
+
*
|
|
4
|
+
* Serves the `@ggui-ai/iframe-runtime` iframe runtime bundle from
|
|
5
|
+
* `runtimePath` (default `/_ggui/iframe-runtime.js`). The thin-shell
|
|
6
|
+
* HTML served from `ui://ggui/render` dynamic-script-loads this
|
|
7
|
+
* URL on boot — the rendering runtime is OUT of the shell and IN
|
|
8
|
+
* this separately-served file (C8 pivot, shrinking the shell from
|
|
9
|
+
* ~175 LOC inline JS to ~30 LOC wrapper).
|
|
10
|
+
*
|
|
11
|
+
* Routing discipline: registered BEFORE the console block because
|
|
12
|
+
* console's default `path` is `/` and its `express.static`
|
|
13
|
+
* + SPA-fallback would otherwise match `/_ggui/iframe-runtime.js` first
|
|
14
|
+
* (Express route table is order-sensitive). Registering early keeps
|
|
15
|
+
* the runtime path from leaking into the console's `index.html`
|
|
16
|
+
* fallback on a missing-bundle day.
|
|
17
|
+
*
|
|
18
|
+
* Missing-bundle posture mirrors the console mount: 503 with a
|
|
19
|
+
* `pnpm --filter @ggui-ai/iframe-runtime build` remediation hint. Silent
|
|
20
|
+
* 404 would be mistaken for "renderer is broken" instead of
|
|
21
|
+
* "renderer bundle wasn't built" — same debugging trap console
|
|
22
|
+
* avoids.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
/**
|
|
26
|
+
* Mount `GET <runtimePath>` onto the express app. Returns nothing —
|
|
27
|
+
* the route self-registers. When the bundle file is missing on disk
|
|
28
|
+
* the mount degrades to a 503 with a build hint.
|
|
29
|
+
*/
|
|
30
|
+
export function mountRuntimeBundleRoute(opts) {
|
|
31
|
+
const { app, runtimePath, runtimeBundleFile, logger } = opts;
|
|
32
|
+
if (existsSync(runtimeBundleFile)) {
|
|
33
|
+
app.get(runtimePath, (_req, res) => {
|
|
34
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
35
|
+
// Short cache — operators iterating on the renderer want
|
|
36
|
+
// fresh copies after rebuild. Production hardening (etag,
|
|
37
|
+
// long-term caching with hashed filenames) is a follow-on
|
|
38
|
+
// concern; same posture console takes.
|
|
39
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
40
|
+
// CORS: the bundle MUST be loadable from `<script type="module"
|
|
41
|
+
// src=...>` inside a sandboxed `srcdoc` iframe (the
|
|
42
|
+
// `<McpAppIframe>` mount path — see `packages/ggui-react/src/
|
|
43
|
+
// McpAppIframe/dispatch.ts::deriveResourceMountSource`). Such an
|
|
44
|
+
// iframe has the `null` origin and module-script fetches always
|
|
45
|
+
// run in CORS mode; without a permissive header browsers reject
|
|
46
|
+
// the response and the renderer never executes (Lane 1 specs
|
|
47
|
+
// pinning `data-ggui-mcp-app-iframe-lifecycle="code-ready"`
|
|
48
|
+
// hang to timeout). The bundle is public — it ships unmodified
|
|
49
|
+
// to anyone who fetched the page, so `*` is the right shape;
|
|
50
|
+
// there's no auth state on the renderer route to protect via a
|
|
51
|
+
// narrower origin allowlist. This pairs with the production
|
|
52
|
+
// `/ui://ggui/render` shell HTML setting `s.type='module'`
|
|
53
|
+
// (`mcp-apps-outbound.ts::GGUI_RENDER_SHELL_SCRIPT_BODY`).
|
|
54
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
55
|
+
// `dotfiles: 'allow'` — express@5's `res.sendFile` (send@1.x)
|
|
56
|
+
// splits the FULL absolute path into segments and applies its
|
|
57
|
+
// default `dotfiles: 'ignore'` policy, which 404s any file whose
|
|
58
|
+
// path crosses a dot-prefixed directory segment (e.g. a checkout
|
|
59
|
+
// under `~/.local/...` or a git worktree under `.../.git/...`).
|
|
60
|
+
// `runtimeBundleFile` is a fixed, server-controlled absolute path
|
|
61
|
+
// — never derived from the request — so there is no traversal
|
|
62
|
+
// surface to protect; allow the bundle to serve regardless of
|
|
63
|
+
// where the package install tree happens to live. express@4's
|
|
64
|
+
// `sendFile` did not subject the parent directories to this check.
|
|
65
|
+
res.sendFile(runtimeBundleFile, { dotfiles: "allow" });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
logger.warn("renderer_bundle_missing", {
|
|
70
|
+
bundleFile: runtimeBundleFile,
|
|
71
|
+
hint: "Run `pnpm --filter @ggui-ai/iframe-runtime build` to produce the bundle. Serving 503 from the mount point until it exists.",
|
|
72
|
+
});
|
|
73
|
+
app.get(runtimePath, (_req, res) => {
|
|
74
|
+
res
|
|
75
|
+
.status(503)
|
|
76
|
+
.type("text/plain")
|
|
77
|
+
.send("renderer bundle not built. Run:\n pnpm --filter @ggui-ai/iframe-runtime build\n");
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/schema-compat.d.ts
CHANGED
|
@@ -1,59 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema compatibility check — verifies that a
|
|
3
|
-
* `actionSpec`
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
2
|
+
* Schema compatibility check — verifies that a GguiSession's declared
|
|
3
|
+
* `actionSpec` schemas line up with the input schemas of the
|
|
4
|
+
* `nextStep` tools they hint at. Wired at two canonical check points:
|
|
5
|
+
* `ggui_render` validation (defensive, fires when the generator
|
|
6
|
+
* eventually emits contract) and blueprint registration (the console
|
|
7
|
+
* blueprint-try endpoint — the real-world site where a GguiSession
|
|
8
|
+
* with a pre-declared `actionSpec` and tool hints lands on the
|
|
9
|
+
* server).
|
|
10
10
|
*
|
|
11
11
|
* **Algorithmic primitive** lives in `@ggui-ai/protocol`:
|
|
12
12
|
*
|
|
13
13
|
* - `isSchemaSubset(superset, subset)` answers "can every value
|
|
14
14
|
* subset accepts also pass superset?".
|
|
15
15
|
* - `zodToJsonSchema(...)` converts a tool's ZodRawShape
|
|
16
|
-
* `inputSchema`
|
|
17
|
-
*
|
|
16
|
+
* `inputSchema` to the JsonSchema shape the subset algorithm
|
|
17
|
+
* consumes.
|
|
18
18
|
*
|
|
19
|
-
* **What this module adds.** A small layer that walks a
|
|
20
|
-
* `actionSpec
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* {@link SchemaCompatMode} policy flag.
|
|
19
|
+
* **What this module adds.** A small layer that walks a GguiSession's
|
|
20
|
+
* `actionSpec`, resolves each declared `nextStep` hint against a
|
|
21
|
+
* toolName → ZodRawShape registry, runs the subset check, and reports
|
|
22
|
+
* a stable {@link SchemaCompatReport}. The caller (console endpoint,
|
|
23
|
+
* render handler) decides whether a non-empty report should throw,
|
|
24
|
+
* warn, or be ignored per the {@link SchemaCompatMode} policy flag.
|
|
26
25
|
*
|
|
27
|
-
* **Direction semantics (load-bearing).**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* returns MUST be accepted by the channel schema (otherwise a
|
|
36
|
-
* tool-fired refresh would emit a payload that subscribers
|
|
37
|
-
* reject). The channel schema is therefore the PERMISSIVE side
|
|
38
|
-
* and the tool's return is the restricted side. Compat relation:
|
|
39
|
-
* `isSchemaSubset(channelSchema, toolReturnSchema)` — i.e.
|
|
40
|
-
* "every toolReturn-accepted value is also channelSchema-
|
|
41
|
-
* accepted".
|
|
42
|
-
*
|
|
43
|
-
* Note: the `StreamChannelEntry.schema` docstring phrases this
|
|
44
|
-
* as "the tool's returns MUST be a superset of channel schema".
|
|
45
|
-
* That historical phrasing is author-facing and slightly
|
|
46
|
-
* misleading if read literally as set-theoretic superset of
|
|
47
|
-
* accepted-values; the semantic intent is the one encoded here
|
|
48
|
-
* — EVERY tool return passes channel validation.
|
|
26
|
+
* **Direction semantics (load-bearing).** For `actionSpec[name]
|
|
27
|
+
* .schema` + `nextStep`: the action's payload is what the agent
|
|
28
|
+
* forwards INTO the hinted tool, so the action schema must be a
|
|
29
|
+
* subset of the tool's `inputSchema`. Compat relation:
|
|
30
|
+
* `isSchemaSubset(toolInputSchema, actionSchema)`. The check is
|
|
31
|
+
* advisory by nature — `nextStep` is a hint the agent owns — but a
|
|
32
|
+
* mismatch on a same-server tool is an author bug worth failing
|
|
33
|
+
* loudly at registration time.
|
|
49
34
|
*/
|
|
50
35
|
import type { ZodRawShape } from 'zod';
|
|
51
|
-
import { type SubsetViolation, type ActionSpec
|
|
36
|
+
import { type SubsetViolation, type ActionSpec } from '@ggui-ai/protocol';
|
|
52
37
|
/**
|
|
53
38
|
* Stance the host takes when a check surfaces violations.
|
|
54
39
|
*
|
|
55
40
|
* - `'reject'` (default) — throw {@link SchemaCompatError} so the
|
|
56
|
-
* containing request fails before the
|
|
41
|
+
* containing request fails before the render commits /
|
|
57
42
|
* before the blueprint registers. The canonical enforcement
|
|
58
43
|
* posture for launch.
|
|
59
44
|
* - `'warn'` — return the report without throwing. The caller is
|
|
@@ -77,25 +62,33 @@ export declare const DEFAULT_SCHEMA_COMPAT_MODE: SchemaCompatMode;
|
|
|
77
62
|
*/
|
|
78
63
|
export interface SchemaCompatFinding {
|
|
79
64
|
/**
|
|
80
|
-
* Which spec side produced this finding. `'action'`
|
|
81
|
-
* `actionSpec[name]
|
|
82
|
-
*
|
|
83
|
-
*
|
|
65
|
+
* Which spec side produced this finding. Always `'action'` — the
|
|
66
|
+
* only spec side carrying a tool reference is `actionSpec[name]
|
|
67
|
+
* .nextStep`. Kept as a literal field so consumers that render the
|
|
68
|
+
* cause have a stable discriminator.
|
|
84
69
|
*/
|
|
85
|
-
readonly kind: 'action'
|
|
70
|
+
readonly kind: 'action';
|
|
86
71
|
/** Action name or channel name. */
|
|
87
72
|
readonly specName: string;
|
|
88
73
|
/** Tool name the spec referenced. */
|
|
89
74
|
readonly toolName: string;
|
|
90
75
|
/** One of:
|
|
91
|
-
* - `'tool-not-found'` — the
|
|
92
|
-
* the composed handler set
|
|
93
|
-
*
|
|
94
|
-
* before any envelope reaches the agentic loop.)
|
|
76
|
+
* - `'tool-not-found'` — the hinted tool is not registered on
|
|
77
|
+
* the composed handler set (and not declared in the contract's
|
|
78
|
+
* `agentCapabilities.tools` cross-MCP catalog).
|
|
95
79
|
* - `'schema-mismatch'` — the subset check failed with at least
|
|
96
80
|
* one violation. See {@link SchemaCompatFinding.violations}.
|
|
97
81
|
*/
|
|
98
82
|
readonly reason: 'tool-not-found' | 'schema-mismatch';
|
|
83
|
+
/**
|
|
84
|
+
* Whether this finding blocks the render (`'error'`) or is purely
|
|
85
|
+
* advisory (`'warn'`). Defaults to error severity when omitted — the
|
|
86
|
+
* throw gate treats any finding lacking `severity: 'warn'` as a
|
|
87
|
+
* hard error. An action `nextStep` tool-not-found is tagged `'warn'`
|
|
88
|
+
* because `nextStep` is a documented HINT the agent owns and ggui
|
|
89
|
+
* never dispatches; an unresolved one must not block the render.
|
|
90
|
+
*/
|
|
91
|
+
readonly severity?: 'error' | 'warn';
|
|
99
92
|
/** Subset violations carried through for rich error rendering.
|
|
100
93
|
* Empty array when `reason: 'tool-not-found'`. */
|
|
101
94
|
readonly violations: readonly SubsetViolation[];
|
|
@@ -111,18 +104,17 @@ export interface SchemaCompatReport {
|
|
|
111
104
|
}
|
|
112
105
|
/**
|
|
113
106
|
* Narrow shape of what the check helper needs from each registered
|
|
114
|
-
* tool. Satisfied by `SharedHandler<ZodRawShape, ZodRawShape>` —
|
|
115
|
-
*
|
|
107
|
+
* tool. Satisfied by `SharedHandler<ZodRawShape, ZodRawShape>` —
|
|
108
|
+
* `name` + `inputSchema` are the only consumed inputs.
|
|
116
109
|
*/
|
|
117
110
|
export interface ToolSchemaRef {
|
|
118
111
|
readonly name: string;
|
|
119
112
|
readonly inputSchema: ZodRawShape;
|
|
120
|
-
readonly outputSchema: ZodRawShape;
|
|
121
113
|
}
|
|
122
114
|
/**
|
|
123
|
-
* Narrow shape of the
|
|
124
|
-
*
|
|
125
|
-
* object carrying them, so both `@ggui-ai/protocol::
|
|
115
|
+
* Narrow shape of the GguiSession subset the checker consumes — the
|
|
116
|
+
* `actionSpec` PLUS the contract's own tool catalog. Accepts any
|
|
117
|
+
* object carrying them, so both `@ggui-ai/protocol::GguiSession` and
|
|
126
118
|
* the console endpoint's manifest contract shape work without a cast.
|
|
127
119
|
*
|
|
128
120
|
* `agentCapabilities.tools` is the contract author's declared catalog
|
|
@@ -133,9 +125,8 @@ export interface ToolSchemaRef {
|
|
|
133
125
|
* schema check (we can't validate a remote server's tool schema from
|
|
134
126
|
* here; the agent owns the cross-MCP call).
|
|
135
127
|
*/
|
|
136
|
-
export interface
|
|
128
|
+
export interface GguiSessionContractShape {
|
|
137
129
|
readonly actionSpec?: ActionSpec;
|
|
138
|
-
readonly streamSpec?: StreamSpec;
|
|
139
130
|
readonly agentCapabilities?: {
|
|
140
131
|
readonly tools?: Readonly<Record<string, unknown>>;
|
|
141
132
|
};
|
|
@@ -150,9 +141,8 @@ export declare class SchemaCompatError extends Error {
|
|
|
150
141
|
constructor(report: SchemaCompatReport, context: string);
|
|
151
142
|
}
|
|
152
143
|
/**
|
|
153
|
-
* Check a
|
|
154
|
-
*
|
|
155
|
-
* semantics.
|
|
144
|
+
* Check a GguiSession's `actionSpec` entries against the tool
|
|
145
|
+
* registry. See {@link SchemaCompatMode} for policy semantics.
|
|
156
146
|
*
|
|
157
147
|
* Returns a {@link SchemaCompatReport}. When `mode === 'reject'`
|
|
158
148
|
* AND findings exist, throws {@link SchemaCompatError} instead —
|
|
@@ -163,5 +153,17 @@ export declare class SchemaCompatError extends Error {
|
|
|
163
153
|
* up in thrown error messages so an operator reading logs sees
|
|
164
154
|
* which ingress surfaced the mismatch.
|
|
165
155
|
*/
|
|
166
|
-
export declare function checkRenderSchemaCompat(render:
|
|
156
|
+
export declare function checkRenderSchemaCompat(render: GguiSessionContractShape, tools: Iterable<ToolSchemaRef>, mode: SchemaCompatMode, context: string): SchemaCompatReport;
|
|
157
|
+
/**
|
|
158
|
+
* Does this report carry at least one finding that should BLOCK the
|
|
159
|
+
* render? A finding blocks unless it is explicitly advisory
|
|
160
|
+
* (`severity: 'warn'`) — i.e. anything lacking `severity: 'warn'`
|
|
161
|
+
* counts as a hard error (default-error semantics). The single
|
|
162
|
+
* source of truth for the throw gate, shared by the internal
|
|
163
|
+
* `'reject'`-mode gate above AND any external host that runs the
|
|
164
|
+
* check in `'warn'` mode and owns its own `ggui_render` enforcement.
|
|
165
|
+
* Keeping ONE predicate guarantees the gates of every deployment of
|
|
166
|
+
* this server family can never silently diverge.
|
|
167
|
+
*/
|
|
168
|
+
export declare function hasErrorFinding(report: SchemaCompatReport): boolean;
|
|
167
169
|
//# sourceMappingURL=schema-compat.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAA2B,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACtD;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC;uDACmD;IACnD,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACnD;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACpD,CAAC;CACH;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBACxB,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAKxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,wBAAwB,EAChC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC9B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,GACd,kBAAkB,CAuFpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAEnE"}
|
package/dist/schema-compat.js
CHANGED
|
@@ -18,9 +18,8 @@ export class SchemaCompatError extends Error {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Check a
|
|
22
|
-
*
|
|
23
|
-
* semantics.
|
|
21
|
+
* Check a GguiSession's `actionSpec` entries against the tool
|
|
22
|
+
* registry. See {@link SchemaCompatMode} for policy semantics.
|
|
24
23
|
*
|
|
25
24
|
* Returns a {@link SchemaCompatReport}. When `mode === 'reject'`
|
|
26
25
|
* AND findings exist, throws {@link SchemaCompatError} instead —
|
|
@@ -77,6 +76,10 @@ export function checkRenderSchemaCompat(render, tools, mode, context) {
|
|
|
77
76
|
specName: actionName,
|
|
78
77
|
toolName,
|
|
79
78
|
reason: 'tool-not-found',
|
|
79
|
+
// Advisory: `nextStep` is a documented HINT the agent owns and
|
|
80
|
+
// ggui never dispatches, so an unresolved one must not block
|
|
81
|
+
// the render. Warn-only — the throw gate skips it.
|
|
82
|
+
severity: 'warn',
|
|
80
83
|
violations: [],
|
|
81
84
|
});
|
|
82
85
|
continue;
|
|
@@ -102,71 +105,40 @@ export function checkRenderSchemaCompat(render, tools, mode, context) {
|
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
|
-
// streamSpec — each channel.tool's outputSchema must be a superset
|
|
106
|
-
// of channel.schema (inverted direction from actions).
|
|
107
|
-
const streamSpec = render.streamSpec ?? {};
|
|
108
|
-
for (const [channelName, entry] of Object.entries(streamSpec)) {
|
|
109
|
-
if (!entry || typeof entry !== 'object')
|
|
110
|
-
continue;
|
|
111
|
-
const toolName = entry.tool;
|
|
112
|
-
if (typeof toolName !== 'string' || toolName.length === 0)
|
|
113
|
-
continue;
|
|
114
|
-
const channelSchema = entry.schema;
|
|
115
|
-
if (!channelSchema)
|
|
116
|
-
continue;
|
|
117
|
-
const tool = byName.get(toolName);
|
|
118
|
-
if (!tool) {
|
|
119
|
-
// Cross-MCP escape hatch — symmetric with the actionSpec arm.
|
|
120
|
-
if (contractDeclaredTools.has(toolName))
|
|
121
|
-
continue;
|
|
122
|
-
findings.push({
|
|
123
|
-
kind: 'stream',
|
|
124
|
-
specName: channelName,
|
|
125
|
-
toolName,
|
|
126
|
-
reason: 'tool-not-found',
|
|
127
|
-
violations: [],
|
|
128
|
-
});
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
const toolOutput = zodToJsonSchema(tool.outputSchema);
|
|
132
|
-
// Direction: every tool-returned value must be accepted by the
|
|
133
|
-
// channel schema. Channel is the superset / permissive side,
|
|
134
|
-
// tool-return is the restricted side.
|
|
135
|
-
const result = isSchemaSubset(channelSchema, toolOutput);
|
|
136
|
-
if (!result.compatible) {
|
|
137
|
-
findings.push({
|
|
138
|
-
kind: 'stream',
|
|
139
|
-
specName: channelName,
|
|
140
|
-
toolName,
|
|
141
|
-
reason: 'schema-mismatch',
|
|
142
|
-
violations: result.violations,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
108
|
const report = {
|
|
147
109
|
compatible: findings.length === 0,
|
|
148
110
|
findings,
|
|
149
111
|
};
|
|
150
|
-
if (mode === 'reject' &&
|
|
112
|
+
if (mode === 'reject' && hasErrorFinding(report)) {
|
|
151
113
|
throw new SchemaCompatError(report, context);
|
|
152
114
|
}
|
|
153
115
|
return report;
|
|
154
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Does this report carry at least one finding that should BLOCK the
|
|
119
|
+
* render? A finding blocks unless it is explicitly advisory
|
|
120
|
+
* (`severity: 'warn'`) — i.e. anything lacking `severity: 'warn'`
|
|
121
|
+
* counts as a hard error (default-error semantics). The single
|
|
122
|
+
* source of truth for the throw gate, shared by the internal
|
|
123
|
+
* `'reject'`-mode gate above AND any external host that runs the
|
|
124
|
+
* check in `'warn'` mode and owns its own `ggui_render` enforcement.
|
|
125
|
+
* Keeping ONE predicate guarantees the gates of every deployment of
|
|
126
|
+
* this server family can never silently diverge.
|
|
127
|
+
*/
|
|
128
|
+
export function hasErrorFinding(report) {
|
|
129
|
+
return report.findings.some((f) => f.severity !== 'warn');
|
|
130
|
+
}
|
|
155
131
|
/**
|
|
156
132
|
* Format a report into a human-readable message suitable for a
|
|
157
133
|
* thrown error or a log line. Output is deterministic — sorted
|
|
158
|
-
* by `
|
|
134
|
+
* by `specName` — so tests can pattern-match on the
|
|
159
135
|
* exact string without ordering flakes.
|
|
160
136
|
*/
|
|
161
137
|
function formatReport(report, context) {
|
|
162
138
|
if (report.compatible) {
|
|
163
139
|
return `${context}: SCHEMA_MISMATCH_ERROR (no findings — internal error)`;
|
|
164
140
|
}
|
|
165
|
-
const sorted = [...report.findings].sort((a, b) =>
|
|
166
|
-
if (a.kind !== b.kind)
|
|
167
|
-
return a.kind < b.kind ? -1 : 1;
|
|
168
|
-
return a.specName < b.specName ? -1 : a.specName > b.specName ? 1 : 0;
|
|
169
|
-
});
|
|
141
|
+
const sorted = [...report.findings].sort((a, b) => a.specName < b.specName ? -1 : a.specName > b.specName ? 1 : 0);
|
|
170
142
|
const lines = sorted.map((f) => {
|
|
171
143
|
if (f.reason === 'tool-not-found') {
|
|
172
144
|
return `- ${f.kind} "${f.specName}" references tool "${f.toolName}" which is not registered`;
|