@opengeni/contracts 0.4.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/contracts",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared zod schemas and wire-contract types for the OpenGeni API.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/index.ts CHANGED
@@ -486,6 +486,10 @@ export const Permission = z.enum([
486
486
  "api_keys:manage",
487
487
  "environments:manage",
488
488
  "environments:use",
489
+ // Attach or rotate per-session third-party MCP server credentials. Deliberately
490
+ // not part of the worker's default first-party MCP permission set: a sandboxed
491
+ // agent must not be able to hand itself new bearer credentials.
492
+ "mcp_servers:attach",
489
493
  "goals:manage",
490
494
  // Bring-your-own-compute (M5). enrollments:read lists a workspace's machines;
491
495
  // enrollments:manage approves a device-flow enrollment (the LOUD whole-machine
@@ -1351,19 +1355,53 @@ export type DocumentSearchRequest = z.infer<typeof DocumentSearchRequest>;
1351
1355
  export const ToolRef = z.object({
1352
1356
  kind: z.literal("mcp"),
1353
1357
  id: z.string().min(1),
1354
- // Non-fatal-on-connect marker for an AUTO-ATTACHED (workspace-default)
1355
- // capability MCP server: when true, a connect / tools-list failure (e.g. an
1356
- // expired capability credential returning 401) must SKIP that server with a
1357
- // logged warning and let the turn proceed, rather than failing the whole
1358
- // turn before the model runs. Absent/false STRICT: an unavailable server
1359
- // fails the turn (the contract for EXPLICITLY-requested tools). This flag is
1360
- // set server-side only, at the default-capability auto-attach seam; it is
1361
- // stripped from client-supplied tool refs so an explicit request always
1362
- // stays strict.
1358
+ // Non-fatal-on-connect marker for MCP server refs that can degrade
1359
+ // gracefully. Absent/false is STRICT: the id must be configured and an
1360
+ // unavailable server fails the turn. `optional:true` is preserved for known
1361
+ // servers and makes runtime connect/list failures skip that server; if the
1362
+ // deployment does not configure the id, validation drops the ref. The server
1363
+ // also sets this for auto-attached workspace-default capability MCPs.
1363
1364
  optional: z.boolean().optional(),
1364
1365
  });
1365
1366
  export type ToolRef = z.infer<typeof ToolRef>;
1366
1367
 
1368
+ const registryId = /^[A-Za-z0-9_-]+$/;
1369
+ const httpsUrl = z.string().url().refine((value) => {
1370
+ try {
1371
+ return new URL(value).protocol === "https:";
1372
+ } catch {
1373
+ return false;
1374
+ }
1375
+ }, { message: "URL must use https" });
1376
+
1377
+ export const SessionMcpServerInput = z.object({
1378
+ id: z.string().min(1).regex(registryId),
1379
+ name: z.string().min(1).optional(),
1380
+ url: httpsUrl,
1381
+ allowedTools: z.array(z.string().min(1)).optional(),
1382
+ timeoutMs: z.number().int().positive().optional(),
1383
+ cacheToolsList: z.boolean().optional(),
1384
+ // Write-only credential headers. Values are encrypted at rest and never
1385
+ // returned in session responses or events; response metadata exposes names.
1386
+ headers: z.record(z.string(), z.string()).optional(),
1387
+ });
1388
+ export type SessionMcpServerInput = z.infer<typeof SessionMcpServerInput>;
1389
+
1390
+ export const SessionMcpCredentialUpdateInput = z.object({
1391
+ id: z.string().min(1).regex(registryId),
1392
+ headers: z.record(z.string(), z.string()),
1393
+ });
1394
+ export type SessionMcpCredentialUpdateInput = z.infer<typeof SessionMcpCredentialUpdateInput>;
1395
+
1396
+ export const SessionMcpServerMetadata = z.object({
1397
+ id: z.string().min(1).regex(registryId),
1398
+ name: z.string().min(1).nullable(),
1399
+ url: httpsUrl,
1400
+ headerNames: z.array(z.string()).default([]),
1401
+ credentialVersion: z.number().int().positive(),
1402
+ }).strict();
1403
+ export type SessionMcpServerMetadata = z.infer<typeof SessionMcpServerMetadata>;
1404
+
1367
1405
  export class ResourceRefConflictError extends Error {
1368
1406
  constructor(message: string) {
1369
1407
  super(message);
@@ -1382,10 +1420,10 @@ export function mergeToolRefs(existing: ToolRef[], additions: ToolRef[]): ToolRe
1382
1420
  order.push(key);
1383
1421
  continue;
1384
1422
  }
1385
- // Strict wins: if the same server appears both auto-attached (optional) and
1386
- // explicitly requested (non-optional), the explicit occurrence upgrades the
1387
- // merged ref to strict a later explicit request of an already-defaulted
1388
- // capability MCP must still fail the turn when the server is unavailable.
1423
+ // Strict wins: if the same server appears both optional and strict, the
1424
+ // strict occurrence upgrades the merged ref so an unavailable server fails
1425
+ // the turn. This preserves the fail-loud default when defaults, packs, and
1426
+ // per-turn tool selections are combined.
1389
1427
  if (prior.optional === true && tool.optional !== true) {
1390
1428
  const { optional: _dropped, ...strict } = prior;
1391
1429
  byKey.set(key, strict);
@@ -2167,6 +2205,9 @@ export const Session = z.object({
2167
2205
  // Non-default first-party MCP token permissions (manager-style sessions);
2168
2206
  // null means the fixed worker default set.
2169
2207
  firstPartyMcpPermissions: z.array(Permission).nullable(),
2208
+ // Per-session third-party MCP servers, metadata only. Credential values are
2209
+ // write-only and never appear here.
2210
+ mcpServers: z.array(SessionMcpServerMetadata).default([]),
2170
2211
  // The manager session that spawned this one via session_create (set only
2171
2212
  // when the creating grant carried a worker-signed sessionId claim); null for
2172
2213
  // direct API creates and scheduled-task runs. When set, this session's
@@ -2772,6 +2813,9 @@ export const CreateSessionRequest = z.object({
2772
2813
  // the orchestration/environment/github tools. Capped at creation: every
2773
2814
  // requested permission must be held by the creating grant (no escalation).
2774
2815
  firstPartyMcpPermissions: z.array(Permission).optional(),
2816
+ // Third-party MCP servers attached only to this session. Credential headers are
2817
+ // write-only: create responses and events expose only SessionMcpServerMetadata.
2818
+ mcpServers: z.array(SessionMcpServerInput).default([]),
2775
2819
  // Shared-sandbox placement (addendum 05 §D.1). Three-way union; OMITTED ⇒
2776
2820
  // today's behavior (a context-dependent default resolved server-side: from
2777
2821
  // inside a session → "shared" with the creator's box, top-level → "new").
@@ -2807,6 +2851,9 @@ export const ClientSessionEvent = z.discriminatedUnion("type", [
2807
2851
  tools: z.array(ToolRef).default([]),
2808
2852
  model: z.string().min(1).optional(),
2809
2853
  reasoningEffort: ReasoningEffort.optional(),
2854
+ // Header-value rotation only. URL/name/tool settings are immutable after
2855
+ // session create; persisted events expose metadata, never header values.
2856
+ mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
2810
2857
  }),
2811
2858
  }),
2812
2859
  z.object({