indelible-mcp 2.4.0 → 2.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "indelible-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
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
@@ -220,7 +220,7 @@ Commands:
220
220
 
221
221
  function printHelp() {
222
222
  console.log(`
223
- Indelible MCP — Blockchain memory for Claude Code (v2.4.0)
223
+ Indelible MCP — Blockchain memory for Claude Code (v2.5.1)
224
224
 
225
225
  Setup:
226
226
  indelible-mcp setup --wif=KEY --pin=PIN Import and encrypt your private key
@@ -316,7 +316,7 @@ function readStdin() {
316
316
 
317
317
  const SERVER_INFO = {
318
318
  name: 'indelible',
319
- version: '2.4.0',
319
+ version: '2.5.1',
320
320
  description: 'Blockchain-backed memory and code storage for Claude Code'
321
321
  }
322
322
 
@@ -380,6 +380,62 @@ const TOOLS = [
380
380
  },
381
381
  required: []
382
382
  }
383
+ },
384
+ {
385
+ name: 'save_file',
386
+ description: 'Save a single file to the blockchain. Encrypted with your key, permanent and restorable.',
387
+ inputSchema: {
388
+ type: 'object',
389
+ properties: {
390
+ file_path: { type: 'string', description: 'Path to the file to save' }
391
+ },
392
+ required: ['file_path']
393
+ }
394
+ },
395
+ {
396
+ name: 'load_file',
397
+ description: 'Load a file from the blockchain by transaction ID.',
398
+ inputSchema: {
399
+ type: 'object',
400
+ properties: {
401
+ txid: { type: 'string', description: 'Transaction ID of the saved file' },
402
+ output_path: { type: 'string', description: 'Path to write the restored file (optional)' }
403
+ },
404
+ required: ['txid']
405
+ }
406
+ },
407
+ {
408
+ name: 'save_project',
409
+ description: 'Save an entire project directory to the blockchain. All files encrypted and bundled.',
410
+ inputSchema: {
411
+ type: 'object',
412
+ properties: {
413
+ dir_path: { type: 'string', description: 'Path to the project directory' },
414
+ name: { type: 'string', description: 'Project name (optional, defaults to directory name)' }
415
+ },
416
+ required: ['dir_path']
417
+ }
418
+ },
419
+ {
420
+ name: 'load_project',
421
+ description: 'Load a project from the blockchain by transaction ID.',
422
+ inputSchema: {
423
+ type: 'object',
424
+ properties: {
425
+ txid: { type: 'string', description: 'Transaction ID of the saved project' },
426
+ output_dir: { type: 'string', description: 'Directory to restore into (optional)' }
427
+ },
428
+ required: ['txid']
429
+ }
430
+ },
431
+ {
432
+ name: 'update_vault_index',
433
+ description: 'Update the on-chain vault index with metadata for all saved files and projects.',
434
+ inputSchema: {
435
+ type: 'object',
436
+ properties: {},
437
+ required: []
438
+ }
383
439
  }
384
440
  ]
385
441
 
@@ -421,6 +477,21 @@ async function handleMcpRequest(request) {
421
477
  case 'load_style':
422
478
  result = await loadStyle(args?.txid)
423
479
  break
480
+ case 'save_file':
481
+ result = await saveFile(args?.file_path)
482
+ break
483
+ case 'load_file':
484
+ result = await loadFile(args?.txid, { outputPath: args?.output_path })
485
+ break
486
+ case 'save_project':
487
+ result = await saveProject(args?.dir_path, { name: args?.name })
488
+ break
489
+ case 'load_project':
490
+ result = await loadProject(args?.txid, { outputDir: args?.output_dir })
491
+ break
492
+ case 'update_vault_index':
493
+ result = await updateVaultIndex()
494
+ break
424
495
  default:
425
496
  throw new Error(`Unknown tool: ${name}`)
426
497
  }
@@ -12,6 +12,7 @@ import { loadConfig, getWif } from '../lib/config.js'
12
12
  import { decrypt } from '../lib/crypto.js'
13
13
  import { getSessions } from '../lib/api-client.js'
14
14
  import * as spv from '../lib/spv.js'
15
+ import { loadStyle } from './load_style.js'
15
16
 
16
17
  const RECENT_MESSAGES_FULL = 25
17
18
  const OLDER_MESSAGES_SUMMARIZED = 15
@@ -226,6 +227,19 @@ export async function loadContext(numSessions = 5) {
226
227
  }
227
228
  }
228
229
 
230
+ // Auto-inject active style if one exists in config
231
+ try {
232
+ const styleResult = await loadStyle()
233
+ if (styleResult.success && styleResult.rules) {
234
+ contextParts.push('')
235
+ contextParts.push(`# AI Interaction Style: ${styleResult.name || 'default'}`)
236
+ contextParts.push(`Loaded from blockchain tx: ${styleResult.txId}`)
237
+ contextParts.push(`Owner: ${styleResult.owner || config.address} | Created: ${styleResult.createdAt || 'Unknown'}`)
238
+ contextParts.push('')
239
+ contextParts.push(styleResult.rules)
240
+ }
241
+ } catch { /* style injection is best-effort — don't break session restore */ }
242
+
229
243
  return {
230
244
  success: true,
231
245
  context: contextParts.join('\n'),
@@ -34,7 +34,7 @@ export async function saveFile(filePath, options = {}) {
34
34
  const filenameEnc = encrypt(filename, wif)
35
35
  const pathEnc = encrypt(relativePath, wif)
36
36
 
37
- let utxos = await spv.getUtxos(config.address)
37
+ let utxos = options.utxos || await spv.getUtxos(config.address)
38
38
  if (!utxos || utxos.length === 0) {
39
39
  return { success: false, error: 'No UTXOs available. Fund your wallet first.' }
40
40
  }
@@ -43,6 +43,7 @@ export async function saveFile(filePath, options = {}) {
43
43
 
44
44
  try {
45
45
  let masterTxId
46
+ let finalChangeUtxos = null
46
47
 
47
48
  if (encrypted.length <= MAX_CHUNK_SIZE) {
48
49
  const payload = {
@@ -57,11 +58,12 @@ export async function saveFile(filePath, options = {}) {
57
58
  timestamp
58
59
  }
59
60
 
60
- const { txHex, txId } = await spv.buildOpReturnTxWithChange(
61
+ const { txHex, txId, changeUtxos } = await spv.buildOpReturnTxWithChange(
61
62
  wif, utxos, JSON.stringify(payload)
62
63
  )
63
64
  const result = await spv.broadcastTx(txHex)
64
65
  masterTxId = result.txid || txId
66
+ finalChangeUtxos = changeUtxos
65
67
  } else {
66
68
  // Chunked upload
67
69
  const chunks = []
@@ -118,11 +120,12 @@ export async function saveFile(filePath, options = {}) {
118
120
  await new Promise(r => setTimeout(r, 500))
119
121
  utxos = await spv.getUtxos(config.address)
120
122
  }
121
- const { txHex, txId } = await spv.buildOpReturnTxWithChange(
123
+ const { txHex, txId, changeUtxos: masterChangeUtxos } = await spv.buildOpReturnTxWithChange(
122
124
  wif, utxos, JSON.stringify(masterPayload)
123
125
  )
124
126
  const result = await spv.broadcastTx(txHex)
125
127
  masterTxId = result.txid || txId
128
+ finalChangeUtxos = masterChangeUtxos
126
129
  }
127
130
 
128
131
  // Track in config
@@ -147,6 +150,7 @@ export async function saveFile(filePath, options = {}) {
147
150
  size: fileSize,
148
151
  contentHash: `sha256:${contentHash}`,
149
152
  chunks: chunkCount,
153
+ changeUtxos: finalChangeUtxos,
150
154
  message: `File "${filename}" saved to blockchain. txId: ${masterTxId}${chunkCount > 1 ? ` (${chunkCount} chunks)` : ''}`
151
155
  }
152
156
  } catch (error) {
@@ -97,11 +97,19 @@ export async function saveProject(dirPath, options = {}) {
97
97
  let totalSize = 0
98
98
 
99
99
  try {
100
+ let lastChangeUtxos = null
101
+
100
102
  for (let i = 0; i < allFiles.length; i++) {
101
103
  const file = allFiles[i]
102
- const result = await saveFile(file.absolutePath, {
104
+ const fileOpts = {
103
105
  relativePath: `${projectName}/${file.relativePath}`
104
- })
106
+ }
107
+ // Chain: pass previous file's change UTXOs so we don't fight over the same UTXO
108
+ if (lastChangeUtxos && lastChangeUtxos.length > 0) {
109
+ fileOpts.utxos = lastChangeUtxos
110
+ }
111
+
112
+ const result = await saveFile(file.absolutePath, fileOpts)
105
113
 
106
114
  if (!result.success) {
107
115
  return {
@@ -112,6 +120,9 @@ export async function saveProject(dirPath, options = {}) {
112
120
  }
113
121
  }
114
122
 
123
+ // Capture change UTXOs for next file
124
+ lastChangeUtxos = result.changeUtxos
125
+
115
126
  savedFiles.push({
116
127
  path: file.relativePath,
117
128
  txid: result.txId,
@@ -142,7 +153,10 @@ export async function saveProject(dirPath, options = {}) {
142
153
  timestamp
143
154
  }
144
155
 
145
- let utxos = await spv.getUtxos(config.address)
156
+ // Use last file's change UTXOs for manifest (chain continues)
157
+ let utxos = (lastChangeUtxos && lastChangeUtxos.length > 0)
158
+ ? lastChangeUtxos
159
+ : await spv.getUtxos(config.address)
146
160
  if (!utxos || utxos.length === 0) {
147
161
  return {
148
162
  success: false,
@@ -151,7 +165,7 @@ export async function saveProject(dirPath, options = {}) {
151
165
  }
152
166
  }
153
167
 
154
- const { txHex, txId } = await spv.buildOpReturnTx(
168
+ const { txHex, txId } = await spv.buildOpReturnTxWithChange(
155
169
  wif, utxos, JSON.stringify(manifest)
156
170
  )
157
171
  const result = await spv.broadcastTx(txHex)