@rel-time/mcp-server 1.2.11 → 1.2.13

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/README.md CHANGED
@@ -40,7 +40,8 @@ Once paid via Lightning or Stripe (or if you pass an existing `rel_key_...` key)
40
40
 
41
41
  | Tool Name | Requires Key? | What It Does |
42
42
  | :--- | :---: | :--- |
43
- | `reltime_anchor_file` | Yes | Computes the SHA-256 hash of a local file **on your machine** (0 bytes uploaded), supports `biometric: true` for **Biometric Anchors** (TouchID/FaceID/YubiKey), anchors via `POST /v1/anchor`, saves `reltime-stamp-<filename>.svg` and `reltime-doomsday-proof-<filename>.json`, and automatically outputs a clickable **Stamped Copy** (`reltime-stamped-<name>.pdf` or `.png`). |
43
+ | `reltime_anchor_file` | Yes | Computes the SHA-256 hash of a local file **on your machine** (0 bytes uploaded), supports `biometric: true` for **Biometric Anchors** (TouchID/FaceID/YubiKey), anchors via `POST /v1/anchor`, saves `reltime-stamp-<filename>.svg`, `reltime-certificate-<filename>.html`, and `reltime-doomsday-proof-<filename>.json`, and automatically outputs a clickable **Stamped Copy** (`reltime-stamped-<name>.pdf` or `.png`). |
44
+ | `reltime_anchor_folder` | Yes | **Bulk folder anchoring (10 to 5,000+ files in one call):** Hashes all files in a local folder 100% locally, supports `biometric: true` (Passkey) or Anonymous mode, automatically skips already-anchored files (safe to resume), and keeps your directory clean by storing per-file Doomsday Proofs inside `<folder>/_reltime_proofs/` plus a master `reltime-batch-manifest.json`. |
44
45
  | `reltime_audit_trail` | Yes | Fetches the account's chronological anchor ledger (`GET /v1/account/history`), aggregates Bitcoin block settlements, VDF `T_index` ticks, and Corporate Hardware Passkey (`signer_public_key`) signatures, optionally verifies a local file or entire folder bit-for-bit (`verify_local_path`), and spins up a self-contained interactive HTML Audit Report (`reltime-audit-report-*.html`) in your browser. |
45
46
  | `reltime_link_passkey` | **No** | Opens `https://rel-time.com/mcp-passkey` in the system browser so the user can touch TouchID / FaceID / YubiKey once, saving `{ public_key_hex, passkey_signature }` to `~/.reltime/passkey.json` for seamless Biometric Anchors. |
