@ggui-ai/mcp-server 0.25.0 → 0.27.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 +48 -20
- package/dist/console-auth.d.ts +10 -10
- package/dist/console-auth.d.ts.map +1 -1
- 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 +29 -17
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscribe.js +247 -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 +13 -5
- package/dist/ggui-session-channel.d.ts.map +1 -1
- package/dist/ggui-session-channel.js +2 -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 +1 -36
- package/dist/llm-backed-negotiator.d.ts.map +1 -1
- package/dist/llm-backed-negotiator.js +0 -196
- package/dist/mcp-apps-outbound.d.ts +39 -90
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +45 -3
- package/dist/mcp-endpoint-routes.d.ts.map +1 -1
- package/dist/mcp-endpoint-routes.js +4 -1
- package/dist/server.d.ts +108 -33
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +181 -48
- 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
|
@@ -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.27.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/
|
|
37
|
-
"@ggui-ai/
|
|
38
|
-
"@ggui-ai/mcp-server-
|
|
39
|
-
"@ggui-ai/mcp-server-
|
|
40
|
-
"@ggui-ai/negotiator": "0.
|
|
41
|
-
"@ggui-ai/preview-a2ui": "0.
|
|
42
|
-
"@ggui-ai/
|
|
43
|
-
"@ggui-ai/
|
|
44
|
-
"@ggui-ai/ui-
|
|
45
|
-
"@ggui-ai/ui-
|
|
35
|
+
"@ggui-ai/console": "0.27.0",
|
|
36
|
+
"@ggui-ai/design": "0.27.0",
|
|
37
|
+
"@ggui-ai/iframe-runtime": "0.27.0",
|
|
38
|
+
"@ggui-ai/mcp-server-handlers": "0.27.0",
|
|
39
|
+
"@ggui-ai/mcp-server-core": "0.27.0",
|
|
40
|
+
"@ggui-ai/negotiator": "0.27.0",
|
|
41
|
+
"@ggui-ai/preview-a2ui": "0.27.0",
|
|
42
|
+
"@ggui-ai/protocol": "0.27.0",
|
|
43
|
+
"@ggui-ai/project-config": "0.27.0",
|
|
44
|
+
"@ggui-ai/ui-gen": "0.27.0",
|
|
45
|
+
"@ggui-ai/ui-registry": "0.27.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.27.0"
|
|
55
54
|
},
|
|
56
55
|
"repository": {
|
|
57
56
|
"type": "git",
|