@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 +2 -1
- package/index.js +361 -39
- package/package.json +1 -1
- package/server.json +3 -3
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
path.
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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(
|
|
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-
|
|
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.
|
|
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-
|
|
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
|
+
"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.
|
|
17
|
+
"version": "1.2.13",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
|
20
20
|
}
|