46
47
  | `reltime_stamp_file` | **No** | Overlays the authentic 64-ray Sun-Stamp onto the bottom-right corner of any local PDF or Image (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`) 100% locally. |
package/index.js CHANGED
@@ -56,7 +56,7 @@ let LAST_INVOICE_PACK = null;
56
56
 
57
57
  const SERVER_INFO = {
58
58
  name: "rel-time-mcp-server",
59
- version: "1.2.11",
59
+ version: "1.2.13",
60
60
  };
61
61
 
62
62
  /**
@@ -341,6 +341,30 @@ function injectPngVerifyBuffer(pngBuffer, verifyUrl) {
341
341
  ]);
342
342
  }
343
343
 
344
+ /**
345
+ * Builds an OS-safe filename (<= 240 UTF-8 bytes) so macOS/Linux 255-byte limits
346
+ * (`ENAMETOOLONG`) never fail on long AI-prompt filenames.
347
+ */
348
+ function safeArtifactFilename(prefix, rawName, ext, hashHex = "") {
349
+ const candidate = `${prefix}${rawName}${ext}`;
350
+ if (Buffer.byteLength(candidate, "utf8") <= 240) {
351
+ return candidate;
352
+ }
353
+ const suffixTag = hashHex
354
+ ? `~${String(hashHex).slice(0, 8)}`
355
+ : `~${crypto.createHash("sha256").update(String(rawName), "utf8").digest("hex").slice(0, 8)}`;
356
+ const maxBodyBytes = Math.max(32, 240 - Buffer.byteLength(prefix, "utf8") - Buffer.byteLength(suffixTag, "utf8") - Buffer.byteLength(ext, "utf8"));
357
+ let trimmed = "";
358
+ let usedBytes = 0;
359
+ for (const ch of String(rawName)) {
360
+ const chBytes = Buffer.byteLength(ch, "utf8");
361
+ if (usedBytes + chBytes > maxBodyBytes) break;
362
+ trimmed += ch;
363
+ usedBytes += chBytes;
364
+ }
365
+ return `${prefix}${trimmed.trim()}${suffixTag}${ext}`;
366
+ }
367
+
344
368
  /**
345
369
  * Stamps the Option 1A (+14° Bitcoin Tilt, pure crisp 64-ray Sun-Stamp, 0% glare, no pill) onto a local Image or PDF.
346
370
  * Returns { stamped: true, output_path, format } or { stamped: false, reason }.
@@ -375,7 +399,9 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
375
399
  .replace(/<\/a>/gi, "");
376
400
 
377
401
  if (isImage) {
378
- const outPath = customOutputPath ? path.resolve(customOutputPath) : path.join(dir, `reltime-stamped-${baseName}.png`);
402
+ const outPath = customOutputPath
403
+ ? path.resolve(customOutputPath)
404
+ : path.join(dir, safeArtifactFilename("reltime-stamped-", baseName, ".png", verifyHash));
379
405
  const img = sharp(filePath);
380
406
  const meta = await img.metadata();
381
407
  const w = (meta.autoOrient && meta.autoOrient.width) || meta.width || 1200;
@@ -410,7 +436,9 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
410
436
  }
411
437
 
412
438
  if (isPdf) {
413
- const outPath = customOutputPath ? path.resolve(customOutputPath) : path.join(dir, `reltime-stamped-${baseName}.pdf`);
439
+ const outPath = customOutputPath
440
+ ? path.resolve(customOutputPath)
441
+ : path.join(dir, safeArtifactFilename("reltime-stamped-", baseName, ".pdf", verifyHash));
414
442
  const pdfBytes = fs.readFileSync(filePath);
415
443
  const pdfDoc = await PDFDocument.load(pdfBytes, { ignoreEncryption: true });
416
444
  const pages = pdfDoc.getPages();
@@ -1527,6 +1555,18 @@ function generateAuditReportHtml(audit) {
1527
1555
  </html>`;
1528
1556
  }
1529
1557
 
