@i4ctime/q-ring 0.16.2 → 0.17.5

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/mcp.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  getSecret,
22
22
  hasSecret,
23
23
  httpRequest,
24
+ listAgentSessionsForAgents,
24
25
  listHooks,
25
26
  listMemory,
26
27
  listSecrets,
@@ -35,18 +36,95 @@ import {
35
36
  setAuditAgentLabel,
36
37
  setPolicyRoot,
37
38
  setSecret,
39
+ summariseSession,
38
40
  tunnelCreate,
39
41
  tunnelDestroy,
40
42
  tunnelList,
41
43
  tunnelRead,
42
44
  verifyAuditChain
43
- } from "./chunk-KFILBHOY.js";
45
+ } from "./chunk-VMDD5QTJ.js";
44
46
 
45
47
  // src/mcp.ts
46
48
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
47
49
 
48
50
  // src/mcp/server.ts
49
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
51
+ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
52
+
53
+ // src/mcp/tool-annotations.ts
54
+ var hints = (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) => ({ readOnlyHint, destructiveHint, idempotentHint, openWorldHint });
55
+ var READ = hints(true, false, true, false);
56
+ var READ_OPEN = hints(true, false, true, true);
57
+ var TOOL_ANNOTATIONS = {
58
+ // secrets
59
+ get_secret: READ,
60
+ list_secrets: READ,
61
+ set_secret: hints(false, true, true, false),
62
+ delete_secret: hints(false, true, true, false),
63
+ has_secret: READ,
64
+ export_secrets: READ,
65
+ import_dotenv: hints(false, false, true, false),
66
+ // existing keys are skipped, not overwritten
67
+ inspect_secret: READ,
68
+ generate_secret: hints(false, true, false, false),
69
+ // saveAs overwrites; fresh value every call
70
+ entangle_secrets: hints(false, false, true, false),
71
+ disentangle_secrets: hints(false, false, true, false),
72
+ // project
73
+ check_project: READ,
74
+ env_generate: READ,
75
+ // renders text, never writes files
76
+ detect_environment: READ,
77
+ get_project_context: READ,
78
+ // tunnels (memory-only)
79
+ tunnel_create: hints(false, false, false, false),
80
+ tunnel_read: hints(false, true, false, false),
81
+ // may self-destruct on read
82
+ tunnel_list: READ,
83
+ tunnel_destroy: hints(false, true, true, false),
84
+ // teleport
85
+ teleport_pack: READ,
86
+ teleport_unpack: hints(false, true, true, false),
87
+ // imports may overwrite keys
88
+ // audit / health
89
+ audit_log: READ,
90
+ detect_anomalies: READ,
91
+ health_check: READ,
92
+ verify_audit_chain: READ,
93
+ export_audit: READ,
94
+ // validation / rotation (network)
95
+ validate_secret: READ_OPEN,
96
+ list_providers: READ,
97
+ rotate_secret: hints(false, true, false, true),
98
+ // replaces the credential upstream and locally
99
+ ci_validate_secrets: READ_OPEN,
100
+ // hooks
101
+ register_hook: hints(false, false, false, false),
102
+ list_hooks: READ,
103
+ remove_hook: hints(false, true, true, false),
104
+ // execution / scanning
105
+ exec_with_secrets: hints(false, true, false, true),
106
+ // arbitrary command
107
+ scan_codebase_for_secrets: READ,
108
+ lint_files: hints(false, true, true, false),
109
+ // fix:true rewrites source files
110
+ analyze_secrets: READ,
111
+ status_dashboard: hints(false, false, false, false),
112
+ // starts a local server, new token per launch
113
+ agent_scan: hints(false, true, false, true),
114
+ // autoRotate replaces expired credentials
115
+ // agent memory
116
+ agent_remember: hints(false, true, true, false),
117
+ agent_recall: READ,
118
+ agent_forget: hints(false, true, true, false),
119
+ // policy
120
+ check_policy: READ,
121
+ get_policy_summary: READ
122
+ };
123
+ function toolAnnotations(name) {
124
+ const a = TOOL_ANNOTATIONS[name];
125
+ if (!a) throw new Error(`q-ring: no tool annotations defined for "${name}"`);
126
+ return a;
127
+ }
50
128
 
51
129
  // src/mcp/tools/secrets.ts
52
130
  import { z as z2 } from "zod";
