@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/README.md +93 -11
- package/dist/{chunk-SQKTDYSO.js → chunk-5ZCQSBVS.js} +352 -18
- package/dist/chunk-5ZCQSBVS.js.map +1 -0
- package/dist/{chunk-KFILBHOY.js → chunk-VMDD5QTJ.js} +247 -18
- package/dist/chunk-VMDD5QTJ.js.map +1 -0
- package/dist/{dashboard-2MLSJZQR.js → dashboard-K7LYTWSL.js} +33 -6
- package/dist/dashboard-K7LYTWSL.js.map +1 -0
- package/dist/{dashboard-WVPR5BQO.js → dashboard-ZN3FPR73.js} +33 -6
- package/dist/dashboard-ZN3FPR73.js.map +1 -0
- package/dist/index.js +659 -115
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +233 -159
- package/dist/mcp.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-KFILBHOY.js.map +0 -1
- package/dist/chunk-SQKTDYSO.js.map +0 -1
- package/dist/dashboard-2MLSJZQR.js.map +0 -1
- package/dist/dashboard-WVPR5BQO.js.map +0 -1
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-
|
|
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
|
-
|
|
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,
|
|
793
|
-
if (
|
|
794
|
-
return this.providers.get(
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3079
|
+
const server2 = new McpServer2({
|
|
3006
3080
|
name: "q-ring",
|
|
3007
3081
|
version: PACKAGE_VERSION
|
|
3008
3082
|
});
|