@sanctuary-framework/mcp-server 1.1.2 → 1.1.4

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/cli.js CHANGED
@@ -20,8 +20,8 @@ import { get, createServer as createServer$1 } from 'https';
20
20
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
21
21
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
22
22
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
23
- import { createInterface as createInterface$1 } from 'readline/promises';
24
- import { createInterface } from 'readline';
23
+ import { createInterface } from 'readline/promises';
24
+ import { createInterface as createInterface$1 } from 'readline';
25
25
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
26
26
 
27
27
  var __defProp = Object.defineProperty;
@@ -13354,7 +13354,7 @@ function renderDashboardV11Html(options = {}) {
13354
13354
  streamUrl,
13355
13355
  identityId,
13356
13356
  fortressId
13357
- });
13357
+ }).replace(/</g, "\\u003c");
13358
13358
  const clientBlock = embedClient ? `<script type="module">${getClientScript()}</script>` : `<!-- client script omitted by render option -->`;
13359
13359
  return `<!doctype html>
13360
13360
  <html lang="en">
@@ -13385,7 +13385,7 @@ function renderDashboardV11Html(options = {}) {
13385
13385
  <aside class="fortress" id="fortress"><p class="muted">Loading fortress column.</p></aside>
13386
13386
  </div>
13387
13387
  <div id="toast-host" aria-live="polite"></div>
13388
- <script id="dashboard-config" type="application/json">${escHtml3(config)}</script>
13388
+ <script id="dashboard-config" type="application/json">${config}</script>
13389
13389
  ${clientBlock}
13390
13390
  </body>
13391
13391
  </html>`;
@@ -27533,14 +27533,14 @@ var init_generator2 = __esm({
27533
27533
  ]);
27534
27534
  }
27535
27535
  });