@@ -252,10 +330,7 @@ function opts(params) {
252
330
  function enforceToolPolicy(toolName, projectPath8) {
253
331
  const decision = checkToolPolicy(toolName, projectPath8);
254
332
  if (!decision.allowed) {
255
- return text(
256
- `Policy Denied: ${decision.reason} (source: ${decision.policySource})`,
257
- true
258
- );
333
+ return text(`Policy Denied: ${decision.reason} (source: ${decision.policySource})`, true);
259
334
  }
260
335
  return null;
261
336
  }
@@ -297,23 +372,18 @@ function registerSecretTools(server2) {
297
372
  teamId,
298
373
  orgId
299
374
  },
375
+ toolAnnotations("get_secret"),
300
376
  async (params) => {
301
377
  const toolBlock = enforceToolPolicy("get_secret", params.projectPath);
302
378
  if (toolBlock) return toolBlock;
303
379
  try {
304
- const keyBlock = checkKeyReadPolicy(
305
- params.key,
306
- void 0,
307
- params.projectPath
308
- );
380
+ const keyBlock = checkKeyReadPolicy(params.key, void 0, params.projectPath);
309
381
  if (!keyBlock.allowed) {
310
382
  return text(`Policy Denied: ${keyBlock.reason}`, true);
311
383
  }
312
384
  const value = getSecret(params.key, opts(params));
313
385
  if (value === null) return text(`Secret "${params.key}" not found`, true);
314
- return text(
315
- JSON.stringify({ ok: true, data: { key: params.key, value } }, null, 2)
316
- );
386
+ return text(JSON.stringify({ ok: true, data: { key: params.key, value } }, null, 2));
317
387
  } catch (err) {
318
388
  return text(err instanceof Error ? err.message : String(err), true);
319
389
  }
@@ -344,22 +414,19 @@ function registerSecretTools(server2) {
344
414
  teamId,
345
415
  orgId
346
416
  },
417
+ toolAnnotations("list_secrets"),
347
418
  async (params) => {
348
419
  const toolBlock = enforceToolPolicy("list_secrets", params.projectPath);
349
420
  if (toolBlock) return toolBlock;
350
421
  let entries = listSecrets(opts(params));
351
422
  if (params.tag) {
352
- entries = entries.filter(
353
- (e) => e.envelope?.meta.tags?.includes(params.tag)
354
- );
423
+ entries = entries.filter((e) => e.envelope?.meta.tags?.includes(params.tag));
355
424
  }
356
425
  if (params.expired) {
357
426
  entries = entries.filter((e) => e.decay?.isExpired);
358
427
  }
359
428
  if (params.stale) {
360
- entries = entries.filter(
361
- (e) => e.decay?.isStale && !e.decay?.isExpired
362
- );
429
+ entries = entries.filter((e) => e.decay?.isStale && !e.decay?.isExpired);
363
430
  }
364
431
  if (params.filter) {
365
432
  entries = filterSecretsByKeyGlob(entries, params.filter);
@@ -386,9 +453,7 @@ function registerSecretTools(server2) {
386
453
  "Mutates the keyring (overwrites any existing value at the same key/scope), writes a 'write' event to the audit log, and triggers any matching hooks. Subject to tool policy. Returns a short confirmation text like '[scope] KEY saved' (or '[scope] KEY set for env:NAME' when `env` is provided)."
387
454
  ].join(" "),
388
455
  {
389
- key: z2.string().describe(
390
- "Secret key name (UPPER_SNAKE_CASE recommended). Example: 'STRIPE_SECRET_KEY'."
391
- ),
456
+ key: z2.string().describe("Secret key name (UPPER_SNAKE_CASE recommended). Example: 'STRIPE_SECRET_KEY'."),
392
457
  value: z2.string().describe(
393
458
  "The secret value to store. Stored as-is; never logged or echoed. May be empty only when `env` is provided to register a new env without a default."
394
459
  ),
@@ -403,18 +468,8 @@ function registerSecretTools(server2) {
403
468
  description: z2.string().optional().describe(
404
469
  "Free-text human-readable description shown in `inspect_secret` and the dashboard."
405
470
  ),
406
- tags: z2.array(z2.string()).optional().describe(
407
- "Tag list for filtering and hook matching. Example: ['production', 'payments']."
408
- ),
409
- rotationFormat: z2.enum([
410
- "hex",
411
- "base64",
412
- "alphanumeric",
413
- "uuid",
414
- "api-key",
415
- "token",
416
- "password"
417
- ]).optional().describe(
471
+ tags: z2.array(z2.string()).optional().describe("Tag list for filtering and hook matching. Example: ['production', 'payments']."),
472
+ rotationFormat: z2.enum(["hex", "base64", "alphanumeric", "uuid", "api-key", "token", "password"]).optional().describe(
418
473
  "Format used by `agent_scan --autoRotate` and `rotate_secret` when this secret expires. Pick the format that matches the upstream service's accepted shape."
419
474
  ),
420
475
  rotationPrefix: z2.string().optional().describe(
@@ -423,6 +478,7 @@ function registerSecretTools(server2) {
423
478
  teamId,
424
479
  orgId
425
480
  },
481
+ toolAnnotations("set_secret"),
426
482
  async (params) => {
427
483
  const toolBlock = enforceToolPolicy("set_secret", params.projectPath);
428
484
  if (toolBlock) return toolBlock;
@@ -444,9 +500,7 @@ function registerSecretTools(server2) {
444
500
  rotationFormat: params.rotationFormat,
445
501
  rotationPrefix: params.rotationPrefix
446
502
  });
447
- return text(
448
- `[${params.scope ?? "global"}] ${params.key} set for env:${params.env}`
449
- );
503
+ return text(`[${params.scope ?? "global"}] ${params.key} set for env:${params.env}`);
450
504
  }
451
505
  setSecret(params.key, params.value, {
452
506
  ...o,
@@ -473,6 +527,7 @@ function registerSecretTools(server2) {
473
527
  teamId,
474
528
  orgId
475
529
  },
530
+ toolAnnotations("delete_secret"),
476
531
  async (params) => {
477
532
  const toolBlock = enforceToolPolicy("delete_secret", params.projectPath);
478
533
  if (toolBlock) return toolBlock;
@@ -497,6 +552,7 @@ function registerSecretTools(server2) {
497
552
  teamId,
498
553
  orgId
499
554
  },
555
+ toolAnnotations("has_secret"),
500
556
  async (params) => {
501
557
  const toolBlock = enforceToolPolicy("has_secret", params.projectPath);
502
558
  if (toolBlock) return toolBlock;
@@ -526,6 +582,7 @@ function registerSecretTools(server2) {
526
582
  teamId,
527
583
  orgId
528
584
  },
585
+ toolAnnotations("export_secrets"),
529
586
  async (params) => {
530
587
  const toolBlock = enforceToolPolicy("export_secrets", params.projectPath);
531
588
  if (toolBlock) return toolBlock;
@@ -559,6 +616,7 @@ function registerSecretTools(server2) {
559
616
  "If true, parse and report what would happen but do not write to the keyring. Useful for previewing imports before committing."
560
617
  )
561
618
  },
619
+ toolAnnotations("import_dotenv"),
562
620
  async (params) => {
563
621
  const toolBlock = enforceToolPolicy("import_dotenv", params.projectPath);
564
622
  if (toolBlock) return toolBlock;
@@ -595,6 +653,7 @@ function registerSecretTools(server2) {
595
653
  teamId,
596
654
  orgId
597
655
  },
656
+ toolAnnotations("inspect_secret"),
598
657
  async (params) => {
599
658
  const toolBlock = enforceToolPolicy("inspect_secret", params.projectPath);
600
659
  if (toolBlock) return toolBlock;
@@ -626,8 +685,7 @@ function registerSecretTools(server2) {
626
685
  if (envelope.meta.entangled?.length) {
627
686
  info.entangled = envelope.meta.entangled;
628
687
  }
629
- if (envelope.meta.description)
630
- info.description = envelope.meta.description;
688
+ if (envelope.meta.description) info.description = envelope.meta.description;
631
689
  if (envelope.meta.tags?.length) info.tags = envelope.meta.tags;
632
690
  return text(JSON.stringify(info, null, 2));
633
691
  }
@@ -640,15 +698,7 @@ function registerSecretTools(server2) {
640
698
  "If `saveAs` is provided this mutates the keyring (one 'write' event) and returns a summary like 'Generated and saved as \"KEY\" (FORMAT, ~N bits entropy)'. Without `saveAs` the call is read-only and returns JSON `{ ok, data: { value } }` containing the freshly generated string."
641
699
  ].join(" "),
642
700
  {
643
- format: z2.enum([
644
- "hex",
645
- "base64",
646
- "alphanumeric",
647
- "uuid",
648
- "api-key",
649
- "token",
650
- "password"
651
- ]).optional().default("api-key").describe(
701
+ format: z2.enum(["hex", "base64", "alphanumeric", "uuid", "api-key", "token", "password"]).optional().default("api-key").describe(
652
702
  "Output shape. 'hex' / 'base64' / 'alphanumeric' = raw random string of `length` characters; 'uuid' = RFC4122 v4; 'api-key' / 'token' = random alphanumeric with optional `prefix`; 'password' = mixed-case alphanumeric with symbols. Defaults to 'api-key'."
653
703
  ),
654
704
  length: z2.number().optional().describe(
@@ -665,6 +715,7 @@ function registerSecretTools(server2) {
665
715
  teamId,
666
716
  orgId
667
717
  },
718
+ toolAnnotations("generate_secret"),
668
719
  async (params) => {
669
720
  const toolBlock = enforceToolPolicy("generate_secret", params.projectPath);
670
721
  if (toolBlock) return toolBlock;
@@ -705,19 +756,14 @@ function registerSecretTools(server2) {
705
756
  "Project root for targetKey when targetScope='project'. Defaults to the server cwd."
706
757
  )
707
758
  },
759
+ toolAnnotations("entangle_secrets"),
708
760
  async (params) => {
709
- const toolBlock = enforceToolPolicy(
710
- "entangle_secrets",
711
- params.sourceProjectPath
712
- );
761
+ const toolBlock = enforceToolPolicy("entangle_secrets", params.sourceProjectPath);
713
762
  if (toolBlock) return toolBlock;
714
763
  for (const key of [params.sourceKey, params.targetKey]) {
715
764
  const decision = checkKeyReadPolicy(key, void 0, params.sourceProjectPath);
716
765
  if (!decision.allowed) {
717
- return text(
718
- `Policy Denied: ${decision.reason} (source: ${decision.policySource})`,
719
- true
720
- );
766
+ return text(`Policy Denied: ${decision.reason} (source: ${decision.policySource})`, true);
721
767
  }
722
768
  }
723
769
  entangleSecrets(
@@ -752,11 +798,9 @@ function registerSecretTools(server2) {
752
798
  sourceProjectPath: z2.string().optional().describe("Project root for sourceKey when sourceScope='project'."),
753
799
  targetProjectPath: z2.string().optional().describe("Project root for targetKey when targetScope='project'.")
754
800
  },
801
+ toolAnnotations("disentangle_secrets"),
755
802
  async (params) => {
756
- const toolBlock = enforceToolPolicy(
757
- "disentangle_secrets",
758
- params.sourceProjectPath
759
- );
803
+ const toolBlock = enforceToolPolicy("disentangle_secrets", params.sourceProjectPath);
760
804
  if (toolBlock) return toolBlock;
761
805
  disentangleSecrets(
762
806
  params.sourceKey,
@@ -789,9 +833,9 @@ var ProviderRegistry = class {
789
833
  get(name) {
790
834
  return this.providers.get(name);
791
835
  }
792
- detectProvider(value, hints) {
793
- if (hints?.provider) {
794
- return this.providers.get(hints.provider);
836
+ detectProvider(value, hints2) {
837
+ if (hints2?.provider) {
838
+ return this.providers.get(hints2.provider);
795
839
  }
796
840
  for (const provider of this.providers.values()) {
797
841
  if (provider.prefixes) {
@@ -1147,6 +1191,7 @@ function registerProjectTools(server2) {
1147
1191
  {
1148
1192
  projectPath: projectPath2
1149
1193
  },
1194
+ toolAnnotations("check_project"),
1150
1195
  async (params) => {
1151
1196
  const toolBlock = enforceToolPolicy("check_project", params.projectPath);
1152
1197
  if (toolBlock) return toolBlock;
@@ -1219,6 +1264,7 @@ function registerProjectTools(server2) {
1219
1264
  projectPath: projectPath2,
1220
1265
  env: env2
1221
1266
  },
1267
+ toolAnnotations("env_generate"),
1222
1268
  async (params) => {
1223
1269
  const toolBlock = enforceToolPolicy("env_generate", params.projectPath);
1224
1270
  if (toolBlock) return toolBlock;
@@ -1269,19 +1315,15 @@ ${warnings.map((w) => `# ${w}`).join("\n")}` : output;
1269
1315
  {
1270
1316
  projectPath: projectPath2
1271
1317
  },
1318
+ toolAnnotations("detect_environment"),
1272
1319
  async (params) => {
1273
- const toolBlock = enforceToolPolicy(
1274
- "detect_environment",
1275
- params.projectPath
1276
- );
1320
+ const toolBlock = enforceToolPolicy("detect_environment", params.projectPath);
1277
1321
  if (toolBlock) return toolBlock;
1278
1322
  const result = collapseEnvironment({
1279
1323
  projectPath: params.projectPath ?? process.cwd()
1280
1324
  });
1281
1325
  if (!result) {
1282
- return text(
1283
- "No environment detected. Set QRING_ENV, NODE_ENV, or create .q-ring.json"
1284
- );
1326
+ return text("No environment detected. Set QRING_ENV, NODE_ENV, or create .q-ring.json");
1285
1327
  }
1286
1328
  return text(JSON.stringify(result, null, 2));
1287
1329
  }
@@ -1299,11 +1341,9 @@ ${warnings.map((w) => `# ${w}`).join("\n")}` : output;
1299
1341
  teamId: teamId2,
1300
1342
  orgId: orgId2
1301
1343
  },
1344
+ toolAnnotations("get_project_context"),
1302
1345
  async (params) => {
1303
- const toolBlock = enforceToolPolicy(
1304
- "get_project_context",
1305
- params.projectPath
1306
- );
1346
+ const toolBlock = enforceToolPolicy("get_project_context", params.projectPath);
1307
1347
  if (toolBlock) return toolBlock;
1308
1348
  const context = getProjectContext(opts(params));
1309
1349
  return text(JSON.stringify(context, null, 2));
@@ -1322,9 +1362,7 @@ function registerTunnelTools(server2) {
1322
1362
  "Mutates only in-memory state \u2014 the value never touches disk and is lost on server restart. Subject to tool policy. Returns JSON `{ ok, data: { id } }` where `id` is an opaque string to pass to `tunnel_read`/`tunnel_destroy`."
1323
1363
  ].join(" "),
1324
1364
  {
1325
- value: z3.string().describe(
1326
- "The plaintext value to tunnel. Held only in process memory; never logged."
1327
- ),
1365
+ value: z3.string().describe("The plaintext value to tunnel. Held only in process memory; never logged."),
1328
1366
  ttlSeconds: z3.number().optional().describe(
1329
1367
  "Auto-destroy the tunnel after this many seconds. Omit for no time limit (then a `maxReads` is highly recommended)."
1330
1368
  ),
@@ -1332,6 +1370,7 @@ function registerTunnelTools(server2) {
1332
1370
  "Self-destruct after this many successful `tunnel_read` calls. Use 1 for true one-shot delivery."
1333
1371
  )
1334
1372
  },
1373
+ toolAnnotations("tunnel_create"),
1335
1374
  async (params) => {
1336
1375
  const toolBlock = enforceToolPolicy("tunnel_create");
1337
1376
  if (toolBlock) return toolBlock;
@@ -1350,10 +1389,9 @@ function registerTunnelTools(server2) {
1350
1389
  "Increments the read counter and may auto-destroy the tunnel if `maxReads` was set. Returns JSON `{ ok, data: { id, value } }` on success, or an error 'Tunnel \"...\" not found or expired' if the tunnel has been destroyed, hit its TTL, or never existed."
1351
1390
  ].join(" "),
1352
1391
  {
1353
- id: z3.string().describe(
1354
- "The opaque tunnel ID returned by `tunnel_create`. Case-sensitive."
1355
- )
1392
+ id: z3.string().describe("The opaque tunnel ID returned by `tunnel_create`. Case-sensitive.")
1356
1393
  },
1394
+ toolAnnotations("tunnel_read"),
1357
1395
  async (params) => {
1358
1396
  const toolBlock = enforceToolPolicy("tunnel_read");
1359
1397
  if (toolBlock) return toolBlock;
@@ -1361,9 +1399,7 @@ function registerTunnelTools(server2) {
1361
1399
  if (value === null) {
1362
1400
  return text(`Tunnel "${params.id}" not found or expired`, true);
1363
1401
  }
1364
- return text(
1365
- JSON.stringify({ ok: true, data: { id: params.id, value } }, null, 2)
1366
- );
1402
+ return text(JSON.stringify({ ok: true, data: { id: params.id, value } }, null, 2));
1367
1403
  }
1368
1404
  );
1369
1405
  server2.tool(
@@ -1374,6 +1410,7 @@ function registerTunnelTools(server2) {
1374
1410
  "Read-only. Returns one line per tunnel formatted as `id | reads:N | max:N | expires:Ns`, or the literal text 'No active tunnels' when the list is empty."
1375
1411
  ].join(" "),
1376
1412
  {},
1413
+ toolAnnotations("tunnel_list"),
1377
1414
  async () => {
1378
1415
  const toolBlock = enforceToolPolicy("tunnel_list");
1379
1416
  if (toolBlock) return toolBlock;
@@ -1384,10 +1421,7 @@ function registerTunnelTools(server2) {
1384
1421
  parts.push(`reads:${t.accessCount}`);
1385
1422
  if (t.maxReads) parts.push(`max:${t.maxReads}`);
1386
1423
  if (t.expiresAt) {
1387
- const rem = Math.max(
1388
- 0,
1389
- Math.floor((t.expiresAt - Date.now()) / 1e3)
1390
- );
1424
+ const rem = Math.max(0, Math.floor((t.expiresAt - Date.now()) / 1e3));
1391
1425
  parts.push(`expires:${rem}s`);
1392
1426
  }
1393
1427
  return parts.join(" | ");
@@ -1405,6 +1439,7 @@ function registerTunnelTools(server2) {
1405
1439
  {
1406
1440
  id: z3.string().describe("The opaque tunnel ID to destroy.")
1407
1441
  },
1442
+ toolAnnotations("tunnel_destroy"),
1408
1443
  async (params) => {
1409
1444
  const toolBlock = enforceToolPolicy("tunnel_destroy");
1410
1445
  if (toolBlock) return toolBlock;
@@ -1558,6 +1593,7 @@ function registerTeleportTools(server2) {
1558
1593
  teamId: teamId3,
1559
1594
  orgId: orgId3
1560
1595
  },
1596
+ toolAnnotations("teleport_pack"),
1561
1597
  async (params) => {
1562
1598
  const toolBlock = enforceToolPolicy("teleport_pack", params.projectPath);
1563
1599
  if (toolBlock) return toolBlock;
@@ -1598,6 +1634,7 @@ function registerTeleportTools(server2) {
1598
1634
  "If true, decrypt and report what would be written but do not mutate the keyring. Useful for verifying bundle contents before commit."
1599
1635
  )
1600
1636
  },
1637
+ toolAnnotations("teleport_unpack"),
1601
1638
  async (params) => {
1602
1639
  const toolBlock = enforceToolPolicy("teleport_unpack", params.projectPath);
1603
1640
  if (toolBlock) return toolBlock;
@@ -1605,18 +1642,14 @@ function registerTeleportTools(server2) {
1605
1642
  const payload = teleportUnpack(params.bundle, params.passphrase);
1606
1643
  if (params.dryRun) {
1607
1644
  const preview = payload.secrets.map((s) => `${s.key} [${s.scope ?? "global"}]`).join("\n");
1608
- return text(
1609
- `Would import ${payload.secrets.length} secrets:
1610
- ${preview}`
1611
- );
1645
+ return text(`Would import ${payload.secrets.length} secrets:
1646
+ ${preview}`);
1612
1647
  }
1613
1648
  const o = opts(params);
1614
1649
  for (const s of payload.secrets) {
1615
1650
  setSecret(s.key, s.value, o);
1616
1651
  }
1617
- return text(
1618
- `Imported ${payload.secrets.length} secret(s) from teleport bundle`
1619
- );
1652
+ return text(`Imported ${payload.secrets.length} secret(s) from teleport bundle`);
1620
1653
  } catch (err) {
1621
1654
  const msg = err instanceof Error ? err.message : String(err);
1622
1655
  return text(JSON.stringify({ ok: false, error: { message: msg } }), true);
@@ -1637,9 +1670,7 @@ function registerAuditTools(server2) {
1637
1670
  "Read-only. Returns one line per event in chronological order, formatted `timestamp | action | key | [scope] | env:NAME | detail`. Returns 'No audit events found' when the filter matches nothing."
1638
1671
  ].join(" "),
1639
1672
  {
1640
- key: z6.string().optional().describe(
1641
- "Limit to events touching this exact key. Omit for the full log."
1642
- ),
1673
+ key: z6.string().optional().describe("Limit to events touching this exact key. Omit for the full log."),
1643
1674
  action: z6.enum([
1644
1675
  "read",
1645
1676
  "write",
@@ -1667,6 +1698,7 @@ function registerAuditTools(server2) {
1667
1698
  "Maximum events to return, newest first. Defaults to 20. Increase for deeper investigations."
1668
1699
  )
1669
1700
  },
1701
+ toolAnnotations("audit_log"),
1670
1702
  async (params) => {
1671
1703
  const toolBlock = enforceToolPolicy("audit_log");
1672
1704
  if (toolBlock) return toolBlock;
@@ -1700,6 +1732,7 @@ function registerAuditTools(server2) {
1700
1732
  "If provided, narrow the scan to this exact key. Omit to scan across every key in the audit log."
1701
1733
  )
1702
1734
  },
1735
+ toolAnnotations("detect_anomalies"),
1703
1736
  async (params) => {
1704
1737
  const toolBlock = enforceToolPolicy("detect_anomalies");
1705
1738
  if (toolBlock) return toolBlock;
@@ -1722,6 +1755,7 @@ function registerAuditTools(server2) {
1722
1755
  teamId: teamId4,
1723
1756
  orgId: orgId4
1724
1757
  },
1758
+ toolAnnotations("health_check"),
1725
1759
  async (params) => {
1726
1760
  const toolBlock = enforceToolPolicy("health_check", params.projectPath);
1727
1761
  if (toolBlock) return toolBlock;
@@ -1758,11 +1792,7 @@ function registerAuditTools(server2) {
1758
1792
  summary.push("", "Issues:", ...issues);
1759
1793
  }
1760
1794
  if (anomalies.length > 0) {
1761
- summary.push(
1762
- "",
1763
- "Anomalies:",
1764
- ...anomalies.map((a) => `[${a.type}] ${a.description}`)
1765
- );
1795
+ summary.push("", "Anomalies:", ...anomalies.map((a) => `[${a.type}] ${a.description}`));
1766
1796
  }
1767
1797
  return text(summary.join("\n"));
1768
1798
  }
@@ -1775,6 +1805,7 @@ function registerAuditTools(server2) {
1775
1805
  "Read-only. Returns JSON `{ ok, valid, brokenAt? }` where `valid` is `true` for an intact chain and `brokenAt` (when present) names the first event whose hash did not match."
1776
1806
  ].join(" "),
1777
1807
  {},
1808
+ toolAnnotations("verify_audit_chain"),
1778
1809
  async () => {
1779
1810
  const toolBlock = enforceToolPolicy("verify_audit_chain");
1780
1811
  if (toolBlock) return toolBlock;
@@ -1800,6 +1831,7 @@ function registerAuditTools(server2) {
1800
1831
  "Output format. 'jsonl' (default) is most stream-friendly; 'json' is a single array; 'csv' is spreadsheet-friendly."
1801
1832
  )
1802
1833
  },
1834
+ toolAnnotations("export_audit"),
1803
1835
  async (params) => {
1804
1836
  const toolBlock = enforceToolPolicy("export_audit");
1805
1837
  if (toolBlock) return toolBlock;
@@ -1838,6 +1870,7 @@ function registerValidationTools(server2) {
1838
1870
  teamId: teamId5,
1839
1871
  orgId: orgId5
1840
1872
  },
1873
+ toolAnnotations("validate_secret"),
1841
1874
  async (params) => {
1842
1875
  const toolBlock = enforceToolPolicy("validate_secret", params.projectPath);
1843
1876
  if (toolBlock) return toolBlock;
@@ -1857,6 +1890,7 @@ function registerValidationTools(server2) {
1857
1890
  "Read-only. Returns JSON array of `{ name, description, prefixes }` objects. `prefixes` are the literal key-value prefixes (e.g. 'sk-' for OpenAI) used for auto-detection."
1858
1891
  ].join(" "),
1859
1892
  {},
1893
+ toolAnnotations("list_providers"),
1860
1894
  async () => {
1861
1895
  const toolBlock = enforceToolPolicy("list_providers");
1862
1896
  if (toolBlock) return toolBlock;
@@ -1885,6 +1919,7 @@ function registerValidationTools(server2) {
1885
1919
  teamId: teamId5,
1886
1920
  orgId: orgId5
1887
1921
  },
1922
+ toolAnnotations("rotate_secret"),
1888
1923
  async (params) => {
1889
1924
  const toolBlock = enforceToolPolicy("rotate_secret", params.projectPath);
1890
1925
  if (toolBlock) return toolBlock;
@@ -1914,11 +1949,9 @@ function registerValidationTools(server2) {
1914
1949
  teamId: teamId5,
1915
1950
  orgId: orgId5
1916
1951
  },
1952
+ toolAnnotations("ci_validate_secrets"),
1917
1953
  async (params) => {
1918
- const toolBlock = enforceToolPolicy(
1919
- "ci_validate_secrets",
1920
- params.projectPath
1921
- );
1954
+ const toolBlock = enforceToolPolicy("ci_validate_secrets", params.projectPath);
1922
1955
  if (toolBlock) return toolBlock;
1923
1956
  const entries = listSecrets(opts(params));
1924
1957
  const secrets = entries.map((e) => {
@@ -1959,18 +1992,14 @@ function registerHookTools(server2) {
1959
1992
  key: z8.string().optional().describe(
1960
1993
  "Trigger only on this exact key name. Pick at most one of `key` / `keyPattern` / `tag` (or combine for stricter matching)."
1961
1994
  ),
1962
- keyPattern: z8.string().optional().describe(
1963
- "Trigger on any key matching this glob pattern. Examples: 'DB_*', 'STRIPE_*'."
1964
- ),
1995
+ keyPattern: z8.string().optional().describe("Trigger on any key matching this glob pattern. Examples: 'DB_*', 'STRIPE_*'."),
1965
1996
  tag: z8.string().optional().describe(
1966
1997
  "Trigger on any secret carrying this exact tag. Combinable with key/keyPattern as an AND filter."
1967
1998
  ),
1968
1999
  scope: z8.enum(["global", "project"]).optional().describe(
1969
2000
  "Restrict the hook to secrets in this scope. Omit to fire across both global and project secrets."
1970
2001
  ),
1971
- actions: z8.array(z8.enum(["write", "delete", "rotate"])).optional().default(["write", "delete", "rotate"]).describe(
1972
- "Which lifecycle actions trigger this hook. Defaults to all three."
1973
- ),
2002
+ actions: z8.array(z8.enum(["write", "delete", "rotate"])).optional().default(["write", "delete", "rotate"]).describe("Which lifecycle actions trigger this hook. Defaults to all three."),
1974
2003
  command: z8.string().optional().describe(
1975
2004
  "Required when type='shell'. The literal shell command to run; q-ring exposes the matching key as $QRING_HOOK_KEY and action as $QRING_HOOK_ACTION."
1976
2005
  ),
@@ -1987,14 +2016,12 @@ function registerHookTools(server2) {
1987
2016
  "Free-text human-readable description, surfaced by `list_hooks` and the dashboard."
1988
2017
  )
1989
2018
  },
2019
+ toolAnnotations("register_hook"),
1990
2020
  async (params) => {
1991
2021
  const toolBlock = enforceToolPolicy("register_hook");
1992
2022
  if (toolBlock) return toolBlock;
1993
2023
  if (!params.key && !params.keyPattern && !params.tag) {
1994
- return text(
1995
- "At least one match criterion required: key, keyPattern, or tag",
1996
- true
1997
- );
2024
+ return text("At least one match criterion required: key, keyPattern, or tag", true);
1998
2025
  }
1999
2026
  const entry = registerHook({
2000
2027
  type: params.type,
@@ -2022,6 +2049,7 @@ function registerHookTools(server2) {
2022
2049
  "Read-only. Returns pretty-printed JSON array of hook entries, or 'No hooks registered' when the registry is empty."
2023
2050
  ].join(" "),
2024
2051
  {},
2052
+ toolAnnotations("list_hooks"),
2025
2053
  async () => {
2026
2054
  const toolBlock = enforceToolPolicy("list_hooks");
2027
2055
  if (toolBlock) return toolBlock;
@@ -2042,6 +2070,7 @@ function registerHookTools(server2) {
2042
2070
  "Hook id returned by `register_hook` or visible in `list_hooks` (opaque string)."
2043
2071
  )
2044
2072
  },
2073
+ toolAnnotations("remove_hook"),
2045
2074
  async (params) => {
2046
2075
  const toolBlock = enforceToolPolicy("remove_hook");
2047
2076
  if (toolBlock) return toolBlock;
@@ -2648,6 +2677,7 @@ function registerToolingTools(server2) {
2648
2677
  teamId: teamId6,
2649
2678
  orgId: orgId6
2650
2679
  },
2680
+ toolAnnotations("exec_with_secrets"),
2651
2681
  async (params) => {
2652
2682
  const toolBlock = enforceToolPolicy("exec_with_secrets", params.projectPath);
2653
2683
  if (toolBlock) return toolBlock;
@@ -2675,10 +2705,7 @@ ${result.stdout}`);
2675
2705
  ${result.stderr}`);
2676
2706
  return text(output.join("\n\n"));
2677
2707
  } catch (err) {
2678
- return text(
2679
- `Execution failed: ${err instanceof Error ? err.message : String(err)}`,
2680
- true
2681
- );
2708
+ return text(`Execution failed: ${err instanceof Error ? err.message : String(err)}`, true);
2682
2709
  }
2683
2710
  }
2684
2711
  );
@@ -2694,6 +2721,7 @@ ${result.stderr}`);
2694
2721
  "Directory to scan, absolute or relative to the server cwd. The scan recurses into subdirectories."
2695
2722
  )
2696
2723
  },
2724
+ toolAnnotations("scan_codebase_for_secrets"),
2697
2725
  async (params) => {
2698
2726
  const toolBlock = enforceToolPolicy("scan_codebase_for_secrets");
2699
2727
  if (toolBlock) return toolBlock;
@@ -2704,10 +2732,7 @@ ${result.stderr}`);
2704
2732
  }
2705
2733
  return text(JSON.stringify(results, null, 2));
2706
2734
  } catch (err) {
2707
- return text(
2708
- `Scan failed: ${err instanceof Error ? err.message : String(err)}`,
2709
- true
2710
- );
2735
+ return text(`Scan failed: ${err instanceof Error ? err.message : String(err)}`, true);
2711
2736
  }
2712
2737
  }
2713
2738
  );
@@ -2719,9 +2744,7 @@ ${result.stderr}`);
2719
2744
  "With `fix: false` this is read-only. With `fix: true` this MUTATES the listed source files in place (review with git diff!) and writes one new secret per finding to the keyring. Returns a JSON array of `{ file, line, key, value, kind }` findings, or 'No hardcoded secrets found in the specified files.'."
2720
2745
  ].join(" "),
2721
2746
  {
2722
- files: z9.array(z9.string()).describe(
2723
- "Absolute or relative paths to lint. Non-existent paths surface as scan errors."
2724
- ),
2747
+ files: z9.array(z9.string()).describe("Absolute or relative paths to lint. Non-existent paths surface as scan errors."),
2725
2748
  fix: z9.boolean().optional().default(false).describe(
2726
2749
  "If true, rewrite the source files to read `process.env.KEY` and store the extracted value in the keyring. If false (default), only report findings."
2727
2750
  ),
@@ -2730,6 +2753,7 @@ ${result.stderr}`);
2730
2753
  teamId: teamId6,
2731
2754
  orgId: orgId6
2732
2755
  },
2756
+ toolAnnotations("lint_files"),
2733
2757
  async (params) => {
2734
2758
  const toolBlock = enforceToolPolicy("lint_files", params.projectPath);
2735
2759
  if (toolBlock) return toolBlock;
@@ -2744,10 +2768,7 @@ ${result.stderr}`);
2744
2768
  }
2745
2769
  return text(JSON.stringify(results, null, 2));
2746
2770
  } catch (err) {
2747
- return text(
2748
- `Lint failed: ${err instanceof Error ? err.message : String(err)}`,
2749
- true
2750
- );
2771
+ return text(`Lint failed: ${err instanceof Error ? err.message : String(err)}`, true);
2751
2772
  }
2752
2773
  }
2753
2774
  );
@@ -2764,6 +2785,7 @@ ${result.stderr}`);
2764
2785
  teamId: teamId6,
2765
2786
  orgId: orgId6
2766
2787
  },
2788
+ toolAnnotations("analyze_secrets"),
2767
2789
  async (params) => {
2768
2790
  const toolBlock = enforceToolPolicy("analyze_secrets", params.projectPath);
2769
2791
  if (toolBlock) return toolBlock;
@@ -2800,15 +2822,14 @@ ${result.stderr}`);
2800
2822
  "TCP port to listen on (default 9876). Pick another port if 9876 is already in use; the call fails if binding errors."
2801
2823
  )
2802
2824
  },
2825
+ toolAnnotations("status_dashboard"),
2803
2826
  async (params) => {
2804
2827
  const toolBlock = enforceToolPolicy("status_dashboard");
2805
2828
  if (toolBlock) return toolBlock;
2806
2829
  if (dashboardInstance) {
2807
- return text(
2808
- `Dashboard already running at ${dashboardInstance.url}`
2809
- );
2830
+ return text(`Dashboard already running at ${dashboardInstance.url}`);
2810
2831
  }
2811
- const { startDashboardServer } = await import("./dashboard-WVPR5BQO.js");
2832
+ const { startDashboardServer } = await import("./dashboard-K7LYTWSL.js");
2812
2833
  dashboardInstance = startDashboardServer({ port: params.port });
2813
2834
  return text(
2814
2835
  `Dashboard started at ${dashboardInstance.url}
@@ -2831,6 +2852,7 @@ Open this URL in a browser to see live quantum status. The token is required for
2831
2852
  "List of absolute project roots to scan. Defaults to `[server.cwd]` when omitted."
2832
2853
  )
2833
2854
  },
2855
+ toolAnnotations("agent_scan"),
2834
2856
  async (params) => {
2835
2857
  const toolBlock = enforceToolPolicy("agent_scan");
2836
2858
  if (toolBlock) return toolBlock;
@@ -2861,6 +2883,7 @@ function registerAgentTools(server2) {
2861
2883
  "Plain-string value to store. JSON-stringify structured data on the caller side if needed."
2862
2884
  )
2863
2885
  },
2886
+ toolAnnotations("agent_remember"),
2864
2887
  async (params) => {
2865
2888
  const toolBlock = enforceToolPolicy("agent_remember");
2866
2889
  if (toolBlock) return toolBlock;
@@ -2876,10 +2899,9 @@ function registerAgentTools(server2) {
2876
2899
  "Read-only. With a `key` argument: returns JSON `{ ok, data: { key, value } }` or a not-found error. Without `key`: returns a JSON listing of every stored key (no values), or 'Agent memory is empty'."
2877
2900
  ].join(" "),
2878
2901
  {
2879
- key: z10.string().optional().describe(
2880
- "Memory key to read. Omit to list every stored key (without values)."
2881
- )
2902
+ key: z10.string().optional().describe("Memory key to read. Omit to list every stored key (without values).")
2882
2903
  },
2904
+ toolAnnotations("agent_recall"),
2883
2905
  async (params) => {
2884
2906
  const toolBlock = enforceToolPolicy("agent_recall");
2885
2907
  if (toolBlock) return toolBlock;
@@ -2889,11 +2911,8 @@ function registerAgentTools(server2) {
2889
2911
  return text(JSON.stringify(entries, null, 2));
2890
2912
  }
2891
2913
  const value = recall(params.key);
2892
- if (value === null)
2893
- return text(`No memory found for "${params.key}"`, true);
2894
- return text(
2895
- JSON.stringify({ ok: true, data: { key: params.key, value } }, null, 2)
2896
- );
2914
+ if (value === null) return text(`No memory found for "${params.key}"`, true);
2915
+ return text(JSON.stringify({ ok: true, data: { key: params.key, value } }, null, 2));
2897
2916
  }
2898
2917
  );
2899
2918
  server2.tool(
@@ -2906,6 +2925,7 @@ function registerAgentTools(server2) {
2906
2925
  {
2907
2926
  key: z10.string().describe("Memory key to delete.")
2908
2927
  },
2928
+ toolAnnotations("agent_forget"),
2909
2929
  async (params) => {
2910
2930
  const toolBlock = enforceToolPolicy("agent_forget");
2911
2931
  if (toolBlock) return toolBlock;
@@ -2933,17 +2953,14 @@ function registerPolicyTools(server2) {
2933
2953
  action: z11.enum(["tool", "key_read", "exec"]).describe(
2934
2954
  "Which policy surface to query. 'tool' = MCP tool gate (needs `toolName`); 'key_read' = secret read gate (needs `key`); 'exec' = exec_with_secrets command gate (needs `command`)."
2935
2955
  ),
2936
- toolName: z11.string().optional().describe(
2937
- "Tool id to evaluate, e.g. 'rotate_secret'. Required when `action` is 'tool'."
2938
- ),
2939
- key: z11.string().optional().describe(
2940
- "Secret key name to evaluate. Required when `action` is 'key_read'."
2941
- ),
2956
+ toolName: z11.string().optional().describe("Tool id to evaluate, e.g. 'rotate_secret'. Required when `action` is 'tool'."),
2957
+ key: z11.string().optional().describe("Secret key name to evaluate. Required when `action` is 'key_read'."),
2942
2958
  command: z11.string().optional().describe(
2943
2959
  "Command to evaluate against the exec allowlist/denylist. Required when `action` is 'exec'."
2944
2960
  ),
2945
2961
  projectPath: projectPath7
2946
2962
  },
2963
+ toolAnnotations("check_policy"),
2947
2964
  async (params) => {
2948
2965
  if (params.action === "tool" && params.toolName) {
2949
2966
  const d = checkToolPolicy(params.toolName, params.projectPath);
@@ -2957,10 +2974,7 @@ function registerPolicyTools(server2) {
2957
2974
  const d = checkExecPolicy(params.command, params.projectPath);
2958
2975
  return text(JSON.stringify(d, null, 2));
2959
2976
  }
2960
- return text(
2961
- "Missing required parameter for the selected action type",
2962
- true
2963
- );
2977
+ return text("Missing required parameter for the selected action type", true);
2964
2978
  }
2965
2979
  );
2966
2980
  server2.tool(
@@ -2973,11 +2987,9 @@ function registerPolicyTools(server2) {
2973
2987
  {
2974
2988
  projectPath: projectPath7
2975
2989
  },
2990
+ toolAnnotations("get_policy_summary"),
2976
2991
  async (params) => {
2977
- const toolBlock = enforceToolPolicy(
2978
- "get_policy_summary",
2979
- params.projectPath
2980
- );
2992
+ const toolBlock = enforceToolPolicy("get_policy_summary", params.projectPath);
2981
2993
  if (toolBlock) return toolBlock;
2982
2994
  const summary = getPolicySummary(params.projectPath);
2983
2995
  return text(JSON.stringify(summary, null, 2));
@@ -2985,8 +2997,70 @@ function registerPolicyTools(server2) {
2985
2997
  );
2986
2998
  }
2987
2999
 
3000
+ // src/mcp/resources.ts
3001
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3002
+ var MIME = "application/json";
3003
+ var LIST_URI = "qring://sessions";
3004
+ var RESOURCE_QUERY = {
3005
+ since: new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString(),
3006
+ limit: 50,
3007
+ maxEvents: 200
3008
+ };
3009
+ function auditVisible() {
3010
+ return checkToolPolicy("audit_log").allowed;
3011
+ }
3012
+ function registerMcpResources(server2) {
3013
+ server2.registerResource(
3014
+ "agent-sessions",
3015
+ LIST_URI,
3016
+ {
3017
+ title: "Agent sessions",
3018
+ description: "Audit activity folded into per-agent sessions (last 7 days): which MCP client did what, when, against which key names. Summaries only \u2014 read qring://sessions/{id} for a session's event timeline. Never contains secret values.",
3019
+ mimeType: MIME
3020
+ },
3021
+ async (uri) => {
3022
+ const sessions = auditVisible() ? listAgentSessionsForAgents(RESOURCE_QUERY).map(summariseSession) : [];
3023
+ return {
3024
+ contents: [{ uri: uri.href, mimeType: MIME, text: JSON.stringify({ sessions }, null, 2) }]
3025
+ };
3026
+ }
3027
+ );
3028
+ server2.registerResource(
3029
+ "agent-session",
3030
+ new ResourceTemplate("qring://sessions/{id}", {
3031
+ list: async () => {
3032
+ if (!auditVisible()) return { resources: [] };
3033
+ return {
3034
+ resources: listAgentSessionsForAgents(RESOURCE_QUERY).map((s) => ({
3035
+ uri: `${LIST_URI}/${s.id}`,
3036
+ name: s.wrapLabel ? `airlock: ${s.wrapLabel}` : s.agent,
3037
+ description: `${s.eventCount} events, ${s.startedAt} \u2192 ${s.endedAt}`,
3038
+ mimeType: MIME
3039
+ }))
3040
+ };
3041
+ }
3042
+ }),
3043
+ {
3044
+ title: "Agent session timeline",
3045
+ description: "One agent session's audit timeline, most recent event first. Key names and actions only \u2014 never secret values.",
3046
+ mimeType: MIME
3047
+ },
3048
+ async (uri, variables) => {
3049
+ const id = String(variables.id ?? "");
3050
+ const session = auditVisible() ? listAgentSessionsForAgents({ ...RESOURCE_QUERY, limit: void 0 }).find(
3051
+ (s) => s.id === id
3052
+ ) : void 0;
3053
+ if (!session) throw new Error(`Session not found: ${id}`);
3054
+ return {
3055
+ contents: [{ uri: uri.href, mimeType: MIME, text: JSON.stringify(session, null, 2) }]
3056
+ };
3057
+ }
3058
+ );
3059
+ }
3060
+
2988
3061
  // src/mcp/tool-registration.ts
2989
3062
  function registerMcpTools(server2) {
3063
+ registerMcpResources(server2);
2990
3064
  registerSecretTools(server2);
2991
3065
  registerProjectTools(server2);
2992
3066
  registerTunnelTools(server2);
@@ -3002,7 +3076,7 @@ function registerMcpTools(server2) {
3002
3076
  // src/mcp/server.ts
3003
3077
  function createMcpServer() {
3004
3078
  setPolicyRoot(process.cwd());
3005
- const server2 = new McpServer({
3079
+ const server2 = new McpServer2({
3006
3080
  name: "q-ring",
3007
3081
  version: PACKAGE_VERSION
3008
3082
  });