@i4ctime/q-ring 0.16.2 → 0.17.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/dist/mcp.js CHANGED
@@ -48,6 +48,82 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
48
48
  // src/mcp/server.ts
49
49
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
50
50
 
51
+ // src/mcp/tool-annotations.ts
52
+ var hints = (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) => ({ readOnlyHint, destructiveHint, idempotentHint, openWorldHint });
53
+ var READ = hints(true, false, true, false);
54
+ var READ_OPEN = hints(true, false, true, true);
55
+ var TOOL_ANNOTATIONS = {
56
+ // secrets
57
+ get_secret: READ,
58
+ list_secrets: READ,
59
+ set_secret: hints(false, true, true, false),
60
+ delete_secret: hints(false, true, true, false),
61
+ has_secret: READ,
62
+ export_secrets: READ,
63
+ import_dotenv: hints(false, false, true, false),
64
+ // existing keys are skipped, not overwritten
65
+ inspect_secret: READ,
66
+ generate_secret: hints(false, true, false, false),
67
+ // saveAs overwrites; fresh value every call
68
+ entangle_secrets: hints(false, false, true, false),
69
+ disentangle_secrets: hints(false, false, true, false),
70
+ // project
71
+ check_project: READ,
72
+ env_generate: READ,
73
+ // renders text, never writes files
74
+ detect_environment: READ,
75
+ get_project_context: READ,
76
+ // tunnels (memory-only)
77
+ tunnel_create: hints(false, false, false, false),
78
+ tunnel_read: hints(false, true, false, false),
79
+ // may self-destruct on read
80
+ tunnel_list: READ,
81
+ tunnel_destroy: hints(false, true, true, false),
82
+ // teleport
83
+ teleport_pack: READ,
84
+ teleport_unpack: hints(false, true, true, false),
85
+ // imports may overwrite keys
86
+ // audit / health
87
+ audit_log: READ,
88
+ detect_anomalies: READ,
89
+ health_check: READ,
90
+ verify_audit_chain: READ,
91
+ export_audit: READ,
92
+ // validation / rotation (network)
93
+ validate_secret: READ_OPEN,
94
+ list_providers: READ,
95
+ rotate_secret: hints(false, true, false, true),
96
+ // replaces the credential upstream and locally
97
+ ci_validate_secrets: READ_OPEN,
98
+ // hooks
99
+ register_hook: hints(false, false, false, false),
100
+ list_hooks: READ,
101
+ remove_hook: hints(false, true, true, false),
102
+ // execution / scanning
103
+ exec_with_secrets: hints(false, true, false, true),
104
+ // arbitrary command
105
+ scan_codebase_for_secrets: READ,
106
+ lint_files: hints(false, true, true, false),
107
+ // fix:true rewrites source files
108
+ analyze_secrets: READ,
109
+ status_dashboard: hints(false, false, false, false),
110
+ // starts a local server, new token per launch
111
+ agent_scan: hints(false, true, false, true),
112
+ // autoRotate replaces expired credentials
113
+ // agent memory
114
+ agent_remember: hints(false, true, true, false),
115
+ agent_recall: READ,
116
+ agent_forget: hints(false, true, true, false),
117
+ // policy
118
+ check_policy: READ,
119
+ get_policy_summary: READ
120
+ };
121
+ function toolAnnotations(name) {
122
+ const a = TOOL_ANNOTATIONS[name];
123
+ if (!a) throw new Error(`q-ring: no tool annotations defined for "${name}"`);
124
+ return a;
125
+ }
126
+
51
127
  // src/mcp/tools/secrets.ts
52
128
  import { z as z2 } from "zod";
53
129
 
