@sirrlock/mcp 0.1.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +100 -27
- package/dist/helpers.d.ts +8 -0
- package/dist/helpers.js +34 -0
- package/dist/helpers.js.map +1 -1
- package/dist/helpers.test.js +68 -0
- package/dist/helpers.test.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +621 -87
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +8 -0
- package/dist/index.test.js +619 -0
- package/dist/index.test.js.map +1 -0
- package/dist/integration.test.js +123 -0
- package/dist/integration.test.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Exposes Sirr as MCP tools so Claude Code can read/write ephemeral secrets.
|
|
7
7
|
*
|
|
8
8
|
* Configuration (env vars):
|
|
9
|
-
* SIRR_SERVER — Sirr server URL (default:
|
|
10
|
-
* SIRR_TOKEN — Bearer token
|
|
9
|
+
* SIRR_SERVER — Sirr server URL (default: https://sirr.sirrlock.com)
|
|
10
|
+
* SIRR_TOKEN — Bearer token: SIRR_MASTER_KEY for full access, or a principal key for org-scoped access
|
|
11
11
|
*
|
|
12
12
|
* Install: npm install -g @sirrlock/mcp
|
|
13
13
|
* Configure in .mcp.json:
|
|
@@ -26,8 +26,9 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
26
26
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
27
27
|
const package_json_1 = require("../package.json");
|
|
28
28
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
29
|
-
const SIRR_SERVER = (process.env["SIRR_SERVER"] ?? "
|
|
29
|
+
const SIRR_SERVER = (process.env["SIRR_SERVER"] ?? "https://sirr.sirrlock.com").replace(/\/$/, "");
|
|
30
30
|
const SIRR_TOKEN = process.env["SIRR_TOKEN"] ?? "";
|
|
31
|
+
const SIRRLOCK_URL = (process.env["SIRRLOCK_URL"] ?? "https://sirrlock.com").replace(/\/$/, "");
|
|
31
32
|
// ── Fetch with timeout ────────────────────────────────────────────────────────
|
|
32
33
|
async function fetchWithTimeout(url, options = {}, ms = 10000) {
|
|
33
34
|
const controller = new AbortController();
|
|
@@ -54,8 +55,9 @@ async function fetchWithTimeout(url, options = {}, ms = 10000) {
|
|
|
54
55
|
const STATUS_HINTS = {
|
|
55
56
|
401: "Check that SIRR_TOKEN matches SIRR_MASTER_KEY on the server. See sirr.dev/errors#401",
|
|
56
57
|
402: "Free tier limit reached. See sirr.dev/errors#402",
|
|
57
|
-
403: "This
|
|
58
|
-
404: "
|
|
58
|
+
403: "This token does not have permission for this operation. See sirr.dev/errors#403",
|
|
59
|
+
404: "Resource not found, expired, or burned. See sirr.dev/errors#404",
|
|
60
|
+
409: "Conflict — a secret with that key already exists in this org. Use patch_secret to update it. See sirr.dev/errors#409",
|
|
59
61
|
500: "Server-side error. See sirr.dev/errors#500",
|
|
60
62
|
};
|
|
61
63
|
function throwSirrError(status, json) {
|
|
@@ -72,49 +74,79 @@ async function sirrRequest(method, path, body) {
|
|
|
72
74
|
},
|
|
73
75
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
74
76
|
});
|
|
75
|
-
const json = (await res.json());
|
|
76
77
|
if (!res.ok) {
|
|
78
|
+
let json = {};
|
|
79
|
+
try {
|
|
80
|
+
json = (await res.json());
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
84
|
+
}
|
|
77
85
|
throwSirrError(res.status, json);
|
|
78
86
|
}
|
|
79
|
-
return json;
|
|
87
|
+
return (await res.json());
|
|
80
88
|
}
|
|
81
89
|
const helpers_1 = require("./helpers");
|
|
82
90
|
// ── Tool definitions ──────────────────────────────────────────────────────────
|
|
83
91
|
const TOOLS = [
|
|
84
92
|
{
|
|
85
93
|
name: "get_secret",
|
|
86
|
-
description: "Retrieve a secret from
|
|
87
|
-
"
|
|
94
|
+
description: "Retrieve a secret from Sirr. " +
|
|
95
|
+
"Two modes: (1) Public dead drop — provide 'id' (hex64 returned by push_secret). " +
|
|
96
|
+
"Fetches from GET /secrets/{id}. The read counter is incremented — if max_reads=1 the secret burns after this call. " +
|
|
97
|
+
"(2) Org-scoped — provide 'key' and 'org' to fetch a named secret from an organization's vault. " +
|
|
88
98
|
"Returns null if the secret does not exist, has expired, or has been burned. " +
|
|
89
|
-
"Accepts bare key names, 'sirr:KEYNAME' references, or 'KEYNAME#id' format. " +
|
|
90
99
|
"IMPORTANT: Do not store, log, memorize, or repeat the returned secret value beyond its immediate use. " +
|
|
91
100
|
"Treat it as ephemeral — use it once for its intended purpose and discard it.",
|
|
92
101
|
inputSchema: {
|
|
93
102
|
type: "object",
|
|
94
103
|
properties: {
|
|
104
|
+
id: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Public secret ID (hex64) returned by push_secret. Use for public dead-drop retrieval.",
|
|
107
|
+
},
|
|
95
108
|
key: {
|
|
96
109
|
type: "string",
|
|
97
|
-
description: "Secret key name. Accepts 'sirr:KEYNAME', 'KEYNAME#id', or bare key name.",
|
|
110
|
+
description: "Secret key name for org-scoped retrieval. Accepts 'sirr:KEYNAME', 'KEYNAME#id', or bare key name.",
|
|
111
|
+
},
|
|
112
|
+
org: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "Organization ID for org-scoped retrieval. Required when using 'key'.",
|
|
98
115
|
},
|
|
99
116
|
},
|
|
100
|
-
required: ["key"],
|
|
101
117
|
},
|
|
102
118
|
},
|
|
103
119
|
{
|
|
104
|
-
name: "
|
|
105
|
-
description: "
|
|
106
|
-
"Use
|
|
107
|
-
"
|
|
120
|
+
name: "check_secret",
|
|
121
|
+
description: "Check whether a secret exists and inspect its metadata — WITHOUT consuming a read. " +
|
|
122
|
+
"Use this to verify a secret is still available before fetching it, or to inspect read counts and expiry. " +
|
|
123
|
+
"Returns status (active/sealed), reads used/remaining, and expiry. " +
|
|
124
|
+
"A 'sealed' secret has exhausted its max_reads; it still exists but cannot be read.",
|
|
108
125
|
inputSchema: {
|
|
109
126
|
type: "object",
|
|
110
127
|
properties: {
|
|
111
128
|
key: {
|
|
112
129
|
type: "string",
|
|
113
|
-
description: "
|
|
130
|
+
description: "Secret key name. Accepts 'sirr:KEYNAME', 'KEYNAME#id', or bare key name.",
|
|
114
131
|
},
|
|
132
|
+
},
|
|
133
|
+
required: ["key"],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "push_secret",
|
|
138
|
+
description: "Push a secret as an anonymous public dead drop — no key, no org required. " +
|
|
139
|
+
"Returns an opaque ID and a one-time URL. Share the URL; the recipient opens it once and it burns. " +
|
|
140
|
+
"Optionally set a TTL (seconds) and/or max read limit. " +
|
|
141
|
+
"Use max_reads=1 (default) for one-time credentials that burn after first access. " +
|
|
142
|
+
"Use ttl_seconds to add a time-based expiry. " +
|
|
143
|
+
"IMPORTANT: Do not store, log, or repeat the pushed value after the call.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
115
147
|
value: {
|
|
116
148
|
type: "string",
|
|
117
|
-
description: "Secret value.",
|
|
149
|
+
description: "Secret value to push.",
|
|
118
150
|
},
|
|
119
151
|
ttl_seconds: {
|
|
120
152
|
type: "number",
|
|
@@ -122,10 +154,35 @@ const TOOLS = [
|
|
|
122
154
|
},
|
|
123
155
|
max_reads: {
|
|
124
156
|
type: "number",
|
|
125
|
-
description: "Optional maximum read count.
|
|
157
|
+
description: "Optional maximum read count. Default: 1 (burn after first read).",
|
|
126
158
|
},
|
|
127
159
|
},
|
|
128
|
-
required: ["
|
|
160
|
+
required: ["value"],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "set_secret",
|
|
165
|
+
description: "Store a named secret in an organization's vault (org-scoped). " +
|
|
166
|
+
"Requires an org ID and a key name. Returns the key and its internal ID. " +
|
|
167
|
+
"Returns a 409 Conflict error if a secret with that key already exists — use patch_secret to update it. " +
|
|
168
|
+
"Use push_secret instead if you want an anonymous, keyless dead drop.",
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
org: {
|
|
173
|
+
type: "string",
|
|
174
|
+
description: "Organization ID to store the secret in.",
|
|
175
|
+
},
|
|
176
|
+
key: {
|
|
177
|
+
type: "string",
|
|
178
|
+
description: "Key name to store the secret under.",
|
|
179
|
+
},
|
|
180
|
+
value: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Secret value.",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
required: ["org", "key", "value"],
|
|
129
186
|
},
|
|
130
187
|
},
|
|
131
188
|
{
|
|
@@ -151,6 +208,32 @@ const TOOLS = [
|
|
|
151
208
|
required: ["key"],
|
|
152
209
|
},
|
|
153
210
|
},
|
|
211
|
+
{
|
|
212
|
+
name: "patch_secret",
|
|
213
|
+
description: "Update an existing secret's value, TTL, or max read count. All fields are optional — only provided fields are changed.",
|
|
214
|
+
inputSchema: {
|
|
215
|
+
type: "object",
|
|
216
|
+
properties: {
|
|
217
|
+
key: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: "Key name to update.",
|
|
220
|
+
},
|
|
221
|
+
value: {
|
|
222
|
+
type: "string",
|
|
223
|
+
description: "New secret value.",
|
|
224
|
+
},
|
|
225
|
+
ttl_seconds: {
|
|
226
|
+
type: "number",
|
|
227
|
+
description: "New TTL in seconds from now.",
|
|
228
|
+
},
|
|
229
|
+
max_reads: {
|
|
230
|
+
type: "number",
|
|
231
|
+
description: "New max read count.",
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
required: ["key"],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
154
237
|
{
|
|
155
238
|
name: "prune_secrets",
|
|
156
239
|
description: "Trigger an immediate sweep of all expired secrets on the server. " +
|
|
@@ -168,6 +251,24 @@ const TOOLS = [
|
|
|
168
251
|
properties: {},
|
|
169
252
|
},
|
|
170
253
|
},
|
|
254
|
+
{
|
|
255
|
+
name: "share_secret",
|
|
256
|
+
description: "Share a sensitive value via a secure burn-after-read link hosted on sirrlock.com. " +
|
|
257
|
+
"No account or token required. " +
|
|
258
|
+
"The link expires after 24 hours or after the recipient opens it once — whichever comes first. " +
|
|
259
|
+
"Returns a URL to send to the recipient. " +
|
|
260
|
+
"IMPORTANT: Do not store or repeat the secret value. Use the returned URL only.",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
value: {
|
|
265
|
+
type: "string",
|
|
266
|
+
description: "The sensitive value to share (password, token, link, etc.).",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
required: ["value"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
171
272
|
{
|
|
172
273
|
name: "sirr_audit",
|
|
173
274
|
description: "Query the Sirr audit log. Returns recent events like secret creates, reads, deletes. " +
|
|
@@ -179,13 +280,17 @@ const TOOLS = [
|
|
|
179
280
|
type: "number",
|
|
180
281
|
description: "Only return events after this Unix timestamp.",
|
|
181
282
|
},
|
|
283
|
+
until: {
|
|
284
|
+
type: "number",
|
|
285
|
+
description: "Only return events before this Unix timestamp.",
|
|
286
|
+
},
|
|
182
287
|
action: {
|
|
183
288
|
type: "string",
|
|
184
289
|
description: "Filter by action type (e.g. secret.create, secret.read, key.create).",
|
|
185
290
|
},
|
|
186
291
|
limit: {
|
|
187
292
|
type: "number",
|
|
188
|
-
description: "Maximum events to return (default:
|
|
293
|
+
description: "Maximum events to return (default: 100, max: 1000).",
|
|
189
294
|
},
|
|
190
295
|
},
|
|
191
296
|
},
|
|
@@ -233,50 +338,188 @@ const TOOLS = [
|
|
|
233
338
|
},
|
|
234
339
|
},
|
|
235
340
|
{
|
|
236
|
-
name: "
|
|
237
|
-
description: "
|
|
238
|
-
|
|
239
|
-
|
|
341
|
+
name: "sirr_key_list",
|
|
342
|
+
description: "List all API keys for the current principal. Key values are never returned.",
|
|
343
|
+
inputSchema: {
|
|
344
|
+
type: "object",
|
|
345
|
+
properties: {},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "sirr_me",
|
|
350
|
+
description: "Get the current authenticated user/org profile from the Sirr server. " +
|
|
351
|
+
"Returns account details and current plan information.",
|
|
352
|
+
inputSchema: {
|
|
353
|
+
type: "object",
|
|
354
|
+
properties: {},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: "sirr_update_me",
|
|
359
|
+
description: "Update the current principal's metadata on the Sirr server.",
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: "object",
|
|
362
|
+
properties: {
|
|
363
|
+
metadata: {
|
|
364
|
+
type: "object",
|
|
365
|
+
additionalProperties: { type: "string" },
|
|
366
|
+
description: "Key/value metadata to set on the principal (replaces existing metadata).",
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
required: ["metadata"],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "sirr_create_key",
|
|
374
|
+
description: "Create a new API key for the current principal via /me/keys. " +
|
|
375
|
+
"The raw key is returned once — save it immediately.",
|
|
240
376
|
inputSchema: {
|
|
241
377
|
type: "object",
|
|
242
378
|
properties: {
|
|
243
|
-
|
|
379
|
+
name: {
|
|
244
380
|
type: "string",
|
|
245
|
-
description: "Human-readable
|
|
381
|
+
description: "Human-readable name for the key.",
|
|
246
382
|
},
|
|
247
|
-
|
|
248
|
-
type: "
|
|
249
|
-
|
|
250
|
-
|
|
383
|
+
valid_for_seconds: {
|
|
384
|
+
type: "number",
|
|
385
|
+
description: "How long the key is valid, in seconds (default: 1 year).",
|
|
386
|
+
},
|
|
387
|
+
valid_before: {
|
|
388
|
+
type: "number",
|
|
389
|
+
description: "Explicit expiry as a Unix timestamp (alternative to valid_for_seconds).",
|
|
251
390
|
},
|
|
252
|
-
|
|
391
|
+
},
|
|
392
|
+
required: ["name"],
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "sirr_delete_key",
|
|
397
|
+
description: "Revoke an API key belonging to the current principal.",
|
|
398
|
+
inputSchema: {
|
|
399
|
+
type: "object",
|
|
400
|
+
properties: {
|
|
401
|
+
keyId: {
|
|
253
402
|
type: "string",
|
|
254
|
-
description: "
|
|
403
|
+
description: "API key ID to delete.",
|
|
255
404
|
},
|
|
256
405
|
},
|
|
257
|
-
required: ["
|
|
406
|
+
required: ["keyId"],
|
|
258
407
|
},
|
|
259
408
|
},
|
|
409
|
+
// ── Org management ──────────────────────────────────────────────────────────
|
|
260
410
|
{
|
|
261
|
-
name: "
|
|
262
|
-
description: "
|
|
411
|
+
name: "sirr_org_create",
|
|
412
|
+
description: "Create a new organization. Requires master key or sirr_admin permission.",
|
|
263
413
|
inputSchema: {
|
|
264
414
|
type: "object",
|
|
265
|
-
properties: {
|
|
415
|
+
properties: {
|
|
416
|
+
name: { type: "string", description: "Organization name (1–128 chars)." },
|
|
417
|
+
metadata: {
|
|
418
|
+
type: "object",
|
|
419
|
+
additionalProperties: { type: "string" },
|
|
420
|
+
description: "Optional key/value metadata.",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
required: ["name"],
|
|
266
424
|
},
|
|
267
425
|
},
|
|
268
426
|
{
|
|
269
|
-
name: "
|
|
270
|
-
description: "
|
|
427
|
+
name: "sirr_org_list",
|
|
428
|
+
description: "List all organizations. Requires master key.",
|
|
429
|
+
inputSchema: { type: "object", properties: {} },
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: "sirr_org_delete",
|
|
433
|
+
description: "Delete an organization by ID. Org must have no principals. Requires master key.",
|
|
271
434
|
inputSchema: {
|
|
272
435
|
type: "object",
|
|
273
436
|
properties: {
|
|
274
|
-
|
|
437
|
+
org_id: { type: "string", description: "Organization ID to delete." },
|
|
438
|
+
},
|
|
439
|
+
required: ["org_id"],
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
// ── Principal management ─────────────────────────────────────────────────────
|
|
443
|
+
{
|
|
444
|
+
name: "sirr_principal_create",
|
|
445
|
+
description: "Create a principal (user/service) in an organization.",
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: "object",
|
|
448
|
+
properties: {
|
|
449
|
+
org_id: { type: "string", description: "Organization ID." },
|
|
450
|
+
name: { type: "string", description: "Principal name (1–128 chars)." },
|
|
451
|
+
role: { type: "string", description: "Role name (must exist in the org or be a built-in role)." },
|
|
452
|
+
metadata: {
|
|
453
|
+
type: "object",
|
|
454
|
+
additionalProperties: { type: "string" },
|
|
455
|
+
description: "Optional key/value metadata.",
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
required: ["org_id", "name", "role"],
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "sirr_principal_list",
|
|
463
|
+
description: "List all principals in an organization.",
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: {
|
|
467
|
+
org_id: { type: "string", description: "Organization ID." },
|
|
468
|
+
},
|
|
469
|
+
required: ["org_id"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "sirr_principal_delete",
|
|
474
|
+
description: "Delete a principal from an organization. Principal must have no active keys.",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
type: "object",
|
|
477
|
+
properties: {
|
|
478
|
+
org_id: { type: "string", description: "Organization ID." },
|
|
479
|
+
principal_id: { type: "string", description: "Principal ID to delete." },
|
|
480
|
+
},
|
|
481
|
+
required: ["org_id", "principal_id"],
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
// ── Role management ──────────────────────────────────────────────────────────
|
|
485
|
+
{
|
|
486
|
+
name: "sirr_role_create",
|
|
487
|
+
description: "Create a custom role in an organization. " +
|
|
488
|
+
"Permissions are a letter string: C=create, R=read, P=patch, D=delete, L=list, M=manage, A=admin.",
|
|
489
|
+
inputSchema: {
|
|
490
|
+
type: "object",
|
|
491
|
+
properties: {
|
|
492
|
+
org_id: { type: "string", description: "Organization ID." },
|
|
493
|
+
name: { type: "string", description: "Role name (1–64 chars)." },
|
|
494
|
+
permissions: {
|
|
275
495
|
type: "string",
|
|
276
|
-
description: "
|
|
496
|
+
description: "Permission letters, e.g. 'CRL' for create+read+list.",
|
|
277
497
|
},
|
|
278
498
|
},
|
|
279
|
-
required: ["
|
|
499
|
+
required: ["org_id", "name", "permissions"],
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: "sirr_role_list",
|
|
504
|
+
description: "List all roles in an organization (built-in and custom).",
|
|
505
|
+
inputSchema: {
|
|
506
|
+
type: "object",
|
|
507
|
+
properties: {
|
|
508
|
+
org_id: { type: "string", description: "Organization ID." },
|
|
509
|
+
},
|
|
510
|
+
required: ["org_id"],
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "sirr_role_delete",
|
|
515
|
+
description: "Delete a custom role from an organization. Cannot delete built-in roles or roles in use.",
|
|
516
|
+
inputSchema: {
|
|
517
|
+
type: "object",
|
|
518
|
+
properties: {
|
|
519
|
+
org_id: { type: "string", description: "Organization ID." },
|
|
520
|
+
role_name: { type: "string", description: "Role name to delete." },
|
|
521
|
+
},
|
|
522
|
+
required: ["org_id", "role_name"],
|
|
280
523
|
},
|
|
281
524
|
},
|
|
282
525
|
];
|
|
@@ -290,21 +533,51 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
290
533
|
try {
|
|
291
534
|
switch (name) {
|
|
292
535
|
case "get_secret": {
|
|
293
|
-
const rawKey = args
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
536
|
+
const { id: rawId, key: rawKey, org: rawOrg } = args;
|
|
537
|
+
// Determine fetch path: public by ID, or org-scoped by key+org
|
|
538
|
+
let fetchPath;
|
|
539
|
+
let label;
|
|
540
|
+
if (rawId) {
|
|
541
|
+
fetchPath = (0, helpers_1.publicSecretsPath)(encodeURIComponent(rawId));
|
|
542
|
+
label = `ID '${rawId}'`;
|
|
543
|
+
}
|
|
544
|
+
else if (rawKey) {
|
|
545
|
+
const key = (0, helpers_1.parseKeyRef)(rawKey);
|
|
546
|
+
const org = rawOrg ?? process.env.SIRR_ORG;
|
|
547
|
+
if (!org) {
|
|
548
|
+
return {
|
|
549
|
+
content: [{ type: "text", text: "Error: 'org' is required when fetching by key name (or set SIRR_ORG env var)." }],
|
|
550
|
+
isError: true,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
fetchPath = (0, helpers_1.orgSecretsPath)(org, encodeURIComponent(key));
|
|
554
|
+
label = `key '${key}' in org '${org}'`;
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
return {
|
|
558
|
+
content: [{ type: "text", text: "Error: provide 'id' (public dead drop) or 'key'+'org' (org-scoped)." }],
|
|
559
|
+
isError: true,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}${fetchPath}`, { headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
563
|
+
if (res.status === 404 || res.status === 410) {
|
|
297
564
|
return {
|
|
298
565
|
content: [
|
|
299
566
|
{
|
|
300
567
|
type: "text",
|
|
301
|
-
text: `Secret
|
|
568
|
+
text: `Secret ${label} not found, expired, or already burned.`,
|
|
302
569
|
},
|
|
303
570
|
],
|
|
304
571
|
};
|
|
305
572
|
}
|
|
306
573
|
if (!res.ok) {
|
|
307
|
-
|
|
574
|
+
let json = {};
|
|
575
|
+
try {
|
|
576
|
+
json = (await res.json());
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
580
|
+
}
|
|
308
581
|
throwSirrError(res.status, json);
|
|
309
582
|
}
|
|
310
583
|
const data = (await res.json());
|
|
@@ -321,25 +594,82 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
321
594
|
],
|
|
322
595
|
};
|
|
323
596
|
}
|
|
597
|
+
case "check_secret": {
|
|
598
|
+
const rawKey = args.key;
|
|
599
|
+
const key = (0, helpers_1.parseKeyRef)(rawKey);
|
|
600
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}${(0, helpers_1.secretsPath)(encodeURIComponent(key))}`, { method: "HEAD", headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
601
|
+
if (res.status === 404) {
|
|
602
|
+
return { content: [{ type: "text", text: `Secret '${key}' not found or expired.` }] };
|
|
603
|
+
}
|
|
604
|
+
const status = res.headers.get("X-Sirr-Status") ?? (res.status === 410 ? "sealed" : "active");
|
|
605
|
+
const readCount = res.headers.get("X-Sirr-Read-Count") ?? "?";
|
|
606
|
+
const readsRemaining = res.headers.get("X-Sirr-Reads-Remaining") ?? "unlimited";
|
|
607
|
+
const expiresAt = res.headers.get("X-Sirr-Expires-At");
|
|
608
|
+
const expiry = expiresAt ? (0, helpers_1.formatTtl)(parseInt(expiresAt, 10)) : "no expiry";
|
|
609
|
+
if (status === "sealed") {
|
|
610
|
+
return { content: [{ type: "text", text: `Secret '${key}' is sealed — all reads exhausted.\n Reads used: ${readCount}\n Expires: ${expiry}` }] };
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
content: [{
|
|
614
|
+
type: "text",
|
|
615
|
+
text: `Secret '${key}' is active.\n Reads used: ${readCount}\n Reads remaining: ${readsRemaining}\n Expires: ${expiry}`,
|
|
616
|
+
}],
|
|
617
|
+
};
|
|
618
|
+
}
|
|
324
619
|
case "push_secret": {
|
|
325
|
-
const {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
max_reads
|
|
331
|
-
|
|
332
|
-
const
|
|
620
|
+
const { value: val, ttl_seconds, max_reads } = args;
|
|
621
|
+
const body = { value: val };
|
|
622
|
+
if (ttl_seconds != null)
|
|
623
|
+
body.ttl_seconds = ttl_seconds;
|
|
624
|
+
if (max_reads != null)
|
|
625
|
+
body.max_reads = max_reads;
|
|
626
|
+
const data = await sirrRequest("POST", (0, helpers_1.publicSecretsPath)(), body);
|
|
627
|
+
const url = `${SIRR_SERVER}/s/${data.id}`;
|
|
628
|
+
const parts = [`Secret pushed.`, `ID: ${data.id}`, `URL: ${url}`];
|
|
333
629
|
if (ttl_seconds)
|
|
334
630
|
parts.push(`Expires in ${(0, helpers_1.formatTtl)(Math.floor(Date.now() / 1000) + ttl_seconds)}.`);
|
|
335
631
|
if (max_reads)
|
|
336
632
|
parts.push(`Burns after ${max_reads} read(s).`);
|
|
337
633
|
return {
|
|
338
|
-
content: [{ type: "text", text: parts.join("
|
|
634
|
+
content: [{ type: "text", text: parts.join("\n") }],
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
case "set_secret": {
|
|
638
|
+
const { org, key, value: val } = args;
|
|
639
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}${(0, helpers_1.orgSecretsPath)(org)}`, {
|
|
640
|
+
method: "POST",
|
|
641
|
+
headers: {
|
|
642
|
+
Authorization: `Bearer ${SIRR_TOKEN}`,
|
|
643
|
+
"Content-Type": "application/json",
|
|
644
|
+
},
|
|
645
|
+
body: JSON.stringify({ key, value: val }),
|
|
646
|
+
});
|
|
647
|
+
if (res.status === 409) {
|
|
648
|
+
return {
|
|
649
|
+
content: [{
|
|
650
|
+
type: "text",
|
|
651
|
+
text: `Conflict: secret '${key}' already exists in org '${org}'. Use patch_secret to update it.`,
|
|
652
|
+
}],
|
|
653
|
+
isError: true,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
if (!res.ok) {
|
|
657
|
+
let json = {};
|
|
658
|
+
try {
|
|
659
|
+
json = (await res.json());
|
|
660
|
+
}
|
|
661
|
+
catch {
|
|
662
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
663
|
+
}
|
|
664
|
+
throwSirrError(res.status, json);
|
|
665
|
+
}
|
|
666
|
+
const data = (await res.json());
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: "text", text: `Secret '${data.key}' stored in org '${org}'.\nID: ${data.id}` }],
|
|
339
669
|
};
|
|
340
670
|
}
|
|
341
671
|
case "list_secrets": {
|
|
342
|
-
const data = await sirrRequest("GET",
|
|
672
|
+
const data = await sirrRequest("GET", (0, helpers_1.secretsPath)());
|
|
343
673
|
if (data.secrets.length === 0) {
|
|
344
674
|
return {
|
|
345
675
|
content: [{ type: "text", text: "No active secrets." }],
|
|
@@ -363,7 +693,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
363
693
|
}
|
|
364
694
|
case "delete_secret": {
|
|
365
695
|
const { key } = args;
|
|
366
|
-
const res = await fetchWithTimeout(`${SIRR_SERVER}
|
|
696
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}${(0, helpers_1.secretsPath)(encodeURIComponent(key))}`, {
|
|
367
697
|
method: "DELETE",
|
|
368
698
|
headers: { Authorization: `Bearer ${SIRR_TOKEN}` },
|
|
369
699
|
});
|
|
@@ -375,7 +705,13 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
375
705
|
};
|
|
376
706
|
}
|
|
377
707
|
if (!res.ok) {
|
|
378
|
-
|
|
708
|
+
let json = {};
|
|
709
|
+
try {
|
|
710
|
+
json = (await res.json());
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
714
|
+
}
|
|
379
715
|
throwSirrError(res.status, json);
|
|
380
716
|
}
|
|
381
717
|
return {
|
|
@@ -387,8 +723,23 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
387
723
|
],
|
|
388
724
|
};
|
|
389
725
|
}
|
|
726
|
+
case "patch_secret": {
|
|
727
|
+
const { key, value: val, ttl_seconds, max_reads } = args;
|
|
728
|
+
const body = {};
|
|
729
|
+
if (val !== undefined)
|
|
730
|
+
body.value = val;
|
|
731
|
+
if (ttl_seconds !== undefined)
|
|
732
|
+
body.ttl_seconds = ttl_seconds;
|
|
733
|
+
if (max_reads !== undefined)
|
|
734
|
+
body.max_reads = max_reads;
|
|
735
|
+
const data = await sirrRequest("PATCH", (0, helpers_1.secretsPath)(encodeURIComponent(key)), body);
|
|
736
|
+
const parts = [`Secret '${key}' updated.`, `Expires: ${(0, helpers_1.formatTtl)(data.expires_at)}`];
|
|
737
|
+
if (data.max_reads != null)
|
|
738
|
+
parts.push(`Max reads: ${data.max_reads} (${data.read_count} used)`);
|
|
739
|
+
return { content: [{ type: "text", text: parts.join("\n ") }] };
|
|
740
|
+
}
|
|
390
741
|
case "prune_secrets": {
|
|
391
|
-
const data = await sirrRequest("POST",
|
|
742
|
+
const data = await sirrRequest("POST", (0, helpers_1.prunePath)());
|
|
392
743
|
return {
|
|
393
744
|
content: [
|
|
394
745
|
{
|
|
@@ -415,16 +766,18 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
415
766
|
};
|
|
416
767
|
}
|
|
417
768
|
case "sirr_audit": {
|
|
418
|
-
const { since, action, limit } = args;
|
|
769
|
+
const { since, until, action, limit } = args;
|
|
419
770
|
const params = new URLSearchParams();
|
|
420
771
|
if (since != null)
|
|
421
772
|
params.set("since", String(since));
|
|
773
|
+
if (until != null)
|
|
774
|
+
params.set("until", String(until));
|
|
422
775
|
if (action != null)
|
|
423
776
|
params.set("action", action);
|
|
424
777
|
if (limit != null)
|
|
425
778
|
params.set("limit", String(limit));
|
|
426
779
|
const qs = params.toString();
|
|
427
|
-
const data = await sirrRequest("GET",
|
|
780
|
+
const data = await sirrRequest("GET", `${(0, helpers_1.auditPath)()}${qs ? `?${qs}` : ""}`);
|
|
428
781
|
if (data.events.length === 0) {
|
|
429
782
|
return {
|
|
430
783
|
content: [{ type: "text", text: "No audit events found." }],
|
|
@@ -445,7 +798,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
445
798
|
const body = { url };
|
|
446
799
|
if (events)
|
|
447
800
|
body.events = events;
|
|
448
|
-
const data = await sirrRequest("POST",
|
|
801
|
+
const data = await sirrRequest("POST", (0, helpers_1.webhooksPath)(), body);
|
|
449
802
|
return {
|
|
450
803
|
content: [
|
|
451
804
|
{
|
|
@@ -456,7 +809,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
456
809
|
};
|
|
457
810
|
}
|
|
458
811
|
case "sirr_webhook_list": {
|
|
459
|
-
const data = await sirrRequest("GET",
|
|
812
|
+
const data = await sirrRequest("GET", (0, helpers_1.webhooksPath)());
|
|
460
813
|
if (data.webhooks.length === 0) {
|
|
461
814
|
return {
|
|
462
815
|
content: [{ type: "text", text: "No webhooks registered." }],
|
|
@@ -474,54 +827,235 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
474
827
|
}
|
|
475
828
|
case "sirr_webhook_delete": {
|
|
476
829
|
const { id } = args;
|
|
477
|
-
await
|
|
830
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}${(0, helpers_1.webhooksPath)(encodeURIComponent(id))}`, { method: "DELETE", headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
831
|
+
if (res.status === 404) {
|
|
832
|
+
return { content: [{ type: "text", text: `Webhook '${id}' not found.` }] };
|
|
833
|
+
}
|
|
834
|
+
if (!res.ok) {
|
|
835
|
+
let json = {};
|
|
836
|
+
try {
|
|
837
|
+
json = (await res.json());
|
|
838
|
+
}
|
|
839
|
+
catch {
|
|
840
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
841
|
+
}
|
|
842
|
+
throwSirrError(res.status, json);
|
|
843
|
+
}
|
|
844
|
+
return { content: [{ type: "text", text: `Webhook '${id}' deleted.` }] };
|
|
845
|
+
}
|
|
846
|
+
case "sirr_key_list": {
|
|
847
|
+
const me = await sirrRequest("GET", "/me");
|
|
848
|
+
if (!me.keys || me.keys.length === 0) {
|
|
849
|
+
return {
|
|
850
|
+
content: [{ type: "text", text: "No API keys." }],
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
const lines = me.keys.map((k) => `• ${k.id} — ${k.name} (expires ${new Date(k.valid_before * 1000).toISOString()})`);
|
|
478
854
|
return {
|
|
479
855
|
content: [
|
|
480
|
-
{
|
|
856
|
+
{
|
|
857
|
+
type: "text",
|
|
858
|
+
text: `${me.keys.length} API key(s):\n${lines.join("\n")}`,
|
|
859
|
+
},
|
|
481
860
|
],
|
|
482
861
|
};
|
|
483
862
|
}
|
|
484
|
-
case "
|
|
485
|
-
const
|
|
486
|
-
const body = { label, permissions };
|
|
487
|
-
if (prefix)
|
|
488
|
-
body.prefix = prefix;
|
|
489
|
-
const data = await sirrRequest("POST", "/keys", body);
|
|
863
|
+
case "sirr_me": {
|
|
864
|
+
const data = await sirrRequest("GET", "/me");
|
|
490
865
|
return {
|
|
491
866
|
content: [
|
|
492
867
|
{
|
|
493
868
|
type: "text",
|
|
494
|
-
text:
|
|
869
|
+
text: JSON.stringify(data, null, 2),
|
|
495
870
|
},
|
|
496
871
|
],
|
|
497
872
|
};
|
|
498
873
|
}
|
|
499
|
-
case "
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
return {
|
|
503
|
-
content: [{ type: "text", text: "No API keys." }],
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
const lines = data.keys.map((k) => `• ${k.id} — ${k.label} [${k.permissions.join(",")}] prefix=${k.prefix ?? "*"}`);
|
|
874
|
+
case "sirr_update_me": {
|
|
875
|
+
const { metadata } = args;
|
|
876
|
+
const data = await sirrRequest("PATCH", "/me", { metadata });
|
|
507
877
|
return {
|
|
508
878
|
content: [
|
|
509
879
|
{
|
|
510
880
|
type: "text",
|
|
511
|
-
text:
|
|
881
|
+
text: `Profile updated.\n${JSON.stringify(data, null, 2)}`,
|
|
512
882
|
},
|
|
513
883
|
],
|
|
514
884
|
};
|
|
515
885
|
}
|
|
516
|
-
case "
|
|
517
|
-
const {
|
|
518
|
-
|
|
886
|
+
case "sirr_create_key": {
|
|
887
|
+
const { name: keyName, valid_for_seconds, valid_before } = args;
|
|
888
|
+
const body = { name: keyName };
|
|
889
|
+
if (valid_for_seconds != null)
|
|
890
|
+
body.valid_for_seconds = valid_for_seconds;
|
|
891
|
+
if (valid_before != null)
|
|
892
|
+
body.valid_before = valid_before;
|
|
893
|
+
const data = await sirrRequest("POST", "/me/keys", body);
|
|
519
894
|
return {
|
|
520
895
|
content: [
|
|
521
|
-
{
|
|
896
|
+
{
|
|
897
|
+
type: "text",
|
|
898
|
+
text: `API key created.\n ID: ${data.id}\n Name: ${data.name}\n Key: ${data.key}\n Valid until: ${new Date(data.valid_before * 1000).toISOString()}\n (Save the key — it won't be shown again)`,
|
|
899
|
+
},
|
|
522
900
|
],
|
|
523
901
|
};
|
|
524
902
|
}
|
|
903
|
+
case "sirr_delete_key": {
|
|
904
|
+
const { keyId } = args;
|
|
905
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}/me/keys/${encodeURIComponent(keyId)}`, { method: "DELETE", headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
906
|
+
if (res.status === 404) {
|
|
907
|
+
return { content: [{ type: "text", text: `API key '${keyId}' not found.` }] };
|
|
908
|
+
}
|
|
909
|
+
if (!res.ok) {
|
|
910
|
+
let json = {};
|
|
911
|
+
try {
|
|
912
|
+
json = (await res.json());
|
|
913
|
+
}
|
|
914
|
+
catch {
|
|
915
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
916
|
+
}
|
|
917
|
+
throwSirrError(res.status, json);
|
|
918
|
+
}
|
|
919
|
+
return { content: [{ type: "text", text: `API key '${keyId}' deleted.` }] };
|
|
920
|
+
}
|
|
921
|
+
// ── Org management ────────────────────────────────────────────────────────
|
|
922
|
+
case "sirr_org_create": {
|
|
923
|
+
const { name: orgName, metadata } = args;
|
|
924
|
+
const data = await sirrRequest("POST", "/orgs", { name: orgName, metadata: metadata ?? {} });
|
|
925
|
+
return {
|
|
926
|
+
content: [{ type: "text", text: `Org created.\n ID: ${data.id}\n Name: ${data.name}` }],
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
case "sirr_org_list": {
|
|
930
|
+
const data = await sirrRequest("GET", "/orgs");
|
|
931
|
+
if (data.orgs.length === 0) {
|
|
932
|
+
return { content: [{ type: "text", text: "No organizations." }] };
|
|
933
|
+
}
|
|
934
|
+
const lines = data.orgs.map((o) => `• ${o.id} — ${o.name}`);
|
|
935
|
+
return {
|
|
936
|
+
content: [{ type: "text", text: `${data.orgs.length} org(s):\n${lines.join("\n")}` }],
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
case "sirr_org_delete": {
|
|
940
|
+
const { org_id } = args;
|
|
941
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}/orgs/${encodeURIComponent(org_id)}`, { method: "DELETE", headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
942
|
+
if (res.status === 404) {
|
|
943
|
+
return { content: [{ type: "text", text: `Org '${org_id}' not found.` }] };
|
|
944
|
+
}
|
|
945
|
+
if (!res.ok) {
|
|
946
|
+
let json = {};
|
|
947
|
+
try {
|
|
948
|
+
json = (await res.json());
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
952
|
+
}
|
|
953
|
+
throwSirrError(res.status, json);
|
|
954
|
+
}
|
|
955
|
+
return { content: [{ type: "text", text: `Org '${org_id}' deleted.` }] };
|
|
956
|
+
}
|
|
957
|
+
// ── Principal management ──────────────────────────────────────────────────
|
|
958
|
+
case "sirr_principal_create": {
|
|
959
|
+
const { org_id, name: pName, role, metadata } = args;
|
|
960
|
+
const data = await sirrRequest("POST", `/orgs/${encodeURIComponent(org_id)}/principals`, { name: pName, role, metadata: metadata ?? {} });
|
|
961
|
+
return {
|
|
962
|
+
content: [{ type: "text", text: `Principal created.\n ID: ${data.id}\n Name: ${data.name}\n Role: ${data.role}` }],
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
case "sirr_principal_list": {
|
|
966
|
+
const { org_id } = args;
|
|
967
|
+
const data = await sirrRequest("GET", `/orgs/${encodeURIComponent(org_id)}/principals`);
|
|
968
|
+
if (data.principals.length === 0) {
|
|
969
|
+
return { content: [{ type: "text", text: "No principals." }] };
|
|
970
|
+
}
|
|
971
|
+
const lines = data.principals.map((p) => `• ${p.id} — ${p.name} [${p.role}]`);
|
|
972
|
+
return {
|
|
973
|
+
content: [{ type: "text", text: `${data.principals.length} principal(s):\n${lines.join("\n")}` }],
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
case "sirr_principal_delete": {
|
|
977
|
+
const { org_id, principal_id } = args;
|
|
978
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}/orgs/${encodeURIComponent(org_id)}/principals/${encodeURIComponent(principal_id)}`, { method: "DELETE", headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
979
|
+
if (res.status === 404) {
|
|
980
|
+
return { content: [{ type: "text", text: `Principal '${principal_id}' not found.` }] };
|
|
981
|
+
}
|
|
982
|
+
if (!res.ok) {
|
|
983
|
+
let json = {};
|
|
984
|
+
try {
|
|
985
|
+
json = (await res.json());
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
989
|
+
}
|
|
990
|
+
throwSirrError(res.status, json);
|
|
991
|
+
}
|
|
992
|
+
return { content: [{ type: "text", text: `Principal '${principal_id}' deleted.` }] };
|
|
993
|
+
}
|
|
994
|
+
// ── Role management ───────────────────────────────────────────────────────
|
|
995
|
+
case "sirr_role_create": {
|
|
996
|
+
const { org_id, name: roleName, permissions } = args;
|
|
997
|
+
const data = await sirrRequest("POST", `/orgs/${encodeURIComponent(org_id)}/roles`, { name: roleName, permissions });
|
|
998
|
+
return {
|
|
999
|
+
content: [{ type: "text", text: `Role '${data.name}' created with permissions '${data.permissions}'.` }],
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
case "sirr_role_list": {
|
|
1003
|
+
const { org_id } = args;
|
|
1004
|
+
const data = await sirrRequest("GET", `/orgs/${encodeURIComponent(org_id)}/roles`);
|
|
1005
|
+
if (data.roles.length === 0) {
|
|
1006
|
+
return { content: [{ type: "text", text: "No roles." }] };
|
|
1007
|
+
}
|
|
1008
|
+
const lines = data.roles.map((r) => `• ${r.name} [${r.permissions}]${r.built_in ? " (built-in)" : ""}`);
|
|
1009
|
+
return {
|
|
1010
|
+
content: [{ type: "text", text: `${data.roles.length} role(s):\n${lines.join("\n")}` }],
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
case "sirr_role_delete": {
|
|
1014
|
+
const { org_id, role_name } = args;
|
|
1015
|
+
const res = await fetchWithTimeout(`${SIRR_SERVER}/orgs/${encodeURIComponent(org_id)}/roles/${encodeURIComponent(role_name)}`, { method: "DELETE", headers: { Authorization: `Bearer ${SIRR_TOKEN}` } });
|
|
1016
|
+
if (res.status === 404) {
|
|
1017
|
+
return { content: [{ type: "text", text: `Role '${role_name}' not found.` }] };
|
|
1018
|
+
}
|
|
1019
|
+
if (!res.ok) {
|
|
1020
|
+
let json = {};
|
|
1021
|
+
try {
|
|
1022
|
+
json = (await res.json());
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
1026
|
+
}
|
|
1027
|
+
throwSirrError(res.status, json);
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
content: [{ type: "text", text: `Role '${role_name}' deleted.` }],
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
case "share_secret": {
|
|
1034
|
+
const { value: shareValue } = args;
|
|
1035
|
+
const res = await fetchWithTimeout(`${SIRRLOCK_URL}/api/public/secret`, {
|
|
1036
|
+
method: "POST",
|
|
1037
|
+
headers: { "Content-Type": "application/json" },
|
|
1038
|
+
body: JSON.stringify({ value: shareValue }),
|
|
1039
|
+
});
|
|
1040
|
+
if (!res.ok) {
|
|
1041
|
+
let json = {};
|
|
1042
|
+
try {
|
|
1043
|
+
json = (await res.json());
|
|
1044
|
+
}
|
|
1045
|
+
catch {
|
|
1046
|
+
json = { error: await res.text().catch(() => "unknown") };
|
|
1047
|
+
}
|
|
1048
|
+
throwSirrError(res.status, json);
|
|
1049
|
+
}
|
|
1050
|
+
const data = (await res.json());
|
|
1051
|
+
const shareUrl = `${SIRRLOCK_URL}/s/${data.key}`;
|
|
1052
|
+
return {
|
|
1053
|
+
content: [{
|
|
1054
|
+
type: "text",
|
|
1055
|
+
text: `${shareUrl}\n\n[This link burns after one read or after 24 hours. Do not store or repeat the original value.]`,
|
|
1056
|
+
}],
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
525
1059
|
default:
|
|
526
1060
|
return {
|
|
527
1061
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|