27536
- function printRecoveryKeyBanner(recoveryKey, filePath, output = process.stderr) {
27536
+ function printSecretBanner(secret, filePath, copy, output = process.stderr) {
27537
27537
  const lines = [
27538
- "SANCTUARY: First Run, Recovery Key Generated",
27538
+ copy.bannerHeader,
27539
27539
  "",
27540
- `Recovery Key: ${recoveryKey}`,
27540
+ `${copy.bannerSecretLabel}: ${secret}`,
27541
27541
  "",
27542
- "SAVE THIS KEY. It will not be shown again.",
27543
- "Without it, your encrypted state is unrecoverable.",
27542
+ copy.bannerSaveLine,
27543
+ copy.bannerLossLine,
27544
27544
  "",
27545
27545
  "Plaintext copy written to:",
27546
27546
  ` ${filePath}`,
@@ -27559,8 +27559,8 @@ ${bottom}
27559
27559
 
27560
27560
  `);
27561
27561
  }
27562
- async function writeRecoveryKeyFile(opts) {
27563
- const filePath = join(opts.storagePath, RECOVERY_KEY_FILENAME);
27562
+ async function writeSecretFile(opts) {
27563
+ const filePath = join(opts.storagePath, opts.copy.fileName);
27564
27564
  try {
27565
27565
  await access(filePath, constants.F_OK);
27566
27566
  return { filePath, written: false };
@@ -27570,54 +27570,47 @@ async function writeRecoveryKeyFile(opts) {
27570
27570
  const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
27571
27571
  const fortressLine = opts.fortressId ? `Fortress: ${opts.fortressId}
27572
27572
  ` : "";
27573
- const content = `SANCTUARY RECOVERY KEY \u2014 DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.
27573
+ const content = `${opts.copy.fileWarningHeader}
27574
27574
  Generated: ${now}
27575
27575
  ` + fortressLine + `
27576
- Recovery key:
27577
- ${opts.recoveryKey}
27578
-
27579
- This file was created on first init. Sanctuary will NOT regenerate this file on
27580
- subsequent runs and will NOT display the key again. After moving this file off
27581
- the host (encrypted backup, password manager, paper safe), delete it from the
27582
- fortress directory. Do NOT keep it in the fortress; the recovery key bypasses
27583
- the cocoon passphrase by design.
27584
- `;
27576
+ ${opts.copy.fileSecretLabel}
27577
+ ${opts.secret}
27578
+
27579
+ ` + opts.copy.fileBody;
27585
27580
  await writeFile(filePath, content, { mode: 384 });
27586
27581
  return { filePath, written: true };
27587
27582
  }
27588
- async function confirmRecoveryKeySaved(io) {
27583
+ async function confirmSecretSaved(copy, declinedError, nonInteractiveError, io) {
27589
27584
  const input = io?.input ?? process.stdin;
27590
27585
  const output = io?.output ?? process.stderr;
27591
27586
  const realStdin = !io && process.stdin.isTTY !== true;
27592
27587
  if (realStdin) {
27593
- throw new RecoveryKeyConfirmationNonInteractiveError();
27588
+ throw new nonInteractiveError();
27594
27589
  }
27595
- const rl = createInterface$1({ input, output });
27590
+ const rl = createInterface({ input, output });
27596
27591
  try {
27597
27592
  const answer = await rl.question(
27598
- "Have you saved the recovery key? [y/N] "
27593
+ `Have you saved the ${copy.promptLabel}? [y/N] `
27599
27594
  );
27600
27595
  const normalized = answer.trim().toLowerCase();
27601
27596
  if (normalized !== "y" && normalized !== "yes") {
27602
- throw new RecoveryKeyConfirmationDeclinedError();
27597
+ throw new declinedError();
27603
27598
  }
27604
27599
  } finally {
27605
27600
  rl.close();
27606
27601
  }
27607
27602
  }
27608
- async function discloseRecoveryKey(opts) {
27603
+ async function discloseSecret(opts, copy, declinedError, nonInteractiveError) {
27609
27604
  const mode = opts.mode ?? "interactive";
27610
- const fileResult = await writeRecoveryKeyFile({
27605
+ const writeOpts = {
27611
27606
  storagePath: opts.storagePath,
27612
- recoveryKey: opts.recoveryKey,
27613
- ...opts.fortressId !== void 0 ? { fortressId: opts.fortressId } : {},
27614
- ...opts.now !== void 0 ? { now: opts.now } : {}
27615
- });
27616
- printRecoveryKeyBanner(
27617
- opts.recoveryKey,
27618
- fileResult.filePath,
27619
- opts.io?.output
27620
- );
27607
+ secret: opts.secret,
27608
+ copy
27609
+ };
27610
+ if (opts.fortressId !== void 0) writeOpts.fortressId = opts.fortressId;
27611
+ if (opts.now !== void 0) writeOpts.now = opts.now;
27612
+ const fileResult = await writeSecretFile(writeOpts);
27613
+ printSecretBanner(opts.secret, fileResult.filePath, copy, opts.io?.output);
27621
27614
  if (mode === "no-confirm" || mode === "stdio-server") {
27622
27615
  return {
27623
27616
  filePath: fileResult.filePath,
@@ -27625,17 +27618,72 @@ async function discloseRecoveryKey(opts) {
27625
27618
  confirmed: false
27626
27619
  };
27627
27620
  }
27628
- await confirmRecoveryKeySaved(opts.io);
27621
+ await confirmSecretSaved(copy, declinedError, nonInteractiveError, opts.io);
27629
27622
  return {
27630
27623
  filePath: fileResult.filePath,
27631
27624
  fileWritten: fileResult.written,
27632
27625
  confirmed: true
27633
27626
  };
27634
27627
  }
27635
- var RECOVERY_KEY_FILENAME, RecoveryKeyConfirmationDeclinedError, RecoveryKeyConfirmationNonInteractiveError;
27628
+ async function discloseRecoveryKey(opts) {
27629
+ const internalOpts = {
27630
+ secret: opts.recoveryKey,
27631
+ storagePath: opts.storagePath
27632
+ };
27633
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
27634
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
27635
+ if (opts.now !== void 0) internalOpts.now = opts.now;
27636
+ if (opts.io !== void 0) internalOpts.io = opts.io;
27637
+ return discloseSecret(
27638
+ internalOpts,
27639
+ RECOVERY_KEY_COPY,
27640
+ RecoveryKeyConfirmationDeclinedError,
27641
+ RecoveryKeyConfirmationNonInteractiveError
27642
+ );
27643
+ }
27644
+ async function disclosePassphrase(opts) {
27645
+ const internalOpts = {
27646
+ secret: opts.passphrase,
27647
+ storagePath: opts.storagePath
27648
+ };
27649
+ if (opts.fortressId !== void 0) internalOpts.fortressId = opts.fortressId;
27650
+ if (opts.mode !== void 0) internalOpts.mode = opts.mode;
27651
+ if (opts.now !== void 0) internalOpts.now = opts.now;
27652
+ if (opts.io !== void 0) internalOpts.io = opts.io;
27653
+ return discloseSecret(
27654
+ internalOpts,
27655
+ PASSPHRASE_BACKUP_COPY,
27656
+ PassphraseConfirmationDeclinedError,
27657
+ PassphraseConfirmationNonInteractiveError
27658
+ );
27659
+ }
27660
+ var RECOVERY_KEY_FILENAME, PASSPHRASE_BACKUP_FILENAME, RECOVERY_KEY_COPY, PASSPHRASE_BACKUP_COPY, RecoveryKeyConfirmationDeclinedError, RecoveryKeyConfirmationNonInteractiveError, PassphraseConfirmationDeclinedError, PassphraseConfirmationNonInteractiveError;
27636
27661
  var init_recovery_key_disclosure = __esm({
27637
27662
  "src/cocoon/recovery-key-disclosure.ts"() {
27638
27663
  RECOVERY_KEY_FILENAME = "recovery-key.txt";
27664
+ PASSPHRASE_BACKUP_FILENAME = "passphrase-backup.txt";
27665
+ RECOVERY_KEY_COPY = {
27666
+ fileName: RECOVERY_KEY_FILENAME,
27667
+ bannerHeader: "SANCTUARY: First Run, Recovery Key Generated",
27668
+ bannerSecretLabel: "Recovery Key",
27669
+ bannerSaveLine: "SAVE THIS KEY. It will not be shown again.",
27670
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
27671
+ fileWarningHeader: "SANCTUARY RECOVERY KEY, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
27672
+ fileSecretLabel: "Recovery key:",
27673
+ 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",
27674
+ promptLabel: "recovery key"
27675
+ };
27676
+ PASSPHRASE_BACKUP_COPY = {
27677
+ fileName: PASSPHRASE_BACKUP_FILENAME,
27678
+ bannerHeader: "SANCTUARY: First Run, Passphrase Generated",
27679
+ bannerSecretLabel: "Passphrase",
27680
+ bannerSaveLine: "SAVE THIS PASSPHRASE. It will not be shown again.",
27681
+ bannerLossLine: "Without it, your encrypted state is unrecoverable.",
27682
+ fileWarningHeader: "SANCTUARY PASSPHRASE, DO NOT COMMIT, DO NOT EMAIL, MOVE OFF-HOST IMMEDIATELY.",
27683
+ fileSecretLabel: "Passphrase:",
27684
+ fileBody: "This file was created on first wrap when Sanctuary generated the passphrase.\nSanctuary will NOT regenerate this file on subsequent runs and will NOT display\nthe passphrase again. After moving this file off the host (encrypted backup,\npassword manager, paper safe), delete it from the fortress directory. Do NOT\nkeep it in the fortress; the keychain copy is recoverable only while the host\nand its OS keyring are intact.\n",
27685
+ promptLabel: "passphrase"
27686
+ };
27639
27687
  RecoveryKeyConfirmationDeclinedError = class extends Error {
27640
27688
  constructor() {
27641
27689
  super(
@@ -27652,6 +27700,22 @@ var init_recovery_key_disclosure = __esm({
27652
27700
  this.name = "RecoveryKeyConfirmationNonInteractiveError";
27653
27701
  }
27654
27702
  };
27703
+ PassphraseConfirmationDeclinedError = class extends Error {
27704
+ constructor() {
27705
+ super(
27706
+ "Passphrase confirmation declined. Save the passphrase (printed above and written to passphrase-backup.txt) before re-running wrap."
27707
+ );
27708
+ this.name = "PassphraseConfirmationDeclinedError";
27709
+ }
27710
+ };
27711
+ PassphraseConfirmationNonInteractiveError = class extends Error {
27712
+ constructor() {
27713
+ super(
27714
+ "Passphrase confirmation requires an interactive terminal. Re-run with --no-open for scripted use, or run from a TTY."
27715
+ );
27716
+ this.name = "PassphraseConfirmationNonInteractiveError";
27717
+ }
27718
+ };
27655
27719
  }
27656
27720
  });
27657
27721
 
@@ -31440,6 +31504,27 @@ async function runWrap(options, deps = {}) {
31440
31504
  );
31441
31505
  }
31442
31506
  await mkdir(storagePath, { recursive: true, mode: 448 });
31507
+ if (passphraseSource === "generated" && passphraseValue !== void 0) {
31508
+ try {
31509
+ await disclosePassphrase({
31510
+ passphrase: passphraseValue,
31511
+ storagePath,
31512
+ fortressId: fortressIdFromStoragePath(storagePath),
31513
+ // --no-open (CI / scripted) or non-TTY stdin both skip the prompt
31514
+ // the same way init's --no-confirm does. Operator who scripted the
31515
+ // call still gets the banner + the file; they will not see a hang.
31516
+ mode: options.noOpen || process.stdin.isTTY !== true ? "no-confirm" : "interactive"
31517
+ });
31518
+ } catch (err) {
31519
+ if (err instanceof PassphraseConfirmationDeclinedError || err instanceof PassphraseConfirmationNonInteractiveError) {
31520
+ console.error(`
31521
+ Sanctuary wrap: ${err.message}
31522
+ `);
31523
+ process.exit(2);
31524
+ }
31525
+ throw err;
31526
+ }
31527
+ }
31443
31528
  const profile = createWrapProfile(upstreamServers);
31444
31529
  const profilePath = join(storagePath, "cocoon-profile.json");
31445
31530
  await writeFile(profilePath, JSON.stringify(profile, null, 2), {
@@ -31900,6 +31985,7 @@ var init_cli2 = __esm({
31900
31985
  init_config();
31901
31986
  init_paths();
31902
31987
  init_runtime();
31988
+ init_recovery_key_disclosure();
31903
31989
  COCOON_GOVERNOR_DEFAULTS = {
31904
31990
  volume_limit: 200,
31905
31991
  rate_limit_per_tool: 20,
@@ -33902,7 +33988,7 @@ async function readValue(stdin, prompt2) {
33902
33988
  }
33903
33989
  async function readFirstLine(stdin) {
33904
33990
  return new Promise((resolve5, reject) => {
33905
- const rl = createInterface({ input: stdin });
33991
+ const rl = createInterface$1({ input: stdin });
33906
33992
  let resolved = false;
33907
33993
  const finish = (value) => {
33908
33994
  if (resolved) return;
@@ -34928,7 +35014,7 @@ var init_reset_passphrase = __esm({
34928
35014
  waiters = [];
34929
35015
  closed = false;
34930
35016
  constructor(stdin) {
34931
- this.rl = createInterface({ input: stdin });
35017
+ this.rl = createInterface$1({ input: stdin });
34932
35018
  this.rl.on("line", (line) => {
34933
35019
  const w = this.waiters.shift();
34934
35020
  if (w) {