@ggui-ai/mcp-server 0.14.0 → 0.16.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/README.md +20 -0
- package/dist/api-renders-routes.d.ts +0 -7
- package/dist/api-renders-routes.d.ts.map +1 -1
- package/dist/api-renders-routes.js +5 -5
- package/dist/api-renders-stream-route.js +4 -4
- package/dist/auth.d.ts +3 -3
- package/dist/auth.js +6 -6
- package/dist/build-mcp.d.ts +1 -1
- package/dist/console-blueprint-routes.js +2 -2
- package/dist/console-chat-routes.d.ts +1 -1
- package/dist/console-chat-routes.js +1 -1
- package/dist/console-keys-routes.js +2 -2
- package/dist/console-llm-keys-routes.d.ts +1 -1
- package/dist/console-llm-keys-routes.js +5 -5
- package/dist/console-registry-routes.d.ts +1 -1
- package/dist/console-registry-routes.js +1 -1
- package/dist/console-theme-routes.d.ts +1 -1
- package/dist/console-theme-routes.js +1 -1
- package/dist/control-service.d.ts +3 -3
- package/dist/control-service.js +3 -3
- package/dist/ggui-session-channel/socket-router.js +2 -2
- package/dist/ggui-session-channel/subscribe.d.ts +1 -1
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscribe.js +2 -2
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +1 -1
- package/dist/ggui-session-channel/subscriber-lifecycle.js +1 -1
- package/dist/ggui-session-channel.d.ts +4 -4
- package/dist/ggui-session-channel.js +4 -4
- package/dist/health-routes.d.ts +7 -6
- package/dist/health-routes.d.ts.map +1 -1
- package/dist/health-routes.js +8 -7
- package/dist/index.js +1 -1
- package/dist/instructions-presets.js +5 -5
- package/dist/mcp-apps-outbound.d.ts +17 -11
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +53 -25
- package/dist/mcp-endpoint-routes.d.ts +12 -4
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +89 -30
- package/dist/oauth-as-routes.d.ts +1 -1
- package/dist/oauth.d.ts +3 -3
- package/dist/oauth.js +3 -3
- package/dist/render-read-gate.d.ts +3 -3
- package/dist/render-read-gate.js +2 -2
- package/dist/runtime-bundle-hash.d.ts +1 -1
- package/dist/runtime-bundle-hash.js +1 -1
- package/dist/schema-compat.d.ts +2 -1
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +5 -5
- package/dist/server.d.ts +48 -76
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +29 -41
- package/dist/thread-transport.js +1 -1
- package/package.json +14 -14
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* POST <universalMcpPath> — DATA PLANE: agent+runtime tools
|
|
5
5
|
* (default `/mcp`)
|
|
6
|
-
* POST <pathPrefix>/:appId — data plane, per-
|
|
6
|
+
* POST <pathPrefix>/:appId — data plane, per-app variant
|
|
7
7
|
* (opt-in via `perAppRouting`)
|
|
8
8
|
* POST /control — CONTROL PLANE: design-time
|
|
9
9
|
* spec/discovery (anonymous) +
|
|
@@ -31,13 +31,50 @@
|
|
|
31
31
|
* (`agent` / `runtime` / `protocol` / `ops`) and the wire-name prefix
|
|
32
32
|
* rules.
|
|
33
33
|
*/
|
|
34
|
-
import { isRecord } from "@ggui-ai/protocol";
|
|
34
|
+
import { MCP_ERROR_CODES, isRecord } from "@ggui-ai/protocol";
|
|
35
35
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
36
36
|
import { randomUUID } from "node:crypto";
|
|
37
37
|
import { resolveIdentity, UnauthenticatedError } from "./auth.js";
|
|
38
38
|
import { buildMcpServer } from "./build-mcp.js";
|
|
39
39
|
import { CONTROL_PATH, DATA_PLANE_AUDIENCES, filterHandlersByAudience, } from "./control-service.js";
|
|
40
40
|
import { buildWwwAuthenticate, resolveIssuerUrl } from "./oauth.js";
|
|
41
|
+
/** The JSON-RPC error object a mapped result becomes on the wire. */
|
|
42
|
+
function jsonRpcError(mapped) {
|
|
43
|
+
return {
|
|
44
|
+
code: mapped.code,
|
|
45
|
+
message: mapped.message,
|
|
46
|
+
...(mapped.data !== undefined ? { data: mapped.data } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** The two statuses an authorization refusal may carry — a mapper is bounded to them. */
|
|
50
|
+
const AUTHORIZATION_REFUSAL_STATUSES = new Set([401, 403]);
|
|
51
|
+
/**
|
|
52
|
+
* A deployment's error mapper may attach JSON-RPC `data` (and its own
|
|
53
|
+
* `code` / `message` / headers) to a per-app authorization refusal, so a
|
|
54
|
+
* client can read a structured reason instead of parsing prose. The
|
|
55
|
+
* refusal stays a 401 or a 403: a mapper answering any other status, or
|
|
56
|
+
* throwing, is ignored and logged, and the default-deny 403 stands
|
|
57
|
+
* byte-identical to a deployment with no mapper at all.
|
|
58
|
+
*/
|
|
59
|
+
function mapAuthorizationRefusal(err, errorMapper, log) {
|
|
60
|
+
if (!errorMapper)
|
|
61
|
+
return undefined;
|
|
62
|
+
let mapped;
|
|
63
|
+
try {
|
|
64
|
+
mapped = errorMapper(err);
|
|
65
|
+
}
|
|
66
|
+
catch (mapperErr) {
|
|
67
|
+
log.warn("error_mapper_failed", { error: String(mapperErr) });
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (mapped === undefined)
|
|
71
|
+
return undefined;
|
|
72
|
+
if (!AUTHORIZATION_REFUSAL_STATUSES.has(mapped.status)) {
|
|
73
|
+
log.warn("per_app_authorize_mapper_out_of_bounds", { status: mapped.status });
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return mapped;
|
|
77
|
+
}
|
|
41
78
|
/**
|
|
42
79
|
* Resolve the resource path that `WWW-Authenticate` should point at
|
|
43
80
|
* for the current request. Per-app `/mcp` requests
|
|
@@ -113,7 +150,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
113
150
|
identity = { identity: { kind: "builder" }, source: "anonymous" };
|
|
114
151
|
}
|
|
115
152
|
else {
|
|
116
|
-
// Diagnostic: did a Bearer credential actually reach the
|
|
153
|
+
// Diagnostic: did a Bearer credential actually reach the server on
|
|
117
154
|
// this request? This splits "the client/transport never sent one"
|
|
118
155
|
// from "we received it but the adapter rejected it" — otherwise
|
|
119
156
|
// indistinguishable in `auth_failed`. Only a short, non-secret
|
|
@@ -145,7 +182,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
145
182
|
}
|
|
146
183
|
res.status(401).json({
|
|
147
184
|
jsonrpc: "2.0",
|
|
148
|
-
error: { code:
|
|
185
|
+
error: { code: MCP_ERROR_CODES.UNAUTHORIZED, message: err.message },
|
|
149
186
|
id: null,
|
|
150
187
|
});
|
|
151
188
|
return;
|
|
@@ -171,7 +208,10 @@ export function mountMcpEndpoints(opts) {
|
|
|
171
208
|
reqLogger.warn("federated_identity_rejected", { route: req.path });
|
|
172
209
|
res.status(403).json({
|
|
173
210
|
jsonrpc: "2.0",
|
|
174
|
-
error: {
|
|
211
|
+
error: {
|
|
212
|
+
code: MCP_ERROR_CODES.UNAUTHORIZED,
|
|
213
|
+
message: "federated identities are not permitted on this route",
|
|
214
|
+
},
|
|
175
215
|
id: null,
|
|
176
216
|
});
|
|
177
217
|
return;
|
|
@@ -196,7 +236,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
196
236
|
res.status(401).json({
|
|
197
237
|
jsonrpc: "2.0",
|
|
198
238
|
error: {
|
|
199
|
-
code:
|
|
239
|
+
code: MCP_ERROR_CODES.UNAUTHORIZED,
|
|
200
240
|
message: `${opsToolName} is an operator tool and needs an authenticated caller. ` +
|
|
201
241
|
`Present a bearer token this deployment accepts, or complete the OAuth flow ` +
|
|
202
242
|
`advertised in WWW-Authenticate (universal connector keys come from the ` +
|
|
@@ -207,10 +247,10 @@ export function mountMcpEndpoints(opts) {
|
|
|
207
247
|
return;
|
|
208
248
|
}
|
|
209
249
|
}
|
|
210
|
-
// Per-
|
|
250
|
+
// Per-app URL routing. When `perAppRouting`
|
|
211
251
|
// is configured AND the request matched the per-app path
|
|
212
252
|
// `/:${paramName}/mcp`, Express populates `req.params[paramName]`
|
|
213
|
-
// with the validated
|
|
253
|
+
// with the validated app id. Use it as `ctx.appId` for this
|
|
214
254
|
// request, overriding `appIdFromIdentity`. The universal `/mcp`
|
|
215
255
|
// route doesn't have the param so it falls through to the
|
|
216
256
|
// identity-based resolution.
|
|
@@ -218,11 +258,14 @@ export function mountMcpEndpoints(opts) {
|
|
|
218
258
|
const hasUrlAppId = typeof urlAppId === "string" && urlAppId.length > 0;
|
|
219
259
|
// Per-app authorize hook — when the deployment configured
|
|
220
260
|
// `perAppRouting.authorize` AND the request matched the per-app
|
|
221
|
-
// path, invoke the callback. Throwing
|
|
222
|
-
//
|
|
223
|
-
//
|
|
261
|
+
// path, invoke the callback. Throwing refuses before the MCP
|
|
262
|
+
// handler ever sees the request, which is the boundary that
|
|
263
|
+
// prevents cross-user blueprint reads when a deployment's own tools bypass
|
|
224
264
|
// AppSync owner-auth via raw DDB. Universal-endpoint requests
|
|
225
|
-
// skip this entirely (no urlAppId).
|
|
265
|
+
// skip this entirely (no urlAppId). The deployment's `errorMapper`
|
|
266
|
+
// may give the refusal a structured JSON-RPC `data` (bounded to
|
|
267
|
+
// 401 / 403, see `mapAuthorizationRefusal`); otherwise — and for
|
|
268
|
+
// every mapping outside those bounds — the default-deny 403 stands.
|
|
226
269
|
if (hasUrlAppId && perAppRouting?.authorize) {
|
|
227
270
|
try {
|
|
228
271
|
await perAppRouting.authorize(urlAppId, identity);
|
|
@@ -232,9 +275,17 @@ export function mountMcpEndpoints(opts) {
|
|
|
232
275
|
urlAppId,
|
|
233
276
|
reason: err instanceof Error ? err.message : String(err),
|
|
234
277
|
});
|
|
235
|
-
|
|
278
|
+
const mapped = mapAuthorizationRefusal(err, errorMapper, reqLogger);
|
|
279
|
+
if (mapped?.headers)
|
|
280
|
+
res.set(mapped.headers);
|
|
281
|
+
res.status(mapped?.status ?? 403).json({
|
|
236
282
|
jsonrpc: "2.0",
|
|
237
|
-
|
|
283
|
+
// #836: a first-party server never answers -32000 — that is the SDK
|
|
284
|
+
// client's own ConnectionClosed number, so a refusal and a dropped
|
|
285
|
+
// socket would read the same. An authorization refusal is UNAUTHORIZED.
|
|
286
|
+
error: mapped
|
|
287
|
+
? jsonRpcError(mapped)
|
|
288
|
+
: { code: MCP_ERROR_CODES.UNAUTHORIZED, message: "Unauthorized" },
|
|
238
289
|
id: null,
|
|
239
290
|
});
|
|
240
291
|
return;
|
|
@@ -248,11 +299,11 @@ export function mountMcpEndpoints(opts) {
|
|
|
248
299
|
// handler should branch on the specific mechanism.
|
|
249
300
|
authSource: identity.source,
|
|
250
301
|
// Identity is the canonical source of two mutually-exclusive
|
|
251
|
-
//
|
|
252
|
-
// Threading them onto HandlerContext here means
|
|
253
|
-
// (
|
|
254
|
-
//
|
|
255
|
-
//
|
|
302
|
+
// fields: `apiKeyHash` for kind=app, `userId` for kind=user.
|
|
303
|
+
// Threading them onto HandlerContext here means a deployment's own
|
|
304
|
+
// handlers (per-key metering, per-user scoping) can read identity
|
|
305
|
+
// directly without a parallel context shape; the handlers this
|
|
306
|
+
// package ships ignore both fields.
|
|
256
307
|
...(identity.identity.kind === "app" ? { apiKeyHash: identity.identity.apiKeyHash } : {}),
|
|
257
308
|
...(identity.identity.kind === "user" ? { userId: identity.identity.userId } : {}),
|
|
258
309
|
// What the credential itself may act on, when the adapter
|
|
@@ -301,7 +352,7 @@ export function mountMcpEndpoints(opts) {
|
|
|
301
352
|
res.set(mapped.headers);
|
|
302
353
|
res.status(mapped.status).json({
|
|
303
354
|
jsonrpc: "2.0",
|
|
304
|
-
error:
|
|
355
|
+
error: jsonRpcError(mapped),
|
|
305
356
|
id: null,
|
|
306
357
|
});
|
|
307
358
|
}
|
|
@@ -316,7 +367,9 @@ export function mountMcpEndpoints(opts) {
|
|
|
316
367
|
}
|
|
317
368
|
};
|
|
318
369
|
const methodNotAllowed = (_req, res) => {
|
|
319
|
-
|
|
370
|
+
// A 405 names what IS allowed (RFC 9110 §15.5.6); every mount here is
|
|
371
|
+
// POST-only.
|
|
372
|
+
res.set("Allow", "POST").status(405).json({
|
|
320
373
|
jsonrpc: "2.0",
|
|
321
374
|
error: {
|
|
322
375
|
code: -32000,
|
|
@@ -341,24 +394,24 @@ export function mountMcpEndpoints(opts) {
|
|
|
341
394
|
anonymousOpsChallenge: controlOpsToolNames,
|
|
342
395
|
});
|
|
343
396
|
// Universal endpoint — `appId` resolved from the auth identity via
|
|
344
|
-
// `appIdFromIdentity`.
|
|
345
|
-
//
|
|
346
|
-
//
|
|
397
|
+
// `appIdFromIdentity`. A deployment's auth adapter may resolve it
|
|
398
|
+
// (e.g. to the user's default app); without one it falls through to
|
|
399
|
+
// userId / DEFAULT_BUILDER_APP_ID.
|
|
347
400
|
//
|
|
348
|
-
// Path defaults to `/mcp` (Streamable HTTP convention).
|
|
349
|
-
//
|
|
350
|
-
//
|
|
401
|
+
// Path defaults to `/mcp` (Streamable HTTP convention). A deployment
|
|
402
|
+
// whose hostname already says "mcp" may serve it at `/` so the
|
|
403
|
+
// bare-root URL is the universal endpoint — no path repeat.
|
|
351
404
|
// Exposes audience tags ['agent', 'runtime'] — runtime tools stay
|
|
352
405
|
// routable on the same endpoint but invisible to the agent's
|
|
353
406
|
// `tools/list` via the `_meta.ui.visibility: ['app']` filter.
|
|
354
407
|
app.post(universalMcpPath, agentMcpHandler);
|
|
355
|
-
// Per-
|
|
408
|
+
// Per-app endpoint — only mounted when the deployment opts in
|
|
356
409
|
// via `perAppRouting`. The same handler reads `req.params[paramName]`
|
|
357
410
|
// and uses it as `ctx.appId` for the request.
|
|
358
411
|
//
|
|
359
412
|
// When `pathPrefix` is set, the route mounts at
|
|
360
|
-
// `${pathPrefix}/:${paramName}` —
|
|
361
|
-
//
|
|
413
|
+
// `${pathPrefix}/:${paramName}` — e.g. `/apps`, so URLs read
|
|
414
|
+
// `<host>/apps/<appId>`. The prefix segments per-app traffic
|
|
362
415
|
// from system routes (`/health`, `/oauth/*`, `/.well-known/*`,
|
|
363
416
|
// `/r/*`) so an opaque appId can never shadow a future static path.
|
|
364
417
|
//
|
|
@@ -386,6 +439,12 @@ export function mountMcpEndpoints(opts) {
|
|
|
386
439
|
});
|
|
387
440
|
const route = pathPrefix !== undefined ? `${pathPrefix}/:${paramName}` : `/:${paramName}`;
|
|
388
441
|
app.post(route, agentMcpHandler);
|
|
442
|
+
// The per-app endpoint is POST-only like every other mount: GET (a
|
|
443
|
+
// client's optional SSE listener) and DELETE (session terminate) read
|
|
444
|
+
// the transport's 405, never Express's text/html 404 — a 404 says "no
|
|
445
|
+
// such resource" and a client retries it every turn (ggui#850).
|
|
446
|
+
app.get(route, methodNotAllowed);
|
|
447
|
+
app.delete(route, methodNotAllowed);
|
|
389
448
|
}
|
|
390
449
|
// /control — the control plane. Hosts every `audience: ['protocol']`
|
|
391
450
|
// tool (design-time spec/discovery, anonymous) and every
|
|
@@ -29,7 +29,7 @@ interface MountOptions {
|
|
|
29
29
|
/** Universal MCP endpoint path — the default RFC 9728 resource. */
|
|
30
30
|
readonly universalMcpPath: string;
|
|
31
31
|
/**
|
|
32
|
-
* Per-
|
|
32
|
+
* Per-app URL routing shape. When configured, a second
|
|
33
33
|
* well-known endpoint mounts under the same path prefix the
|
|
34
34
|
* per-app `/mcp` handler lives on.
|
|
35
35
|
*/
|
package/dist/oauth.d.ts
CHANGED
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
* Auth codes + DCR clients live in-memory ({@link InMemoryOAuthStorage}).
|
|
52
52
|
* For multi-replica deployments (2+ processes behind one load
|
|
53
53
|
* balancer), either:
|
|
54
|
-
* - Use
|
|
55
|
-
* `/oauth/authorize` and `/oauth/token
|
|
54
|
+
* - Use sticky sessions at your load balancer / reverse proxy so the
|
|
55
|
+
* same replica handles both `/oauth/authorize` and `/oauth/token`.
|
|
56
56
|
* - Plug a Redis-backed {@link OAuthStorage} via the
|
|
57
57
|
* `oauth.storage` config option (production posture).
|
|
58
58
|
*
|
|
59
59
|
* DCR clients are short-lived in practice — Claude Desktop registers
|
|
60
|
-
* once per install + caches the `client_id`. A
|
|
60
|
+
* once per install + caches the `client_id`. A server restart drops all
|
|
61
61
|
* registrations; clients re-register transparently on next failure.
|
|
62
62
|
*/
|
|
63
63
|
import type { Request, Response } from 'express';
|
package/dist/oauth.js
CHANGED
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
* Auth codes + DCR clients live in-memory ({@link InMemoryOAuthStorage}).
|
|
52
52
|
* For multi-replica deployments (2+ processes behind one load
|
|
53
53
|
* balancer), either:
|
|
54
|
-
* - Use
|
|
55
|
-
* `/oauth/authorize` and `/oauth/token
|
|
54
|
+
* - Use sticky sessions at your load balancer / reverse proxy so the
|
|
55
|
+
* same replica handles both `/oauth/authorize` and `/oauth/token`.
|
|
56
56
|
* - Plug a Redis-backed {@link OAuthStorage} via the
|
|
57
57
|
* `oauth.storage` config option (production posture).
|
|
58
58
|
*
|
|
59
59
|
* DCR clients are short-lived in practice — Claude Desktop registers
|
|
60
|
-
* once per install + caches the `client_id`. A
|
|
60
|
+
* once per install + caches the `client_id`. A server restart drops all
|
|
61
61
|
* registrations; clients re-register transparently on next failure.
|
|
62
62
|
*/
|
|
63
63
|
import { createHash, randomBytes } from 'node:crypto';
|
|
@@ -15,7 +15,7 @@ export interface RenderReadRowView {
|
|
|
15
15
|
* first time.
|
|
16
16
|
*
|
|
17
17
|
* Absent stays a legitimate state: builder and anonymous
|
|
18
|
-
* single-
|
|
18
|
+
* single-app flows mint rows with no subject, and those still
|
|
19
19
|
* pass rung 4.
|
|
20
20
|
*/
|
|
21
21
|
readonly userId?: string;
|
|
@@ -31,9 +31,9 @@ export interface RenderReadRowView {
|
|
|
31
31
|
* that subject. The row's subject is its `userId`, written at
|
|
32
32
|
* commit; see {@link RenderReadRowView.userId} for why this rung
|
|
33
33
|
* only starts binding now.
|
|
34
|
-
* 4. Everything else same-app passes: app credentials (
|
|
34
|
+
* 4. Everything else same-app passes: app credentials (app trust —
|
|
35
35
|
* the app is obligated to enforce its own user-ownership before
|
|
36
|
-
* fetching on a user's behalf), builder/anonymous single-
|
|
36
|
+
* fetching on a user's behalf), builder/anonymous single-app
|
|
37
37
|
* flows, and rows with no subject.
|
|
38
38
|
*
|
|
39
39
|
* Deny is surfaced by the CALLER byte-identically to a missing row —
|
package/dist/render-read-gate.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* that subject. The row's subject is its `userId`, written at
|
|
10
10
|
* commit; see {@link RenderReadRowView.userId} for why this rung
|
|
11
11
|
* only starts binding now.
|
|
12
|
-
* 4. Everything else same-app passes: app credentials (
|
|
12
|
+
* 4. Everything else same-app passes: app credentials (app trust —
|
|
13
13
|
* the app is obligated to enforce its own user-ownership before
|
|
14
|
-
* fetching on a user's behalf), builder/anonymous single-
|
|
14
|
+
* fetching on a user's behalf), builder/anonymous single-app
|
|
15
15
|
* flows, and rows with no subject.
|
|
16
16
|
*
|
|
17
17
|
* Deny is surfaced by the CALLER byte-identically to a missing row —
|
|
@@ -45,7 +45,7 @@ export declare function resolveHashedRuntimeBundleUrl(plainUrl: string, bundleFi
|
|
|
45
45
|
* `bundleFile` (default: the workspace's built bundle — the same file
|
|
46
46
|
* `createGguiServer` hashes, so both derive the same value), or
|
|
47
47
|
* `undefined` when the bundle is unreadable. Deployments that compose
|
|
48
|
-
* their own render handler (
|
|
48
|
+
* their own render handler (a production deployment's `handlers:` shape)
|
|
49
49
|
* use this to build a `createCodeModuleUrlMinter` that stamps the SAME
|
|
50
50
|
* `<rt>` the co-resident factory's variant route serves (ggui#522
|
|
51
51
|
* slice 2) — re-deriving the scheme by hand is how the codeUrl binding
|
|
@@ -69,7 +69,7 @@ export function resolveHashedRuntimeBundleUrl(plainUrl, bundleFile = RUNTIME_BUN
|
|
|
69
69
|
* `bundleFile` (default: the workspace's built bundle — the same file
|
|
70
70
|
* `createGguiServer` hashes, so both derive the same value), or
|
|
71
71
|
* `undefined` when the bundle is unreadable. Deployments that compose
|
|
72
|
-
* their own render handler (
|
|
72
|
+
* their own render handler (a production deployment's `handlers:` shape)
|
|
73
73
|
* use this to build a `createCodeModuleUrlMinter` that stamps the SAME
|
|
74
74
|
* `<rt>` the co-resident factory's variant route serves (ggui#522
|
|
75
75
|
* slice 2) — re-deriving the scheme by hand is how the codeUrl binding
|
package/dist/schema-compat.d.ts
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* loudly at registration time.
|
|
34
34
|
*/
|
|
35
35
|
import type { ZodRawShape } from 'zod';
|
|
36
|
+
import { DomainError } from '@ggui-ai/protocol';
|
|
36
37
|
import { type SubsetViolation, type ActionSpec } from '@ggui-ai/protocol';
|
|
37
38
|
/**
|
|
38
39
|
* Stance the host takes when a check surfaces violations.
|
|
@@ -136,7 +137,7 @@ export interface GguiSessionContractShape {
|
|
|
136
137
|
* finding. Carries the full {@link SchemaCompatReport} so callers
|
|
137
138
|
* can log / surface the detail beyond the `message` string.
|
|
138
139
|
*/
|
|
139
|
-
export declare class SchemaCompatError extends
|
|
140
|
+
export declare class SchemaCompatError extends DomainError<'schema_mismatch_error'> {
|
|
140
141
|
readonly report: SchemaCompatReport;
|
|
141
142
|
constructor(report: SchemaCompatReport, context: string);
|
|
142
143
|
}
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,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,WAAW,CAAC,uBAAuB,CAAC;IACzE,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBACxB,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAIxD;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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DomainError } from '@ggui-ai/protocol';
|
|
1
2
|
import { isSchemaSubset, zodToJsonSchema, } from '@ggui-ai/protocol';
|
|
2
3
|
/**
|
|
3
4
|
* Default mode applied when the host does not override. Matches the
|
|
@@ -9,11 +10,10 @@ export const DEFAULT_SCHEMA_COMPAT_MODE = 'reject';
|
|
|
9
10
|
* finding. Carries the full {@link SchemaCompatReport} so callers
|
|
10
11
|
* can log / surface the detail beyond the `message` string.
|
|
11
12
|
*/
|
|
12
|
-
export class SchemaCompatError extends
|
|
13
|
+
export class SchemaCompatError extends DomainError {
|
|
13
14
|
report;
|
|
14
15
|
constructor(report, context) {
|
|
15
|
-
super(formatReport(report, context));
|
|
16
|
-
this.name = 'SchemaCompatError';
|
|
16
|
+
super('schema_mismatch_error', formatReport(report, context));
|
|
17
17
|
this.report = report;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -136,7 +136,7 @@ export function hasErrorFinding(report) {
|
|
|
136
136
|
*/
|
|
137
137
|
function formatReport(report, context) {
|
|
138
138
|
if (report.compatible) {
|
|
139
|
-
return `${context}
|
|
139
|
+
return `${context} — no findings (internal error)`;
|
|
140
140
|
}
|
|
141
141
|
const sorted = [...report.findings].sort((a, b) => a.specName < b.specName ? -1 : a.specName > b.specName ? 1 : 0);
|
|
142
142
|
const lines = sorted.map((f) => {
|
|
@@ -153,7 +153,7 @@ function formatReport(report, context) {
|
|
|
153
153
|
return `- ${f.kind} "${f.specName}" (tool "${f.toolName}") — schema mismatch${suffix}`;
|
|
154
154
|
});
|
|
155
155
|
return [
|
|
156
|
-
`${context}
|
|
156
|
+
`${context} — ${report.findings.length} finding${report.findings.length > 1 ? 's' : ''}`,
|
|
157
157
|
...lines,
|
|
158
158
|
].join('\n');
|
|
159
159
|
}
|