@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.cjs +122 -36
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +127 -41
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +98 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +98 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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">${
|
|
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
|
|
27536
|
+
function printSecretBanner(secret, filePath, copy, output = process.stderr) {
|
|
27537
27537
|
const lines = [
|
|
27538
|
-
|
|
27538
|
+
copy.bannerHeader,
|
|
27539
27539
|
"",
|
|
27540
|
-
|
|
27540
|
+
`${copy.bannerSecretLabel}: ${secret}`,
|
|
27541
27541
|
"",
|
|
27542
|
-
|
|
27543
|
-
|
|
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
|
|
27563
|
-
const filePath = join(opts.storagePath,
|
|
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 =
|
|
27573
|
+
const content = `${opts.copy.fileWarningHeader}
|
|
27574
27574
|
Generated: ${now}
|
|
27575
27575
|
` + fortressLine + `
|
|
27576
|
-
|
|
27577
|
-
${opts.
|
|
27578
|
-
|
|
27579
|
-
|
|
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
|
|
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
|
|
27588
|
+
throw new nonInteractiveError();
|
|
27594
27589
|
}
|
|
27595
|
-
const rl = createInterface
|
|
27590
|
+
const rl = createInterface({ input, output });
|
|
27596
27591
|
try {
|
|
27597
27592
|
const answer = await rl.question(
|
|
27598
|
-
|
|
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
|
|
27597
|
+
throw new declinedError();
|
|
27603
27598
|
}
|
|
27604
27599
|
} finally {
|
|
27605
27600
|
rl.close();
|
|
27606
27601
|
}
|
|
27607
27602
|
}
|
|
27608
|
-
async function
|
|
27603
|
+
async function discloseSecret(opts, copy, declinedError, nonInteractiveError) {
|
|
27609
27604
|
const mode = opts.mode ?? "interactive";
|
|
27610
|
-
const
|
|
27605
|
+
const writeOpts = {
|
|
27611
27606
|
storagePath: opts.storagePath,
|
|
27612
|
-
|
|
27613
|
-
|
|
27614
|
-
|
|
27615
|
-
|
|
27616
|
-
|
|
27617
|
-
|
|
27618
|
-
|
|
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
|
|
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
|
-
|
|
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) {
|