indelible-mcp 5.8.1 → 5.8.2

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.
Files changed (3) hide show
  1. package/LICENSE +1 -1
  2. package/package.json +1 -1
  3. package/src/index.js +60 -10
package/LICENSE CHANGED
@@ -15,7 +15,7 @@ Additional Use Grant: You may make use of the Licensed Work for personal,
15
15
  commercial blockchain storage, session saving, or
16
16
  encrypted vault service without written permission
17
17
  from the Licensor.
18
- Change Date: August 18, 2030
18
+ Change Date: August 19, 2030
19
19
  Change License: MIT License
20
20
 
21
21
  Terms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indelible-mcp",
3
- "version": "5.8.1",
3
+ "version": "5.8.2",
4
4
  "description": "Blockchain-backed memory and code storage for Claude Code. Save AI conversations and source code permanently on BSV.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -1025,7 +1025,8 @@ function claimRefusalMessage(claim2) {
1025
1025
  if (r === "WALLET_VIEW_DEGRADED") {
1026
1026
  const seen = Number.isFinite(claim2.visibleSats) ? `${claim2.visibleSats} sats` : "the coins known locally";
1027
1027
  const needs = Number.isFinite(claim2.need) ? ` (needs about ${claim2.need} sats)` : "";
1028
- return `Cannot reach the network to see your full wallet right now. The locally-known coins (${seen}) cannot fund this save${needs}, but your wallet may hold more that is not visible offline. Nothing was spent. Retry when the connection recovers \u2014 do not add funds based on this message.`;
1028
+ const opener = claim2.operator_reason === "INDEX_PARTIAL" ? "The network's view of your wallet is temporarily incomplete (the index is catching up)." : "Cannot reach the network to see your full wallet right now.";
1029
+ return `${opener} The currently-visible coins (${seen}) cannot fund this save${needs}, but your wallet may hold more that is not visible yet. Nothing was spent. Retry in a few minutes \u2014 do not add funds based on this message.`;
1029
1030
  }
1030
1031
  if (r === "NO_COINS") return "No UTXOs available. Fund your wallet.";
1031
1032
  return "No UTXOs available. Fund your wallet.";
@@ -1100,11 +1101,13 @@ async function claimSpendableUtxos(address, bridgeFallback, opts = {}) {
1100
1101
  }
1101
1102
  }
1102
1103
  let _bridgeDegraded = false;
