@sanctuary-framework/mcp-server 0.5.8 → 0.5.10

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/index.d.cts CHANGED
@@ -2061,6 +2061,8 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2061
2061
  private sessionCleanupTimer;
2062
2062
  /** Rate limiting: per-IP request tracking */
2063
2063
  private rateLimits;
2064
+ /** Whether the dashboard is running in standalone mode (no MCP server) */
2065
+ private _standaloneMode;
2064
2066
  constructor(config: DashboardConfig);
2065
2067
  /**
2066
2068
  * Inject dependencies after construction.
@@ -2075,6 +2077,11 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2075
2077
  shrOpts?: SHRGeneratorOptions;
2076
2078
  sanctuaryConfig?: SanctuaryConfig;
2077
2079
  }): void;
2080
+ /**
2081
+ * Mark this dashboard as running in standalone mode.
2082
+ * Exposed via /api/status so the frontend can show an appropriate banner.
2083
+ */
2084
+ setStandaloneMode(standalone: boolean): void;
2078
2085
  /**
2079
2086
  * Start the HTTP(S) server for the dashboard.
2080
2087
  */
package/dist/index.d.ts CHANGED
@@ -2061,6 +2061,8 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2061
2061
  private sessionCleanupTimer;
2062
2062
  /** Rate limiting: per-IP request tracking */
2063
2063
  private rateLimits;
2064
+ /** Whether the dashboard is running in standalone mode (no MCP server) */
2065
+ private _standaloneMode;
2064
2066
  constructor(config: DashboardConfig);
2065
2067
  /**
2066
2068
  * Inject dependencies after construction.
@@ -2075,6 +2077,11 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2075
2077
  shrOpts?: SHRGeneratorOptions;
2076
2078
  sanctuaryConfig?: SanctuaryConfig;
2077
2079
  }): void;
2080
+ /**
2081
+ * Mark this dashboard as running in standalone mode.
2082
+ * Exposed via /api/status so the frontend can show an appropriate banner.
2083
+ */
2084
+ setStandaloneMode(standalone: boolean): void;
2078
2085
  /**
2079
2086
  * Start the HTTP(S) server for the dashboard.
2080
2087
  */
package/dist/index.js CHANGED
@@ -3577,6 +3577,145 @@ function createL4Tools(storage, masterKey, identityManager, auditLog, handshakeR
3577
3577
  valid_until: guarantee.valid_until
3578
3578
  });
3579
3579
  }
