@ggui-ai/mcp-server 0.24.0 → 0.26.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 +2 -2
- package/dist/api-renders-routes.d.ts +46 -5
- package/dist/api-renders-routes.d.ts.map +1 -1
- package/dist/api-renders-routes.js +98 -39
- package/dist/api-renders-stream-route.d.ts +2 -1
- package/dist/api-renders-stream-route.d.ts.map +1 -1
- package/dist/api-renders-stream-route.js +14 -2
- package/dist/build-mcp.d.ts +16 -0
- package/dist/build-mcp.d.ts.map +1 -1
- package/dist/build-mcp.js +82 -12
- package/dist/console-auth.d.ts +10 -10
- package/dist/console-auth.d.ts.map +1 -1
- package/dist/control-service.d.ts.map +1 -1
- package/dist/control-service.js +0 -3
- package/dist/first-seen.d.ts +14 -0
- package/dist/first-seen.d.ts.map +1 -0
- package/dist/first-seen.js +19 -0
- package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
- package/dist/ggui-session-channel/action-ingress.js +96 -8
- package/dist/ggui-session-channel/channel-subscriptions.d.ts +0 -1
- package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
- package/dist/ggui-session-channel/channel-subscriptions.js +4 -1
- package/dist/ggui-session-channel/internal-types.d.ts +20 -12
- package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
- package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
- package/dist/ggui-session-channel/outbound.js +18 -6
- package/dist/ggui-session-channel/stream-epoch.d.ts +61 -0
- package/dist/ggui-session-channel/stream-epoch.d.ts.map +1 -0
- package/dist/ggui-session-channel/stream-epoch.js +47 -0
- package/dist/ggui-session-channel/subscribe.d.ts +40 -53
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscribe.js +288 -124
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscriber-lifecycle.js +17 -7
- package/dist/ggui-session-channel.d.ts +43 -7
- package/dist/ggui-session-channel.d.ts.map +1 -1
- package/dist/ggui-session-channel.js +7 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/instructions-presets.d.ts +22 -21
- package/dist/instructions-presets.d.ts.map +1 -1
- package/dist/instructions-presets.js +38 -38
- package/dist/llm-backed-negotiator.d.ts +4 -37
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +28 -210
- package/dist/mcp-apps-outbound.d.ts +40 -91
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +100 -15
- package/dist/mcp-endpoint-routes.d.ts +1 -0
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +38 -4
- package/dist/runtime-bundle-hash.d.ts +7 -0
- package/dist/runtime-bundle-hash.d.ts.map +1 -1
- package/dist/runtime-bundle-hash.js +8 -2
- package/dist/runtime-bundle-route.d.ts.map +1 -1
- package/dist/runtime-bundle-route.js +15 -0
- package/dist/server.d.ts +143 -85
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +224 -110
- package/dist/storage.d.ts +9 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +4 -3
- package/dist/view-proof-gate.d.ts +96 -0
- package/dist/view-proof-gate.d.ts.map +1 -0
- package/dist/view-proof-gate.js +167 -0
- package/package.json +14 -15
- package/dist/render-read-gate.d.ts +0 -50
- package/dist/render-read-gate.d.ts.map +0 -1
- package/dist/render-read-gate.js +0 -36
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view-origin proof's measuring gate (ggui#1415, release N): for a tool
|
|
3
|
+
* that declares a view proof, verify the call's proof before the handler
|
|
4
|
+
* runs, put the verdict on the handler context, and name it on the
|
|
5
|
+
* `tool_invoked` line. It never refuses a call and never fails one: at this
|
|
6
|
+
* release the proof is measured, not enforced.
|
|
7
|
+
*
|
|
8
|
+
* Built once per server (`createViewProofGate`, holding the secret the ws
|
|
9
|
+
* envelopes are signed with) and run per call (`runViewProofGate`) from
|
|
10
|
+
* `buildMcpServer`'s per-tool callback.
|
|
11
|
+
*/
|
|
12
|
+
import { ViewProofRepeatCache, type GguiSessionStore, type ViewProofVerdict, type VerifyViewProofInput } from '@ggui-ai/mcp-server-core';
|
|
13
|
+
import { type HandlerContext, type SharedHandler } from '@ggui-ai/mcp-server-handlers';
|
|
14
|
+
import { type ViewRootSrc } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
15
|
+
import type { ZodRawShape } from 'zod';
|
|
16
|
+
/**
|
|
17
|
+
* Which era a call's session belongs to, for a `required` call whose proof
|
|
18
|
+
* is not valid; the first match wins. `row_absent`: no store, no row, or
|
|
19
|
+
* the read failed. `foreign`: another app owns the row. `unkeyed`: the
|
|
20
|
+
* session's ids are too long for a view key. `legacy`: created before
|
|
21
|
+
* `keyedSince`, or `keyedSince` is unset. `current`: everything else.
|
|
22
|
+
*/
|
|
23
|
+
export type ViewProofEra = 'current' | 'legacy' | 'unkeyed' | 'foreign' | 'row_absent';
|
|
24
|
+
/** How old the proof's root is on the server's clock (`now − rootIat`). */
|
|
25
|
+
export type ViewProofRootAge = 'lt_3m' | 'lt_1h' | 'lt_1d' | 'lt_7d' | 'lt_30d' | 'ge_30d';
|
|
26
|
+
/**
|
|
27
|
+
* The view clock against the server's (`vtime − now`), bucketed at 30 s,
|
|
28
|
+
* 5 min and 1 h on each side. Relay latency makes it negative (`behind`),
|
|
29
|
+
* so `ahead` can only be the view's clock running ahead.
|
|
30
|
+
*/
|
|
31
|
+
export type ViewProofSkew = 'behind_ge_1h' | 'behind_lt_1h' | 'behind_lt_5m' | 'within_30s' | 'ahead_lt_5m' | 'ahead_lt_1h' | 'ahead_ge_1h';
|
|
32
|
+
/**
|
|
33
|
+
* The proof's fields on a `tool_invoked` line: ids, flags and buckets only.
|
|
34
|
+
* Never the proof, its root, its nonce, its tags, the view key or any
|
|
35
|
+
* argument.
|
|
36
|
+
*/
|
|
37
|
+
export interface ViewProofLineFields {
|
|
38
|
+
readonly viewProof?: ViewProofVerdict['verdict'];
|
|
39
|
+
readonly viewProofReason?: string;
|
|
40
|
+
readonly viewProofErrorClass?: string;
|
|
41
|
+
/** Valid only: the session the proof's root names (it equals the call's). */
|
|
42
|
+
readonly viewProofSessionId?: string;
|
|
43
|
+
/** Not valid: the call's session as the caller claims it, capped. */
|
|
44
|
+
readonly claimedSessionId?: string;
|
|
45
|
+
readonly claimedSessionIdTruncated?: true;
|
|
46
|
+
readonly viewProofRootAge?: ViewProofRootAge;
|
|
47
|
+
readonly viewProofRootSrc?: ViewRootSrc;
|
|
48
|
+
readonly viewProofSkew?: ViewProofSkew;
|
|
49
|
+
/** Dispatch only: the gesture's user-activation flag, self-asserted by any key holder. */
|
|
50
|
+
readonly viewProofActivation?: boolean;
|
|
51
|
+
/** This replica saw the same `(session, nonce)` within the repeat window. */
|
|
52
|
+
readonly viewProofRepeat?: true;
|
|
53
|
+
readonly viewProofEra?: ViewProofEra;
|
|
54
|
+
/** The tool declares a proof and the server has no verifier. */
|
|
55
|
+
readonly viewProofUnverifiable?: true;
|
|
56
|
+
}
|
|
57
|
+
/** A server's gate: its verifier, and what it reads to classify an era. */
|
|
58
|
+
export interface ViewProofGate {
|
|
59
|
+
verify(input: VerifyViewProofInput): ViewProofVerdict;
|
|
60
|
+
/** Whether a session's ids fit a view key's root. */
|
|
61
|
+
keyable(sessionId: string, appId: string): boolean;
|
|
62
|
+
readonly sessionStore: GguiSessionStore | undefined;
|
|
63
|
+
/** Epoch ms from which every replica of this deployment issues view keys. */
|
|
64
|
+
readonly keyedSince: number | undefined;
|
|
65
|
+
readonly repeatCache: ViewProofRepeatCache;
|
|
66
|
+
now(): number;
|
|
67
|
+
}
|
|
68
|
+
export interface CreateViewProofGateOptions {
|
|
69
|
+
/** The secret the server's ws envelopes are signed with. */
|
|
70
|
+
readonly secret: string;
|
|
71
|
+
readonly sessionStore: GguiSessionStore | undefined;
|
|
72
|
+
readonly keyedSince?: number;
|
|
73
|
+
readonly now?: () => number;
|
|
74
|
+
}
|
|
75
|
+
/** Build a server's gate once; the secret stays in its closures. */
|
|
76
|
+
export declare function createViewProofGate(opts: CreateViewProofGateOptions): ViewProofGate;
|
|
77
|
+
/**
|
|
78
|
+
* Refuse, at boot, a handler that declares a view proof on a tool the
|
|
79
|
+
* proof's bound-argument table does not name: no view can sign a call to
|
|
80
|
+
* it, so every call would read `missing` for a reason no one could find.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertViewProofDeclarations(handlers: ReadonlyArray<Pick<SharedHandler<ZodRawShape, ZodRawShape>, 'name' | 'viewProof'>>): void;
|
|
83
|
+
/** The tools whose declared proof a server without a verifier cannot check. */
|
|
84
|
+
export declare function unverifiableViewProofTools(handlers: ReadonlyArray<Pick<SharedHandler<ZodRawShape, ZodRawShape>, 'name' | 'viewProof'>>): string[];
|
|
85
|
+
/** What the gate hands back: the context the handler runs with, and the line fields. */
|
|
86
|
+
export interface ViewProofGateResult {
|
|
87
|
+
readonly ctx: HandlerContext;
|
|
88
|
+
readonly fields: ViewProofLineFields;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run the gate for one call. `gate` is undefined on a server with no
|
|
92
|
+
* verifier. Total: it never throws, and never changes what the handler
|
|
93
|
+
* answers; a declaration it cannot read leaves the call as it came.
|
|
94
|
+
*/
|
|
95
|
+
export declare function runViewProofGate(gate: ViewProofGate | undefined, handler: Pick<SharedHandler<ZodRawShape, ZodRawShape>, 'name' | 'viewProof'>, input: Record<string, unknown>, ctx: HandlerContext): Promise<ViewProofGateResult>;
|
|
96
|
+
//# sourceMappingURL=view-proof-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view-proof-gate.d.ts","sourceRoot":"","sources":["../src/view-proof-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,oBAAoB,EAGpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAEvF,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3F;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,cAAc,GACd,cAAc,GACd,YAAY,GACZ,aAAa,GACb,aAAa,GACb,aAAa,CAAC;AAElB;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,IAAI,CAAC;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;IACxC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,0FAA0F;IAC1F,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,6EAA6E;IAC7E,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,gEAAgE;IAChE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC;CACvC;AAED,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,gBAAgB,CAAC;IACtD,qDAAqD;IACrD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IACnD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpD,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,GAAG,IAAI,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,0BAA0B,GAAG,aAAa,CAWnF;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,GAC3F,IAAI,CAON;AAED,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,GAC3F,MAAM,EAAE,CAEV;AAwDD,wFAAwF;AACxF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,GAAG,SAAS,EAC/B,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,EAC5E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA+C9B"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view-origin proof's measuring gate (ggui#1415, release N): for a tool
|
|
3
|
+
* that declares a view proof, verify the call's proof before the handler
|
|
4
|
+
* runs, put the verdict on the handler context, and name it on the
|
|
5
|
+
* `tool_invoked` line. It never refuses a call and never fails one: at this
|
|
6
|
+
* release the proof is measured, not enforced.
|
|
7
|
+
*
|
|
8
|
+
* Built once per server (`createViewProofGate`, holding the secret the ws
|
|
9
|
+
* envelopes are signed with) and run per call (`runViewProofGate`) from
|
|
10
|
+
* `buildMcpServer`'s per-tool callback.
|
|
11
|
+
*/
|
|
12
|
+
import { ViewProofRepeatCache, mintViewRoot, verifyViewProof, } from '@ggui-ai/mcp-server-core';
|
|
13
|
+
import { createSessionRowReads, viewProofUseFor, } from '@ggui-ai/mcp-server-handlers';
|
|
14
|
+
import { isViewProofTool } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
15
|
+
/** Build a server's gate once; the secret stays in its closures. */
|
|
16
|
+
export function createViewProofGate(opts) {
|
|
17
|
+
const now = opts.now ?? Date.now;
|
|
18
|
+
return {
|
|
19
|
+
verify: (input) => verifyViewProof(input, opts.secret),
|
|
20
|
+
keyable: (sessionId, appId) => mintViewRoot({ sessionId, appId, src: 'result' }, opts.secret).viewKey !== undefined,
|
|
21
|
+
sessionStore: opts.sessionStore,
|
|
22
|
+
keyedSince: opts.keyedSince,
|
|
23
|
+
repeatCache: new ViewProofRepeatCache(10 * 60 * 1000, 10_000, now),
|
|
24
|
+
now,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Refuse, at boot, a handler that declares a view proof on a tool the
|
|
29
|
+
* proof's bound-argument table does not name: no view can sign a call to
|
|
30
|
+
* it, so every call would read `missing` for a reason no one could find.
|
|
31
|
+
*/
|
|
32
|
+
export function assertViewProofDeclarations(handlers) {
|
|
33
|
+
const unbound = handlers.filter((h) => h.viewProof !== undefined && !isViewProofTool(h.name)).map((h) => h.name);
|
|
34
|
+
if (unbound.length > 0) {
|
|
35
|
+
throw new Error(`A view proof is declared on ${unbound.join(', ')}, which the view proof's bound-argument table does not name, so no view can sign a call to it. Remove the declaration, or add the tool to the table in @ggui-ai/protocol.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** The tools whose declared proof a server without a verifier cannot check. */
|
|
39
|
+
export function unverifiableViewProofTools(handlers) {
|
|
40
|
+
return handlers.filter((h) => h.viewProof !== undefined).map((h) => h.name);
|
|
41
|
+
}
|
|
42
|
+
const CLAIMED_SESSION_ID_MAX_CHARS = 128;
|
|
43
|
+
const SECOND = 1000;
|
|
44
|
+
const MINUTE = 60 * SECOND;
|
|
45
|
+
const HOUR = 60 * MINUTE;
|
|
46
|
+
const DAY = 24 * HOUR;
|
|
47
|
+
function rootAge(ageMs) {
|
|
48
|
+
if (ageMs < 3 * MINUTE)
|
|
49
|
+
return 'lt_3m';
|
|
50
|
+
if (ageMs < HOUR)
|
|
51
|
+
return 'lt_1h';
|
|
52
|
+
if (ageMs < DAY)
|
|
53
|
+
return 'lt_1d';
|
|
54
|
+
if (ageMs < 7 * DAY)
|
|
55
|
+
return 'lt_7d';
|
|
56
|
+
if (ageMs < 30 * DAY)
|
|
57
|
+
return 'lt_30d';
|
|
58
|
+
return 'ge_30d';
|
|
59
|
+
}
|
|
60
|
+
function skew(deltaMs) {
|
|
61
|
+
const size = Math.abs(deltaMs);
|
|
62
|
+
if (size < 30 * SECOND)
|
|
63
|
+
return 'within_30s';
|
|
64
|
+
const side = deltaMs < 0 ? 'behind' : 'ahead';
|
|
65
|
+
if (size < 5 * MINUTE)
|
|
66
|
+
return `${side}_lt_5m`;
|
|
67
|
+
if (size < HOUR)
|
|
68
|
+
return `${side}_lt_1h`;
|
|
69
|
+
return `${side}_ge_1h`;
|
|
70
|
+
}
|
|
71
|
+
function claimed(sessionId) {
|
|
72
|
+
if (sessionId === '')
|
|
73
|
+
return {};
|
|
74
|
+
if (sessionId.length <= CLAIMED_SESSION_ID_MAX_CHARS)
|
|
75
|
+
return { claimedSessionId: sessionId };
|
|
76
|
+
return { claimedSessionId: sessionId.slice(0, CLAIMED_SESSION_ID_MAX_CHARS), claimedSessionIdTruncated: true };
|
|
77
|
+
}
|
|
78
|
+
async function classifyEra(gate, ctx, sessionId) {
|
|
79
|
+
const store = gate.sessionStore;
|
|
80
|
+
if (store === undefined || ctx.sessionRows === undefined)
|
|
81
|
+
return 'row_absent';
|
|
82
|
+
let row;
|
|
83
|
+
try {
|
|
84
|
+
row = await ctx.sessionRows.read(store, sessionId);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A failed read is `row_absent`. The handler reads the same memoized
|
|
88
|
+
// rejection and names the failure on its own ownership line.
|
|
89
|
+
return 'row_absent';
|
|
90
|
+
}
|
|
91
|
+
if (row === null)
|
|
92
|
+
return 'row_absent';
|
|
93
|
+
if (row.appId !== ctx.appId)
|
|
94
|
+
return 'foreign';
|
|
95
|
+
if (!gate.keyable(sessionId, row.appId))
|
|
96
|
+
return 'unkeyed';
|
|
97
|
+
if (gate.keyedSince === undefined)
|
|
98
|
+
return 'legacy';
|
|
99
|
+
// A component or system render's creation time is epoch ms; an embedded
|
|
100
|
+
// MCP App's is an ISO string. One this server cannot read is `legacy`,
|
|
101
|
+
// the era that is accepted for life.
|
|
102
|
+
const createdAt = row.render.createdAt;
|
|
103
|
+
const createdMs = typeof createdAt === 'number' ? createdAt : Date.parse(createdAt);
|
|
104
|
+
if (!Number.isFinite(createdMs) || createdMs < gate.keyedSince)
|
|
105
|
+
return 'legacy';
|
|
106
|
+
return 'current';
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Run the gate for one call. `gate` is undefined on a server with no
|
|
110
|
+
* verifier. Total: it never throws, and never changes what the handler
|
|
111
|
+
* answers; a declaration it cannot read leaves the call as it came.
|
|
112
|
+
*/
|
|
113
|
+
export async function runViewProofGate(gate, handler, input, ctx) {
|
|
114
|
+
let use;
|
|
115
|
+
try {
|
|
116
|
+
use = viewProofUseFor(handler.viewProof, input);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return {
|
|
120
|
+
ctx,
|
|
121
|
+
fields: { viewProof: 'invalid', viewProofReason: 'verifier_error', viewProofErrorClass: errorName(err) },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (use === undefined)
|
|
125
|
+
return { ctx, fields: {} };
|
|
126
|
+
if (gate === undefined)
|
|
127
|
+
return { ctx, fields: { viewProofUnverifiable: true } };
|
|
128
|
+
const toolName = handler.name;
|
|
129
|
+
if (!isViewProofTool(toolName))
|
|
130
|
+
return { ctx, fields: { viewProofUnverifiable: true } };
|
|
131
|
+
const sessionId = typeof input['sessionId'] === 'string' ? input['sessionId'] : '';
|
|
132
|
+
const verdict = gate.verify({ requestMeta: ctx.requestMeta, toolName, args: input, sessionId, appId: ctx.appId });
|
|
133
|
+
const withProof = { ...ctx, viewProof: verdict, sessionRows: ctx.sessionRows ?? createSessionRowReads() };
|
|
134
|
+
if (verdict.verdict === 'valid') {
|
|
135
|
+
const t = gate.now();
|
|
136
|
+
return {
|
|
137
|
+
ctx: withProof,
|
|
138
|
+
fields: {
|
|
139
|
+
viewProof: 'valid',
|
|
140
|
+
viewProofSessionId: sessionId,
|
|
141
|
+
viewProofRootAge: rootAge(t - verdict.rootIat * SECOND),
|
|
142
|
+
...(verdict.src !== undefined ? { viewProofRootSrc: verdict.src } : {}),
|
|
143
|
+
viewProofSkew: skew(verdict.vtime - t),
|
|
144
|
+
...(toolName === 'ggui_runtime_submit_action' && input['kind'] === 'dispatch'
|
|
145
|
+
? { viewProofActivation: verdict.userActivation }
|
|
146
|
+
: {}),
|
|
147
|
+
...(gate.repeatCache.observe(sessionId, verdict.nonce) ? { viewProofRepeat: true } : {}),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const era = use === 'required' ? await classifyEra(gate, withProof, sessionId) : undefined;
|
|
152
|
+
return {
|
|
153
|
+
ctx: withProof,
|
|
154
|
+
fields: {
|
|
155
|
+
viewProof: verdict.verdict,
|
|
156
|
+
viewProofReason: verdict.reason,
|
|
157
|
+
...(verdict.verdict === 'invalid' && verdict.reason === 'verifier_error'
|
|
158
|
+
? { viewProofErrorClass: verdict.errorClass }
|
|
159
|
+
: {}),
|
|
160
|
+
...claimed(sessionId),
|
|
161
|
+
...(era !== undefined ? { viewProofEra: era } : {}),
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function errorName(err) {
|
|
166
|
+
return (err instanceof Error ? err.name : typeof err).slice(0, 64);
|
|
167
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Self-hosted MCP server for the ggui protocol. An HTTP/MCP binding layer over @ggui-ai/mcp-server-handlers and the reference adapters from @ggui-ai/mcp-server-core — run it via `ggui serve`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -28,30 +28,29 @@
|
|
|
28
28
|
"@modelcontextprotocol/ext-apps": "^1.7.2",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
30
30
|
"express": "^5.2.1",
|
|
31
|
-
"nodemailer": "^
|
|
31
|
+
"nodemailer": "^10.0.13",
|
|
32
32
|
"resend": "^6.12.2",
|
|
33
33
|
"ws": "^8.20.1",
|
|
34
34
|
"zod": "^4.3.6",
|
|
35
|
-
"@ggui-ai/console": "0.
|
|
36
|
-
"@ggui-ai/design": "0.
|
|
37
|
-
"@ggui-ai/
|
|
38
|
-
"@ggui-ai/
|
|
39
|
-
"@ggui-ai/mcp-server-handlers": "0.
|
|
40
|
-
"@ggui-ai/negotiator": "0.
|
|
41
|
-
"@ggui-ai/preview-a2ui": "0.
|
|
42
|
-
"@ggui-ai/project-config": "0.
|
|
43
|
-
"@ggui-ai/protocol": "0.
|
|
44
|
-
"@ggui-ai/ui-
|
|
45
|
-
"@ggui-ai/ui-
|
|
35
|
+
"@ggui-ai/console": "0.26.0",
|
|
36
|
+
"@ggui-ai/design": "0.26.0",
|
|
37
|
+
"@ggui-ai/mcp-server-core": "0.26.0",
|
|
38
|
+
"@ggui-ai/iframe-runtime": "0.26.0",
|
|
39
|
+
"@ggui-ai/mcp-server-handlers": "0.26.0",
|
|
40
|
+
"@ggui-ai/negotiator": "0.26.0",
|
|
41
|
+
"@ggui-ai/preview-a2ui": "0.26.0",
|
|
42
|
+
"@ggui-ai/project-config": "0.26.0",
|
|
43
|
+
"@ggui-ai/protocol": "0.26.0",
|
|
44
|
+
"@ggui-ai/ui-registry": "0.26.0",
|
|
45
|
+
"@ggui-ai/ui-gen": "0.26.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/express": "^5.0.0",
|
|
49
49
|
"@types/node": "^22.0.0",
|
|
50
|
-
"@types/nodemailer": "^8.0.0",
|
|
51
50
|
"@types/ws": "^8.5.10",
|
|
52
51
|
"typescript": "^5.0.0",
|
|
53
52
|
"vitest": "^3.2.6",
|
|
54
|
-
"@ggui-ai/protocol-conformance": "0.
|
|
53
|
+
"@ggui-ai/protocol-conformance": "0.26.0"
|
|
55
54
|
},
|
|
56
55
|
"repository": {
|
|
57
56
|
"type": "git",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { HandlerContext } from "@ggui-ai/mcp-server-handlers";
|
|
2
|
-
/** The two row fields the read gate consults. */
|
|
3
|
-
export interface RenderReadRowView {
|
|
4
|
-
readonly appId: string;
|
|
5
|
-
/**
|
|
6
|
-
* The row's SUBJECT — the end user a render belongs to, written at
|
|
7
|
-
* commit and nowhere else.
|
|
8
|
-
*
|
|
9
|
-
* `userId` rather than the richer `endUserIdentity` block, and that
|
|
10
|
-
* is the whole of #446. The gate used to read `endUserIdentity`,
|
|
11
|
-
* which no writer has populated since the repo split deleted the one
|
|
12
|
-
* that did (`f99b81c28`) — so the subject rung has been reading a
|
|
13
|
-
* field that is always absent, and passing every caller through as
|
|
14
|
-
* "row has no subject". Reading `userId` binds the rung for the
|
|
15
|
-
* first time.
|
|
16
|
-
*
|
|
17
|
-
* Absent stays a legitimate state: builder and anonymous
|
|
18
|
-
* single-app flows mint rows with no subject, and those still
|
|
19
|
-
* pass rung 4.
|
|
20
|
-
*/
|
|
21
|
-
readonly userId?: string;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Per-session render-resource read gate (rehydration access control).
|
|
25
|
-
*
|
|
26
|
-
* Rungs, in order (spec §3):
|
|
27
|
-
* 1. Fail closed without a request context.
|
|
28
|
-
* 2. App boundary — the row's appId must equal the caller's.
|
|
29
|
-
* 3. Subject binding — a caller carrying an end-user identity (kind
|
|
30
|
-
* 'user', any auth source) reading a subject-bound row must BE
|
|
31
|
-
* that subject. The row's subject is its `userId`, written at
|
|
32
|
-
* commit; see {@link RenderReadRowView.userId} for why this rung
|
|
33
|
-
* only starts binding now.
|
|
34
|
-
* 4. Everything else same-app passes: app credentials (app trust —
|
|
35
|
-
* the app is obligated to enforce its own user-ownership before
|
|
36
|
-
* fetching on a user's behalf), builder/anonymous single-app
|
|
37
|
-
* flows, and rows with no subject.
|
|
38
|
-
*
|
|
39
|
-
* Deny is surfaced by the CALLER byte-identically to a missing row —
|
|
40
|
-
* reads must not oracle which sessionIds exist. This function only
|
|
41
|
-
* returns the boolean; making the two indistinguishable is the caller's
|
|
42
|
-
* obligation, and it is a real one, because "refused" and "never
|
|
43
|
-
* existed" travel completely different code paths to get there. The
|
|
44
|
-
* resource handler discharges it by collapsing a refusal to "absent"
|
|
45
|
-
* and letting every downstream branch run as it would for a locator
|
|
46
|
-
* that never existed, so both arrive at the same typed failure with the
|
|
47
|
-
* same bytes.
|
|
48
|
-
*/
|
|
49
|
-
export declare function renderReadAllowed(row: RenderReadRowView, ctx: HandlerContext | undefined): boolean;
|
|
50
|
-
//# sourceMappingURL=render-read-gate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render-read-gate.d.ts","sourceRoot":"","sources":["../src/render-read-gate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,iDAAiD;AACjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,iBAAiB,EACtB,GAAG,EAAE,cAAc,GAAG,SAAS,GAC9B,OAAO,CAOT"}
|
package/dist/render-read-gate.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-session render-resource read gate (rehydration access control).
|
|
3
|
-
*
|
|
4
|
-
* Rungs, in order (spec §3):
|
|
5
|
-
* 1. Fail closed without a request context.
|
|
6
|
-
* 2. App boundary — the row's appId must equal the caller's.
|
|
7
|
-
* 3. Subject binding — a caller carrying an end-user identity (kind
|
|
8
|
-
* 'user', any auth source) reading a subject-bound row must BE
|
|
9
|
-
* that subject. The row's subject is its `userId`, written at
|
|
10
|
-
* commit; see {@link RenderReadRowView.userId} for why this rung
|
|
11
|
-
* only starts binding now.
|
|
12
|
-
* 4. Everything else same-app passes: app credentials (app trust —
|
|
13
|
-
* the app is obligated to enforce its own user-ownership before
|
|
14
|
-
* fetching on a user's behalf), builder/anonymous single-app
|
|
15
|
-
* flows, and rows with no subject.
|
|
16
|
-
*
|
|
17
|
-
* Deny is surfaced by the CALLER byte-identically to a missing row —
|
|
18
|
-
* reads must not oracle which sessionIds exist. This function only
|
|
19
|
-
* returns the boolean; making the two indistinguishable is the caller's
|
|
20
|
-
* obligation, and it is a real one, because "refused" and "never
|
|
21
|
-
* existed" travel completely different code paths to get there. The
|
|
22
|
-
* resource handler discharges it by collapsing a refusal to "absent"
|
|
23
|
-
* and letting every downstream branch run as it would for a locator
|
|
24
|
-
* that never existed, so both arrive at the same typed failure with the
|
|
25
|
-
* same bytes.
|
|
26
|
-
*/
|
|
27
|
-
export function renderReadAllowed(row, ctx) {
|
|
28
|
-
if (ctx === undefined)
|
|
29
|
-
return false;
|
|
30
|
-
if (ctx.appId !== row.appId)
|
|
31
|
-
return false;
|
|
32
|
-
if (row.userId !== undefined && ctx.userId !== undefined) {
|
|
33
|
-
return ctx.userId === row.userId;
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
36
|
-
}
|