@ggui-ai/mcp-server 0.2.0-alpha.4 → 0.3.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.
Files changed (152) hide show
  1. package/dist/admin-blueprints-transport.d.ts.map +1 -1
  2. package/dist/admin-blueprints-transport.js +2 -1
  3. package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
  4. package/dist/admin-oauth-providers-transport.js +7 -5
  5. package/dist/api-renders-routes.d.ts +85 -0
  6. package/dist/api-renders-routes.d.ts.map +1 -0
  7. package/dist/api-renders-routes.js +372 -0
  8. package/dist/build-mcp.d.ts +1 -1
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +39 -5
  11. package/dist/code-routes.d.ts +47 -0
  12. package/dist/code-routes.d.ts.map +1 -0
  13. package/dist/code-routes.js +81 -0
  14. package/dist/code-store-fs.js +2 -2
  15. package/dist/console-auth.d.ts +10 -10
  16. package/dist/console-auth.d.ts.map +1 -1
  17. package/dist/console-auth.js +5 -5
  18. package/dist/console-blueprint-routes.d.ts +71 -0
  19. package/dist/console-blueprint-routes.d.ts.map +1 -0
  20. package/dist/console-blueprint-routes.js +348 -0
  21. package/dist/console-chat-routes.d.ts +80 -0
  22. package/dist/console-chat-routes.d.ts.map +1 -0
  23. package/dist/console-chat-routes.js +182 -0
  24. package/dist/console-config-routes.d.ts +37 -0
  25. package/dist/console-config-routes.d.ts.map +1 -0
  26. package/dist/console-config-routes.js +91 -0
  27. package/dist/console-headers.d.ts +1 -1
  28. package/dist/console-info-routes.d.ts +84 -0
  29. package/dist/console-info-routes.d.ts.map +1 -0
  30. package/dist/console-info-routes.js +135 -0
  31. package/dist/console-keys-routes.d.ts +50 -0
  32. package/dist/console-keys-routes.d.ts.map +1 -0
  33. package/dist/console-keys-routes.js +222 -0
  34. package/dist/console-llm-keys-routes.d.ts +47 -0
  35. package/dist/console-llm-keys-routes.d.ts.map +1 -0
  36. package/dist/console-llm-keys-routes.js +443 -0
  37. package/dist/console-mcp-tools-routes.d.ts +41 -0
  38. package/dist/console-mcp-tools-routes.d.ts.map +1 -0
  39. package/dist/console-mcp-tools-routes.js +60 -0
  40. package/dist/console-registry-routes.d.ts +66 -0
  41. package/dist/console-registry-routes.d.ts.map +1 -0
  42. package/dist/console-registry-routes.js +276 -0
  43. package/dist/console-session-routes.d.ts +89 -0
  44. package/dist/console-session-routes.d.ts.map +1 -0
  45. package/dist/console-session-routes.js +385 -0
  46. package/dist/console-sessions-routes.d.ts +52 -0
  47. package/dist/console-sessions-routes.d.ts.map +1 -0
  48. package/dist/console-sessions-routes.js +106 -0
  49. package/dist/console-static-routes.d.ts +54 -0
  50. package/dist/console-static-routes.d.ts.map +1 -0
  51. package/dist/console-static-routes.js +190 -0
  52. package/dist/console-theme-routes.d.ts +3 -3
  53. package/dist/console-theme-routes.js +1 -1
  54. package/dist/console-timeline.d.ts +5 -5
  55. package/dist/console-timeline.d.ts.map +1 -1
  56. package/dist/console-timeline.js +27 -26
  57. package/dist/console-welcome.js +2 -2
  58. package/dist/email-login.d.ts.map +1 -1
  59. package/dist/email-login.js +2 -3
  60. package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
  61. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
  62. package/dist/ggui-session-channel/action-ingress.js +228 -0
  63. package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
  64. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
  65. package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
  66. package/dist/ggui-session-channel/internal-types.d.ts +102 -0
  67. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
  68. package/dist/ggui-session-channel/internal-types.js +6 -0
  69. package/dist/ggui-session-channel/outbound.d.ts +81 -0
  70. package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
  71. package/dist/ggui-session-channel/outbound.js +174 -0
  72. package/dist/ggui-session-channel/socket-router.d.ts +38 -0
  73. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
  74. package/dist/ggui-session-channel/socket-router.js +213 -0
  75. package/dist/ggui-session-channel/subscribe.d.ts +165 -0
  76. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
  77. package/dist/ggui-session-channel/subscribe.js +370 -0
  78. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
  79. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
  80. package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
  81. package/dist/ggui-session-channel.d.ts +425 -0
  82. package/dist/ggui-session-channel.d.ts.map +1 -0
  83. package/dist/ggui-session-channel.js +262 -0
  84. package/dist/health-routes.d.ts +76 -0
  85. package/dist/health-routes.d.ts.map +1 -0
  86. package/dist/health-routes.js +145 -0
  87. package/dist/index.d.ts +10 -11
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +8 -9
  90. package/dist/instructions-presets.d.ts +3 -3
  91. package/dist/instructions-presets.js +24 -24
  92. package/dist/llm-backed-negotiator.d.ts +68 -67
  93. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  94. package/dist/llm-backed-negotiator.js +82 -221
  95. package/dist/mcp-apps-outbound.d.ts +47 -48
  96. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  97. package/dist/mcp-apps-outbound.js +154 -177
  98. package/dist/mcp-endpoint-routes.d.ts +88 -0
  99. package/dist/mcp-endpoint-routes.d.ts.map +1 -0
  100. package/dist/mcp-endpoint-routes.js +359 -0
  101. package/dist/mcp-mounts.d.ts +2 -76
  102. package/dist/mcp-mounts.d.ts.map +1 -1
  103. package/dist/mcp-mounts.js +0 -76
  104. package/dist/oauth-as-routes.d.ts +60 -0
  105. package/dist/oauth-as-routes.d.ts.map +1 -0
  106. package/dist/oauth-as-routes.js +82 -0
  107. package/dist/oauth-clients-routes.d.ts +39 -0
  108. package/dist/oauth-clients-routes.d.ts.map +1 -0
  109. package/dist/oauth-clients-routes.js +87 -0
  110. package/dist/oauth-login-types.d.ts +1 -20
  111. package/dist/oauth-login-types.d.ts.map +1 -1
  112. package/dist/oauth-login-types.js +30 -7
  113. package/dist/oauth-login.d.ts.map +1 -1
  114. package/dist/oauth-login.js +3 -2
  115. package/dist/oauth-providers-store.d.ts.map +1 -1
  116. package/dist/oauth-providers-store.js +5 -5
  117. package/dist/oauth.d.ts +9 -8
  118. package/dist/oauth.d.ts.map +1 -1
  119. package/dist/oauth.js +41 -19
  120. package/dist/pairing-transport.d.ts.map +1 -1
  121. package/dist/pairing-transport.js +2 -1
  122. package/dist/request-context.d.ts +2 -2
  123. package/dist/request-context.js +2 -2
  124. package/dist/reserved-validators.d.ts.map +1 -1
  125. package/dist/reserved-validators.js +9 -1
  126. package/dist/route-param.d.ts +9 -0
  127. package/dist/route-param.d.ts.map +1 -0
  128. package/dist/route-param.js +10 -0
  129. package/dist/runtime-bundle-route.d.ts +43 -0
  130. package/dist/runtime-bundle-route.d.ts.map +1 -0
  131. package/dist/runtime-bundle-route.js +80 -0
  132. package/dist/schema-compat.d.ts +64 -62
  133. package/dist/schema-compat.d.ts.map +1 -1
  134. package/dist/schema-compat.js +23 -51
  135. package/dist/server.d.ts +179 -193
  136. package/dist/server.d.ts.map +1 -1
  137. package/dist/server.js +644 -3759
  138. package/dist/storage.d.ts +5 -5
  139. package/dist/storage.d.ts.map +1 -1
  140. package/dist/storage.js +5 -5
  141. package/dist/thread-transport.d.ts.map +1 -1
  142. package/dist/thread-transport.js +4 -3
  143. package/dist/user-session-auth.d.ts +7 -21
  144. package/dist/user-session-auth.d.ts.map +1 -1
  145. package/dist/user-session-auth.js +7 -28
  146. package/package.json +16 -15
  147. package/dist/mcp-apps-inbound.d.ts +0 -86
  148. package/dist/mcp-apps-inbound.d.ts.map +0 -1
  149. package/dist/mcp-apps-inbound.js +0 -283
  150. package/dist/render-channel.d.ts +0 -694
  151. package/dist/render-channel.d.ts.map +0 -1
  152. package/dist/render-channel.js +0 -1775