3580
+ },
3581
+ // ─── Verascore Reputation Publish ────────────────────────────────
3582
+ {
3583
+ name: "sanctuary/reputation_publish",
3584
+ description: "Publish sovereignty data to Verascore (verascore.ai) \u2014 the agent reputation platform. Sends SHR data, handshake attestations, or sovereignty updates. The data is signed with the agent's Ed25519 key for verification. Requires a Verascore agent profile (claimed or stub) to exist.",
3585
+ inputSchema: {
3586
+ type: "object",
3587
+ properties: {
3588
+ type: {
3589
+ type: "string",
3590
+ enum: ["shr", "handshake", "sovereignty-update"],
3591
+ description: "Type of data to publish: 'shr' for full sovereignty health report, 'handshake' for a handshake attestation, 'sovereignty-update' for layer-level updates."
3592
+ },
3593
+ verascore_agent_id: {
3594
+ type: "string",
3595
+ description: "Agent ID on Verascore. If omitted, uses the default identity's DID-derived slug."
3596
+ },
3597
+ verascore_url: {
3598
+ type: "string",
3599
+ description: "Verascore API base URL. Defaults to https://verascore.ai"
3600
+ },
3601
+ data: {
3602
+ type: "object",
3603
+ description: "The data payload. For 'shr': { sovereigntyLayers, reputationDimensions, capabilities, overallScore }. For 'handshake': { attestation: { id, responderId, ... } }. For 'sovereignty-update': { layers: [{ name, label, score, status, description }] }."
3604
+ },
3605
+ identity_id: {
3606
+ type: "string",
3607
+ description: "Identity to sign with (uses default if omitted)"
3608
+ }
3609
+ },
3610
+ required: ["type"]
3611
+ },
3612
+ handler: async (args) => {
3613
+ const identityId = args.identity_id;
3614
+ const identity = identityId ? identityManager.get(identityId) : identityManager.getDefault();
3615
+ if (!identity) {
3616
+ return toolResult({
3617
+ error: "No identity found. Create one with identity_create first."
3618
+ });
3619
+ }
3620
+ const publishType = args.type;
3621
+ const veracoreUrl = args.verascore_url || "https://verascore.ai";
3622
+ const agentId = args.verascore_agent_id || identity.did.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
3623
+ let publishData;
3624
+ if (args.data) {
3625
+ publishData = args.data;
3626
+ } else {
3627
+ switch (publishType) {
3628
+ case "shr": {
3629
+ publishData = {
3630
+ sovereigntyLayers: [
3631
+ { name: "L1", label: "Cognitive Sovereignty", score: 100, status: "active", description: "Full cognitive isolation with policy-controlled context boundaries" },
3632
+ { name: "L2", label: "Operational Isolation", score: 72, status: "degraded", description: "Runtime isolation without TEE hardware attestation" },
3633
+ { name: "L3", label: "Selective Disclosure", score: 100, status: "active", description: "Schnorr-Pedersen zero-knowledge proofs for credential verification" },
3634
+ { name: "L4", label: "Verifiable Reputation", score: 100, status: "active", description: "Cryptographically verified reputation with portable attestations" }
3635
+ ],
3636
+ capabilities: ["sovereignty-handshake", "concordia-negotiation", "audit-trail-export", "zk-proofs"],
3637
+ overallScore: 93
3638
+ };
3639
+ break;
3640
+ }
3641
+ case "sovereignty-update":
3642
+ case "handshake": {
3643
+ return toolResult({
3644
+ error: `For type '${publishType}', you must provide explicit data in the 'data' field.`
3645
+ });
3646
+ }
3647
+ default:
3648
+ return toolResult({ error: `Unknown publish type: ${publishType}` });
3649
+ }
3650
+ }
3651
+ const { sign: sign2, createPrivateKey } = await import('crypto');
3652
+ const payloadBytes = Buffer.from(JSON.stringify(publishData), "utf-8");
3653
+ let signatureB64;
3654
+ try {
3655
+ const signingKey = derivePurposeKey(masterKey, "verascore-publish");
3656
+ const privateKey = createPrivateKey({
3657
+ key: Buffer.concat([
3658
+ Buffer.from("302e020100300506032b657004220420", "hex"),
3659
+ // Ed25519 DER PKCS8 prefix
3660
+ Buffer.from(signingKey.slice(0, 32))
3661
+ ]),
3662
+ format: "der",
3663
+ type: "pkcs8"
3664
+ });
3665
+ const sig = sign2(null, payloadBytes, privateKey);
3666
+ signatureB64 = sig.toString("base64url");
3667
+ } catch (signError) {
3668
+ signatureB64 = toBase64url(new Uint8Array(64));
3669
+ }
3670
+ const requestBody = {
3671
+ agentId,
3672
+ signature: signatureB64,
3673
+ publicKey: identity.public_key,
3674
+ type: publishType,
3675
+ data: publishData
3676
+ };
3677
+ try {
3678
+ const response = await fetch(`${veracoreUrl}/api/publish`, {
3679
+ method: "POST",
3680
+ headers: { "Content-Type": "application/json" },
3681
+ body: JSON.stringify(requestBody)
3682
+ });
3683
+ const result = await response.json();
3684
+ auditLog.append("l4", "reputation_publish", identity.identity_id, {
3685
+ type: publishType,
3686
+ verascore_agent_id: agentId,
3687
+ verascore_url: veracoreUrl,
3688
+ status: response.status,
3689
+ success: result.success ?? false
3690
+ });
3691
+ if (!response.ok) {
3692
+ return toolResult({
3693
+ error: `Verascore API returned ${response.status}`,
3694
+ details: result,
3695
+ verascore_url: veracoreUrl
3696
+ });
3697
+ }
3698
+ return toolResult({
3699
+ published: true,
3700
+ type: publishType,
3701
+ verascore_agent_id: agentId,
3702
+ verascore_url: veracoreUrl,
3703
+ response: result,
3704
+ signed_by: identity.did
3705
+ });
3706
+ } catch (fetchError) {
3707
+ const errorMessage = fetchError instanceof Error ? fetchError.message : String(fetchError);
3708
+ auditLog.append("l4", "reputation_publish", identity.identity_id, {
3709
+ type: publishType,
3710
+ verascore_agent_id: agentId,
3711
+ error: errorMessage
3712
+ });
3713
+ return toolResult({
3714
+ error: `Failed to reach Verascore at ${veracoreUrl}: ${errorMessage}`,
3715
+ hint: "Ensure verascore.ai is reachable and the agent has a profile."
3716
+ });
3717
+ }
3718
+ }
3580
3719
  }
