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