@@ -252,10 +328,7 @@ function opts(params) {
252
328
  function enforceToolPolicy(toolName, projectPath8) {
253
329
  const decision = checkToolPolicy(toolName, projectPath8);
254
330
  if (!decision.allowed) {
255
- return text(
256
- `Policy Denied: ${decision.reason} (source: ${decision.policySource})`,
257
- true
258
- );
331
+ return text(`Policy Denied: ${decision.reason} (source: ${decision.policySource})`, true);
259
332
  }
260
333
  return null;
261
334
  }
@@ -297,23 +370,18 @@ function registerSecretTools(server2) {
297
370
  teamId,
298
371
  orgId
299
372
  },
373
+ toolAnnotations("get_secret"),
300
374
  async (params) => {
301
375
  const toolBlock = enforceToolPolicy("get_secret", params.projectPath);
302
376
  if (toolBlock) return toolBlock;
303
377
  try {
304
- const keyBlock = checkKeyReadPolicy(
305
- params.key,
306
- void 0,
307
- params.projectPath
308
- );
378
+ const keyBlock = checkKeyReadPolicy(params.key, void 0, params.projectPath);
309
379
  if (!keyBlock.allowed) {
310
380
  return text(`Policy Denied: ${keyBlock.reason}`, true);
311
381
  }
312
382
  const value = getSecret(params.key, opts(params));
313
383
  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
- );
384
+ return text(JSON.stringify({ ok: true, data: { key: params.key, value } }, null, 2));
317
385
  } catch (err) {
318
386
  return text(err instanceof Error ? err.message : String(err), true);
319
387
  }
@@ -344,22 +412,19 @@ function registerSecretTools(server2) {
344
412
  teamId,
345
413
  orgId
346
414
  },
415
+ toolAnnotations("list_secrets"),
347
416
  async (params) => {
348
417
  const toolBlock = enforceToolPolicy("list_secrets", params.projectPath);
349
418
  if (toolBlock) return toolBlock;
350
419
  let entries = listSecrets(opts(params));
351
420
  if (params.tag) {
352
- entries = entries.filter(
353
- (e) => e.envelope?.meta.tags?.includes(params.tag)
354
- );
421
+ entries = entries.filter((e) => e.envelope?.meta.tags?.includes(params.tag));
355
422
  }
356
423
  if (params.expired) {
357
424
  entries = entries.filter((e) => e.decay?.isExpired);
358
425
  }
359
426
  if (params.stale) {
360
- entries = entries.filter(
361
- (e) => e.decay?.isStale && !e.decay?.isExpired
362
- );
427
+ entries = entries.filter((e) => e.decay?.isStale && !e.decay?.isExpired);
363
428
  }
364
429
  if (params.filter) {
365
430
  entries = filterSecretsByKeyGlob(entries, params.filter);
@@ -386,9 +451,7 @@ function registerSecretTools(server2) {
386
451
  "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
452
  ].join(" "),
388
453
  {
389
- key: z2.string().describe(
390
- "Secret key name (UPPER_SNAKE_CASE recommended). Example: 'STRIPE_SECRET_KEY'."
391
- ),
454
+ key: z2.string().describe("Secret key name (UPPER_SNAKE_CASE recommended). Example: 'STRIPE_SECRET_KEY'."),
392
455
  value: z2.string().describe(
393
456
  "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
457
  ),
@@ -403,18 +466,8 @@ function registerSecretTools(server2) {
403
466
  description: z2.string().optional().describe(
404
467
  "Free-text human-readable description shown in `inspect_secret` and the dashboard."
405
468
  ),
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(
469
+ tags: z2.array(z2.string()).optional().describe("Tag list for filtering and hook matching. Example: ['production', 'payments']."),
470
+ rotationFormat: z2.enum(["hex", "base64", "alphanumeric", "uuid", "api-key", "token", "password"]).optional().describe(
418
471
  "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
472
  ),
420
473
  rotationPrefix: z2.string().optional().describe(
@@ -423,6 +476,7 @@ function registerSecretTools(server2) {
423
476
  teamId,
424
477
  orgId
425
478
  },
479
+ toolAnnotations("set_secret"),
426
480
  async (params) => {
427
481
  const toolBlock = enforceToolPolicy("set_secret", params.projectPath);
428
482
  if (toolBlock) return toolBlock;
@@ -444,9 +498,7 @@ function registerSecretTools(server2) {
444
498
  rotationFormat: params.rotationFormat,
445
499
  rotationPrefix: params.rotationPrefix
446
500
  });
447
- return text(
448
- `[${params.scope ?? "global"}] ${params.key} set for env:${params.env}`
449
- );
501
+ return text(`[${params.scope ?? "global"}] ${params.key} set for env:${params.env}`);
450
502
  }
451
503
  setSecret(params.key, params.value, {
452
504
  ...o,
@@ -473,6 +525,7 @@ function registerSecretTools(server2) {
473
525
  teamId,
474
526
  orgId
475
527
  },
528
+ toolAnnotations("delete_secret"),
476
529
  async (params) => {
477
530
  const toolBlock = enforceToolPolicy("delete_secret", params.projectPath);
478
531
  if (toolBlock) return toolBlock;
@@ -497,6 +550,7 @@ function registerSecretTools(server2) {
497
550
  teamId,
498
551
  orgId
499
552
  },
553
+ toolAnnotations("has_secret"),
500
554
  async (params) => {
501
555
  const toolBlock = enforceToolPolicy("has_secret", params.projectPath);
502
556
  if (toolBlock) return toolBlock;
@@ -526,6 +580,7 @@ function registerSecretTools(server2) {
526
580
  teamId,
527
581
  orgId
528
582
  },
583
+ toolAnnotations("export_secrets"),
529
584
  async (params) => {
530
585
  const toolBlock = enforceToolPolicy("export_secrets", params.projectPath);
531
586
  if (toolBlock) return toolBlock;
@@ -559,6 +614,7 @@ function registerSecretTools(server2) {
559
614
  "If true, parse and report what would happen but do not write to the keyring. Useful for previewing imports before committing."
560
615
  )
561
616
  },
617
+ toolAnnotations("import_dotenv"),
562
618
  async (params) => {
563
619
  const toolBlock = enforceToolPolicy("import_dotenv", params.projectPath);
564
620
  if (toolBlock) return toolBlock;
@@ -595,6 +651,7 @@ function registerSecretTools(server2) {
595
651
  teamId,
596
652
  orgId
597
653
  },
654
+ toolAnnotations("inspect_secret"),
598
655
  async (params) => {
599
656
  const toolBlock = enforceToolPolicy("inspect_secret", params.projectPath);
600
657
  if (toolBlock) return toolBlock;
@@ -626,8 +683,7 @@ function registerSecretTools(server2) {
626
683
  if (envelope.meta.entangled?.length) {
627
684
  info.entangled = envelope.meta.entangled;
628
685
  }
629
- if (envelope.meta.description)
630
- info.description = envelope.meta.description;
686
+ if (envelope.meta.description) info.description = envelope.meta.description;
631
687
  if (envelope.meta.tags?.length) info.tags = envelope.meta.tags;
632
688
  return text(JSON.stringify(info, null, 2));
633
689
  }
@@ -640,15 +696,7 @@ function registerSecretTools(server2) {
640
696
  "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
697
  ].join(" "),
642
698
  {
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(
699
+ format: z2.enum(["hex", "base64", "alphanumeric", "uuid", "api-key", "token", "password"]).optional().default("api-key").describe(
652
700
  "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
701
  ),
654
702
  length: z2.number().optional().describe(
@@ -665,6 +713,7 @@ function registerSecretTools(server2) {
665
713
  teamId,
666
714
  orgId
667
715
  },
716
+ toolAnnotations("generate_secret"),
668
717
  async (params) => {
669
718
  const toolBlock = enforceToolPolicy("generate_secret", params.projectPath);
670
719
  if (toolBlock) return toolBlock;
@@ -705,19 +754,14 @@ function registerSecretTools(server2) {
705
754
  "Project root for targetKey when targetScope='project'. Defaults to the server cwd."
706
755
  )
707
756
  },
757
+ toolAnnotations("entangle_secrets"),
708
758
  async (params) => {
709
- const toolBlock = enforceToolPolicy(
710
- "entangle_secrets",
711
- params.sourceProjectPath
712
- );
759
+ const toolBlock = enforceToolPolicy("entangle_secrets", params.sourceProjectPath);
713
760
  if (toolBlock) return toolBlock;
714
761
  for (const key of [params.sourceKey, params.targetKey]) {
715
762
  const decision = checkKeyReadPolicy(key, void 0, params.sourceProjectPath);
716
763
  if (!decision.allowed) {
717
- return text(
718
- `Policy Denied: ${decision.reason} (source: ${decision.policySource})`,
719
- true
720
- );
764
+ return text(`Policy Denied: ${decision.reason} (source: ${decision.policySource})`, true);
721
765
  }
722
766
  }
723
767
  entangleSecrets(
@@ -752,11 +796,9 @@ function registerSecretTools(server2) {
752
796
  sourceProjectPath: z2.string().optional().describe("Project root for sourceKey when sourceScope='project'."),
753
797
  targetProjectPath: z2.string().optional().describe("Project root for targetKey when targetScope='project'.")
754
798
  },
799
+ toolAnnotations("disentangle_secrets"),
755
800
  async (params) => {
756
- const toolBlock = enforceToolPolicy(
757
- "disentangle_secrets",
758
- params.sourceProjectPath
759
- );
801
+ const toolBlock = enforceToolPolicy("disentangle_secrets", params.sourceProjectPath);
760
802
  if (toolBlock) return toolBlock;
761
803
  disentangleSecrets(
762
804
  params.sourceKey,
@@ -789,9 +831,9 @@ var ProviderRegistry = class {
789
831
  get(name) {
790
832
  return this.providers.get(name);
791
833
  }
792
- detectProvider(value, hints) {
793
- if (hints?.provider) {
794
- return this.providers.get(hints.provider);
834
+ detectProvider(value, hints2) {
835
+ if (hints2?.provider) {
836
+ return this.providers.get(hints2.provider);
795
837
  }
796
838
  for (const provider of this.providers.values()) {
797
839
  if (provider.prefixes) {
@@ -1147,6 +1189,7 @@ function registerProjectTools(server2) {
1147
1189
  {
1148
1190
  projectPath: projectPath2
1149
1191
  },
1192
+ toolAnnotations("check_project"),
1150
1193
  async (params) => {
1151
1194
  const toolBlock = enforceToolPolicy("check_project", params.projectPath);
1152
1195
  if (toolBlock) return toolBlock;
@@ -1219,6 +1262,7 @@ function registerProjectTools(server2) {
1219
1262
  projectPath: projectPath2,
1220
1263
  env: env2
1221
1264
  },
1265
+ toolAnnotations("env_generate"),
1222
1266
  async (params) => {
1223
1267
  const toolBlock = enforceToolPolicy("env_generate", params.projectPath);
1224
1268
  if (toolBlock) return toolBlock;
@@ -1269,19 +1313,15 @@ ${warnings.map((w) => `# ${w}`).join("\n")}` : output;
1269
1313
  {
1270
1314
  projectPath: projectPath2
1271
1315
  },
1316
+ toolAnnotations("detect_environment"),
1272
1317
  async (params) => {
1273
- const toolBlock = enforceToolPolicy(
1274
- "detect_environment",
1275
- params.projectPath
1276
- );
1318
+ const toolBlock = enforceToolPolicy("detect_environment", params.projectPath);
1277
1319
  if (toolBlock) return toolBlock;
1278
1320
  const result = collapseEnvironment({
1279
1321
  projectPath: params.projectPath ?? process.cwd()
1280
1322
  });
1281
1323
  if (!result) {
1282
- return text(
1283
- "No environment detected. Set QRING_ENV, NODE_ENV, or create .q-ring.json"
1284
- );
1324
+ return text("No environment detected. Set QRING_ENV, NODE_ENV, or create .q-ring.json");
1285
1325
  }
1286
1326
  return text(JSON.stringify(result, null, 2));
1287
1327
  }
@@ -1299,11 +1339,9 @@ ${warnings.map((w) => `# ${w}`).join("\n")}` : output;
1299
1339
  teamId: teamId2,
1300
1340
  orgId: orgId2
1301
1341
  },
1342
+ toolAnnotations("get_project_context"),
1302
1343
  async (params) => {
1303
- const toolBlock = enforceToolPolicy(
1304
- "get_project_context",
1305
- params.projectPath
1306
- );
1344
+ const toolBlock = enforceToolPolicy("get_project_context", params.projectPath);
1307
1345
  if (toolBlock) return toolBlock;
1308
1346
  const context = getProjectContext(opts(params));
1309
1347
  return text(JSON.stringify(context, null, 2));
@@ -1322,9 +1360,7 @@ function registerTunnelTools(server2) {
1322
1360
  "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
1361
  ].join(" "),
1324
1362
  {
1325
- value: z3.string().describe(
1326
- "The plaintext value to tunnel. Held only in process memory; never logged."
1327
- ),
1363
+ value: z3.string().describe("The plaintext value to tunnel. Held only in process memory; never logged."),
1328
1364
  ttlSeconds: z3.number().optional().describe(
1329
1365
  "Auto-destroy the tunnel after this many seconds. Omit for no time limit (then a `maxReads` is highly recommended)."
1330
1366
  ),
@@ -1332,6 +1368,7 @@ function registerTunnelTools(server2) {
1332
1368
  "Self-destruct after this many successful `tunnel_read` calls. Use 1 for true one-shot delivery."
1333
1369
  )
1334
1370
  },
1371
+ toolAnnotations("tunnel_create"),
1335
1372
  async (params) => {
1336
1373
  const toolBlock = enforceToolPolicy("tunnel_create");
1337
1374
  if (toolBlock) return toolBlock;
@@ -1350,10 +1387,9 @@ function registerTunnelTools(server2) {
1350
1387
  "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
1388
  ].join(" "),
1352
1389
  {
1353
- id: z3.string().describe(
1354
- "The opaque tunnel ID returned by `tunnel_create`. Case-sensitive."
1355
- )
1390
+ id: z3.string().describe("The opaque tunnel ID returned by `tunnel_create`. Case-sensitive.")
1356
1391
  },
1392
+ toolAnnotations("tunnel_read"),
1357
1393
  async (params) => {
1358
1394
  const toolBlock = enforceToolPolicy("tunnel_read");
1359
1395
  if (toolBlock) return toolBlock;
@@ -1361,9 +1397,7 @@ function registerTunnelTools(server2) {
1361
1397
  if (value === null) {
1362
1398
  return text(`Tunnel "${params.id}" not found or expired`, true);
1363
1399
  }
1364
- return text(
1365
- JSON.stringify({ ok: true, data: { id: params.id, value } }, null, 2)
1366
- );
1400
+ return text(JSON.stringify({ ok: true, data: { id: params.id, value } }, null, 2));
1367
1401
  }
1368
1402
  );
1369
1403
  server2.tool(
@@ -1374,6 +1408,7 @@ function registerTunnelTools(server2) {
1374
1408
  "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
1409
  ].join(" "),
1376
1410
  {},
1411
+ toolAnnotations("tunnel_list"),
1377
1412
  async () => {
1378
1413
  const toolBlock = enforceToolPolicy("tunnel_list");
1379
1414
  if (toolBlock) return toolBlock;
@@ -1384,10 +1419,7 @@ function registerTunnelTools(server2) {
1384
1419
  parts.push(`reads:${t.accessCount}`);
1385
1420
  if (t.maxReads) parts.push(`max:${t.maxReads}`);
1386
1421
  if (t.expiresAt) {
1387
- const rem = Math.max(
1388
- 0,
1389
- Math.floor((t.expiresAt - Date.now()) / 1e3)
1390
- );
1422
+ const rem = Math.max(0, Math.floor((t.expiresAt - Date.now()) / 1e3));
1391
1423
  parts.push(`expires:${rem}s`);
1392
1424
  }
1393
1425
  return parts.join(" | ");
@@ -1405,6 +1437,7 @@ function registerTunnelTools(server2) {
1405
1437
  {
1406
1438
  id: z3.string().describe("The opaque tunnel ID to destroy.")
1407
1439
  },
1440
+ toolAnnotations("tunnel_destroy"),
1408
1441
  async (params) => {
1409
1442
  const toolBlock = enforceToolPolicy("tunnel_destroy");
1410
1443
  if (toolBlock) return toolBlock;
@@ -1558,6 +1591,7 @@ function registerTeleportTools(server2) {
1558
1591
  teamId: teamId3,
1559
1592
  orgId: orgId3
1560
1593
  },
1594
+ toolAnnotations("teleport_pack"),
1561
1595
  async (params) => {
1562
1596
  const toolBlock = enforceToolPolicy("teleport_pack", params.projectPath);
1563
1597
  if (toolBlock) return toolBlock;
@@ -1598,6 +1632,7 @@ function registerTeleportTools(server2) {
1598
1632
  "If true, decrypt and report what would be written but do not mutate the keyring. Useful for verifying bundle contents before commit."
1599
1633
  )
1600
1634
  },
1635
+ toolAnnotations("teleport_unpack"),
1601
1636
  async (params) => {
1602
1637
  const toolBlock = enforceToolPolicy("teleport_unpack", params.projectPath);
1603
1638
  if (toolBlock) return toolBlock;
@@ -1605,18 +1640,14 @@ function registerTeleportTools(server2) {
1605
1640
  const payload = teleportUnpack(params.bundle, params.passphrase);
1606
1641
  if (params.dryRun) {
1607
1642
  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
- );
1643
+ return text(`Would import ${payload.secrets.length} secrets:
1644
+ ${preview}`);
1612
1645
  }
1613
1646
  const o = opts(params);
1614
1647
  for (const s of payload.secrets) {
1615
1648
  setSecret(s.key, s.value, o);
1616
1649
  }
1617
- return text(
1618
- `Imported ${payload.secrets.length} secret(s) from teleport bundle`
1619
- );
1650
+ return text(`Imported ${payload.secrets.length} secret(s) from teleport bundle`);
1620
1651
  } catch (err) {
1621
1652
  const msg = err instanceof Error ? err.message : String(err);
1622
1653
  return text(JSON.stringify({ ok: false, error: { message: msg } }), true);
@@ -1637,9 +1668,7 @@ function registerAuditTools(server2) {
1637
1668
  "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
1669
  ].join(" "),
1639
1670
  {
1640
- key: z6.string().optional().describe(
1641
- "Limit to events touching this exact key. Omit for the full log."
1642
- ),
1671
+ key: z6.string().optional().describe("Limit to events touching this exact key. Omit for the full log."),
1643
1672
  action: z6.enum([
1644
1673
  "read",
1645
1674
  "write",
@@ -1667,6 +1696,7 @@ function registerAuditTools(server2) {
1667
1696
  "Maximum events to return, newest first. Defaults to 20. Increase for deeper investigations."
1668
1697
  )
1669
1698
  },
1699
+ toolAnnotations("audit_log"),
1670
1700
  async (params) => {
1671
1701
  const toolBlock = enforceToolPolicy("audit_log");
1672
1702
  if (toolBlock) return toolBlock;
@@ -1700,6 +1730,7 @@ function registerAuditTools(server2) {
1700
1730
  "If provided, narrow the scan to this exact key. Omit to scan across every key in the audit log."
1701
1731
  )
1702
1732
  },
1733
+ toolAnnotations("detect_anomalies"),
1703
1734
  async (params) => {
1704
1735
  const toolBlock = enforceToolPolicy("detect_anomalies");
1705
1736
  if (toolBlock) return toolBlock;
@@ -1722,6 +1753,7 @@ function registerAuditTools(server2) {
1722
1753
  teamId: teamId4,
1723
1754
  orgId: orgId4
1724
1755
  },
1756
+ toolAnnotations("health_check"),
1725
1757
  async (params) => {
1726
1758
  const toolBlock = enforceToolPolicy("health_check", params.projectPath);
1727
1759
  if (toolBlock) return toolBlock;
@@ -1758,11 +1790,7 @@ function registerAuditTools(server2) {
1758
1790
  summary.push("", "Issues:", ...issues);
1759
1791
  }
1760
1792
  if (anomalies.length > 0) {
1761
- summary.push(
1762
- "",
1763
- "Anomalies:",
1764
- ...anomalies.map((a) => `[${a.type}] ${a.description}`)
1765
- );
1793
+ summary.push("", "Anomalies:", ...anomalies.map((a) => `[${a.type}] ${a.description}`));
1766
1794
  }
1767
1795
  return text(summary.join("\n"));
1768
1796
  }
@@ -1775,6 +1803,7 @@ function registerAuditTools(server2) {
1775
1803
  "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
1804
  ].join(" "),
1777
1805
  {},
1806
+ toolAnnotations("verify_audit_chain"),
1778
1807
  async () => {
1779
1808
  const toolBlock = enforceToolPolicy("verify_audit_chain");
1780
1809
  if (toolBlock) return toolBlock;
@@ -1800,6 +1829,7 @@ function registerAuditTools(server2) {
1800
1829
  "Output format. 'jsonl' (default) is most stream-friendly; 'json' is a single array; 'csv' is spreadsheet-friendly."
1801
1830
  )
1802
1831
  },
1832
+ toolAnnotations("export_audit"),
1803
1833
  async (params) => {
1804
1834
  const toolBlock = enforceToolPolicy("export_audit");
1805
1835
  if (toolBlock) return toolBlock;
@@ -1838,6 +1868,7 @@ function registerValidationTools(server2) {
1838
1868
  teamId: teamId5,
1839
1869
  orgId: orgId5
1840
1870
  },
1871
+ toolAnnotations("validate_secret"),
1841
1872
  async (params) => {
1842
1873
  const toolBlock = enforceToolPolicy("validate_secret", params.projectPath);
1843
1874
  if (toolBlock) return toolBlock;
@@ -1857,6 +1888,7 @@ function registerValidationTools(server2) {
1857
1888
  "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
1889
  ].join(" "),
1859
1890
  {},
1891
+ toolAnnotations("list_providers"),
1860
1892
  async () => {
1861
1893
  const toolBlock = enforceToolPolicy("list_providers");
1862
1894
  if (toolBlock) return toolBlock;
@@ -1885,6 +1917,7 @@ function registerValidationTools(server2) {
1885
1917
  teamId: teamId5,
1886
1918
  orgId: orgId5
1887
1919
  },
1920
+ toolAnnotations("rotate_secret"),
1888
1921
  async (params) => {
1889
1922
  const toolBlock = enforceToolPolicy("rotate_secret", params.projectPath);
1890
1923
  if (toolBlock) return toolBlock;
@@ -1914,11 +1947,9 @@ function registerValidationTools(server2) {
1914
1947
  teamId: teamId5,
1915
1948
  orgId: orgId5
1916
1949
  },
1950
+ toolAnnotations("ci_validate_secrets"),
1917
1951
  async (params) => {
1918
- const toolBlock = enforceToolPolicy(
1919
- "ci_validate_secrets",
1920
- params.projectPath
1921
- );
1952
+ const toolBlock = enforceToolPolicy("ci_validate_secrets", params.projectPath);
1922
1953
  if (toolBlock) return toolBlock;
1923
1954
  const entries = listSecrets(opts(params));
1924
1955
  const secrets = entries.map((e) => {
@@ -1959,18 +1990,14 @@ function registerHookTools(server2) {
1959
1990
  key: z8.string().optional().describe(
1960
1991
  "Trigger only on this exact key name. Pick at most one of `key` / `keyPattern` / `tag` (or combine for stricter matching)."
1961
1992
  ),
1962
- keyPattern: z8.string().optional().describe(
1963
- "Trigger on any key matching this glob pattern. Examples: 'DB_*', 'STRIPE_*'."
1964
- ),
1993
+ keyPattern: z8.string().optional().describe("Trigger on any key matching this glob pattern. Examples: 'DB_*', 'STRIPE_*'."),
1965
1994
  tag: z8.string().optional().describe(
1966
1995
  "Trigger on any secret carrying this exact tag. Combinable with key/keyPattern as an AND filter."
1967
1996
  ),
1968
1997
  scope: z8.enum(["global", "project"]).optional().describe(
1969
1998
  "Restrict the hook to secrets in this scope. Omit to fire across both global and project secrets."
1970
1999
  ),
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
- ),
2000
+ 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
2001
  command: z8.string().optional().describe(
1975
2002
  "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
2003
  ),
@@ -1987,14 +2014,12 @@ function registerHookTools(server2) {
1987
2014
  "Free-text human-readable description, surfaced by `list_hooks` and the dashboard."
1988
2015
  )
1989
2016
  },
2017
+ toolAnnotations("register_hook"),
1990
2018
  async (params) => {
1991
2019
  const toolBlock = enforceToolPolicy("register_hook");
1992
2020
  if (toolBlock) return toolBlock;
1993
2021
  if (!params.key && !params.keyPattern && !params.tag) {
1994
- return text(
1995
- "At least one match criterion required: key, keyPattern, or tag",
1996
- true
1997
- );
2022
+ return text("At least one match criterion required: key, keyPattern, or tag", true);
1998
2023
  }
1999
2024
  const entry = registerHook({
2000
2025
  type: params.type,
@@ -2022,6 +2047,7 @@ function registerHookTools(server2) {
2022
2047
  "Read-only. Returns pretty-printed JSON array of hook entries, or 'No hooks registered' when the registry is empty."
2023
2048
  ].join(" "),
2024
2049
  {},
2050
+ toolAnnotations("list_hooks"),
2025
2051
  async () => {
2026
2052
  const toolBlock = enforceToolPolicy("list_hooks");
2027
2053
  if (toolBlock) return toolBlock;
@@ -2042,6 +2068,7 @@ function registerHookTools(server2) {
2042
2068
  "Hook id returned by `register_hook` or visible in `list_hooks` (opaque string)."
2043
2069
  )
2044
2070
  },
2071
+ toolAnnotations("remove_hook"),
2045
2072
  async (params) => {
2046
2073
  const toolBlock = enforceToolPolicy("remove_hook");
2047
2074
  if (toolBlock) return toolBlock;
@@ -2648,6 +2675,7 @@ function registerToolingTools(server2) {
2648
2675
  teamId: teamId6,
2649
2676
  orgId: orgId6
2650
2677
  },
2678
+ toolAnnotations("exec_with_secrets"),
2651
2679
  async (params) => {
2652
2680
  const toolBlock = enforceToolPolicy("exec_with_secrets", params.projectPath);
2653
2681
  if (toolBlock) return toolBlock;
@@ -2675,10 +2703,7 @@ ${result.stdout}`);
2675
2703
  ${result.stderr}`);
2676
2704
  return text(output.join("\n\n"));
2677
2705
  } catch (err) {
2678
- return text(
2679
- `Execution failed: ${err instanceof Error ? err.message : String(err)}`,
2680
- true
2681
- );
2706
+ return text(`Execution failed: ${err instanceof Error ? err.message : String(err)}`, true);
2682
2707
  }
2683
2708
  }
2684
2709
  );
@@ -2694,6 +2719,7 @@ ${result.stderr}`);
2694
2719
  "Directory to scan, absolute or relative to the server cwd. The scan recurses into subdirectories."
2695
2720
  )
2696
2721
  },
2722
+ toolAnnotations("scan_codebase_for_secrets"),
2697
2723
  async (params) => {
2698
2724
  const toolBlock = enforceToolPolicy("scan_codebase_for_secrets");
2699
2725
  if (toolBlock) return toolBlock;
@@ -2704,10 +2730,7 @@ ${result.stderr}`);
2704
2730
  }
2705
2731
  return text(JSON.stringify(results, null, 2));
2706
2732
  } catch (err) {
2707
- return text(
2708
- `Scan failed: ${err instanceof Error ? err.message : String(err)}`,
2709
- true
2710
- );
2733
+ return text(`Scan failed: ${err instanceof Error ? err.message : String(err)}`, true);
2711
2734
  }
2712
2735
  }
2713
2736
  );
@@ -2719,9 +2742,7 @@ ${result.stderr}`);
2719
2742
  "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
2743
  ].join(" "),
2721
2744
  {
2722
- files: z9.array(z9.string()).describe(
2723
- "Absolute or relative paths to lint. Non-existent paths surface as scan errors."
2724
- ),
2745
+ files: z9.array(z9.string()).describe("Absolute or relative paths to lint. Non-existent paths surface as scan errors."),
2725
2746
  fix: z9.boolean().optional().default(false).describe(
2726
2747
  "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
2748
  ),
@@ -2730,6 +2751,7 @@ ${result.stderr}`);
2730
2751
  teamId: teamId6,
2731
2752
  orgId: orgId6
2732
2753
  },
2754
+ toolAnnotations("lint_files"),
2733
2755
  async (params) => {
2734
2756
  const toolBlock = enforceToolPolicy("lint_files", params.projectPath);
2735
2757
  if (toolBlock) return toolBlock;
@@ -2744,10 +2766,7 @@ ${result.stderr}`);
2744
2766
  }
2745
2767
  return text(JSON.stringify(results, null, 2));
2746
2768
  } catch (err) {
2747
- return text(
2748
- `Lint failed: ${err instanceof Error ? err.message : String(err)}`,
2749
- true
2750
- );
2769
+ return text(`Lint failed: ${err instanceof Error ? err.message : String(err)}`, true);
2751
2770
  }
2752
2771
  }
2753
2772
  );
@@ -2764,6 +2783,7 @@ ${result.stderr}`);
2764
2783
  teamId: teamId6,
2765
2784
  orgId: orgId6
2766
2785
  },
2786
+ toolAnnotations("analyze_secrets"),
2767
2787
  async (params) => {
2768
2788
  const toolBlock = enforceToolPolicy("analyze_secrets", params.projectPath);
2769
2789
  if (toolBlock) return toolBlock;
@@ -2800,13 +2820,12 @@ ${result.stderr}`);
2800
2820
  "TCP port to listen on (default 9876). Pick another port if 9876 is already in use; the call fails if binding errors."
2801
2821
  )
2802
2822
  },
2823
+ toolAnnotations("status_dashboard"),
2803
2824
  async (params) => {
2804
2825
  const toolBlock = enforceToolPolicy("status_dashboard");
2805
2826
  if (toolBlock) return toolBlock;
2806
2827
  if (dashboardInstance) {
2807
- return text(
2808
- `Dashboard already running at ${dashboardInstance.url}`
2809
- );
2828
+ return text(`Dashboard already running at ${dashboardInstance.url}`);
2810
2829
  }
2811
2830
  const { startDashboardServer } = await import("./dashboard-WVPR5BQO.js");
2812
2831
  dashboardInstance = startDashboardServer({ port: params.port });
@@ -2831,6 +2850,7 @@ Open this URL in a browser to see live quantum status. The token is required for
2831
2850
  "List of absolute project roots to scan. Defaults to `[server.cwd]` when omitted."
2832
2851
  )
2833
2852
  },
2853
+ toolAnnotations("agent_scan"),
2834
2854
  async (params) => {
2835
2855
  const toolBlock = enforceToolPolicy("agent_scan");
2836
2856
  if (toolBlock) return toolBlock;
@@ -2861,6 +2881,7 @@ function registerAgentTools(server2) {
2861
2881
  "Plain-string value to store. JSON-stringify structured data on the caller side if needed."
2862
2882
  )
2863
2883
  },
2884
+ toolAnnotations("agent_remember"),
2864
2885
  async (params) => {
2865
2886
  const toolBlock = enforceToolPolicy("agent_remember");
2866
2887
  if (toolBlock) return toolBlock;
@@ -2876,10 +2897,9 @@ function registerAgentTools(server2) {
2876
2897
  "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
2898
  ].join(" "),
2878
2899
  {
2879
- key: z10.string().optional().describe(
2880
- "Memory key to read. Omit to list every stored key (without values)."
2881
- )
2900
+ key: z10.string().optional().describe("Memory key to read. Omit to list every stored key (without values).")
2882
2901
  },
2902
+ toolAnnotations("agent_recall"),
2883
2903
  async (params) => {
2884
2904
  const toolBlock = enforceToolPolicy("agent_recall");
2885
2905
  if (toolBlock) return toolBlock;
@@ -2889,11 +2909,8 @@ function registerAgentTools(server2) {
2889
2909
  return text(JSON.stringify(entries, null, 2));
2890
2910
  }
2891
2911
  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
- );
2912
+ if (value === null) return text(`No memory found for "${params.key}"`, true);
2913
+ return text(JSON.stringify({ ok: true, data: { key: params.key, value } }, null, 2));
2897
2914
  }
2898
2915
  );
2899
2916
  server2.tool(
@@ -2906,6 +2923,7 @@ function registerAgentTools(server2) {
2906
2923
  {
2907
2924
  key: z10.string().describe("Memory key to delete.")
2908
2925
  },
2926
+ toolAnnotations("agent_forget"),
2909
2927
  async (params) => {
2910
2928
  const toolBlock = enforceToolPolicy("agent_forget");
2911
2929
  if (toolBlock) return toolBlock;
@@ -2933,17 +2951,14 @@ function registerPolicyTools(server2) {
2933
2951
  action: z11.enum(["tool", "key_read", "exec"]).describe(
2934
2952
  "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
2953
  ),
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
- ),
2954
+ toolName: z11.string().optional().describe("Tool id to evaluate, e.g. 'rotate_secret'. Required when `action` is 'tool'."),
2955
+ key: z11.string().optional().describe("Secret key name to evaluate. Required when `action` is 'key_read'."),
2942
2956
  command: z11.string().optional().describe(
2943
2957
  "Command to evaluate against the exec allowlist/denylist. Required when `action` is 'exec'."
2944
2958
  ),
2945
2959
  projectPath: projectPath7
2946
2960
  },
2961
+ toolAnnotations("check_policy"),
2947
2962
  async (params) => {
2948
2963
  if (params.action === "tool" && params.toolName) {
2949
2964
  const d = checkToolPolicy(params.toolName, params.projectPath);
@@ -2957,10 +2972,7 @@ function registerPolicyTools(server2) {
2957
2972
  const d = checkExecPolicy(params.command, params.projectPath);
2958
2973
  return text(JSON.stringify(d, null, 2));
2959
2974
  }
2960
- return text(
2961
- "Missing required parameter for the selected action type",
2962
- true
2963
- );
2975
+ return text("Missing required parameter for the selected action type", true);
2964
2976
  }
2965
2977
  );
2966
2978
  server2.tool(
@@ -2973,11 +2985,9 @@ function registerPolicyTools(server2) {
2973
2985
  {
2974
2986
  projectPath: projectPath7
2975
2987
  },
2988
+ toolAnnotations("get_policy_summary"),
2976
2989
  async (params) => {
2977
- const toolBlock = enforceToolPolicy(
2978
- "get_policy_summary",
2979
- params.projectPath
2980
- );
2990
+ const toolBlock = enforceToolPolicy("get_policy_summary", params.projectPath);
2981
2991
  if (toolBlock) return toolBlock;
2982
2992
  const summary = getPolicySummary(params.projectPath);
2983
2993
  return text(JSON.stringify(summary, null, 2));