@guuey/config 0.1.0 → 0.1.2
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/dist/agent.d.ts +108 -7
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +163 -13
- package/dist/browser.d.ts +21 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +20 -0
- package/dist/colocated.d.ts +44 -0
- package/dist/colocated.d.ts.map +1 -0
- package/dist/colocated.js +55 -0
- package/dist/guuey-context.d.ts +90 -0
- package/dist/guuey-context.d.ts.map +1 -0
- package/dist/guuey-context.js +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/registry.d.ts +7 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +24 -6
- package/dist/schema.d.ts +15 -6
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -3
- package/package.json +11 -1
- package/dist/agent-loader.d.ts +0 -65
- package/dist/agent-loader.d.ts.map +0 -1
- package/dist/agent-loader.js +0 -155
- package/dist/mcp-proxy.d.ts +0 -123
- package/dist/mcp-proxy.d.ts.map +0 -1
- package/dist/mcp-proxy.js +0 -133
- package/dist/mcp-servers.d.ts +0 -173
- package/dist/mcp-servers.d.ts.map +0 -1
- package/dist/mcp-servers.js +0 -147
package/dist/agent.d.ts
CHANGED
|
@@ -49,20 +49,30 @@ import { z } from 'zod';
|
|
|
49
49
|
*/
|
|
50
50
|
export declare const AGENT_FRAMEWORKS: readonly ["claude-agent-sdk", "openai-agents-sdk", "google-adk", "vanilla"];
|
|
51
51
|
export type AgentFramework = (typeof AGENT_FRAMEWORKS)[number];
|
|
52
|
+
/**
|
|
53
|
+
* Cross-app profile access posture. `'read'` = the agent may recall the
|
|
54
|
+
* user's cross-app profile; `'read-write'` additionally lets it write this
|
|
55
|
+
* app's section. Absent = no profile access (default-closed, consent-gated).
|
|
56
|
+
*/
|
|
57
|
+
export declare const ProfileAccessSchema: z.ZodEnum<{
|
|
58
|
+
read: "read";
|
|
59
|
+
"read-write": "read-write";
|
|
60
|
+
}>;
|
|
61
|
+
/** Static TypeScript type derived from {@link ProfileAccessSchema}. */
|
|
62
|
+
export type ProfileAccess = z.infer<typeof ProfileAccessSchema>;
|
|
52
63
|
/**
|
|
53
64
|
* A single MCP server entry inside `agent.mcpServers`.
|
|
54
65
|
*
|
|
55
66
|
* Discriminated union on `kind` — one slot per hosting mode:
|
|
56
|
-
* - `colocated` —
|
|
67
|
+
* - `colocated` — guuey-managed HTTP child inside the agent pod
|
|
57
68
|
* - `hosted` — guuey-hosted registry MCP (Starter+)
|
|
58
69
|
* - `proxied` — 3rd-party SaaS via mcp-proxy credential broker (v2)
|
|
59
70
|
* - `external` — builder-hosted, reached by URL (plain or federated)
|
|
60
71
|
*/
|
|
61
72
|
declare const McpServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
62
73
|
kind: z.ZodLiteral<"colocated">;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
74
|
+
source: z.ZodString;
|
|
75
|
+
devPort: z.ZodOptional<z.ZodNumber>;
|
|
66
76
|
}, z.core.$strict>, z.ZodObject<{
|
|
67
77
|
kind: z.ZodLiteral<"hosted">;
|
|
68
78
|
server: z.ZodOptional<z.ZodString>;
|
|
@@ -81,6 +91,11 @@ declare const McpServerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
81
91
|
federate: z.ZodOptional<z.ZodBoolean>;
|
|
82
92
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
83
93
|
devPort: z.ZodOptional<z.ZodNumber>;
|
|
94
|
+
mcpResourceUrl: z.ZodOptional<z.ZodURL>;
|
|
95
|
+
profileAccess: z.ZodOptional<z.ZodEnum<{
|
|
96
|
+
read: "read";
|
|
97
|
+
"read-write": "read-write";
|
|
98
|
+
}>>;
|
|
84
99
|
}, z.core.$strict>], "kind">;
|
|
85
100
|
/**
|
|
86
101
|
* Tool-gate block — allowlist applied first (intersect with what the MCP
|
|
@@ -150,6 +165,40 @@ declare const DeploySchema: z.ZodObject<{
|
|
|
150
165
|
* Exported as a zod object so the top-level `GuueyJsonV1` schema (in
|
|
151
166
|
* `./schema.ts`) can nest it. Static type via {@link GuueyAgent}.
|
|
152
167
|
*/
|
|
168
|
+
/**
|
|
169
|
+
* The `agent.mcpServers` map alone — for consumers that resolve/lower the
|
|
170
|
+
* servers SUBTREE without validating the whole snapshot (deploy-controller's
|
|
171
|
+
* resolve-mcp): whole-snapshot strictness made lowering fail open on any
|
|
172
|
+
* schema field the running consumer predates.
|
|
173
|
+
*/
|
|
174
|
+
export declare const McpServersSection: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
175
|
+
kind: z.ZodLiteral<"colocated">;
|
|
176
|
+
source: z.ZodString;
|
|
177
|
+
devPort: z.ZodOptional<z.ZodNumber>;
|
|
178
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
179
|
+
kind: z.ZodLiteral<"hosted">;
|
|
180
|
+
server: z.ZodOptional<z.ZodString>;
|
|
181
|
+
source: z.ZodOptional<z.ZodString>;
|
|
182
|
+
devPort: z.ZodOptional<z.ZodNumber>;
|
|
183
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
184
|
+
kind: z.ZodLiteral<"proxied">;
|
|
185
|
+
connection: z.ZodString;
|
|
186
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
187
|
+
kind: z.ZodLiteral<"external">;
|
|
188
|
+
url: z.ZodURL;
|
|
189
|
+
transport: z.ZodOptional<z.ZodEnum<{
|
|
190
|
+
http: "http";
|
|
191
|
+
sse: "sse";
|
|
192
|
+
}>>;
|
|
193
|
+
federate: z.ZodOptional<z.ZodBoolean>;
|
|
194
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
195
|
+
devPort: z.ZodOptional<z.ZodNumber>;
|
|
196
|
+
mcpResourceUrl: z.ZodOptional<z.ZodURL>;
|
|
197
|
+
profileAccess: z.ZodOptional<z.ZodEnum<{
|
|
198
|
+
read: "read";
|
|
199
|
+
"read-write": "read-write";
|
|
200
|
+
}>>;
|
|
201
|
+
}, z.core.$strict>], "kind">>;
|
|
153
202
|
export declare const AgentSectionV1: z.ZodObject<{
|
|
154
203
|
mode: z.ZodOptional<z.ZodEnum<{
|
|
155
204
|
code: "code";
|
|
@@ -161,6 +210,7 @@ export declare const AgentSectionV1: z.ZodObject<{
|
|
|
161
210
|
"google-adk": "google-adk";
|
|
162
211
|
vanilla: "vanilla";
|
|
163
212
|
}>>;
|
|
213
|
+
entry: z.ZodOptional<z.ZodString>;
|
|
164
214
|
model: z.ZodOptional<z.ZodString>;
|
|
165
215
|
modelProvider: z.ZodOptional<z.ZodEnum<{
|
|
166
216
|
openai: "openai";
|
|
@@ -171,9 +221,8 @@ export declare const AgentSectionV1: z.ZodObject<{
|
|
|
171
221
|
}, z.core.$strict>]>>;
|
|
172
222
|
mcpServers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
173
223
|
kind: z.ZodLiteral<"colocated">;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
224
|
+
source: z.ZodString;
|
|
225
|
+
devPort: z.ZodOptional<z.ZodNumber>;
|
|
177
226
|
}, z.core.$strict>, z.ZodObject<{
|
|
178
227
|
kind: z.ZodLiteral<"hosted">;
|
|
179
228
|
server: z.ZodOptional<z.ZodString>;
|
|
@@ -192,6 +241,11 @@ export declare const AgentSectionV1: z.ZodObject<{
|
|
|
192
241
|
federate: z.ZodOptional<z.ZodBoolean>;
|
|
193
242
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
194
243
|
devPort: z.ZodOptional<z.ZodNumber>;
|
|
244
|
+
mcpResourceUrl: z.ZodOptional<z.ZodURL>;
|
|
245
|
+
profileAccess: z.ZodOptional<z.ZodEnum<{
|
|
246
|
+
read: "read";
|
|
247
|
+
"read-write": "read-write";
|
|
248
|
+
}>>;
|
|
195
249
|
}, z.core.$strict>], "kind">>>;
|
|
196
250
|
tools: z.ZodOptional<z.ZodObject<{
|
|
197
251
|
allowlist: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -223,6 +277,10 @@ export declare const AgentSectionV1: z.ZodObject<{
|
|
|
223
277
|
user: "user";
|
|
224
278
|
app: "app";
|
|
225
279
|
}>>>;
|
|
280
|
+
profileAccess: z.ZodOptional<z.ZodEnum<{
|
|
281
|
+
read: "read";
|
|
282
|
+
"read-write": "read-write";
|
|
283
|
+
}>>;
|
|
226
284
|
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
227
285
|
secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
228
286
|
endpoint: z.ZodOptional<z.ZodObject<{
|
|
@@ -270,6 +328,49 @@ export type GuueyAgentDeploy = z.infer<typeof DeploySchema>;
|
|
|
270
328
|
* `X-API-Key: ${env.KEY}`) and non-secret literals (`Content-Type`) pass.
|
|
271
329
|
*/
|
|
272
330
|
export declare function validateNoLiteralSecrets(agent: GuueyAgent | undefined): string[];
|
|
331
|
+
/**
|
|
332
|
+
* Validate that every `kind: 'colocated'` entry's NAME (the `mcpServers`
|
|
333
|
+
* map key) is safe to compose into `colocatedResourceUrl` — i.e. passes
|
|
334
|
+
* {@link isValidColocatedServerName} (from `./colocated.ts`, the single
|
|
335
|
+
* source of truth for the rule). Returns a list of human-readable
|
|
336
|
+
* violation messages (empty = clean).
|
|
337
|
+
*/
|
|
338
|
+
export declare function validateColocatedServerNames(agent: GuueyAgent | undefined): string[];
|
|
339
|
+
/**
|
|
340
|
+
* Validate that no `agent.mcpServers` entry uses `kind: 'proxied'`. Returns a
|
|
341
|
+
* list of human-readable violation messages (empty = clean), one per proxied
|
|
342
|
+
* entry, naming the server so the builder knows which ref to change.
|
|
343
|
+
*/
|
|
344
|
+
export declare function validateNoProxiedServers(agent: GuueyAgent | undefined): string[];
|
|
345
|
+
/**
|
|
346
|
+
* The reserved colocated key the auto-injected memory MCP is booted + spliced
|
|
347
|
+
* under (memmcp). NEVER builder-declarable — the memory child's own server
|
|
348
|
+
* advertises this same name and its aud is `colocatedResourceUrl(appId, this)`.
|
|
349
|
+
*/
|
|
350
|
+
export declare const RESERVED_MEMORY_SERVER_NAME = "guuey-memory";
|
|
351
|
+
/**
|
|
352
|
+
* The reserved colocated key the auto-injected cross-app profile MCP is
|
|
353
|
+
* booted + spliced under (profile T1+). NEVER builder-declarable — mirrors
|
|
354
|
+
* {@link RESERVED_MEMORY_SERVER_NAME}'s reservation for the same reason: a
|
|
355
|
+
* builder-declared server under this key would be silently replaced by the
|
|
356
|
+
* platform entry at invoke time.
|
|
357
|
+
*/
|
|
358
|
+
export declare const RESERVED_PROFILE_SERVER_NAME = "guuey-profile";
|
|
359
|
+
/**
|
|
360
|
+
* Every `mcpServers` map key the platform reserves. Extensible — one entry
|
|
361
|
+
* today ({@link RESERVED_MEMORY_SERVER_NAME}); future platform-injected
|
|
362
|
+
* servers add their key here and inherit the deploy-time rejection for free.
|
|
363
|
+
*/
|
|
364
|
+
export declare const RESERVED_MCP_SERVER_NAMES: readonly string[];
|
|
365
|
+
/**
|
|
366
|
+
* Validate that no `agent.mcpServers` entry uses a platform-RESERVED name
|
|
367
|
+
* ({@link RESERVED_MCP_SERVER_NAMES}) — regardless of `kind` (the key is
|
|
368
|
+
* reserved, not just one hosting mode; a builder must not shadow it as
|
|
369
|
+
* `colocated`, `external`, or anything else). Returns a list of human-readable
|
|
370
|
+
* violation messages (empty = clean), one per reserved entry. Mirrors
|
|
371
|
+
* {@link validateNoProxiedServers}'s shape.
|
|
372
|
+
*/
|
|
373
|
+
export declare function validateReservedServerNames(agent: GuueyAgent | undefined): string[];
|
|
273
374
|
/**
|
|
274
375
|
* Platform default MCP server map. Applied by the pod when `agent.mcpServers`
|
|
275
376
|
* is absent. Exposed here so non-pod consumers (CLI dry-run, lints) can show
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/agent.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
*/
|
|
42
42
|
import { z } from 'zod';
|
|
43
43
|
import { AGENT_SIZES } from './hosting.js';
|
|
44
|
+
import { isValidColocatedServerName } from './colocated.js';
|
|
44
45
|
/**
|
|
45
46
|
* Supported framework adapters. The pod runtime selects the matching
|
|
46
47
|
* `@guuey/framework-*` adapter at boot. `vanilla` skips the framework
|
|
@@ -61,18 +62,25 @@ export const AGENT_FRAMEWORKS = [
|
|
|
61
62
|
*/
|
|
62
63
|
const HeadersSchema = z.record(z.string().min(1), z.string());
|
|
63
64
|
/**
|
|
64
|
-
* `
|
|
65
|
-
*
|
|
66
|
-
*
|
|
65
|
+
* Cross-app profile access posture. `'read'` = the agent may recall the
|
|
66
|
+
* user's cross-app profile; `'read-write'` additionally lets it write this
|
|
67
|
+
* app's section. Absent = no profile access (default-closed, consent-gated).
|
|
68
|
+
*/
|
|
69
|
+
export const ProfileAccessSchema = z.enum(['read', 'read-write']);
|
|
70
|
+
/**
|
|
71
|
+
* `kind: 'colocated'` — MCP server runs as a guuey-managed HTTP child
|
|
72
|
+
* **inside the agent pod** (co-locate = same gVisor sandbox). COGS: ~$0
|
|
73
|
+
* (rides the agent pod). `source` is a project-relative path the Router
|
|
74
|
+
* lowering builds/boots as a local HTTP server; `devPort` mirrors the
|
|
75
|
+
* `hosted`/`external` dev-loop story (name→localhost URL resolution for
|
|
76
|
+
* `guuey dev`).
|
|
67
77
|
*/
|
|
68
78
|
const ColocatedMcp = z.strictObject({
|
|
69
79
|
kind: z.literal('colocated'),
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
/** Static headers (less common for stdio, but supported for symmetry). */
|
|
75
|
-
headers: HeadersSchema.optional(),
|
|
80
|
+
/** Source directory relative to `guuey.json`. Required. */
|
|
81
|
+
source: z.string().min(1),
|
|
82
|
+
/** Local dev-loop port (`guuey dev`) this MCP is served on for name→localhost URL resolution. */
|
|
83
|
+
devPort: z.number().int().min(1).max(65535).optional(),
|
|
76
84
|
});
|
|
77
85
|
/**
|
|
78
86
|
* `kind: 'hosted'` — a workspace-owned registry MCP running on guuey's
|
|
@@ -104,7 +112,7 @@ const HostedMcp = z
|
|
|
104
112
|
*/
|
|
105
113
|
const ProxiedMcp = z.strictObject({
|
|
106
114
|
kind: z.literal('proxied'),
|
|
107
|
-
/** mcp-proxy connection id (
|
|
115
|
+
/** mcp-proxy connection id (not yet available — rides with the mcp-proxy credential broker). */
|
|
108
116
|
connection: z.string().min(1),
|
|
109
117
|
});
|
|
110
118
|
/**
|
|
@@ -130,12 +138,24 @@ const ExternalMcp = z.strictObject({
|
|
|
130
138
|
headers: HeadersSchema.optional(),
|
|
131
139
|
/** Local dev-loop port (`guuey dev`) this MCP is served on for name→localhost URL resolution. */
|
|
132
140
|
devPort: z.number().int().min(1).max(65535).optional(),
|
|
141
|
+
/**
|
|
142
|
+
* INTERNAL — set only by Router lowering; when present the federation mint
|
|
143
|
+
* uses this as the RFC 8707 resource instead of `url`.
|
|
144
|
+
*/
|
|
145
|
+
mcpResourceUrl: z.url().optional(),
|
|
146
|
+
/**
|
|
147
|
+
* INTERNAL — set only by Router lowering when this entry is the spliced
|
|
148
|
+
* profile MCP; carries the agent's `profileAccess` posture onto the entry
|
|
149
|
+
* itself so the profile child's tool gate (read vs read-write) survives
|
|
150
|
+
* independent of the top-level `agent.profileAccess` field.
|
|
151
|
+
*/
|
|
152
|
+
profileAccess: ProfileAccessSchema.optional(),
|
|
133
153
|
});
|
|
134
154
|
/**
|
|
135
155
|
* A single MCP server entry inside `agent.mcpServers`.
|
|
136
156
|
*
|
|
137
157
|
* Discriminated union on `kind` — one slot per hosting mode:
|
|
138
|
-
* - `colocated` —
|
|
158
|
+
* - `colocated` — guuey-managed HTTP child inside the agent pod
|
|
139
159
|
* - `hosted` — guuey-hosted registry MCP (Starter+)
|
|
140
160
|
* - `proxied` — 3rd-party SaaS via mcp-proxy credential broker (v2)
|
|
141
161
|
* - `external` — builder-hosted, reached by URL (plain or federated)
|
|
@@ -238,6 +258,13 @@ const StorageScopeSchema = z.array(z.enum(['user', 'app']));
|
|
|
238
258
|
* Exported as a zod object so the top-level `GuueyJsonV1` schema (in
|
|
239
259
|
* `./schema.ts`) can nest it. Static type via {@link GuueyAgent}.
|
|
240
260
|
*/
|
|
261
|
+
/**
|
|
262
|
+
* The `agent.mcpServers` map alone — for consumers that resolve/lower the
|
|
263
|
+
* servers SUBTREE without validating the whole snapshot (deploy-controller's
|
|
264
|
+
* resolve-mcp): whole-snapshot strictness made lowering fail open on any
|
|
265
|
+
* schema field the running consumer predates.
|
|
266
|
+
*/
|
|
267
|
+
export const McpServersSection = z.record(z.string().min(1), McpServerSchema);
|
|
241
268
|
export const AgentSectionV1 = z.strictObject({
|
|
242
269
|
// ── Deploy routing ──
|
|
243
270
|
/**
|
|
@@ -256,6 +283,14 @@ export const AgentSectionV1 = z.strictObject({
|
|
|
256
283
|
mode: z.enum(['code', 'declarative']).optional(),
|
|
257
284
|
// ── Framework + runtime ──
|
|
258
285
|
framework: z.enum(AGENT_FRAMEWORKS).optional(),
|
|
286
|
+
/**
|
|
287
|
+
* Graceful code-mode: a module (path relative to the project root, built
|
|
288
|
+
* output) whose default export is the framework-native agent object or a
|
|
289
|
+
* factory `(guuey: GuueyContext) => agent`. The platform host imports and
|
|
290
|
+
* runs it — the dev writes zero harness code. Mutually exclusive with the
|
|
291
|
+
* full-worker `worker` field (which wins when both are present).
|
|
292
|
+
*/
|
|
293
|
+
entry: z.string().min(1).optional(),
|
|
259
294
|
model: z.string().min(1).optional(),
|
|
260
295
|
/**
|
|
261
296
|
* Managed-LLM provider selector — only meaningful for
|
|
@@ -274,7 +309,7 @@ export const AgentSectionV1 = z.strictObject({
|
|
|
274
309
|
* `ggui` explicitly to keep it alongside other servers.
|
|
275
310
|
*
|
|
276
311
|
* Each entry is a discriminated union on `kind`:
|
|
277
|
-
* - `'colocated'` —
|
|
312
|
+
* - `'colocated'` — guuey-managed HTTP child inside the agent pod
|
|
278
313
|
* - `'hosted'` — guuey-hosted registry MCP (Starter+)
|
|
279
314
|
* - `'proxied'` — 3rd-party SaaS via mcp-proxy (v2)
|
|
280
315
|
* - `'external'` — builder-hosted URL (plain or federated)
|
|
@@ -288,6 +323,7 @@ export const AgentSectionV1 = z.strictObject({
|
|
|
288
323
|
auth: AuthSchema.optional(),
|
|
289
324
|
memory: MemorySchema.optional(),
|
|
290
325
|
storage: StorageScopeSchema.optional(),
|
|
326
|
+
profileAccess: ProfileAccessSchema.optional(),
|
|
291
327
|
// ── Env + secrets ──
|
|
292
328
|
/** Literal non-sensitive env vars baked into the pod at boot. */
|
|
293
329
|
env: z.record(z.string().min(1), z.string()).optional(),
|
|
@@ -389,7 +425,7 @@ export function validateNoLiteralSecrets(agent) {
|
|
|
389
425
|
if (!servers)
|
|
390
426
|
return violations;
|
|
391
427
|
for (const [serverName, server] of Object.entries(servers)) {
|
|
392
|
-
// Only
|
|
428
|
+
// Only the `external` union arm carries a `headers` field.
|
|
393
429
|
const headers = 'headers' in server ? server.headers : undefined;
|
|
394
430
|
if (!headers)
|
|
395
431
|
continue;
|
|
@@ -412,6 +448,120 @@ export function validateNoLiteralSecrets(agent) {
|
|
|
412
448
|
}
|
|
413
449
|
return violations;
|
|
414
450
|
}
|
|
451
|
+
// ── No-invalid-colocated-names validation (deploy-time contract enforcement) ──
|
|
452
|
+
//
|
|
453
|
+
// `agent.mcpServers`' key is schema-typed only `z.string().min(1)` — any
|
|
454
|
+
// non-empty string parses. But at pod boot, `lowerColocated` composes the
|
|
455
|
+
// KEY (not just `source`) into `colocatedResourceUrl(appId, name)`, which
|
|
456
|
+
// THROWS for anything outside `/^[A-Za-z0-9_-]+$/` (see `./colocated.ts`).
|
|
457
|
+
// An invalid colocated name therefore parses fine client-side and only
|
|
458
|
+
// fails once the pod is already booting, as an unactionable
|
|
459
|
+
// `POD_FATAL_BOOT_ERROR` crash-loop. `validateColocatedServerNames` is the
|
|
460
|
+
// deploy-time pre-flight that catches it first — mirrors
|
|
461
|
+
// `validateNoLiteralSecrets`'s shape (explicit lint, called by
|
|
462
|
+
// `@guuey/cli`'s `commands/deploy.ts` right before upload).
|
|
463
|
+
/**
|
|
464
|
+
* Validate that every `kind: 'colocated'` entry's NAME (the `mcpServers`
|
|
465
|
+
* map key) is safe to compose into `colocatedResourceUrl` — i.e. passes
|
|
466
|
+
* {@link isValidColocatedServerName} (from `./colocated.ts`, the single
|
|
467
|
+
* source of truth for the rule). Returns a list of human-readable
|
|
468
|
+
* violation messages (empty = clean).
|
|
469
|
+
*/
|
|
470
|
+
export function validateColocatedServerNames(agent) {
|
|
471
|
+
const violations = [];
|
|
472
|
+
const servers = agent?.mcpServers;
|
|
473
|
+
if (!servers)
|
|
474
|
+
return violations;
|
|
475
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
476
|
+
if (server.kind === 'colocated' && !isValidColocatedServerName(name)) {
|
|
477
|
+
violations.push(`colocated MCP server name "${name}" is invalid — use only letters, digits, hyphen, underscore (it becomes part of a URL and a storage scope)`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return violations;
|
|
481
|
+
}
|
|
482
|
+
// ── No-proxied-servers validation (deploy-time contract enforcement) ────────
|
|
483
|
+
//
|
|
484
|
+
// `kind: 'proxied'` keeps its schema arm (the documented mcp-proxy
|
|
485
|
+
// credential-broker deferral — wires when the 2nd stored-creds customer signs
|
|
486
|
+
// up) but has NO runtime support today: a pod that booted one would silently
|
|
487
|
+
// miss those tools. `validateNoProxiedServers` is the synchronous deploy-time
|
|
488
|
+
// pre-flight (mirrors `validateColocatedServerNames`'s shape) that rejects it
|
|
489
|
+
// with a fast, actionable error. The deploy-controller's
|
|
490
|
+
// `resolveMcpServersInSnapshot` carries the authoritative backstop throw for
|
|
491
|
+
// code deploys, which never pass through this validator.
|
|
492
|
+
/**
|
|
493
|
+
* Validate that no `agent.mcpServers` entry uses `kind: 'proxied'`. Returns a
|
|
494
|
+
* list of human-readable violation messages (empty = clean), one per proxied
|
|
495
|
+
* entry, naming the server so the builder knows which ref to change.
|
|
496
|
+
*/
|
|
497
|
+
export function validateNoProxiedServers(agent) {
|
|
498
|
+
const violations = [];
|
|
499
|
+
const servers = agent?.mcpServers;
|
|
500
|
+
if (!servers)
|
|
501
|
+
return violations;
|
|
502
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
503
|
+
if (server.kind === 'proxied') {
|
|
504
|
+
violations.push(`MCP server "${name}": kind 'proxied' (the mcp-proxy credential broker) is not yet supported — use 'external', 'hosted', or 'colocated'.`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return violations;
|
|
508
|
+
}
|
|
509
|
+
// ── Reserved platform MCP server names (deploy-time reservation) ────────────
|
|
510
|
+
//
|
|
511
|
+
// Some `mcpServers` map keys are OWNED by the platform: the runtime splices a
|
|
512
|
+
// platform-managed entry under them (memmcp — the auto-injected memory MCP is
|
|
513
|
+
// spliced under `guuey-memory` for authenticated invokes; see
|
|
514
|
+
// `nocode-runtime/src/run-seam.ts` + `boot-colocated.ts`). A builder that
|
|
515
|
+
// declares a server under a reserved key would (a) boot as builder code under
|
|
516
|
+
// the same key the platform child owns, and (b) be silently REPLACED by the
|
|
517
|
+
// platform entry at invoke time. `validateReservedServerNames` is the
|
|
518
|
+
// synchronous deploy-time pre-flight that rejects it with a fast, actionable
|
|
519
|
+
// 400 — the primary guard; the run-seam collision guard is defense-in-depth for
|
|
520
|
+
// stale pre-validator snapshots. Single source of the reserved literal: this
|
|
521
|
+
// module. The boot/splice layer (`boot-colocated.ts`) imports it from here.
|
|
522
|
+
/**
|
|
523
|
+
* The reserved colocated key the auto-injected memory MCP is booted + spliced
|
|
524
|
+
* under (memmcp). NEVER builder-declarable — the memory child's own server
|
|
525
|
+
* advertises this same name and its aud is `colocatedResourceUrl(appId, this)`.
|
|
526
|
+
*/
|
|
527
|
+
export const RESERVED_MEMORY_SERVER_NAME = 'guuey-memory';
|
|
528
|
+
/**
|
|
529
|
+
* The reserved colocated key the auto-injected cross-app profile MCP is
|
|
530
|
+
* booted + spliced under (profile T1+). NEVER builder-declarable — mirrors
|
|
531
|
+
* {@link RESERVED_MEMORY_SERVER_NAME}'s reservation for the same reason: a
|
|
532
|
+
* builder-declared server under this key would be silently replaced by the
|
|
533
|
+
* platform entry at invoke time.
|
|
534
|
+
*/
|
|
535
|
+
export const RESERVED_PROFILE_SERVER_NAME = 'guuey-profile';
|
|
536
|
+
/**
|
|
537
|
+
* Every `mcpServers` map key the platform reserves. Extensible — one entry
|
|
538
|
+
* today ({@link RESERVED_MEMORY_SERVER_NAME}); future platform-injected
|
|
539
|
+
* servers add their key here and inherit the deploy-time rejection for free.
|
|
540
|
+
*/
|
|
541
|
+
export const RESERVED_MCP_SERVER_NAMES = [
|
|
542
|
+
RESERVED_MEMORY_SERVER_NAME,
|
|
543
|
+
RESERVED_PROFILE_SERVER_NAME,
|
|
544
|
+
];
|
|
545
|
+
/**
|
|
546
|
+
* Validate that no `agent.mcpServers` entry uses a platform-RESERVED name
|
|
547
|
+
* ({@link RESERVED_MCP_SERVER_NAMES}) — regardless of `kind` (the key is
|
|
548
|
+
* reserved, not just one hosting mode; a builder must not shadow it as
|
|
549
|
+
* `colocated`, `external`, or anything else). Returns a list of human-readable
|
|
550
|
+
* violation messages (empty = clean), one per reserved entry. Mirrors
|
|
551
|
+
* {@link validateNoProxiedServers}'s shape.
|
|
552
|
+
*/
|
|
553
|
+
export function validateReservedServerNames(agent) {
|
|
554
|
+
const violations = [];
|
|
555
|
+
const servers = agent?.mcpServers;
|
|
556
|
+
if (!servers)
|
|
557
|
+
return violations;
|
|
558
|
+
for (const name of Object.keys(servers)) {
|
|
559
|
+
if (RESERVED_MCP_SERVER_NAMES.includes(name)) {
|
|
560
|
+
violations.push(`MCP server "${name}": that name is reserved by the platform (an auto-injected server uses it) — rename this server.`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return violations;
|
|
564
|
+
}
|
|
415
565
|
/**
|
|
416
566
|
* Platform default MCP server map. Applied by the pod when `agent.mcpServers`
|
|
417
567
|
* is absent. Exposed here so non-pod consumers (CLI dry-run, lints) can show
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@guuey/config/browser` — the package minus `loader.js`.
|
|
3
|
+
*
|
|
4
|
+
* `loader.js` imports `node:fs` (guuey.json file resolution), which a
|
|
5
|
+
* browser bundler cannot chunk — importing the root barrel from client
|
|
6
|
+
* code fails `next build` outright (Turbopack: "does not support
|
|
7
|
+
* external modules (request: node:fs)"). Browser consumers (Guuey
|
|
8
|
+
* Studio's agent-config) import this entry instead; Node consumers
|
|
9
|
+
* (CLI, backend, pods) keep the root barrel.
|
|
10
|
+
*
|
|
11
|
+
* Every module re-exported here must stay free of Node builtins.
|
|
12
|
+
*/
|
|
13
|
+
export * from './schema.js';
|
|
14
|
+
export * from './agent.js';
|
|
15
|
+
export * from './app.js';
|
|
16
|
+
export * from './ggui.js';
|
|
17
|
+
export * from './hosting.js';
|
|
18
|
+
export * from './system-prompt.js';
|
|
19
|
+
export * from './registry.js';
|
|
20
|
+
export * from './guuey-context.js';
|
|
21
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@guuey/config/browser` — the package minus `loader.js`.
|
|
3
|
+
*
|
|
4
|
+
* `loader.js` imports `node:fs` (guuey.json file resolution), which a
|
|
5
|
+
* browser bundler cannot chunk — importing the root barrel from client
|
|
6
|
+
* code fails `next build` outright (Turbopack: "does not support
|
|
7
|
+
* external modules (request: node:fs)"). Browser consumers (Guuey
|
|
8
|
+
* Studio's agent-config) import this entry instead; Node consumers
|
|
9
|
+
* (CLI, backend, pods) keep the root barrel.
|
|
10
|
+
*
|
|
11
|
+
* Every module re-exported here must stay free of Node builtins.
|
|
12
|
+
*/
|
|
13
|
+
export * from './schema.js';
|
|
14
|
+
export * from './agent.js';
|
|
15
|
+
export * from './app.js';
|
|
16
|
+
export * from './ggui.js';
|
|
17
|
+
export * from './hosting.js';
|
|
18
|
+
export * from './system-prompt.js';
|
|
19
|
+
export * from './registry.js';
|
|
20
|
+
export * from './guuey-context.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthetic resource-URL helper for `kind: 'colocated'` MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* A colocated MCP has no real network URL (it's a guuey-managed HTTP child
|
|
5
|
+
* inside the agent pod, reached over a loopback/localhost hop the Router
|
|
6
|
+
* wires up) — but RFC 8707 federation still needs a stable `aud`/resource
|
|
7
|
+
* value to mint against, and `@guuey/state`'s KV needs a stable scope key.
|
|
8
|
+
* `colocatedResourceUrl` composes both from `(appId, serverName)`.
|
|
9
|
+
*
|
|
10
|
+
* The returned URL is consumed by `backend/amplify/functions/oidcMint/
|
|
11
|
+
* handler.ts`'s `parseMcpResourceUrl`, which requires: `https://` prefix,
|
|
12
|
+
* a trailing `/`, and a total length ≤ 512 chars. Both segments are
|
|
13
|
+
* validated against `/^[A-Za-z0-9_-]+$/` BEFORE composing the URL — they
|
|
14
|
+
* are used verbatim as URL path segments AND as a KV scope key, so an
|
|
15
|
+
* unvalidated segment (e.g. containing `/` or whitespace) could smuggle an
|
|
16
|
+
* extra path segment or break the scope contract.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The synthetic origin every colocated resource URL lives on. Guuey-owned
|
|
20
|
+
* and compile-time-fixed (NOT env-varying — the host never resolves; it
|
|
21
|
+
* exists only as a stable RFC 8707 `aud` / KV-scope namespace), so
|
|
22
|
+
* consumers that must recognize "is this URL guuey-controlled?" (e.g. the
|
|
23
|
+
* stateApi admin-ownership walk's origin filter) import THIS constant
|
|
24
|
+
* instead of re-declaring the string or threading a new env var.
|
|
25
|
+
*/
|
|
26
|
+
export declare const COLOCATED_ORIGIN = "https://colocated.guuey.com";
|
|
27
|
+
/**
|
|
28
|
+
* Whether `value` is safe to use as a `colocatedResourceUrl` path segment /
|
|
29
|
+
* KV scope key (letters, digits, hyphen, underscore only). Single source of
|
|
30
|
+
* truth for that rule — reused by {@link assertSafeSegment} here AND by
|
|
31
|
+
* `./agent.ts#validateColocatedServerNames` (the CLI deploy-time check
|
|
32
|
+
* `@guuey/cli`'s `commands/deploy.ts` runs before upload), so a bad
|
|
33
|
+
* colocated server name is rejected before deploy instead of surfacing only
|
|
34
|
+
* as a `POD_FATAL_BOOT_ERROR` crash-loop when the pod's `lowerColocated`
|
|
35
|
+
* calls `colocatedResourceUrl` at boot.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isValidColocatedServerName(value: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Build the synthetic `https://colocated.guuey.com/<appId>/<serverName>/`
|
|
40
|
+
* resource URL for a colocated MCP server. Throws if either segment fails
|
|
41
|
+
* the safe-segment check.
|
|
42
|
+
*/
|
|
43
|
+
export declare function colocatedResourceUrl(appId: string, serverName: string): string;
|
|
44
|
+
//# sourceMappingURL=colocated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colocated.d.ts","sourceRoot":"","sources":["../src/colocated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAE9D;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjE;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAI9E"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthetic resource-URL helper for `kind: 'colocated'` MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* A colocated MCP has no real network URL (it's a guuey-managed HTTP child
|
|
5
|
+
* inside the agent pod, reached over a loopback/localhost hop the Router
|
|
6
|
+
* wires up) — but RFC 8707 federation still needs a stable `aud`/resource
|
|
7
|
+
* value to mint against, and `@guuey/state`'s KV needs a stable scope key.
|
|
8
|
+
* `colocatedResourceUrl` composes both from `(appId, serverName)`.
|
|
9
|
+
*
|
|
10
|
+
* The returned URL is consumed by `backend/amplify/functions/oidcMint/
|
|
11
|
+
* handler.ts`'s `parseMcpResourceUrl`, which requires: `https://` prefix,
|
|
12
|
+
* a trailing `/`, and a total length ≤ 512 chars. Both segments are
|
|
13
|
+
* validated against `/^[A-Za-z0-9_-]+$/` BEFORE composing the URL — they
|
|
14
|
+
* are used verbatim as URL path segments AND as a KV scope key, so an
|
|
15
|
+
* unvalidated segment (e.g. containing `/` or whitespace) could smuggle an
|
|
16
|
+
* extra path segment or break the scope contract.
|
|
17
|
+
*/
|
|
18
|
+
const SAFE_SEGMENT_RE = /^[A-Za-z0-9_-]+$/;
|
|
19
|
+
/**
|
|
20
|
+
* The synthetic origin every colocated resource URL lives on. Guuey-owned
|
|
21
|
+
* and compile-time-fixed (NOT env-varying — the host never resolves; it
|
|
22
|
+
* exists only as a stable RFC 8707 `aud` / KV-scope namespace), so
|
|
23
|
+
* consumers that must recognize "is this URL guuey-controlled?" (e.g. the
|
|
24
|
+
* stateApi admin-ownership walk's origin filter) import THIS constant
|
|
25
|
+
* instead of re-declaring the string or threading a new env var.
|
|
26
|
+
*/
|
|
27
|
+
export const COLOCATED_ORIGIN = 'https://colocated.guuey.com';
|
|
28
|
+
/**
|
|
29
|
+
* Whether `value` is safe to use as a `colocatedResourceUrl` path segment /
|
|
30
|
+
* KV scope key (letters, digits, hyphen, underscore only). Single source of
|
|
31
|
+
* truth for that rule — reused by {@link assertSafeSegment} here AND by
|
|
32
|
+
* `./agent.ts#validateColocatedServerNames` (the CLI deploy-time check
|
|
33
|
+
* `@guuey/cli`'s `commands/deploy.ts` runs before upload), so a bad
|
|
34
|
+
* colocated server name is rejected before deploy instead of surfacing only
|
|
35
|
+
* as a `POD_FATAL_BOOT_ERROR` crash-loop when the pod's `lowerColocated`
|
|
36
|
+
* calls `colocatedResourceUrl` at boot.
|
|
37
|
+
*/
|
|
38
|
+
export function isValidColocatedServerName(value) {
|
|
39
|
+
return SAFE_SEGMENT_RE.test(value);
|
|
40
|
+
}
|
|
41
|
+
function assertSafeSegment(value, label) {
|
|
42
|
+
if (!isValidColocatedServerName(value)) {
|
|
43
|
+
throw new Error(`colocatedResourceUrl: ${label} "${value}" must match ${SAFE_SEGMENT_RE.source} (it composes a URL path segment and a KV scope key)`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Build the synthetic `https://colocated.guuey.com/<appId>/<serverName>/`
|
|
48
|
+
* resource URL for a colocated MCP server. Throws if either segment fails
|
|
49
|
+
* the safe-segment check.
|
|
50
|
+
*/
|
|
51
|
+
export function colocatedResourceUrl(appId, serverName) {
|
|
52
|
+
assertSafeSegment(appId, 'appId');
|
|
53
|
+
assertSafeSegment(serverName, 'serverName');
|
|
54
|
+
return `${COLOCATED_ORIGIN}/${appId}/${serverName}/`;
|
|
55
|
+
}
|