@sanctuary-framework/mcp-server 1.1.1 → 1.1.3

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/index.cjs CHANGED
@@ -21,7 +21,7 @@ var url = require('url');
21
21
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
22
22
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
23
23
  var sse_js = require('@modelcontextprotocol/sdk/client/sse.js');
24
- require('readline/promises');
24
+ var promises$1 = require('readline/promises');
25
25
 
26
26
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
27
27
  var __defProp = Object.defineProperty;
@@ -11141,6 +11141,20 @@ async function handleRequest(deps, req, res) {
11141
11141
  const url = new URL(req.url ?? "/", `http://${host}`);
11142
11142
  const method = (req.method ?? "GET").toUpperCase();
11143
11143
  const path = url.pathname;
11144
+ if (deps.v11Bindings) {
11145
+ const handled = await dispatchV11Request(
11146
+ {
11147
+ bindings: deps.v11Bindings,
11148
+ ...deps.authToken !== void 0 ? { authToken: deps.authToken } : {},
11149
+ loopbackAutoAuth: deps.loopbackAutoAuth ?? false
11150
+ },
11151
+ req,
11152
+ res,
11153
+ url,
11154
+ method
11155
+ );
11156
+ if (handled) return true;
11157
+ }
11144
11158
  if (!isAuthorized(deps, req, url)) {
11145
11159
  writeJSON(res, 401, { error: "unauthorized" });
11146
11160
  return true;
@@ -12770,6 +12784,47 @@ function handleDashboardV11Route(deps, req, res) {
12770
12784
  return true;
12771
12785
  }
12772
12786
 
12787
+ // src/dashboard/v1_1/dispatch.ts
12788
+ async function dispatchV11Request(inputs, req, res, url, method) {
12789
+ const { bindings, authToken, loopbackAutoAuth } = inputs;
12790
+ if (method === "GET" && (url.pathname === "/v1.1" || url.pathname === "/v1.1/")) {
12791
+ return handleDashboardV11Route(
12792
+ {
12793
+ identityId: bindings.identityId,
12794
+ fortressId: bindings.fortressId,
12795
+ ...authToken !== void 0 ? { authToken } : {}
12796
+ },
12797
+ req,
12798
+ res
12799
+ );
12800
+ }
12801
+ if (url.pathname.startsWith("/api/hub/")) {
12802
+ const authConfig = {
12803
+ loopbackAutoAuth,
12804
+ ...authToken !== void 0 ? { authToken } : {}
12805
+ };
12806
+ return handleHubRoute(
12807
+ { authConfig, service: bindings.hubService },
12808
+ req,
12809
+ res
12810
+ );
12811
+ }
12812
+ if (method === "GET" && url.pathname === "/api/identities") {
12813
+ const authConfig = {
12814
+ loopbackAutoAuth,
12815
+ ...authToken !== void 0 ? { authToken } : {}
12816
+ };
12817
+ const aliasReq = Object.create(req);
12818
+ aliasReq.url = "/api/hub/agents" + url.search;
12819
+ return handleHubRoute(
12820
+ { authConfig, service: bindings.hubService },
12821
+ aliasReq,
12822
+ res
12823
+ );
12824
+ }
12825
+ return false;
12826
+ }
12827
+
12773
12828
  // src/principal-policy/dashboard.ts
12774
12829
  var SESSION_TTL_REMOTE_MS = 5 * 60 * 1e3;
12775
12830
  var SESSION_TTL_LOCAL_MS = 24 * 60 * 60 * 1e3;
@@ -12896,45 +12951,17 @@ var DashboardApprovalChannel = class {
12896
12951
  */
12897
12952
  async dispatchV11(req, res, url, method) {
12898
12953
  if (!this.v11Bindings) return false;
12899
- if (method === "GET" && (url.pathname === "/v1.1" || url.pathname === "/v1.1/")) {
12900
- const handled = handleDashboardV11Route(
12901
- {
12902
- identityId: this.v11Bindings.identityId,
12903
- fortressId: this.v11Bindings.fortressId,
12904
- ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
12905
- },
12906
- req,
12907
- res
12908
- );
12909
- return handled;
12910
- }
12911
- if (url.pathname.startsWith("/api/hub/")) {
12912
- const authConfig = {
12913
- loopbackAutoAuth: this._autoAuthLocalhost,
12914
- ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
12915
- };
12916
- const handled = await handleHubRoute(
12917
- { authConfig, service: this.v11Bindings.hubService },
12918
- req,
12919
- res
12920
- );
12921
- return handled;
12922
- }
12923
- if (method === "GET" && url.pathname === "/api/identities") {
12924
- const authConfig = {
12925
- loopbackAutoAuth: this._autoAuthLocalhost,
12926
- ...this.authToken !== void 0 ? { authToken: this.authToken } : {}
12927
- };
12928
- const aliasReq = Object.create(req);
12929
- aliasReq.url = "/api/hub/agents" + url.search;
12930
- const handled = await handleHubRoute(
12931
- { authConfig, service: this.v11Bindings.hubService },
12932
- aliasReq,
12933
- res
12934
- );
12935
- return handled;
12936
- }
12937
- return false;
12954
+ return dispatchV11Request(
12955
+ {
12956
+ bindings: this.v11Bindings,
12957
+ ...this.authToken !== void 0 ? { authToken: this.authToken } : {},
12958
+ loopbackAutoAuth: this._autoAuthLocalhost
12959
+ },
12960
+ req,
12961
+ res,
12962
+ url,
12963
+ method
12964
+ );
12938
12965
  }
12939
12966
  /**
12940
12967
  * v0.10.2: enable (or disable) the loopback auto-auth fast path. See
@@ -26331,14 +26358,41 @@ function createComplianceTools(deps) {
26331
26358
  init_random();
26332
26359
  init_encoding();
26333
26360
  var RECOVERY_KEY_FILENAME = "recovery-key.txt";
26334
- function printRecoveryKeyBanner(recoveryKey, filePath, output = process.stderr) {
26361
+ var RECOVERY_KEY_COPY = {
26362
+ fileName: RECOVERY_KEY_FILENAME,
26363
+ bannerHeader: "SANCTUARY: First Run, Recovery Key Generated",
26364
+ bannerSecretLabel: "Recovery Key",
26365
+ bannerSaveLine: "SAVE THIS KEY. It will not be shown again.",
26366
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
26367
+ fileWarningHeader: "SANCTUARY RECOVERY KEY, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
26368
+ fileSecretLabel: "Recovery key:",
26369
+ fileBody: "This file was created on first init. Sanctuary will NOT regenerate this file on\nsubsequent runs and will NOT display the key again. After moving this file off\nthe host (encrypted backup, password manager, paper safe), delete it from the\nfortress directory. Do NOT keep it in the fortress; the recovery key bypasses\nthe cocoon passphrase by design.\n",
26370
+ promptLabel: "recovery key"
26371
+ };
26372
+ var RecoveryKeyConfirmationDeclinedError = class extends Error {
26373
+ constructor() {
26374
+ super(
26375
+ "Recovery key confirmation declined. Save the recovery key (printed above and written to recovery-key.txt) before re-running init."
26376
+ );
26377
+ this.name = "RecoveryKeyConfirmationDeclinedError";
26378
+ }
26379
+ };
26380
+ var RecoveryKeyConfirmationNonInteractiveError = class extends Error {
26381
+ constructor() {
26382
+ super(
26383
+ "Recovery key confirmation requires an interactive terminal. Re-run with --no-confirm for CI/scripted use, or run from a TTY."
26384
+ );
26385
+ this.name = "RecoveryKeyConfirmationNonInteractiveError";
26386
+ }
26387
+ };
26388
+ function printSecretBanner(secret, filePath, copy, output = process.stderr) {
26335
26389
  const lines = [
26336
- "SANCTUARY: First Run, Recovery Key Generated",
26390
+ copy.bannerHeader,
26337
26391
  "",
26338
- `Recovery Key: ${recoveryKey}`,
26392
+ `${copy.bannerSecretLabel}: ${secret}`,
26339
26393
  "",
26340
- "SAVE THIS KEY. It will not be shown again.",
26341
- "Without it, your encrypted state is unrecoverable.",
26394
+ copy.bannerSaveLine,
26395
+ copy.bannerLossLine,
26342
26396
  "",
26343
26397
  "Plaintext copy written to:",
26344
26398
  ` ${filePath}`,
@@ -26357,8 +26411,8 @@ ${bottom}
26357
26411
 
26358
26412
  `);
26359
26413
  }
26360
- async function writeRecoveryKeyFile(opts) {
26361
- const filePath = path.join(opts.storagePath, RECOVERY_KEY_FILENAME);
26414
+ async function writeSecretFile(opts) {
26415
+ const filePath = path.join(opts.storagePath, opts.copy.fileName);
26362
26416
  try {
26363
26417
  await promises.access(filePath, promises.constants.F_OK);
26364
26418
  return { filePath, written: false };
@@ -26368,40 +26422,76 @@ async function writeRecoveryKeyFile(opts) {
26368
26422
  const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
26369
26423
  const fortressLine = opts.fortressId ? `Fortress: ${opts.fortressId}
26370
26424
  ` : "";
26371
- const content = `SANCTUARY RECOVERY KEY \u2014 DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.
26425
+ const content = `${opts.copy.fileWarningHeader}
26372
26426
  Generated: ${now}
26373
26427
  ` + fortressLine + `
26374
- Recovery key:
26375
- ${opts.recoveryKey}
26376
-
26377
- This file was created on first init. Sanctuary will NOT regenerate this file on
26378
- subsequent runs and will NOT display the key again. After moving this file off
26379
- the host (encrypted backup, password manager, paper safe), delete it from the
26380
- fortress directory. Do NOT keep it in the fortress; the recovery key bypasses
26381
- the cocoon passphrase by design.
26382
- `;
26428
+ ${opts.copy.fileSecretLabel}
26429
+ ${opts.secret}
26430
+
26431
+ ` + opts.copy.fileBody;
26383
26432
  await promises.writeFile(filePath, content, { mode: 384 });
26384
26433
  return { filePath, written: true };
26385
26434
  }
26386
- async function discloseRecoveryKey(opts) {
26387
- const fileResult = await writeRecoveryKeyFile({
26435
+ async function confirmSecretSaved(copy, declinedError, nonInteractiveError, io) {
26436
+ const input = io?.input ?? process.stdin;
26437
+ const output = io?.output ?? process.stderr;
26438
+ const realStdin = !io && process.stdin.isTTY !== true;
26439
+ if (realStdin) {
26440
+ throw new nonInteractiveError();
26441
+ }
26442
+ const rl = promises$1.createInterface({ input, output });
26443
+ try {
26444
+ const answer = await rl.question(
26445
+ `Have you saved the ${copy.promptLabel}? [y/N] `
26446
+ );
26447
+ const normalized = answer.trim().toLowerCase();
26448
+ if (normalized !== "y" && normalized !== "yes") {
26449
+ throw new declinedError();
26450
+ }
26451
+ } finally {
26452
+ rl.close();
26453
+ }
26454
+ }
26455
+ async function discloseSecret(opts, copy, declinedError, nonInteractiveError) {
26456
+ const mode = opts.mode ?? "interactive";
26457
+ const writeOpts = {
26388
26458
  storagePath: opts.storagePath,
26389
- recoveryKey: opts.recoveryKey,
26390
- ...opts.fortressId !== void 0 ? { fortressId: opts.fortressId } : {},
26391
- ...opts.now !== void 0 ? { now: opts.now } : {}
26392
- });
26393
- printRecoveryKeyBanner(
26394
- opts.recoveryKey,
26395
- fileResult.filePath,
26396
- opts.io?.output
26397
- );
26398
- {
26459
+ secret: opts.secret,
26460
+ copy
26461
+ };
26462
+ if (opts.fortressId !== void 0) writeOpts.fortressId = opts.fortressId;
26463
+ if (opts.now !== void 0) writeOpts.now = opts.now;
26464
+ const fileResult = await writeSecretFile(writeOpts);
26465
+ printSecretBanner(opts.secret, fileResult.filePath, copy, opts.io?.output);
26466
+ if (mode === "no-confirm" || mode === "stdio-server") {
26399
26467
  return {
26400
26468
  filePath: fileResult.filePath,
26401
26469
  fileWritten: fileResult.written,
26402
26470
  confirmed: false
26403
26471
  };
26404
26472
  }
26473
+ await confirmSecretSaved(copy, declinedError, nonInteractiveError, opts.io);
26474
+ return {
26475
+ filePath: fileResult.filePath,
26476
+ fileWritten: fileResult.written,
26477
+ confirmed: true
26478
+ };
26479
+ }
26480
+ async function discloseRecoveryKey(opts) {
26481
+ const internalOpts = {
26482
+ secret: opts.recoveryKey,
26483
+ storagePath: opts.storagePath
26484
+ };
26485
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
26486
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
26487
+ if (opts.now !== void 0) internalOpts.now = opts.now;
26488
+ if (opts.io !== void 0) internalOpts.io = opts.io;
26489
+ return discloseSecret(
26490
+ internalOpts,
26491
+ RECOVERY_KEY_COPY,
26492
+ RecoveryKeyConfirmationDeclinedError,
26493
+ RecoveryKeyConfirmationNonInteractiveError
26494
+ );
26405
26495
  }
26406
26496
 
26407
26497
  // src/hub/agent-registry.ts
@@ -28823,14 +28913,18 @@ async function startDashboardServer(options) {
28823
28913
  }
28824
28914
  }
28825
28915
  };
28826
- const deps = {
28827
- sources: options.sources,
28828
- authToken: options.authToken,
28829
- approvals: options.approvals,
28830
- onEvent
28831
- };
28916
+ let v11Bindings = null;
28917
+ let v11LoopbackAutoAuth = false;
28832
28918
  const server = http.createServer(async (req, res) => {
28833
28919
  try {
28920
+ const deps = {
28921
+ sources: options.sources,
28922
+ authToken: options.authToken,
28923
+ approvals: options.approvals,
28924
+ onEvent,
28925
+ v11Bindings,
28926
+ loopbackAutoAuth: v11LoopbackAutoAuth
28927
+ };
28834
28928
  const served = await handleRequest(deps, req, res);
28835
28929
  if (!served) {
28836
28930
  res.writeHead(404, { "Content-Type": "application/json" });
@@ -28868,7 +28962,13 @@ async function startDashboardServer(options) {
28868
28962
  publishActivity: (entry) => publish({ type: "activity", data: entry }),
28869
28963
  publishApproval: (approval) => publish({ type: "approval", data: approval }),
28870
28964
  publishInbox: (item) => publish({ type: "inbox", data: item }),
28871
- publishAgentStatus: (snapshot) => publish({ type: "agent_status", data: snapshot })
28965
+ publishAgentStatus: (snapshot) => publish({ type: "agent_status", data: snapshot }),
28966
+ setV11Bindings: (bindings) => {
28967
+ v11Bindings = bindings;
28968
+ },
28969
+ setV11LoopbackAutoAuth: (enabled) => {
28970
+ v11LoopbackAutoAuth = enabled;
28971
+ }
28872
28972
  };
28873
28973
  }
28874
28974
 
@@ -29531,7 +29631,9 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
29531
29631
  if (recoveryKey) {
29532
29632
  await discloseRecoveryKey({
29533
29633
  recoveryKey,
29534
- storagePath: config.storage_path});
29634
+ storagePath: config.storage_path,
29635
+ mode: "stdio-server"
29636
+ });
29535
29637
  }
29536
29638
  return {
29537
29639
  server,