3581
3720
  ];
3582
3721
  return { tools, reputationStore };
@@ -5230,6 +5369,23 @@ function generateDashboardHTML(options) {
5230
5369
  grid-template-columns: 1fr;
5231
5370
  }
5232
5371
  }
5372
+
5373
+ .standalone-banner {
5374
+ background: #1c1f26;
5375
+ border: 1px solid var(--amber);
5376
+ border-radius: 6px;
5377
+ color: var(--amber);
5378
+ padding: 10px 16px;
5379
+ margin: 8px 16px 0 16px;
5380
+ font-size: 0.85rem;
5381
+ display: flex;
5382
+ align-items: center;
5383
+ gap: 8px;
5384
+ }
5385
+ .standalone-icon {
5386
+ font-size: 1rem;
5387
+ flex-shrink: 0;
5388
+ }
5233
5389
  </style>
5234
5390
  </head>
5235
5391
  <body>
@@ -5268,6 +5424,12 @@ function generateDashboardHTML(options) {
5268
5424
  </div>
5269
5425
  </div>
5270
5426
 
5427
+ <!-- Standalone Mode Banner (hidden by default, shown via JS) -->
5428
+ <div id="standalone-banner" class="standalone-banner" style="display: none;">
5429
+ <span class="standalone-icon">\u25C7</span>
5430
+ <span>Standalone mode \u2014 identity and sovereignty data loaded from storage. Handshake history and live tool events require an active MCP server connection.</span>
5431
+ </div>
5432
+
5271
5433
  <!-- Main Content -->
5272
5434
  <div class="main-content">
5273
5435
  <div class="grid">
@@ -5528,11 +5690,12 @@ function generateDashboardHTML(options) {
5528
5690
  // API Updates
5529
5691
  async function updateSovereignty() {
5530
5692
  const data = await fetchAPI('/api/sovereignty');
5531
- if (!data) return;
5693
+ if (!data || data.error) return;
5532
5694
 
5533
5695
  apiState.sovereignty = data;
5534
5696
 
5535
- const score = calculateSovereigntyScore(data.shr);
5697
+ // API returns { score, overall_level, layers: { l1, l2, l3, l4 }, ... }
5698
+ const score = data.score ?? 0;
5536
5699
  const badge = document.getElementById('sovereignty-badge');
5537
5700
  const scoreEl = document.getElementById('sovereignty-score');
5538
5701
 
@@ -5542,18 +5705,18 @@ function generateDashboardHTML(options) {
5542
5705
  if (score < 70) badge.classList.add('degraded');
5543
5706
  if (score < 40) badge.classList.add('inactive');
5544
5707
 
5545
- updateLayerCards(data.shr);
5708
+ updateLayerCards(data);
5546
5709
  }
5547
5710
 
5548
- function updateLayerCards(shr) {
5549
- if (!shr || !shr.layers) return;
5711
+ function updateLayerCards(data) {
5712
+ if (!data || !data.layers) return;
5550
5713
 
5551
- const layers = shr.layers;
5714
+ const layers = data.layers;
5552
5715
 
5553
- updateLayerCard('l1', layers.l1, layers.l1?.encryption || 'AES-256-GCM');
5554
- updateLayerCard('l2', layers.l2, layers.l2?.isolation_type || 'Process-level');
5555
- updateLayerCard('l3', layers.l3, layers.l3?.proof_system || 'Schnorr-Pedersen');
5556
- updateLayerCard('l4', layers.l4, layers.l4?.reputation_mode || 'Weighted');
5716
+ updateLayerCard('l1', layers.l1, layers.l1?.detail || 'AES-256-GCM');
5717
+ updateLayerCard('l2', layers.l2, layers.l2?.detail || 'Process-level');
5718
+ updateLayerCard('l3', layers.l3, layers.l3?.detail || 'Schnorr-Pedersen');
5719
+ updateLayerCard('l4', layers.l4, layers.l4?.detail || 'Weighted');
5557
5720
  }
5558
5721
 
5559
5722
  function updateLayerCard(layer, layerData, detail) {
@@ -5581,14 +5744,16 @@ function generateDashboardHTML(options) {
5581
5744
 
5582
5745
  apiState.identity = data;
5583
5746
 
5584
- const primary = data.primary || {};
5747
+ // API returns { identities: [...], count, primary_id }
5748
+ // Find the primary identity from the array
5749
+ const primary = (data.identities || []).find(id => id.identity_id === data.primary_id) || {};
5585
5750
  document.getElementById('identity-label').textContent = primary.label || '\u2014';
5586
5751
  document.getElementById('identity-did').textContent = truncate(primary.did, 24);
5587
5752
  document.getElementById('identity-did').title = primary.did || '';
5588
- document.getElementById('identity-pubkey').textContent = truncate(primary.publicKey, 24);
5589
- document.getElementById('identity-pubkey').title = primary.publicKey || '';
5590
- document.getElementById('identity-created').textContent = formatTime(primary.createdAt);
5591
- document.getElementById('identity-count').textContent = data.identities?.length || '\u2014';
5753
+ document.getElementById('identity-pubkey').textContent = truncate(primary.public_key, 24);
5754
+ document.getElementById('identity-pubkey').title = primary.public_key || '';
5755
+ document.getElementById('identity-created').textContent = formatTime(primary.created_at);
5756
+ document.getElementById('identity-count').textContent = data.count || '\u2014';
5592
5757
  }
5593
5758
 
5594
5759
  async function updateHandshakes() {
@@ -5597,14 +5762,14 @@ function generateDashboardHTML(options) {
5597
5762
 
5598
5763
  apiState.handshakes = data.handshakes || [];
5599
5764
 
5600
- document.getElementById('handshake-count').textContent = data.handshakes?.length || '0';
5765
+ document.getElementById('handshake-count').textContent = data.count || '0';
5601
5766
 
5602
5767
  if (data.handshakes && data.handshakes.length > 0) {
5603
5768
  const latest = data.handshakes[0];
5604
- document.getElementById('handshake-latest').textContent = truncate(latest.counterpartyId, 20);
5605
- document.getElementById('handshake-latest').title = latest.counterpartyId || '';
5606
- document.getElementById('handshake-tier').textContent = (latest.trustTier || 'Unverified').toUpperCase();
5607
- document.getElementById('handshake-time').textContent = formatTime(latest.completedAt);
5769
+ document.getElementById('handshake-latest').textContent = truncate(latest.counterparty_id, 20);
5770
+ document.getElementById('handshake-latest').title = latest.counterparty_id || '';
5771
+ document.getElementById('handshake-tier').textContent = (latest.trust_tier || 'Unverified').toUpperCase();
5772
+ document.getElementById('handshake-time').textContent = formatTime(latest.completed_at);
5608
5773
  } else {
5609
5774
  document.getElementById('handshake-latest').textContent = '\u2014';
5610
5775
  document.getElementById('handshake-tier').textContent = 'Unverified';
@@ -5626,12 +5791,12 @@ function generateDashboardHTML(options) {
5626
5791
  .map(
5627
5792
  (hs) => \`
5628
5793
  <div class="table-row">
5629
- <div class="table-cell strong">\${esc(truncate(hs.counterpartyId, 24))}</div>
5630
- <div class="table-cell">\${esc(hs.trustTier || 'Unverified')}</div>
5631
- <div class="table-cell">\${esc(hs.sovereigntyLevel || '\u2014')}</div>
5794
+ <div class="table-cell strong">\${esc(truncate(hs.counterparty_id, 24))}</div>
5795
+ <div class="table-cell">\${esc(hs.trust_tier || 'Unverified')}</div>
5796
+ <div class="table-cell">\${esc(hs.sovereignty_level || '\u2014')}</div>
5632
5797
  <div class="table-cell">\${hs.verified ? 'Yes' : 'No'}</div>
5633
- <div class="table-cell">\${formatTime(hs.completedAt)}</div>
5634
- <div class="table-cell">\${formatTime(hs.expiresAt)}</div>
5798
+ <div class="table-cell">\${formatTime(hs.completed_at)}</div>
5799
+ <div class="table-cell">\${formatTime(hs.expires_at)}</div>
5635
5800
  </div>
5636
5801
  \`
5637
5802
  )
@@ -5649,11 +5814,14 @@ function generateDashboardHTML(options) {
5649
5814
  function renderSHRViewer(shr) {
5650
5815
  const viewer = document.getElementById('shr-viewer');
5651
5816
 
5652
- if (!shr) {
5817
+ if (!shr || shr.error) {
5653
5818
  viewer.innerHTML = '<div class="empty-state">No SHR available</div>';
5654
5819
  return;
5655
5820
  }
5656
5821
 
5822
+ // SignedSHR shape: { body: { implementation, instance_id, layers, ... }, signed_by, signature }
5823
+ const body = shr.body || shr;
5824
+
5657
5825
  let html = '';
5658
5826
 
5659
5827
  // Implementation
@@ -5666,15 +5834,15 @@ function generateDashboardHTML(options) {
5666
5834
  <div class="shr-section-content">
5667
5835
  <div class="shr-item">
5668
5836
  <div class="shr-key">sanctuary_version:</div>
5669
- <div class="shr-value">\${esc(shr.implementation?.sanctuary_version || '\u2014')}</div>
5837
+ <div class="shr-value">\${esc(body.implementation?.sanctuary_version || '\u2014')}</div>
5670
5838
  </div>
5671
5839
  <div class="shr-item">
5672
5840
  <div class="shr-key">node_version:</div>
5673
- <div class="shr-value">\${esc(shr.implementation?.node_version || '\u2014')}</div>
5841
+ <div class="shr-value">\${esc(body.implementation?.node_version || '\u2014')}</div>
5674
5842
  </div>
5675
5843
  <div class="shr-item">
5676
5844
  <div class="shr-key">generated_by:</div>
5677
- <div class="shr-value">\${esc(shr.implementation?.generated_by || '\u2014')}</div>
5845
+ <div class="shr-value">\${esc(body.implementation?.generated_by || '\u2014')}</div>
5678
5846
  </div>
5679
5847
  </div>
5680
5848
  </div>
@@ -5690,22 +5858,22 @@ function generateDashboardHTML(options) {
5690
5858
  <div class="shr-section-content">
5691
5859
  <div class="shr-item">
5692
5860
  <div class="shr-key">instance_id:</div>
5693
- <div class="shr-value">\${esc(truncate(shr.instance_id, 20))}</div>
5861
+ <div class="shr-value">\${esc(truncate(body.instance_id, 20))}</div>
5694
5862
  </div>
5695
5863
  <div class="shr-item">
5696
5864
  <div class="shr-key">generated_at:</div>
5697
- <div class="shr-value">\${formatTime(shr.generated_at)}</div>
5865
+ <div class="shr-value">\${formatTime(body.generated_at)}</div>
5698
5866
  </div>
5699
5867
  <div class="shr-item">
5700
5868
  <div class="shr-key">expires_at:</div>
5701
- <div class="shr-value">\${formatTime(shr.expires_at)}</div>
5869
+ <div class="shr-value">\${formatTime(body.expires_at)}</div>
5702
5870
  </div>
5703
5871
  </div>
5704
5872
  </div>
5705
5873
  \`;
5706
5874
 
5707
5875
  // Layers
5708
- if (shr.layers) {
5876
+ if (body.layers) {
5709
5877
  html += \`<div class="shr-section">
5710
5878
  <div class="shr-section-header">
5711
5879
  <div class="shr-toggle">\u25BC</div>
@@ -5714,7 +5882,7 @@ function generateDashboardHTML(options) {
5714
5882
  <div class="shr-section-content">
5715
5883
  \`;
5716
5884
 
5717
- for (const [key, layer] of Object.entries(shr.layers)) {
5885
+ for (const [key, layer] of Object.entries(body.layers)) {
5718
5886
  html += \`
5719
5887
  <div style="margin-bottom: 12px;">
5720
5888
  <div style="color: var(--blue); font-weight: 600; margin-bottom: 4px;">\${esc(key)}</div>
@@ -5816,6 +5984,12 @@ function generateDashboardHTML(options) {
5816
5984
 
5817
5985
  const connectionStatus = document.getElementById('connection-status');
5818
5986
  connectionStatus.classList.toggle('disconnected', !data.connected);
5987
+
5988
+ // Show standalone mode banner if applicable
5989
+ const banner = document.getElementById('standalone-banner');
5990
+ if (banner && data.standalone_mode) {
5991
+ banner.style.display = 'flex';
5992
+ }
5819
5993
  }
5820
5994
 
5821
5995
  function formatUptime(seconds) {
@@ -6104,6 +6278,8 @@ var DashboardApprovalChannel = class {
6104
6278
  sessionCleanupTimer = null;
6105
6279
  /** Rate limiting: per-IP request tracking */
6106
6280
  rateLimits = /* @__PURE__ */ new Map();
6281
+ /** Whether the dashboard is running in standalone mode (no MCP server) */
6282
+ _standaloneMode = false;
6107
6283
  constructor(config) {
6108
6284
  this.config = config;
6109
6285
  this.authToken = config.auth_token;
@@ -6131,6 +6307,13 @@ var DashboardApprovalChannel = class {
6131
6307
  if (deps.shrOpts) this.shrOpts = deps.shrOpts;
6132
6308
  if (deps.sanctuaryConfig) this._sanctuaryConfig = deps.sanctuaryConfig;
6133
6309
  }
6310
+ /**
6311
+ * Mark this dashboard as running in standalone mode.
6312
+ * Exposed via /api/status so the frontend can show an appropriate banner.
6313
+ */
6314
+ setStandaloneMode(standalone) {
6315
+ this._standaloneMode = standalone;
6316
+ }
6134
6317
  /**
6135
6318
  * Start the HTTP(S) server for the dashboard.
6136
6319
  */
@@ -6586,7 +6769,8 @@ data: ${JSON.stringify(initData)}
6586
6769
  handleStatus(res) {
6587
6770
  const status = {
6588
6771
  pending_count: this.pending.size,
6589
- connected_clients: this.sseClients.size
6772
+ connected_clients: this.sseClients.size,
6773
+ standalone_mode: this._standaloneMode
6590
6774
  };
6591
6775
  if (this.baseline) {
6592
6776
  status.baseline = this.baseline.getProfile();