@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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/dist/api-renders-routes.d.ts +46 -5
  3. package/dist/api-renders-routes.d.ts.map +1 -1
  4. package/dist/api-renders-routes.js +98 -39
  5. package/dist/api-renders-stream-route.d.ts +2 -1
  6. package/dist/api-renders-stream-route.d.ts.map +1 -1
  7. package/dist/api-renders-stream-route.js +14 -2
  8. package/dist/build-mcp.d.ts +16 -0
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +48 -20
  11. package/dist/console-auth.d.ts +10 -10
  12. package/dist/console-auth.d.ts.map +1 -1
  13. package/dist/first-seen.d.ts +14 -0
  14. package/dist/first-seen.d.ts.map +1 -0
  15. package/dist/first-seen.js +19 -0
  16. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
  17. package/dist/ggui-session-channel/action-ingress.js +96 -8
  18. package/dist/ggui-session-channel/channel-subscriptions.d.ts +0 -1
  19. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
  20. package/dist/ggui-session-channel/channel-subscriptions.js +4 -1
  21. package/dist/ggui-session-channel/internal-types.d.ts +20 -12
  22. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
  23. package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
  24. package/dist/ggui-session-channel/outbound.js +18 -6
  25. package/dist/ggui-session-channel/stream-epoch.d.ts +61 -0
  26. package/dist/ggui-session-channel/stream-epoch.d.ts.map +1 -0
  27. package/dist/ggui-session-channel/stream-epoch.js +47 -0
  28. package/dist/ggui-session-channel/subscribe.d.ts +29 -17
  29. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
  30. package/dist/ggui-session-channel/subscribe.js +247 -124
  31. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
  32. package/dist/ggui-session-channel/subscriber-lifecycle.js +17 -7
  33. package/dist/ggui-session-channel.d.ts +13 -5
  34. package/dist/ggui-session-channel.d.ts.map +1 -1
  35. package/dist/ggui-session-channel.js +2 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/instructions-presets.d.ts +22 -21
  40. package/dist/instructions-presets.d.ts.map +1 -1
  41. package/dist/instructions-presets.js +38 -38
  42. package/dist/llm-backed-negotiator.d.ts +1 -36
  43. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  44. package/dist/llm-backed-negotiator.js +0 -196
  45. package/dist/mcp-apps-outbound.d.ts +39 -90
  46. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  47. package/dist/mcp-apps-outbound.js +45 -3
  48. package/dist/mcp-endpoint-routes.d.ts.map +1 -1
  49. package/dist/mcp-endpoint-routes.js +4 -1
  50. package/dist/server.d.ts +108 -33
  51. package/dist/server.d.ts.map +1 -1
  52. package/dist/server.js +181 -48
  53. package/dist/storage.d.ts +9 -1
  54. package/dist/storage.d.ts.map +1 -1
  55. package/dist/storage.js +4 -3
  56. package/dist/view-proof-gate.d.ts +96 -0
  57. package/dist/view-proof-gate.d.ts.map +1 -0
  58. package/dist/view-proof-gate.js +167 -0
  59. 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.25.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": "^9.1.1",
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.25.0",
36
- "@ggui-ai/iframe-runtime": "0.25.0",
37
- "@ggui-ai/design": "0.25.0",
38
- "@ggui-ai/mcp-server-core": "0.25.0",
39
- "@ggui-ai/mcp-server-handlers": "0.25.0",
40
- "@ggui-ai/negotiator": "0.25.0",
41
- "@ggui-ai/preview-a2ui": "0.25.0",
42
- "@ggui-ai/project-config": "0.25.0",
43
- "@ggui-ai/protocol": "0.25.0",
44
- "@ggui-ai/ui-registry": "0.25.0",
45
- "@ggui-ai/ui-gen": "0.25.0"
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.25.0"
53
+ "@ggui-ai/protocol-conformance": "0.27.0"
55
54
  },
56
55
  "repository": {
57
56
  "type": "git",