1104
+ let _bridgePartial = null;
1103
1105
  const bridgeView = async () => {
1104
1106
  if (!bridgeFallback) return [];
1105
1107
  try {
1106
1108
  const v = await bridgeFallback(address);
1107
1109
  _bridgeDegraded = false;
1110
+ _bridgePartial = v?.coverage?.known && v.coverage.partial ? v.coverage : null;
1108
1111
  return v;
1109
1112
  } catch {
1110
1113
  _bridgeDegraded = true;
@@ -1121,6 +1124,17 @@ async function claimSpendableUtxos(address, bridgeFallback, opts = {}) {
1121
1124
  } else {
1122
1125
  candidates = unionCandidates(address, await bridgeView());
1123
1126
  }
1127
+ if (_phase === "union" && candidates.length === 0 && (_bridgeDegraded || _bridgePartial)) {
1128
+ return {
1129
+ refused: true,
1130
+ reason: "WALLET_VIEW_DEGRADED",
1131
+ operator_reason: _bridgeDegraded ? "BRIDGE_UNREACHABLE" : "INDEX_PARTIAL",
1132
+ ..._bridgePartial ? { index_holes: _bridgePartial.holes, index_source: _bridgePartial.source } : {},
1133
+ underlying: "NO_COINS",
1134
+ visibleSats: 0,
1135
+ reservedSats: 0
1136
+ };
1137
+ }
1124
1138
  const claimed = await withFileLock(LOCK_PATH, async () => {
1125
1139
  const store = readReservations();
1126
1140
  releaseDeadPreSign(store, now);
@@ -1137,8 +1151,17 @@ async function claimSpendableUtxos(address, bridgeFallback, opts = {}) {
1137
1151
  if (wholeWallet.ok) return { busy: true };
1138
1152
  const { ok: _ok, ...evidence } = selected;
1139
1153
  const reservedSats = (candidates || []).filter((u) => reserved.has(inputKey(u))).reduce((s, u) => s + (u?.value || 0), 0);
1140
- if (_bridgeDegraded && evidence.reason !== "TX_TOO_LARGE") {
1141
- return { refused: true, reason: "WALLET_VIEW_DEGRADED", underlying: evidence.reason, need: evidence.need, visibleSats: evidence.available, reservedSats };
1154
+ if ((_bridgeDegraded || _bridgePartial) && evidence.reason !== "TX_TOO_LARGE") {
1155
+ return {
1156
+ refused: true,
1157
+ reason: "WALLET_VIEW_DEGRADED",
1158
+ operator_reason: _bridgeDegraded ? "BRIDGE_UNREACHABLE" : "INDEX_PARTIAL",
1159
+ ..._bridgePartial ? { index_holes: _bridgePartial.holes, index_source: _bridgePartial.source } : {},
1160
+ underlying: evidence.reason,
1161
+ need: evidence.need,
1162
+ visibleSats: evidence.available,
1163
+ reservedSats
1164
+ };
1142
1165
  }
1143
1166
  return { refused: true, ...evidence, reservedSats };
1144
1167
  }
@@ -1597,14 +1620,27 @@ async function checkHealth() {
1597
1620
  return false;
1598
1621
  }
1599
1622
  }
1623
+ function coverageFromHeaders(headers) {
1624
+ try {
1625
+ const src = headers?.get?.("x-utxo-source") || null;
1626
+ const holesRaw = headers?.get?.("x-utxo-holes");
1627
+ if (src === null && (holesRaw === null || holesRaw === void 0)) return { known: false, partial: false, source: null, holes: 0 };
1628
+ const holes = Number.parseInt(holesRaw || "0", 10) || 0;
1629
+ const partial = holes > 0 || /partial/i.test(src || "");
1630
+ return { known: true, partial, source: src, holes };
1631
+ } catch {
1632
+ return { known: false, partial: false, source: null, holes: 0 };
1633
+ }
1634
+ }
1600
1635
  async function getUtxos(address) {
1601
1636
  const path = `/api/address/${address}/unspent`;
1602
1637
  const res = await spvFetch(path);
1603
1638
  const first = await res.json();
1639
+ const firstCoverage = coverageFromHeaders(res.headers);
1604
1640
  if (Array.isArray(first) && first.length > 0) {
1605
1641
  process.stderr.write(`[MCP] UTXOs from bridge: ${first.length}
1606
1642
  `);
1607
- return first;
1643
+ return withCoverage(first, firstCoverage);
1608
1644
  }
1609
1645
  let reached = 0;
1610
1646
  try {
@@ -1615,8 +1651,9 @@ async function getUtxos(address) {
1615
1651
  headers: { "X-API-Key": bridge.apiKey }
1616
1652
  });
1617
1653
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
1618
- return { name: bridge.name, utxos: await r.json() };
1654
+ return { name: bridge.name, utxos: await r.json(), coverage: coverageFromHeaders(r.headers) };
1619
1655
  }));
1656
+ let anyPartial = null;
1620
1657
  for (const s of settled) {
1621
1658
  if (s.status !== "fulfilled") continue;
1622
1659
  reached++;
@@ -1624,8 +1661,14 @@ async function getUtxos(address) {
1624
1661
  if (Array.isArray(u) && u.length > 0) {
1625
1662
  process.stderr.write(`[MCP] UTXOs recovered via fan-out from ${s.value.name}: ${u.length}
1626
1663
  `);
1627
- return u;
1664
+ return withCoverage(u, s.value.coverage);
1628
1665
  }
1666
+ if (s.value.coverage?.known && s.value.coverage.partial) anyPartial = s.value.coverage;
1667
+ }
1668
+ if (anyPartial) {
1669
+ process.stderr.write(`[MCP] UTXOs: 0 across ${reached} bridge(s), but a bridge reports a PARTIAL index (holes=${anyPartial.holes})
1670
+ `);
1671
+ return withCoverage([], anyPartial);
1629
1672
  }
1630
1673
  } catch (e) {
1631
1674
  process.stderr.write(`[MCP] UTXO fan-out error: ${e.message}
@@ -1633,7 +1676,7 @@ async function getUtxos(address) {
1633
1676
  }
1634
1677
  process.stderr.write(`[MCP] UTXOs: 0 across ${reached} bridge(s)
1635
1678
  `);
1636
- return Array.isArray(first) ? first : [];
1679
+ return withCoverage(Array.isArray(first) ? first : [], firstCoverage);
1637
1680
  }
1638
1681
  async function getAddressHistory(address) {
1639
1682
  const res = await spvFetch(`/api/address/${address}/history`);
@@ -2169,7 +2212,7 @@ function extractEncryptedFromTx(tx) {
2169
2212
  const match = str.match(/([A-Za-z0-9+/=]{12,}):([A-Za-z0-9+/=]{20,}):([A-Za-z0-9+/=]{20,})/);
2170
2213
  return match ? match[0] : null;
2171
2214
  }
2172
- var SEED_BRIDGES, OLD_BRIDGE_IPS, bridgeHealth, HEALTH_CHECK_INTERVAL, MAX_FAILURES, migrationDone, SOURCE_FETCH_CONCURRENCY, ARC_NETWORK_STATUSES, StoredError, _stickyBridge, STICKY_WINDOW_MS;
2215
+ var SEED_BRIDGES, OLD_BRIDGE_IPS, bridgeHealth, HEALTH_CHECK_INTERVAL, MAX_FAILURES, migrationDone, withCoverage, SOURCE_FETCH_CONCURRENCY, ARC_NETWORK_STATUSES, StoredError, _stickyBridge, STICKY_WINDOW_MS;
2173
2216
  var init_spv = __esm({
2174
2217
  "mcp-server/lib/spv.js"() {
2175
2218
  init_config_customer();
@@ -2196,6 +2239,13 @@ var init_spv = __esm({
2196
2239
  HEALTH_CHECK_INTERVAL = 6e4;
2197
2240
  MAX_FAILURES = 3;
2198
2241
  migrationDone = false;
2242
+ withCoverage = (utxos, coverage) => {
2243
+ try {
2244
+ Object.defineProperty(utxos, "coverage", { value: coverage, enumerable: false });
2245
+ } catch {
2246
+ }
2247
+ return utxos;
2248
+ };
2199
2249
  SOURCE_FETCH_CONCURRENCY = 8;
2200
2250
  ARC_NETWORK_STATUSES = /* @__PURE__ */ new Set([
2201
2251
  // Tightened 2026-06-17 to match the relay-federation bridge: a node must have
@@ -17454,7 +17504,7 @@ Answer THAT message and nothing else \u2014 the wire also carries unrelated conv
17454
17504
  }
17455
17505
  function printHelp() {
17456
17506
  console.log(`
17457
- Indelible MCP \u2014 Blockchain memory for Claude Code (v5.8.1)
17507
+ Indelible MCP \u2014 Blockchain memory for Claude Code (v5.8.2)
17458
17508
 
17459
17509
  Setup:
17460
17510
  indelible-mcp Set up interactively (recommended \u2014 your key is never written to shell history)
@@ -17783,7 +17833,7 @@ function readStdin() {
17783
17833
  }
17784
17834
  var SERVER_INFO = {
17785
17835
  name: "indelible",
17786
- version: "5.8.1",
17836
+ version: "5.8.2",
17787
17837
  description: "Blockchain-backed memory and code storage for Claude Code"
17788
17838
  };
17789
17839
  var TOOLS = [