@sanctuary-framework/mcp-server 0.10.3 → 0.10.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/dist/cli.cjs CHANGED
@@ -7119,11 +7119,20 @@ function generateDashboardHTML(options) {
7119
7119
 
7120
7120
  // SSE Setup
7121
7121
  function setupSSE() {
7122
- const eventSource = new EventSource(API_BASE + '/api/events', {
7123
- headers: {
7124
- 'Authorization': 'Bearer ' + AUTH_TOKEN,
7125
- },
7126
- });
7122
+ // v0.10.5: the standard browser EventSource API does not support a
7123
+ // headers option \u2014 it is silently dropped. Auth must travel as a
7124
+ // cookie (set by /auth/session and sent automatically by the
7125
+ // browser) or as a ?session= query parameter, both of which Stack
7126
+ // A's checkAuth honours. Loopback callers also bypass auth via the
7127
+ // v0.10.2 _autoAuthLocalhost path, which is the path moltbook
7128
+ // hits when the dashboard is auto-opened on 127.0.0.1.
7129
+ //
7130
+ // The endpoint itself is /events \u2014 Stack A's route table mounts it
7131
+ // there, and the previous /api/events URL was a 404 in every real
7132
+ // boot from v0.10.0 through v0.10.4. The retry loop that result
7133
+ // produced is exactly the "status bar flashing blue continuously"
7134
+ // moltbook reported on v0.10.4.
7135
+ const eventSource = new EventSource(API_BASE + '/events');
7127
7136
 
7128
7137
  eventSource.addEventListener('init', (e) => {
7129
7138
  console.log('Connected to SSE');
@@ -27423,10 +27432,64 @@ var init_multi_server = __esm({
27423
27432
  // src/dashboard-standalone.ts
27424
27433
  var dashboard_standalone_exports = {};
27425
27434
  __export(dashboard_standalone_exports, {
27435
+ discoverableSubTenants: () => discoverableSubTenants,
27436
+ renderTenantDiscoveryHint: () => renderTenantDiscoveryHint,
27426
27437
  startStandaloneDashboard: () => startStandaloneDashboard
27427
27438
  });
27439
+ async function discoverableSubTenants(currentStoragePath) {
27440
+ let all;
27441
+ try {
27442
+ all = await discoverTenants();
27443
+ } catch {
27444
+ return [];
27445
+ }
27446
+ return all.filter((t) => t.storage_path !== currentStoragePath && t.initialized);
27447
+ }
27448
+ function renderTenantDiscoveryHint(tenants) {
27449
+ if (tenants.length === 0) {
27450
+ return `No wrapped tenants discovered on this host.
27451
+ Run \`sanctuary wrap\` to create one, or set SANCTUARY_STORAGE_PATH
27452
+ if your tenant lives outside ~/.sanctuary/.`;
27453
+ }
27454
+ const lines = tenants.map((t) => {
27455
+ const runtime = t.runtime ? ` (running on :${t.runtime.dashboard_port})` : "";
27456
+ return ` \u2022 ${t.name}${runtime}
27457
+ storage: ${t.storage_path}
27458
+ keychain: ${t.keychain_service}`;
27459
+ });
27460
+ if (tenants.length === 1) {
27461
+ return `Detected 1 wrapped tenant on this host:
27462
+ ` + lines.join("\n") + `
27463
+
27464
+ Boot the dashboard against it with:
27465
+ sanctuary dashboard --tenant ${tenants[0].name}
27466
+ `;
27467
+ }
27468
+ return `Detected ${tenants.length} wrapped tenants on this host:
27469
+ ` + lines.join("\n") + `
27470
+
27471
+ Pick one explicitly:
27472
+ sanctuary dashboard --tenant <name>
27473
+
27474
+ Or browse all of them in the multi-tenant overview (no decryption):
27475
+ sanctuary dashboard --multi
27476
+ `;
27477
+ }
27428
27478
  async function startStandaloneDashboard(options = {}) {
27429
27479
  process.env.SANCTUARY_DASHBOARD_ENABLED = "true";
27480
+ if (options.tenant !== void 0) {
27481
+ const match = await findTenant(options.tenant);
27482
+ if (!match) {
27483
+ const available = await discoverTenants();
27484
+ const names = available.map((t) => t.name).join(", ") || "(none \u2014 run `sanctuary wrap`)";
27485
+ throw new Error(
27486
+ `Sanctuary Dashboard: --tenant "${options.tenant}" did not match any wrapped tenant.
27487
+ Available tenants: ${names}
27488
+ List details with \`sanctuary agents\`.`
27489
+ );
27490
+ }
27491
+ process.env.SANCTUARY_STORAGE_PATH = match.storage_path;
27492
+ }
27430
27493
  const config = await loadConfig(options.configPath);
27431
27494
  await promises.mkdir(config.storage_path, { recursive: true, mode: 448 });
27432
27495
  const storage = new FilesystemStorage(`${config.storage_path}/state`);
@@ -27476,12 +27539,16 @@ async function startStandaloneDashboard(options = {}) {
27476
27539
  } else {
27477
27540
  const { hashToString: hashToString2 } = await Promise.resolve().then(() => (init_hashing(), hashing_exports));
27478
27541
  const { stringToBytes: stringToBytes2, bytesToString: bytesToString2, fromBase64url: fromBase64url2, constantTimeEqual: constantTimeEqual2 } = await Promise.resolve().then(() => (init_encoding(), encoding_exports));
27542
+ const otherTenants = await discoverableSubTenants(config.storage_path);
27479
27543
  const existingHash = await storage.read("_meta", "recovery-key-hash");
27480
27544
  if (existingHash) {
27481
27545
  const envRecoveryKey = process.env.SANCTUARY_RECOVERY_KEY;
27482
27546
  if (!envRecoveryKey) {
27483
27547
  throw new Error(
27484
- "Sanctuary Dashboard: Existing encrypted data found but no credentials provided.\nProvide SANCTUARY_PASSPHRASE or SANCTUARY_RECOVERY_KEY to start the dashboard.\nThe dashboard needs the same credentials as the MCP server to read encrypted data."
27548
+ `Sanctuary Dashboard: Existing encrypted data found at ${config.storage_path} but no credentials provided.
27549
+ Provide SANCTUARY_PASSPHRASE or SANCTUARY_RECOVERY_KEY to start the dashboard against this storage path.
27550
+
27551
+ ` + (otherTenants.length > 0 ? renderTenantDiscoveryHint(otherTenants) + "\n" : "") + `See server/docs/keychain-schema.md for the keychain layout.`
27485
27552
  );
27486
27553
  }
27487
27554
  let recoveryKeyBytes;
@@ -27512,7 +27579,19 @@ async function startStandaloneDashboard(options = {}) {
27512
27579
  const hasKeyParams = existingNamespaces.some((e) => e.key === "key-params");
27513
27580
  if (hasKeyParams) {
27514
27581
  throw new Error(
27515
- "Sanctuary Dashboard: Existing encrypted data found (passphrase-protected).\nProvide SANCTUARY_PASSPHRASE to start the dashboard.\nThe dashboard needs the same credentials as the MCP server to read encrypted data."
27582
+ `Sanctuary Dashboard: Existing encrypted data found at ${config.storage_path} (passphrase-protected).
27583
+ No passphrase was supplied via --passphrase, SANCTUARY_PASSPHRASE,
27584
+ or the per-tenant Keychain item ${keychainServiceFor(config.storage_path, os.homedir())}.
27585
+
27586
+ ` + (otherTenants.length > 0 ? renderTenantDiscoveryHint(otherTenants) + "\n" : "") + `See server/docs/keychain-schema.md for the keychain layout and recovery options.`
27587
+ );
27588
+ }
27589
+ if (otherTenants.length > 0) {
27590
+ throw new Error(
27591
+ `Sanctuary Dashboard: ${config.storage_path} has no Sanctuary state, but other wrapped tenants exist on this host.
27592
+ Refusing to generate a new recovery key over the default root \u2014 that would obscure the existing tenants.
27593
+
27594
+ ` + renderTenantDiscoveryHint(otherTenants)
27516
27595
  );
27517
27596
  }
27518
27597
  console.error(
@@ -27602,27 +27681,26 @@ async function startStandaloneDashboard(options = {}) {
27602
27681
  if (loadResult.total > 0 && loadResult.loaded === 0) {
27603
27682
  const service = keychainServiceFor(config.storage_path, os.homedir());
27604
27683
  const sourceLabel = passphraseSource === "option" ? "--passphrase option" : passphraseSource === "env" ? "SANCTUARY_PASSPHRASE env var" : passphraseSource === "keychain" ? `macOS Keychain (service ${service})` : passphraseSource === "fallback-file" ? "encrypted fallback file" : "recovery key";
27684
+ const otherTenants = await discoverableSubTenants(config.storage_path);
27685
+ const hint = otherTenants.length > 0 ? `
27686
+ ${renderTenantDiscoveryHint(otherTenants).split("\n").join("\n ")}
27687
+ ` : "";
27605
27688
  console.error(
27606
27689
  `
27607
27690
  \u26A0 WARNING: Encrypted identities found but NONE loaded
27608
27691
  ${loadResult.total} encrypted identity file(s) in ${config.storage_path}/state/_identities/
27609
27692
  0 could be decrypted with the master key derived from the ${sourceLabel}.
27610
27693
 
27611
- The dashboard will show empty panels. Since v0.10.0 each wrapped
27612
- tenant has its OWN passphrase, stored under a per-tenant Keychain
27613
- service name. A single SANCTUARY_PASSPHRASE unlocks at most one
27614
- tenant \u2014 it is not a global master credential.
27615
-
27616
- To diagnose:
27617
- \u2022 List tenants on this host: sanctuary agents
27618
- \u2022 Multi-tenant overview (no decrypt): sanctuary dashboard --multi
27619
- \u2022 Point at a specific tenant: SANCTUARY_STORAGE_PATH=<path> sanctuary dashboard
27620
- \u2022 Inspect this tenant's Keychain: security find-generic-password -s '${service}' -w
27621
-
27622
- If this tenant's passphrase lives only in a different Keychain item
27623
- or on another machine, restore it before this dashboard can read
27624
- any state. Sanctuary will never auto-regenerate \u2014 that would
27625
- permanently destroy the data encrypted under the prior key.
27694
+ The dashboard will show empty panels. Each wrapped tenant has its
27695
+ own passphrase under its own per-tenant Keychain service
27696
+ (this tenant's service: ${service}) \u2014 there is no global master
27697
+ credential. Setting SANCTUARY_PASSPHRASE here will not help unless
27698
+ that value is the passphrase that originally encrypted the
27699
+ identity files at this storage path.
27700
+ ` + hint + `
27701
+ Diagnostic recipes: server/docs/keychain-schema.md
27702
+ Sanctuary will never auto-regenerate \u2014 that would permanently
27703
+ destroy the data encrypted under the prior key.
27626
27704
  `
27627
27705
  );
27628
27706
  } else if (loadResult.failed > 0) {
@@ -27653,6 +27731,7 @@ var init_dashboard_standalone = __esm({
27653
27731
  init_sovereignty_profile();
27654
27732
  init_runtime();
27655
27733
  init_passphrase();
27734
+ init_discovery();
27656
27735
  }
27657
27736
  });
27658
27737
 
@@ -27821,6 +27900,7 @@ async function runStandaloneDashboard(args) {
27821
27900
  let port;
27822
27901
  let host;
27823
27902
  let multi = false;
27903
+ let tenant;
27824
27904
  for (let i = 0; i < args.length; i++) {
27825
27905
  if (args[i] === "--passphrase" && args[i + 1]) {
27826
27906
  console.error(
@@ -27833,6 +27913,8 @@ async function runStandaloneDashboard(args) {
27833
27913
  host = args[++i];
27834
27914
  } else if (args[i] === "--multi") {
27835
27915
  multi = true;
27916
+ } else if (args[i] === "--tenant" && args[i + 1]) {
27917
+ tenant = args[++i];
27836
27918
  } else if (args[i] === "--help" || args[i] === "-h") {
27837
27919
  printDashboardHelp();
27838
27920
  process.exit(0);
@@ -27862,7 +27944,8 @@ async function runStandaloneDashboard(args) {
27862
27944
  await startStandaloneDashboard2({
27863
27945
  passphrase,
27864
27946
  port,
27865
- host
27947
+ host,
27948
+ ...tenant !== void 0 ? { tenant } : {}
27866
27949
  });
27867
27950
  console.error(`
27868
27951
  Sanctuary Dashboard running (standalone mode). Press Ctrl+C to stop.
@@ -27996,6 +28079,10 @@ Usage:
27996
28079
  Options:
27997
28080
  --port <port> Dashboard port (default: from config or 3501; 3500 for --multi)
27998
28081
  --host <host> Bind address (default: 127.0.0.1)
28082
+ --tenant <name> Boot against a specific wrapped tenant by the name printed
28083
+ by \`sanctuary agents\`. Resolves the per-tenant storage
28084
+ path and Keychain entry automatically. Use this on multi-
28085
+ tenant hosts instead of guessing SANCTUARY_PASSPHRASE.
27999
28086
  --multi Start the multi-agent overview instead of a single-tenant
28000
28087
  dashboard. Does not decrypt any tenant state \u2014 scans every
28001
28088
  tenant on the host and deep-links into per-tenant dashboards.