@@ -0,0 +1,443 @@
1
+ /**
2
+ * BYOK LLM-keys plane — gated /ggui/console/llm-keys. Two postures:
3
+ *
4
+ * - `gateMode: 'admin-token'` (default — OSS-personal):
5
+ * gate accepts the admin bearer. Single global keyset; the
6
+ * /settings UI lets the operator paste keys for everyone.
7
+ * - `gateMode: 'auth-adapter'` (multi-tenant): gate
8
+ * calls the server's `AuthAdapter`. Each user's keys are
9
+ * scoped by `scopeFromRequest` (default: `userId`/`appId`).
10
+ *
11
+ * Wire shape (same in both gates):
12
+ * GET /ggui/console/llm-keys — list providers + presence
13
+ * POST /ggui/console/llm-keys — set { provider, key }
14
+ * DELETE /ggui/console/llm-keys/:provider — clear (idempotent)
15
+ * POST /ggui/console/llm-keys/:provider/probe — auth-validation probe
16
+ *
17
+ * Plaintext is NEVER returned on GET — unlike pairing tokens, an LLM
18
+ * key is a one-way paste (operator already has the key elsewhere; the
19
+ * server is just persisting it). The presence + source signal is
20
+ * enough for the /settings UI.
21
+ */
22
+ import { resolveIdentity, UnauthenticatedError } from "./auth.js";
23
+ import { applyDevtoolSecurityHeaders } from "./console-headers.js";
24
+ const ADMIN_COOKIE_NAME_LLM = "ggui_console_admin";
25
+ /**
26
+ * The LLM_PROVIDERS allowlist matches `LlmProvider` minus `bedrock`
27
+ * (which is IAM-based and never paste-resolvable — see byok-resolver
28
+ * PROVIDER_ENV_NAMES bedrock note). Order is the operator-facing
29
+ * display order: anthropic first since the OSS triad ships claude
30
+ * as the default model.
31
+ */
32
+ const LLM_PROVIDERS = [
33
+ "anthropic",
34
+ "openai",
35
+ "google",
36
+ "openrouter",
37
+ ];
38
+ /**
39
+ * Mirror of `byok-resolver.ts::PROVIDER_ENV_NAMES` — env-var name
40
+ * ordered list per provider, first non-empty wins. Mirrored rather
41
+ * than imported because `@ggui-ai/cli` is downstream of this
42
+ * package and we can't depend back the other direction.
43
+ */
44
+ const PROVIDER_ENV_NAMES = {
45
+ anthropic: ["ANTHROPIC_API_KEY"],
46
+ google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
47
+ openai: ["OPENAI_API_KEY"],
48
+ openrouter: ["OPENROUTER_API_KEY"],
49
+ };
50
+ /**
51
+ * Mount the LLM-keys routes onto the express app. Returns nothing —
52
+ * the routes self-register.
53
+ */
54
+ export function mountConsoleLlmKeysRoutes(opts) {
55
+ const { app, providerKeys, gateMode, adminToken, auth, logger } = opts;
56
+ const defaultScope = (_req, identity) => {
57
+ if (identity) {
58
+ if (identity.identity.kind === "user") {
59
+ return identity.identity.userId;
60
+ }
61
+ if (identity.identity.kind === "app") {
62
+ return identity.identity.appId;
63
+ }
64
+ }
65
+ return "global";
66
+ };
67
+ const scopeFromRequest = opts.scopeFromRequest ?? defaultScope;
68
+ const requestHasAdminAuthLlm = (req) => {
69
+ if (adminToken === null)
70
+ return false;
71
+ const authHeader = req.headers["authorization"];
72
+ if (typeof authHeader === "string") {
73
+ const match = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
74
+ if (match && match[1] === adminToken)
75
+ return true;
76
+ }
77
+ const cookieHeader = req.headers["cookie"];
78
+ if (typeof cookieHeader === "string") {
79
+ for (const raw of cookieHeader.split(";")) {
80
+ const trimmed = raw.trim();
81
+ const eq = trimmed.indexOf("=");
82
+ if (eq <= 0)
83
+ continue;
84
+ const name = trimmed.slice(0, eq);
85
+ if (name !== ADMIN_COOKIE_NAME_LLM)
86
+ continue;
87
+ const value = decodeURIComponent(trimmed.slice(eq + 1));
88
+ if (value === adminToken)
89
+ return true;
90
+ }
91
+ }
92
+ return false;
93
+ };
94
+ // Per-request identity stash. Populated by the gate when in
95
+ // 'auth-adapter' mode; left null under 'admin-token'. Route
96
+ // handlers read it via `getRequestIdentity(req)` so the scope
97
+ // resolver receives the resolved AuthResult without re-running
98
+ // `resolveIdentity`.
99
+ const llmKeysIdentityByRequest = new WeakMap();
100
+ const getRequestIdentity = (req) => llmKeysIdentityByRequest.get(req) ?? null;
101
+ // Path-prefix gate.
102
+ //
103
+ // admin-token → same shape as /keys (Bearer admin OR
104
+ // ggui_console_admin cookie).
105
+ // auth-adapter → calls `resolveIdentity(auth, req)`. `kind:'builder'`
106
+ // is rejected with 401 — the multi-tenant posture
107
+ // is meaningless without a real per-caller id.
108
+ app.use("/ggui/console/llm-keys", (req, res, next) => {
109
+ if (gateMode === "admin-token") {
110
+ if (requestHasAdminAuthLlm(req))
111
+ return next();
112
+ applyDevtoolSecurityHeaders(res);
113
+ res.status(401).json({ error: "admin_auth_required" });
114
+ return;
115
+ }
116
+ // 'auth-adapter' — the multi-tenant gate.
117
+ resolveIdentity(auth, req)
118
+ .then((identity) => {
119
+ if (identity.identity.kind === "builder") {
120
+ applyDevtoolSecurityHeaders(res);
121
+ res.status(401).json({
122
+ error: "tenant_required",
123
+ message: "Multi-tenant /llm-keys requires an end-user or app identity. " +
124
+ 'The configured AuthAdapter resolved kind:"builder" — pair a ' +
125
+ 'real user/app bearer or use providerKeysGate:"admin-token".',
126
+ });
127
+ return;
128
+ }
129
+ llmKeysIdentityByRequest.set(req, identity);
130
+ next();
131
+ })
132
+ .catch((err) => {
133
+ applyDevtoolSecurityHeaders(res);
134
+ if (err instanceof UnauthenticatedError) {
135
+ res.status(401).json({ error: "unauthenticated" });
136
+ return;
137
+ }
138
+ logger.warn("console_llm_keys_auth_failed", { error: String(err) });
139
+ res.status(500).json({ error: "auth_unexpected_error" });
140
+ });
141
+ });
142
+ // GET /ggui/console/llm-keys — list providers + presence.
143
+ // Each row reports:
144
+ // - name: 'anthropic' | 'openai' | 'google' | 'openrouter'
145
+ // - configured: boolean — true when EITHER env OR file has a key
146
+ // - source: 'env' | 'file' | null — env wins on collision
147
+ // - envName: which env var fired (only present when source='env')
148
+ // - envNames: the env-var names this provider accepts
149
+ // (informational — the /settings UI shows them so
150
+ // operators know they can `export` instead of pasting)
151
+ app.get("/ggui/console/llm-keys", async (req, res) => {
152
+ applyDevtoolSecurityHeaders(res);
153
+ try {
154
+ const scope = scopeFromRequest(req, getRequestIdentity(req));
155
+ const filePresent = new Set(await providerKeys.listProviders(scope));
156
+ // Build rows with an optional 12-char prefix preview so the
157
+ // operator can confirm WHICH key is loaded without exposing
158
+ // the secret. 12 chars covers the discriminating prefix
159
+ // (`sk-ant-api03`, `sk-ant-oat01`, `sk-or-v1-…`, etc.) while
160
+ // staying safely under the entropy floor — these prefixes
161
+ // are not secret on their own.
162
+ const rows = await Promise.all(LLM_PROVIDERS.map(async (provider) => {
163
+ const envNames = PROVIDER_ENV_NAMES[provider];
164
+ let envHit;
165
+ let envValue;
166
+ for (const name of envNames) {
167
+ const value = process.env[name];
168
+ if (value !== undefined && value.length > 0) {
169
+ envHit = name;
170
+ envValue = value;
171
+ break;
172
+ }
173
+ }
174
+ const inFile = filePresent.has(provider);
175
+ const source = envHit !== undefined ? "env" : inFile ? "file" : null;
176
+ let keyPreview;
177
+ if (envValue !== undefined) {
178
+ keyPreview = envValue.slice(0, 12);
179
+ }
180
+ else if (inFile) {
181
+ try {
182
+ const ref = await providerKeys.get(scope, provider);
183
+ if (ref)
184
+ keyPreview = ref.key.slice(0, 12);
185
+ }
186
+ catch {
187
+ // Best-effort — preview is advisory; never block the GET.
188
+ }
189
+ }
190
+ return {
191
+ name: provider,
192
+ configured: source !== null,
193
+ source,
194
+ ...(envHit !== undefined ? { envName: envHit } : {}),
195
+ envNames: [...envNames],
196
+ inFile,
197
+ ...(keyPreview !== undefined ? { keyPreview } : {}),
198
+ };
199
+ }));
200
+ res.json({
201
+ providers: rows,
202
+ scope,
203
+ });
204
+ }
205
+ catch (err) {
206
+ logger.warn("console_llm_keys_list_failed", {
207
+ error: String(err),
208
+ });
209
+ res.status(500).json({
210
+ error: "list_failed",
211
+ message: err instanceof Error
212
+ ? `LLM keys list failed — ${err.message}`
213
+ : `LLM keys list failed — ${String(err)}`,
214
+ });
215
+ }
216
+ });
217
+ // POST /ggui/console/llm-keys — set a provider's key.
218
+ // Body: { provider: 'anthropic'|'openai'|..., key: string }.
219
+ // Returns: { provider, source: 'file', envOverridden: boolean }.
220
+ // `envOverridden: true` means the operator pasted a key but env
221
+ // var also set — the resolver still picks env. The /settings UI
222
+ // surfaces this so operators don't think their paste is in effect.
223
+ app.post("/ggui/console/llm-keys", async (req, res) => {
224
+ applyDevtoolSecurityHeaders(res);
225
+ const body = req.body;
226
+ const provider = typeof body?.provider === "string" ? body.provider : "";
227
+ const key = typeof body?.key === "string" ? body.key.trim() : "";
228
+ if (!LLM_PROVIDERS.includes(provider)) {
229
+ res.status(400).json({
230
+ error: "invalid_provider",
231
+ message: `provider must be one of ${LLM_PROVIDERS.join(", ")}.`,
232
+ });
233
+ return;
234
+ }
235
+ if (key.length === 0 || key.length > 4096) {
236
+ res.status(400).json({
237
+ error: "invalid_key",
238
+ message: "`key` is required (non-empty string, ≤4096 chars).",
239
+ });
240
+ return;
241
+ }
242
+ try {
243
+ const typedProvider = provider;
244
+ const scope = scopeFromRequest(req, getRequestIdentity(req));
245
+ await providerKeys.set(scope, typedProvider, key);
246
+ const envNames = PROVIDER_ENV_NAMES[typedProvider];
247
+ let envOverride;
248
+ for (const name of envNames) {
249
+ const value = process.env[name];
250
+ if (value !== undefined && value.length > 0) {
251
+ envOverride = name;
252
+ break;
253
+ }
254
+ }
255
+ res.json({
256
+ provider: typedProvider,
257
+ source: "file",
258
+ envOverridden: envOverride !== undefined,
259
+ ...(envOverride !== undefined ? { envName: envOverride } : {}),
260
+ });
261
+ }
262
+ catch (err) {
263
+ logger.warn("console_llm_keys_set_failed", {
264
+ error: String(err),
265
+ provider,
266
+ });
267
+ res.status(500).json({
268
+ error: "set_failed",
269
+ message: err instanceof Error
270
+ ? `LLM key set failed — ${err.message}`
271
+ : `LLM key set failed — ${String(err)}`,
272
+ });
273
+ }
274
+ });
275
+ // DELETE /ggui/console/llm-keys/:provider — clear (idempotent).
276
+ // 204 even when the key wasn't set; mirrors `ProviderKeyStore.delete`
277
+ // contract. NEVER touches env — the operator owns env separately.
278
+ app.delete("/ggui/console/llm-keys/:provider", async (req, res) => {
279
+ applyDevtoolSecurityHeaders(res);
280
+ const provider = req.params["provider"];
281
+ if (!LLM_PROVIDERS.includes(provider)) {
282
+ res.status(400).json({
283
+ error: "invalid_provider",
284
+ message: `provider must be one of ${LLM_PROVIDERS.join(", ")}.`,
285
+ });
286
+ return;
287
+ }
288
+ try {
289
+ const typedProvider = provider;
290
+ const scope = scopeFromRequest(req, getRequestIdentity(req));
291
+ await providerKeys.delete(scope, typedProvider);
292
+ res.status(204).end();
293
+ }
294
+ catch (err) {
295
+ logger.warn("console_llm_keys_delete_failed", {
296
+ error: String(err),
297
+ provider,
298
+ });
299
+ res.status(500).json({
300
+ error: "delete_failed",
301
+ message: err instanceof Error
302
+ ? `LLM key delete failed — ${err.message}`
303
+ : `LLM key delete failed — ${String(err)}`,
304
+ });
305
+ }
306
+ });
307
+ // POST /ggui/console/llm-keys/:provider/probe — auth-validation health
308
+ // probe for the configured key. Hits each provider's cheapest
309
+ // auth-checking endpoint with a 5s timeout. Status code is always
310
+ // 200 — the `ok` flag carries the verdict so the UI can paint a dot
311
+ // without branching on HTTP status. NEVER returns or logs the key
312
+ // value (latency + ok-flag + provider name only).
313
+ const probeProvider = async (provider, key) => {
314
+ const start = Date.now();
315
+ const ac = new AbortController();
316
+ const timer = setTimeout(() => {
317
+ ac.abort();
318
+ }, 5000);
319
+ try {
320
+ let url;
321
+ const headers = {};
322
+ if (provider === "anthropic") {
323
+ url = "https://api.anthropic.com/v1/models?limit=1";
324
+ headers["x-api-key"] = key;
325
+ headers["anthropic-version"] = "2023-06-01";
326
+ }
327
+ else if (provider === "openai") {
328
+ url = "https://api.openai.com/v1/models";
329
+ headers["Authorization"] = `Bearer ${key}`;
330
+ }
331
+ else if (provider === "google") {
332
+ // Google uses query-param key auth — no Authorization header.
333
+ url = `https://generativelanguage.googleapis.com/v1beta/models?pageSize=1&key=${encodeURIComponent(key)}`;
334
+ }
335
+ else {
336
+ // openrouter — auth/key endpoint validates without listing.
337
+ url = "https://openrouter.ai/api/v1/auth/key";
338
+ headers["Authorization"] = `Bearer ${key}`;
339
+ }
340
+ const res = await globalThis.fetch(url, {
341
+ method: "GET",
342
+ headers,
343
+ signal: ac.signal,
344
+ });
345
+ const latencyMs = Date.now() - start;
346
+ if (res.ok) {
347
+ return { ok: true, latencyMs };
348
+ }
349
+ let detail = "";
350
+ try {
351
+ const body = (await res.json());
352
+ const errField = body.error;
353
+ const errMessage = typeof errField === "string"
354
+ ? errField
355
+ : errField !== null &&
356
+ typeof errField === "object" &&
357
+ "message" in errField &&
358
+ typeof errField.message === "string"
359
+ ? errField.message
360
+ : typeof body.message === "string"
361
+ ? body.message
362
+ : "";
363
+ if (errMessage.length > 0)
364
+ detail = ` ${errMessage}`;
365
+ }
366
+ catch {
367
+ // Non-JSON body — error code alone is enough for the dot.
368
+ }
369
+ return {
370
+ ok: false,
371
+ latencyMs,
372
+ error: `HTTP ${res.status}${detail}`.slice(0, 200),
373
+ };
374
+ }
375
+ catch (err) {
376
+ const latencyMs = Date.now() - start;
377
+ return {
378
+ ok: false,
379
+ latencyMs,
380
+ error: String(err).slice(0, 200),
381
+ };
382
+ }
383
+ finally {
384
+ clearTimeout(timer);
385
+ }
386
+ };
387
+ app.post("/ggui/console/llm-keys/:provider/probe", async (req, res) => {
388
+ applyDevtoolSecurityHeaders(res);
389
+ const provider = req.params["provider"];
390
+ if (!LLM_PROVIDERS.includes(provider)) {
391
+ res.status(400).json({
392
+ error: "invalid_provider",
393
+ message: `provider must be one of ${LLM_PROVIDERS.join(", ")}.`,
394
+ });
395
+ return;
396
+ }
397
+ try {
398
+ const typedProvider = provider;
399
+ // Resolve env-first-then-file, mirroring the GET handler's
400
+ // source rule. Env wins on collision so the probe matches what
401
+ // the generation pipeline would actually send.
402
+ const envNames = PROVIDER_ENV_NAMES[typedProvider];
403
+ let resolvedKey = null;
404
+ for (const name of envNames) {
405
+ const value = process.env[name];
406
+ if (value !== undefined && value.length > 0) {
407
+ resolvedKey = value;
408
+ break;
409
+ }
410
+ }
411
+ if (resolvedKey === null) {
412
+ const scope = scopeFromRequest(req, getRequestIdentity(req));
413
+ const ref = await providerKeys.get(scope, typedProvider);
414
+ if (ref !== null && ref.key.length > 0) {
415
+ resolvedKey = ref.key;
416
+ }
417
+ }
418
+ if (resolvedKey === null) {
419
+ res.status(400).json({ ok: false, error: "not_configured" });
420
+ return;
421
+ }
422
+ const result = await probeProvider(typedProvider, resolvedKey);
423
+ logger.info("console_llm_keys_probe", {
424
+ provider: typedProvider,
425
+ ok: result.ok,
426
+ latencyMs: result.latencyMs,
427
+ });
428
+ res.json(result);
429
+ }
430
+ catch (err) {
431
+ logger.warn("console_llm_keys_probe_failed", {
432
+ error: String(err),
433
+ provider,
434
+ });
435
+ res.status(500).json({
436
+ error: "probe_failed",
437
+ message: err instanceof Error
438
+ ? `LLM key probe failed — ${err.message}`
439
+ : `LLM key probe failed — ${String(err)}`,
440
+ });
441
+ }
442
+ });
443
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Console MCP-tool inventory route.
3
+ *
4
+ * GET /ggui/console/mcp/tools — registered MCP-tool inventory for
5
+ * the console SPA's `/mcp` page. Operator-facing "what tools does
6
+ * my server expose?" — same handler set the `/mcp` JSON-RPC
7
+ * endpoint surfaces via `tools/list`, but rendered as cards
8
+ * instead of curl output.
9
+ *
10
+ * Currently LIST-only — name + title? + description + input/output
11
+ * JSON Schema. A "test invoke" form is deferred — invoking a tool
12
+ * from the console needs a same-origin bearer claim story (console
13
+ * session cookie currently authenticates only the live-channel WS
14
+ * upgrade).
15
+ *
16
+ * Schema conversion: handlers carry Zod raw shapes; the wire
17
+ * needs JSON Schema. `z.toJSONSchema(z.object(rawShape))` does
18
+ * the conversion. Failure (e.g. an exotic Zod type the v4
19
+ * converter doesn't yet support) reports `{}` for that field
20
+ * and warn-logs — operators still see name + description, just
21
+ * without typed input/output detail.
22
+ */
23
+ import type { SharedHandler } from "@ggui-ai/mcp-server-handlers";
24
+ import type { Express } from "express";
25
+ import { type ZodRawShape } from "zod";
26
+ import type { Logger } from "./logger.js";
27
+ interface MountOptions {
28
+ /** Express app to mount onto. */
29
+ readonly app: Express;
30
+ /** Composed handler list the inventory projects. */
31
+ readonly handlers: ReadonlyArray<SharedHandler<ZodRawShape, ZodRawShape>>;
32
+ /** Structured logger for schema-conversion warnings. */
33
+ readonly logger: Logger;
34
+ }
35
+ /**
36
+ * Mount `GET /ggui/console/mcp/tools` onto the express app. Returns
37
+ * nothing — the route self-registers.
38
+ */
39
+ export declare function mountConsoleMcpToolsRoutes(opts: MountOptions): void;
40
+ export {};
41
+ //# sourceMappingURL=console-mcp-tools-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-mcp-tools-routes.d.ts","sourceRoot":"","sources":["../src/console-mcp-tools-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAK,KAAK,WAAW,EAAE,MAAM,KAAK,CAAC;AAE1C,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,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;IAC1E,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAwCnE"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Console MCP-tool inventory route.
3
+ *
4
+ * GET /ggui/console/mcp/tools — registered MCP-tool inventory for
5
+ * the console SPA's `/mcp` page. Operator-facing "what tools does
6
+ * my server expose?" — same handler set the `/mcp` JSON-RPC
7
+ * endpoint surfaces via `tools/list`, but rendered as cards
8
+ * instead of curl output.
9
+ *
10
+ * Currently LIST-only — name + title? + description + input/output
11
+ * JSON Schema. A "test invoke" form is deferred — invoking a tool
12
+ * from the console needs a same-origin bearer claim story (console
13
+ * session cookie currently authenticates only the live-channel WS
14
+ * upgrade).
15
+ *
16
+ * Schema conversion: handlers carry Zod raw shapes; the wire
17
+ * needs JSON Schema. `z.toJSONSchema(z.object(rawShape))` does
18
+ * the conversion. Failure (e.g. an exotic Zod type the v4
19
+ * converter doesn't yet support) reports `{}` for that field
20
+ * and warn-logs — operators still see name + description, just
21
+ * without typed input/output detail.
22
+ */
23
+ import { z } from "zod";
24
+ import { applyDevtoolSecurityHeaders } from "./console-headers.js";
25
+ /**
26
+ * Mount `GET /ggui/console/mcp/tools` onto the express app. Returns
27
+ * nothing — the route self-registers.
28
+ */
29
+ export function mountConsoleMcpToolsRoutes(opts) {
30
+ const { app, handlers, logger } = opts;
31
+ app.get("/ggui/console/mcp/tools", async (_req, res) => {
32
+ applyDevtoolSecurityHeaders(res);
33
+ const safeToJsonSchema = (shape) => {
34
+ try {
35
+ return z.toJSONSchema(z.object(shape));
36
+ }
37
+ catch (err) {
38
+ logger.warn("console_mcp_tools_schema_conversion_failed", {
39
+ error: String(err),
40
+ });
41
+ return {};
42
+ }
43
+ };
44
+ const tools = handlers.map((h) => {
45
+ const summary = {
46
+ name: h.name,
47
+ ...(h.title !== undefined ? { title: h.title } : {}),
48
+ description: h.description,
49
+ inputSchema: safeToJsonSchema(h.inputSchema),
50
+ outputSchema: safeToJsonSchema(h.outputSchema),
51
+ };
52
+ return summary;
53
+ });
54
+ // Stable sort by name for deterministic operator reading order
55
+ // — handlers are registered in module-import order, which is
56
+ // arbitrary from the operator's perspective.
57
+ tools.sort((a, b) => a.name.localeCompare(b.name));
58
+ res.json({ tools, total: tools.length });
59
+ });
60
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Console blueprint-catalog read/maintenance routes.
3
+ *
4
+ * GET /ggui/console/registry — declared-blueprint +
5
+ * primitive catalog (boot-time content).
6
+ * GET /ggui/console/blueprints/cached — intent-keyed
7
+ * generation-cache listing (legacy surface).
8
+ * DELETE /ggui/console/blueprints/cached/:id — invalidate one entry.
9
+ * POST /ggui/console/blueprints/cached/clear — bulk-clear the scope.
10
+ * GET /ggui/console/blueprints/registry — contract-keyed runtime
11
+ * registry the matcher actually consults (per-row tier
12
+ * diagnostics: kind, contractKey, hitCount, lastHitAt,
13
+ * provenance).
14
+ *
15
+ * `/registry` sources:
16
+ * - `blueprints[]` ← `uiRegistry?.list()` (full manifest entries;
17
+ * surfaces `name` + `description?` + `category?`). Same registry
18
+ * the `/ggui/console/blueprint/:id` endpoint resolves against, so
19
+ * the /registry click-through to /preview/<id> is guaranteed to
20
+ * hit the same dataset.
21
+ * - `primitives[]` ← `primitiveCatalogs` (`DiscoveredPrimitiveCatalog`
22
+ * shape from `@ggui-ai/project-config`). Each entry flattens
23
+ * one primitive per row, tagging it with its catalog's
24
+ * `source` ('package' | 'local') + `import` specifier.
25
+ *
26
+ * Scope: read-only on `/registry` (authoring happens on disk and the
27
+ * server re-reads on boot; zero-config is an honest empty shape).
28
+ * The cache/registry surfaces are scoped to `DEFAULT_BUILDER_APP_ID`
29
+ * because the OSS server is single-tenant by construction — same
30
+ * scope the render handler writes to, so list/invalidate/clear see
31
+ * what the real cache writes.
32
+ *
33
+ * Enumeration gate: `listGenerationCache` + `clearGenerationCache` +
34
+ * `listBlueprints` require an EnumerableVectorStore. Every OSS
35
+ * default satisfies it (`InMemoryVectorStore`, `SqliteVectorStore`);
36
+ * deployments that wire a non-enumerable backend get an honest `501`
37
+ * with a reason code rather than a silent empty list or a runtime
38
+ * throw.
39
+ *
40
+ * No bearer auth (same rule as the other console endpoints —
41
+ * same-origin operator-facing).
42
+ */
43
+ import type { VectorStore } from "@ggui-ai/mcp-server-core";
44
+ import type { DiscoveredPrimitiveCatalog } from "@ggui-ai/project-config/node";
45
+ import type { UiRegistry } from "@ggui-ai/ui-registry";
46
+ import type { Express } from "express";
47
+ import type { Logger } from "./logger.js";
48
+ interface MountOptions {
49
+ /** Express app to mount onto. */
50
+ readonly app: Express;
51
+ /** Declared-blueprint registry (absent = empty `blueprints[]`). */
52
+ readonly uiRegistry?: UiRegistry;
53
+ /** Discovered primitive catalogs (absent = empty `primitives[]`). */
54
+ readonly primitiveCatalogs?: ReadonlyArray<DiscoveredPrimitiveCatalog>;
55
+ /** Vector store backing the generation cache + runtime registry. */
56
+ readonly vectors: VectorStore;
57
+ /** Structured logger. */
58
+ readonly logger: Logger;
59
+ }
60
+ /**
61
+ * Mount the console catalog routes onto the express app. Returns
62
+ * nothing — the routes self-register.
63
+ */
64
+ export declare function mountConsoleRegistryRoutes(opts: MountOptions): void;
65
+ export {};
66
+ //# sourceMappingURL=console-registry-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-registry-routes.d.ts","sourceRoot":"","sources":["../src/console-registry-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAS5D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,aAAa,CAAC,0BAA0B,CAAC,CAAC;IACvE,oEAAoE;IACpE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,yBAAyB;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA4PnE"}