1558
+ function formatBytes(bytes) {
1559
+ if (bytes === undefined || bytes === null || Number.isNaN(Number(bytes))) {
1560
+ return "Zero-Knowledge (32-Byte Digest)";
1561
+ }
1562
+ const b = Number(bytes);
1563
+ if (b === 0) return "0 Bytes";
1564
+ const k = 1024;
1565
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
1566
+ const i = Math.min(sizes.length - 1, Math.floor(Math.log(b) / Math.log(k)));
1567
+ return `${parseFloat((b / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
1568
+ }
1569
+
1530
1570
  function inferMimeTypeFromFileName(fileName) {
1531
1571
  if (!fileName || fileName === "Zero-Knowledge Hash Payload") {
1532
1572
  return "application/octet-stream (Zero-Knowledge)";
@@ -2111,6 +2151,49 @@ const TOOLS = [
2111
2151
  required: ["file_path"],
2112
2152
  },
2113
2153
  },
2154
+ {
2155
+ name: "reltime_anchor_folder",
2156
+ description:
2157
+ "Bulk zero-knowledge folder anchoring (10 to 5,000+ files in a single call): Hashes all files in a local folder 100% locally (0 file bytes uploaded), supports Biometric (Passkey) or Anonymous anchoring, skips any files that are already anchored (safe to resume), and keeps the user's folder clean by storing per-file Doomsday Proofs inside a single `_reltime_proofs/` subfolder plus a master `reltime-batch-manifest.json`.",
2158
+ inputSchema: {
2159
+ type: "object",
2160
+ properties: {
2161
+ folder_path: {
2162
+ type: "string",
2163
+ description: "Absolute path to the local directory to hash and anchor.",
2164
+ },
2165
+ recursive: {
2166
+ type: "boolean",
2167
+ description: "If true, recursively anchors files in subdirectories. Defaults to false (top-level files only).",
2168
+ },
2169
+ biometric: {
2170
+ type: "boolean",
2171
+ description:
2172
+ "If true, performs Biometric Anchors using your linked hardware Passkey (`~/.reltime/passkey.json`) across the entire folder without repeated TouchID prompts.",
2173
+ },
2174
+ anchor_mode: {
2175
+ type: "string",
2176
+ enum: ["standard", "temporal"],
2177
+ description: "Use 'standard' (default) or 'temporal' (VDF T_index sequential ordering for Superhighway & Autonomous Agents).",
2178
+ },
2179
+ artifact_mode: {
2180
+ type: "string",
2181
+ enum: ["proofs_subfolder", "manifest_only", "full"],
2182
+ description:
2183
+ "Controls local output files so bulk runs never clutter the folder: 'proofs_subfolder' (default) writes JSON proofs into `<folder>/_reltime_proofs/` + `reltime-batch-manifest.json`; 'manifest_only' writes only `reltime-batch-manifest.json`; 'full' also writes SVG Sun-Stamps and HTML certificates.",
2184
+ },
2185
+ concurrency: {
2186
+ type: "number",
2187
+ description: "Number of concurrent anchor requests (default: 5, max: 10).",
2188
+ },
2189
+ api_key: {
2190
+ type: "string",
2191
+ description: "Optional override for RELTIME_API_KEY.",
2192
+ },
2193
+ },
2194
+ required: ["folder_path"],
2195
+ },
2196
+ },
2114
2197
  {
2115
2198
  name: "reltime_link_passkey",
2116
2199
  description:
@@ -2450,18 +2533,36 @@ async function executeTool(name, args = {}) {
2450
2533
  const verifyUrl = `https://rel-time.com/verify?t=${lookupHash}`;
2451
2534
  const stampEndpointUrl = `${API_BASE}/stamp/${lookupHash}`;
2452
2535
 
2453
- const svgContent = data.visual_fingerprint || (await (await fetch(stampEndpointUrl)).text());
2536
+ let svgContent = data.visual_fingerprint || "";
2537
+ const needsSvg = args.save_sun_stamp_svg !== false || args.save_stamped_copy !== false;
2538
+ if (!svgContent && needsSvg) {
2539
+ try {
2540
+ const stampRes = await fetch(stampEndpointUrl);
2541
+ if (stampRes.ok) {
2542
+ svgContent = await stampRes.text();
2543
+ }
2544
+ } catch (e) {
2545
+ // Ignore SVG fetch error if offline/rate-limited
2546
+ }
2547
+ }
2454
2548
 
2455
2549
  let savedSvgPath = null;
2456
- if (args.save_sun_stamp_svg !== false) {
2457
- savedSvgPath = path.join(path.dirname(filePath), `reltime-stamp-${path.basename(filePath)}.svg`);
2458
- fs.writeFileSync(savedSvgPath, svgContent, "utf8");
2550
+ if (args.save_sun_stamp_svg !== false && svgContent) {
2551
+ try {
2552
+ savedSvgPath = path.join(
2553
+ path.dirname(filePath),
2554
+ safeArtifactFilename("reltime-stamp-", path.basename(filePath), ".svg", clientHash)
2555
+ );
2556
+ fs.writeFileSync(savedSvgPath, svgContent, "utf8");
2557
+ } catch (e) {
2558
+ savedSvgPath = null;
2559
+ }
2459
2560
  }
2460
2561
 
2461
2562
  // Save initial Doomsday Proof JSON alongside the file
2462
2563
  const savedProofJsonPath = path.join(
2463
2564
  path.dirname(filePath),
2464
- `reltime-doomsday-proof-${path.basename(filePath)}.json`
2565
+ safeArtifactFilename("reltime-doomsday-proof-", path.basename(filePath), ".json", clientHash)
2465
2566
  );
2466
2567
  fs.writeFileSync(
2467
2568
  savedProofJsonPath,
@@ -2489,40 +2590,48 @@ async function executeTool(name, args = {}) {
2489
2590
 
2490
2591
  // 3. Automatically generate Stamped Copy (.PDF or .PNG) for PDFs and Images
2491
2592
  let stampedCopyResult = null;
2492
- if (args.save_stamped_copy !== false) {
2493
- stampedCopyResult = await stampLocalFileOptionA(filePath, svgContent, null, clientHash);
2593
+ if (args.save_stamped_copy !== false && svgContent) {
2594
+ try {
2595
+ stampedCopyResult = await stampLocalFileOptionA(filePath, svgContent, null, clientHash);
2596
+ } catch (e) {
2597
+ stampedCopyResult = { status: "skipped", error: e.message };
2598
+ }
2494
2599
  }
2495
2600
 
2496
2601
  // 4. Generate 4-Plate Sovereign Cryptographic Provenance Certificate HTML
2497
2602
  let savedCertificateHtmlPath = null;
2498
2603
  if (args.save_certificate_html !== false) {
2499
- const certHtml = generateProvenanceCertificateHtml({
2500
- fileName: path.basename(filePath),
2501
- fileSize: formatBytes(fileBuffer.length),
2502
- mimeType: inferMimeTypeFromFileName(path.basename(filePath)),
2503
- originalFileHash,
2504
- attestedHash: clientHash,
2505
- signerPublicKey: passkeyCreds ? passkeyCreds.public_key_hex : null,
2506
- anchorMode: data.anchor_mode || anchorMode,
2507
- tIndex,
2508
- vdfTicks: data.vdf_ticks || data.solar_ticks || 0,
2509
- status: data.status || "pending_finality",
2510
- blockHeight: data.settlement?.bitcoin_block_height || null,
2511
- blockTimeUtc: data.settlement?.bitcoin_block_time
2512
- ? new Date(data.settlement.bitcoin_block_time * 1000).toISOString().replace("T", " ").slice(0, 19) + " UTC"
2513
- : null,
2514
- merkleProofCount: Array.isArray(data.settlement?.merkle_proof_path)
2515
- ? data.settlement.merkle_proof_path.length
2516
- : null,
2517
- txid: data.settlement?.bitcoin_txid || null,
2518
- });
2519
- savedCertificateHtmlPath = path.join(
2520
- path.dirname(filePath),
2521
- `reltime-certificate-${path.basename(filePath)}.html`
2522
- );
2523
- fs.writeFileSync(savedCertificateHtmlPath, certHtml, "utf8");
2524
- if (args.open_certificate_in_browser) {
2525
- openSystemBrowser(savedCertificateHtmlPath);
2604
+ try {
2605
+ const certHtml = generateProvenanceCertificateHtml({
2606
+ fileName: path.basename(filePath),
2607
+ fileSize: formatBytes(fileBuffer.length),
2608
+ mimeType: inferMimeTypeFromFileName(path.basename(filePath)),
2609
+ originalFileHash,
2610
+ attestedHash: clientHash,
2611
+ signerPublicKey: passkeyCreds ? passkeyCreds.public_key_hex : null,
2612
+ anchorMode: data.anchor_mode || anchorMode,
2613
+ tIndex,
2614
+ vdfTicks: data.vdf_ticks || data.solar_ticks || 0,
2615
+ status: data.status || "pending_finality",
2616
+ blockHeight: data.settlement?.bitcoin_block_height || null,
2617
+ blockTimeUtc: data.settlement?.bitcoin_block_time
2618
+ ? new Date(data.settlement.bitcoin_block_time * 1000).toISOString().replace("T", " ").slice(0, 19) + " UTC"
2619
+ : null,
2620
+ merkleProofCount: Array.isArray(data.settlement?.merkle_proof_path)
2621
+ ? data.settlement.merkle_proof_path.length
2622
+ : null,
2623
+ txid: data.settlement?.bitcoin_txid || null,
2624
+ });
2625
+ savedCertificateHtmlPath = path.join(
2626
+ path.dirname(filePath),
2627
+ safeArtifactFilename("reltime-certificate-", path.basename(filePath), ".html", clientHash)
2628
+ );
2629
+ fs.writeFileSync(savedCertificateHtmlPath, certHtml, "utf8");
2630
+ if (args.open_certificate_in_browser) {
2631
+ openSystemBrowser(savedCertificateHtmlPath);
2632
+ }
2633
+ } catch (e) {
2634
+ savedCertificateHtmlPath = null;
2526
2635
  }
2527
2636
  }
2528
2637
 
@@ -2548,6 +2657,214 @@ async function executeTool(name, args = {}) {
2548
2657
  };
2549
2658
  }
2550
2659
 
2660
+ if (name === "reltime_anchor_folder") {
2661
+ if (!key) {
2662
+ return buildPackSelectionPrompt(
2663
+ "Before anchoring a folder of files, a Rel-Time API key is required. Ask the user which pack they want to provision first:"
2664
+ );
2665
+ }
2666
+ const folderPath = resolveLocalFilePath(args.folder_path);
2667
+ if (!fs.existsSync(folderPath) || !fs.statSync(folderPath).isDirectory()) {
2668
+ throw new Error(`Directory not found at path: ${folderPath}`);
2669
+ }
2670
+
2671
+ const recursive = Boolean(args.recursive);
2672
+ const anchorMode = args.anchor_mode || "standard";
2673
+ const artifactMode = args.artifact_mode || "proofs_subfolder";
2674
+ const concurrency = Math.max(1, Math.min(10, Number(args.concurrency) || 5));
2675
+
2676
+ let passkeyCreds = null;
2677
+ if (args.biometric) {
2678
+ passkeyCreds = loadSavedPasskey();
2679
+ if (!passkeyCreds) {
2680
+ passkeyCreds = await launchBrowserPasskeyBridge();
2681
+ }
2682
+ }
2683
+
2684
+ const isReltimeGeneratedFile = (fname) => {
2685
+ const lower = fname.toLowerCase();
2686
+ return (
2687
+ fname.startsWith(".") ||
2688
+ lower.startsWith("reltime-stamp-") ||
2689
+ lower.startsWith("reltime-doomsday-proof-") ||
2690
+ lower.startsWith("reltime-certificate-") ||
2691
+ lower.startsWith("reltime-stamped-") ||
2692
+ lower.startsWith("reltime-audit-report-") ||
2693
+ lower === "reltime-batch-manifest.json"
2694
+ );
2695
+ };
2696
+
2697
+ const targetFiles = [];
2698
+ const walkDir = (dir) => {
2699
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
2700
+ for (const entry of entries) {
2701
+ if (entry.name.startsWith(".") || entry.name === "_reltime_proofs" || entry.name === "node_modules") {
2702
+ continue;
2703
+ }
2704
+ const fullPath = path.join(dir, entry.name);
2705
+ if (entry.isDirectory()) {
2706
+ if (recursive) walkDir(fullPath);
2707
+ } else if (entry.isFile() && !isReltimeGeneratedFile(entry.name)) {
2708
+ targetFiles.push(fullPath);
2709
+ }
2710
+ }
2711
+ };
2712
+ walkDir(folderPath);
2713
+
2714
+ const proofsDir = path.join(folderPath, "_reltime_proofs");
2715
+ if (artifactMode === "proofs_subfolder" && !fs.existsSync(proofsDir)) {
2716
+ fs.mkdirSync(proofsDir, { recursive: true });
2717
+ }
2718
+
2719
+ const manifestPath = path.join(folderPath, "reltime-batch-manifest.json");
2720
+ let existingManifest = { anchored_files: {} };
2721
+ if (fs.existsSync(manifestPath)) {
2722
+ try {
2723
+ existingManifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")) || { anchored_files: {} };
2724
+ if (!existingManifest.anchored_files) existingManifest.anchored_files = {};
2725
+ } catch (e) {
2726
+ existingManifest = { anchored_files: {} };
2727
+ }
2728
+ }
2729
+
2730
+ const results = [];
2731
+ const skipped = [];
2732
+ const errors = [];
2733
+ let outOfCredits = false;
2734
+
2735
+ const toProcess = [];
2736
+ for (const fPath of targetFiles) {
2737
+ const relPath = path.relative(folderPath, fPath);
2738
+ const baseName = path.basename(fPath);
2739
+ const sidecarProof = path.join(
2740
+ path.dirname(fPath),
2741
+ safeArtifactFilename("reltime-doomsday-proof-", baseName, ".json")
2742
+ );
2743
+ const legacySidecarProof = path.join(path.dirname(fPath), `reltime-doomsday-proof-${baseName}.json`);
2744
+ const subfolderProof = path.join(
2745
+ proofsDir,
2746
+ safeArtifactFilename("reltime-doomsday-proof-", relPath.replace(/[\\/]/g, "__"), ".json")
2747
+ );
2748
+ if (
2749
+ existingManifest.anchored_files[relPath] ||
2750
+ fs.existsSync(sidecarProof) ||
2751
+ (Buffer.byteLength(`reltime-doomsday-proof-${baseName}.json`, "utf8") <= 255 && fs.existsSync(legacySidecarProof)) ||
2752
+ fs.existsSync(subfolderProof)
2753
+ ) {
2754
+ skipped.push(relPath);
2755
+ } else {
2756
+ toProcess.push({ fPath, relPath, baseName, subfolderProof, sidecarProof });
2757
+ }
2758
+ }
2759
+
2760
+ let cursor = 0;
2761
+ const worker = async () => {
2762
+ while (cursor < toProcess.length && !outOfCredits) {
2763
+ const idx = cursor++;
2764
+ const item = toProcess[idx];
2765
+ if (!item) break;
2766
+ try {
2767
+ const buf = fs.readFileSync(item.fPath);
2768
+ const originalFileHash = crypto.createHash("sha256").update(buf).digest("hex");
2769
+ const clientHash =
2770
+ args.biometric && passkeyCreds
2771
+ ? crypto.createHash("sha256").update(`${originalFileHash}${passkeyCreds.public_key_hex}`, "utf8").digest("hex")
2772
+ : originalFileHash;
2773
+
2774
+ const reqBody = {
2775
+ client_hash: clientHash,
2776
+ anchor_mode: anchorMode,
2777
+ };
2778
+ if (args.biometric && passkeyCreds) {
2779
+ reqBody.original_file_hash = originalFileHash;
2780
+ reqBody.passkey_signature = passkeyCreds.passkey_signature;
2781
+ }
2782
+
2783
+ const res = await fetch(`${API_BASE}/anchor`, {
2784
+ method: "POST",
2785
+ headers: {
2786
+ "Content-Type": "application/json",
2787
+ Authorization: `Bearer ${key}`,
2788
+ },
2789
+ body: JSON.stringify(reqBody),
2790
+ });
2791
+
2792
+ if (res.status === 402) {
2793
+ outOfCredits = true;
2794
+ errors.push({ file: item.relPath, error: "Insufficient Hash Credits (HTTP 402)" });
2795
+ break;
2796
+ }
2797
+
2798
+ const data = await res.json();
2799
+ if (!res.ok) {
2800
+ errors.push({ file: item.relPath, error: `HTTP ${res.status}: ${JSON.stringify(data)}` });
2801
+ continue;
2802
+ }
2803
+
2804
+ const tIndex = data.t_index || data.thermodynamic_proof;
2805
+ const lookupHash = (data.anchor_mode || anchorMode) === "temporal" ? tIndex : clientHash;
2806
+ const verifyUrl = `https://rel-time.com/verify?t=${lookupHash}`;
2807
+ const proofRecord = {
2808
+ protocol: "Rel-Time Sovereign Provenance Protocol",
2809
+ file_name: item.baseName,
2810
+ relative_path: item.relPath,
2811
+ identity_mode: args.biometric ? "BIOMETRIC_PASSKEY" : "ANONYMOUS",
2812
+ original_file_sha256_hash: originalFileHash,
2813
+ anchored_client_hash: clientHash,
2814
+ signer_public_key: passkeyCreds ? passkeyCreds.public_key_hex : null,
2815
+ t_index: tIndex,
2816
+ vdf_ticks: data.vdf_ticks || data.solar_ticks,
2817
+ anchor_mode: data.anchor_mode || anchorMode,
2818
+ status: data.status || "pending_finality",
2819
+ verify_url: verifyUrl,
2820
+ };
2821
+
2822
+ if (artifactMode === "proofs_subfolder") {
2823
+ fs.writeFileSync(item.subfolderProof, JSON.stringify(proofRecord, null, 2), "utf8");
2824
+ } else if (artifactMode === "full") {
2825
+ fs.writeFileSync(item.sidecarProof, JSON.stringify(proofRecord, null, 2), "utf8");
2826
+ }
2827
+
2828
+ existingManifest.anchored_files[item.relPath] = proofRecord;
2829
+ results.push(proofRecord);
2830
+ } catch (err) {
2831
+ errors.push({ file: item.relPath, error: err.message });
2832
+ }
2833
+ }
2834
+ };
2835
+
2836
+ const workers = Array.from({ length: Math.min(concurrency, Math.max(1, toProcess.length)) }, () => worker());
2837
+ await Promise.all(workers);
2838
+
2839
+ const manifestPayload = {
2840
+ protocol: "Rel-Time Sovereign Provenance Protocol — Batch Folder Manifest",
2841
+ folder_path: folderPath,
2842
+ updated_at: new Date().toISOString(),
2843
+ identity_mode: args.biometric ? "BIOMETRIC_PASSKEY" : "ANONYMOUS",
2844
+ signer_public_key: passkeyCreds ? passkeyCreds.public_key_hex : null,
2845
+ total_anchored_in_manifest: Object.keys(existingManifest.anchored_files).length,
2846
+ anchored_files: existingManifest.anchored_files,
2847
+ };
2848
+ fs.writeFileSync(manifestPath, JSON.stringify(manifestPayload, null, 2), "utf8");
2849
+
2850
+ return {
2851
+ status: outOfCredits ? "stopped_insufficient_credits" : "completed",
2852
+ folder_path: folderPath,
2853
+ total_files_found: targetFiles.length,
2854
+ newly_anchored_count: results.length,
2855
+ skipped_already_anchored_count: skipped.length,
2856
+ error_count: errors.length,
2857
+ identity_mode: args.biometric ? "BIOMETRIC_PASSKEY" : "ANONYMOUS",
2858
+ signer_public_key: passkeyCreds ? passkeyCreds.public_key_hex : null,
2859
+ artifact_mode: artifactMode,
2860
+ manifest_path: manifestPath,
2861
+ proofs_subfolder: artifactMode === "proofs_subfolder" ? proofsDir : null,
2862
+ errors: errors.slice(0, 20),
2863
+ next_step:
2864
+ "You can now run `reltime_audit_trail` with `verify_local_path` set to this folder to generate the full interactive HTML Cryptographic Audit Report.",
2865
+ };
2866
+ }
2867
+
2551
2868
  if (name === "reltime_stamp_file") {
2552
2869
  const filePath = resolveLocalFilePath(args.file_path);
2553
2870
  if (!fs.existsSync(filePath)) {
@@ -2813,7 +3130,7 @@ async function executeTool(name, args = {}) {
2813
3130
  };
2814
3131
  }
2815
3132
 
2816
- const maxEntries = Math.min(500, Math.max(1, Number(args.limit) || 100));
3133
+ const maxEntries = Math.min(5000, Math.max(1, Number(args.limit) || 2000));
2817
3134
  const pageSize = Math.min(100, maxEntries);
2818
3135
  const pagesToFetch = Math.ceil(maxEntries / pageSize);
2819
3136
  let rawHistory = [];
@@ -2957,9 +3274,14 @@ async function executeTool(name, args = {}) {
2957
3274
  if (item.startsWith(".")) continue;
2958
3275
  if (
2959
3276
  item.startsWith("reltime-stamp-") ||
3277
+ item.startsWith("reltime-stamped-") ||
2960
3278
  item.startsWith("reltime-doomsday-proof-") ||
2961
3279
  item.startsWith("reltime-proof-") ||
2962
- item.startsWith("reltime-audit-report-")
3280
+ item.startsWith("reltime-certificate-") ||
3281
+ item.startsWith("reltime-audit-report-") ||
3282
+ item.startsWith("reltime-audit-trail-") ||
3283
+ item === "reltime-batch-manifest.json" ||
3284
+ item === "_reltime_proofs"
2963
3285
  ) {
2964
3286
  continue;
2965
3287
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rel-time/mcp-server",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "mcpName": "io.github.relsun369-dev/rel-time",
5
5
  "description": "Official Model Context Protocol (MCP) server for Rel-Time (rel-time.com) — Zero-Knowledge Bitcoin & VDF Provenance, Local File Hashing, and Sun-Stamp Generation for Claude, Cursor, Windsurf, and Antigravity.",
6
6
  "main": "index.js",
package/server.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.relsun369-dev/rel-time",
4
4
  "title": "Rel-Time Protocol (Bitcoin & VDF Provenance)",
5
5
  "description": "Zero-knowledge Bitcoin Mainnet & VDF timestamping, Passkey signing, Sun-Stamps, and tamper audits.",
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/relsun369-dev/reltime-mcp",
9
9
  "source": "github"
10
10
  },
11
- "version": "1.2.11",
11
+ "version": "1.2.13",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@rel-time/mcp-server",
17
- "version": "1.2.11",
17
+ "version": "1.2.13",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  }