@sanctuary-framework/mcp-server 1.1.4 → 1.1.6

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
@@ -12,7 +12,7 @@ import { argon2id } from 'hash-wasm';
12
12
  import { hkdf } from '@noble/hashes/hkdf';
13
13
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
14
14
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
15
- import { statSync, existsSync, readFileSync, mkdirSync } from 'fs';
15
+ import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync, renameSync, chmodSync } from 'fs';
16
16
  import { fileURLToPath } from 'url';
17
17
  import { exec, execSync, spawn } from 'child_process';
18
18
  import { createServer as createServer$2, get as get$1 } from 'http';
@@ -28136,6 +28136,13 @@ var init_hub_service = __esm({
28136
28136
  nowIso() {
28137
28137
  return this.now().toISOString();
28138
28138
  }
28139
+ refreshPersistedLocalAgents() {
28140
+ const readPersistedLocalAgents2 = this.deps.readPersistedLocalAgents;
28141
+ if (!readPersistedLocalAgents2) return;
28142
+ for (const record of readPersistedLocalAgents2()) {
28143
+ this.deps.agentRegistry.put(record);
28144
+ }
28145
+ }
28139
28146
  // ── Inbox ───────────────────────────────────────────────────────────
28140
28147
  listInbox() {
28141
28148
  const items = aggregateInbox(this.deps.inboxSources, this.inboxStore);
@@ -28147,6 +28154,7 @@ var init_hub_service = __esm({
28147
28154
  }
28148
28155
  // ── Agents ──────────────────────────────────────────────────────────
28149
28156
  listAgents(filter) {
28157
+ this.refreshPersistedLocalAgents();
28150
28158
  const safeFilter = {
28151
28159
  ...filter ?? {},
28152
28160
  identity_id: this.deps.identityId
@@ -28580,12 +28588,76 @@ var init_hub = __esm({
28580
28588
  init_api_router();
28581
28589
  }
28582
28590
  });
28591
+ function localAgentsFilePath(storagePath) {
28592
+ return join(storagePath, "state", "_hub", "local-agents.json");
28593
+ }
28594
+ function readPersistedLocalAgents(storagePath) {
28595
+ const filePath = localAgentsFilePath(storagePath);
28596
+ if (!existsSync(filePath)) return [];
28597
+ try {
28598
+ const raw = readFileSync(filePath, "utf8");
28599
+ const parsed = JSON.parse(raw);
28600
+ if (!parsed || !Array.isArray(parsed.agents)) return [];
28601
+ return parsed.agents;
28602
+ } catch {
28603
+ return [];
28604
+ }
28605
+ }
28606
+ function writePersistedLocalAgents(storagePath, agents) {
28607
+ const filePath = localAgentsFilePath(storagePath);
28608
+ const dir = dirname(filePath);
28609
+ mkdirSync(dir, { recursive: true, mode: 448 });
28610
+ const payload = {
28611
+ version: PERSISTED_VERSION,
28612
+ agents
28613
+ };
28614
+ const tmpPath = `${filePath}.tmp`;
28615
+ writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}
28616
+ `, {
28617
+ mode: 384
28618
+ });
28619
+ renameSync(tmpPath, filePath);
28620
+ chmodSync(filePath, 384);
28621
+ }
28622
+ function upsertPersistedLocalAgent(storagePath, record) {
28623
+ const existing = readPersistedLocalAgents(storagePath);
28624
+ const idx = existing.findIndex((r) => r.agent_id === record.agent_id);
28625
+ let next;
28626
+ if (idx >= 0) {
28627
+ const prior = existing[idx];
28628
+ if (prior === void 0) {
28629
+ next = [...existing, record];
28630
+ } else {
28631
+ const updated = {
28632
+ ...record,
28633
+ wrapped_at: prior.wrapped_at,
28634
+ last_activity_at: (/* @__PURE__ */ new Date()).toISOString()
28635
+ };
28636
+ next = [...existing];
28637
+ next[idx] = updated;
28638
+ }
28639
+ } else {
28640
+ next = [...existing, record];
28641
+ }
28642
+ writePersistedLocalAgents(storagePath, next);
28643
+ return next;
28644
+ }
28645
+ var PERSISTED_VERSION;
28646
+ var init_agent_registry_persistence = __esm({
28647
+ "src/hub/agent-registry-persistence.ts"() {
28648
+ PERSISTED_VERSION = "1.1";
28649
+ }
28650
+ });
28583
28651
  function buildV11Bindings(inputs) {
28584
- const registry = new InMemoryLocalAgentRegistry([]);
28652
+ const seed = inputs.storagePath !== void 0 ? readPersistedLocalAgents(inputs.storagePath) : [];
28653
+ const registry = new InMemoryLocalAgentRegistry(seed);
28654
+ const storagePath = inputs.storagePath;
28655
+ const readPersisted = storagePath !== void 0 ? () => readPersistedLocalAgents(storagePath) : void 0;
28585
28656
  const hubService = new HubService({
28586
28657
  identityId: inputs.identityId,
28587
28658
  fortressId: inputs.fortressId,
28588
28659
  agentRegistry: registry,
28660
+ ...readPersisted ? { readPersistedLocalAgents: readPersisted } : {},
28589
28661
  inboxSources: {
28590
28662
  listPendingApprovals: () => [],
28591
28663
  listRecentBlockedEgress: () => [],
@@ -28619,6 +28691,7 @@ var init_wiring = __esm({
28619
28691
  "src/dashboard/v1_1/wiring.ts"() {
28620
28692
  init_hub();
28621
28693
  init_errors4();
28694
+ init_agent_registry_persistence();
28622
28695
  CapabilityErrorAgentController = class {
28623
28696
  fail(action) {
28624
28697
  throw new HubCapabilityError(
@@ -30651,7 +30724,12 @@ Refusing to start the cocoon while the reset-history marker is unreadable.`
30651
30724
  buildV11Bindings({
30652
30725
  identityId: embeddedHubIdentityId,
30653
30726
  fortressId: fortressIdFromStoragePath(config.storage_path),
30654
- auditLog
30727
+ auditLog,
30728
+ // v1.1.5 (Finding Z): rehydrate the hub agent registry from
30729
+ // `<storagePath>/state/_hub/local-agents.json` so the embedded
30730
+ // dashboard surfaces wraps performed by prior `sanctuary wrap`
30731
+ // invocations against this same fortress.
30732
+ storagePath: config.storage_path
30655
30733
  })
30656
30734
  );
30657
30735
  await dashboard.start();
@@ -31294,6 +31372,7 @@ __export(cli_exports, {
31294
31372
  PORT_FALLBACK_ATTEMPTS: () => PORT_FALLBACK_ATTEMPTS,
31295
31373
  formatMcpServerCount: () => formatMcpServerCount,
31296
31374
  formatWrapSuccess: () => formatWrapSuccess,
31375
+ formatWrapSuccessNoDashboard: () => formatWrapSuccessNoDashboard,
31297
31376
  parseCocoonArgs: () => parseCocoonArgs,
31298
31377
  parseWrapArgs: () => parseWrapArgs,
31299
31378
  promoteFortressToStoragePath: () => promoteFortressToStoragePath,
@@ -31566,6 +31645,28 @@ async function runWrap(options, deps = {}) {
31566
31645
  backupPath
31567
31646
  );
31568
31647
  if (!verifyOk) process.exit(1);
31648
+ try {
31649
+ upsertPersistedLocalAgent(
31650
+ storagePath,
31651
+ buildLocalAgentRecord({
31652
+ storagePath,
31653
+ platform: agentConfig.platform
31654
+ })
31655
+ );
31656
+ } catch (err) {
31657
+ console.error(
31658
+ ` Note: v1.1 hub agent record not persisted (${err.message}). Re-run \`sanctuary wrap\` to retry, or check storage permissions on ${storagePath}.`
31659
+ );
31660
+ }
31661
+ if (options.noDashboard) {
31662
+ const toolName2 = toolNameFor(agentConfig.platform, agentConfig.servers);
31663
+ printWrapSuccessNoDashboard({
31664
+ toolName: toolName2,
31665
+ version: readPackageVersion(),
31666
+ toolCount: countUpstreamTools(upstreamServers),
31667
+ serverCount: upstreamServers.length});
31668
+ return;
31669
+ }
31569
31670
  const authToken = generateAuthToken();
31570
31671
  const startFn = deps.startDashboard ?? ((opts) => startDashboard({
31571
31672
  port: opts.port,
@@ -31605,7 +31706,12 @@ async function runWrap(options, deps = {}) {
31605
31706
  buildV11Bindings({
31606
31707
  identityId: `fortress:${storagePath}`,
31607
31708
  fortressId: fortressIdFromStoragePath(storagePath),
31608
- auditLog: wrapAuditLog
31709
+ auditLog: wrapAuditLog,
31710
+ // v1.1.5 (Finding Z): rehydrate from the file the upsert
31711
+ // above just wrote, so the registry the wrap-auto dashboard
31712
+ // serves contains this wrap plus any prior wraps against the
31713
+ // same fortress.
31714
+ storagePath
31609
31715
  })
31610
31716
  );
31611
31717
  dashboard.setV11LoopbackAutoAuth(true);
@@ -31745,6 +31851,32 @@ function formatWrapSuccess(info) {
31745
31851
  function printWrapSuccess(info) {
31746
31852
  console.error(formatWrapSuccess(info));
31747
31853
  }
31854
+ function formatWrapSuccessNoDashboard(info) {
31855
+ const g = (s) => `\x1B[32m${s}\x1B[0m`;
31856
+ const d = (s) => `\x1B[2m${s}\x1B[0m`;
31857
+ const b = (s) => `\x1B[1m${s}\x1B[0m`;
31858
+ const check = "\u2713";
31859
+ const lines = [];
31860
+ lines.push("");
31861
+ lines.push(
31862
+ ` ${g(check)} Wrapped ${b(info.toolName)} with Sanctuary v${info.version}`
31863
+ );
31864
+ lines.push(
31865
+ ` ${g(check)} ${info.toolCount} tools registered across ${info.serverCount} upstream server${info.serverCount !== 1 ? "s" : ""}`
31866
+ );
31867
+ lines.push(
31868
+ ` ${d("Dashboard spawn skipped per --no-dashboard. Run `sanctuary dashboard` separately for a persistent dashboard.")}`
31869
+ );
31870
+ lines.push("");
31871
+ lines.push(
31872
+ ` ${b("Your agent is protected.")} L1 Full / L2 Degraded (no TEE) / L3 Full / L4 Full.`
31873
+ );
31874
+ lines.push("");
31875
+ return lines.join("\n");
31876
+ }
31877
+ function printWrapSuccessNoDashboard(info) {
31878
+ console.error(formatWrapSuccessNoDashboard(info));
31879
+ }
31748
31880
  async function verifyRewrittenConfig(configPath, backupPath) {
31749
31881
  try {
31750
31882
  const raw = await readFile(configPath, "utf-8");
@@ -31861,6 +31993,57 @@ function toolNameFor(platform4, _servers) {
31861
31993
  return "your agent";
31862
31994
  }
31863
31995
  }
31996
+ function harnessKindForPlatform(platform4) {
31997
+ switch (platform4) {
31998
+ case "openclaw":
31999
+ return "openclaw";
32000
+ case "hermes":
32001
+ return "hermes";
32002
+ case "claude-code":
32003
+ return "claude_code";
32004
+ case "cursor":
32005
+ return "cursor";
32006
+ case "cline":
32007
+ return "cline";
32008
+ case "generic":
32009
+ return "generic_mcp";
32010
+ default: {
32011
+ return "other";
32012
+ }
32013
+ }
32014
+ }
32015
+ function buildLocalAgentRecord(input) {
32016
+ const harness = harnessKindForPlatform(input.platform);
32017
+ const fortressId = fortressIdFromStoragePath(input.storagePath);
32018
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
32019
+ return {
32020
+ version: "1.1",
32021
+ agent_id: `agent:${harness}:${fortressId}`,
32022
+ identity_id: `fortress:${input.storagePath}`,
32023
+ harness,
32024
+ model_provider: {
32025
+ vendor: "unknown",
32026
+ model_id: "unknown",
32027
+ runs_locally: false
32028
+ },
32029
+ policy_id: "unbound",
32030
+ status: "active",
32031
+ budget_summary: {
32032
+ last_refreshed_at: nowIso
32033
+ },
32034
+ last_activity_at: nowIso,
32035
+ wrapped_at: nowIso,
32036
+ capabilities: {
32037
+ can_pause: false,
32038
+ can_resume: false,
32039
+ can_restart: false,
32040
+ can_unwrap: true,
32041
+ can_lockdown: false,
32042
+ can_chat: false,
32043
+ can_change_template: false
32044
+ }
32045
+ };
32046
+ }
31864
32047
  function countUpstreamTools(servers) {
31865
32048
  return servers.length === 0 ? 0 : servers.length;
31866
32049
  }
@@ -31921,6 +32104,9 @@ function parseWrapArgs(argv) {
31921
32104
  case "--no-open":
31922
32105
  options.noOpen = true;
31923
32106
  break;
32107
+ case "--no-dashboard":
32108
+ options.noDashboard = true;
32109
+ break;
31924
32110
  case "--fortress":
31925
32111
  options.fortress = argv[++i];
31926
32112
  break;
@@ -31961,6 +32147,11 @@ function printWrapHelp() {
31961
32147
  --port <port> Preferred dashboard port (default: 3501)
31962
32148
  --dry-run Show what would happen without making changes
31963
32149
  --no-open Do not auto-open the dashboard in a browser
32150
+ --no-dashboard Do not spawn a per-call dashboard server. Wrap still
32151
+ persists the agent record so a separately-running
32152
+ \`sanctuary dashboard\` (or a later wrap) sees the
32153
+ harness. Use this for the clean operator setup
32154
+ (one persistent dashboard + many wraps).
31964
32155
  --help, -h Show this help
31965
32156
 
31966
32157
  What happens:
@@ -31978,6 +32169,7 @@ var init_cli2 = __esm({
31978
32169
  init_passphrase();
31979
32170
  init_dashboard2();
31980
32171
  init_wiring();
32172
+ init_agent_registry_persistence();
31981
32173
  init_filesystem();
31982
32174
  init_key_derivation();
31983
32175
  init_encoding();
@@ -35615,7 +35807,12 @@ Refusing to start the dashboard while the reset-history marker is unreadable.`
35615
35807
  buildV11Bindings({
35616
35808
  identityId: hubIdentityId,
35617
35809
  fortressId: fortressIdFromStoragePath(config.storage_path),
35618
- auditLog
35810
+ auditLog,
35811
+ // v1.1.5 (Finding Z): rehydrate the hub agent registry from
35812
+ // `<storagePath>/state/_hub/local-agents.json` so the standalone
35813
+ // dashboard surfaces wraps performed by prior `sanctuary wrap`
35814
+ // invocations against this same fortress.
35815
+ storagePath: config.storage_path
35619
35816
  })
35620
35817
  );
35621
35818
  const hostIsLoopback = dashboardHost === "127.0.0.1" || dashboardHost === "::1" || dashboardHost === "localhost";
@@ -35645,6 +35842,10 @@ Refusing to start the dashboard while the reset-history marker is unreadable.`
35645
35842
  console.error(`Sanctuary Dashboard v${SANCTUARY_VERSION} (standalone mode)`);
35646
35843
  console.error(`Storage: ${config.storage_path}`);
35647
35844
  console.error(`Identities loaded: ${loadResult.loaded}`);
35845
+ const persistedAgentsCount = readPersistedLocalAgents(
35846
+ config.storage_path
35847
+ ).length;
35848
+ console.error(`Local agents loaded: ${persistedAgentsCount}`);
35648
35849
  console.error(`Listening: http://${dashboardHost}:${dashboardPort}`);
35649
35850
  if (loadResult.total > 0 && loadResult.loaded === 0) {
35650
35851
  const service = keychainServiceFor(config.storage_path, homedir());
@@ -35703,6 +35904,7 @@ var init_dashboard_standalone = __esm({
35703
35904
  init_recovery_key_disclosure();
35704
35905
  init_discovery();
35705
35906
  init_wiring();
35907
+ init_agent_registry_persistence();
35706
35908
  }
35707
35909
  });
35708
35910