@mikeargento/bitgraph-mcp 0.3.0 → 0.4.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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @mikeargento/bitgraph-mcp
2
2
 
3
- MCP server for BitGraph. It gives any MCP client, Claude Code and Claude Desktop among them, the same three gestures the website has: make a BitGraph of a file, check whether bytes are on record, and fetch a proof.
3
+ MCP server for BitGraph. It gives any MCP client, Claude Code and Claude Desktop among them, the same three gestures the website has: make a BitGraph, check whether bytes are on record, and fetch a proof.
4
4
 
5
- Making a BitGraph builds a fused artifact from the file, on your machine: the file's digest is the origin; an unused slot is allocated in the ledger before any artifact exists; a commitment to that signed slot is placed into a new artifact built from the file with a registered placement (a 48-byte trailer for formats that ignore trailing bytes, a small tar container otherwise); the artifact is hashed and its digest is committed under the same slot. Those bytes could not have been finalized before the slot was allocated and were committed no later than the commit. The fused bytes are not kept: the original plus the proof rebuilds them, and the Frame for each file comes back in the structured result.
5
+ Making a BitGraph is one call for any number of files or folders, and everything in the call becomes ONE BitGraph, the way a drop on the site works: a single file is fused on its own slot, with its Frame; two or more files become a set under one slot, one position for all of them. For a set, on your machine each file is read once for its SHA-256 (the origin) and a hasher state; an unused slot is allocated in the ledger before any new file exists; every file's new fused bytes, the original plus a registered placement carrying a commitment to that signed slot (a 48-byte trailer for formats that ignore trailing bytes, a small tar container with the original first for everything else), are hashed from that state without being written or held; and the canonical list of those digests (above 2,000 files, a Merkle root over it) is committed under the same slot. Those bytes could not have been finalized before the slot was allocated and were committed no later than the commit. The new files are virtual: the original plus the set proof rebuilds them, and a lookup by any file's own digest finds the set.
6
6
 
7
- BitGraph gives bytes a causal position in a forward-only ledger. Ethereum serves as a public clock, not a storage layer: the ledger periodically records the hash of a recent Ethereum block, and since a block's hash cannot be known before the block exists, the sequence is pinned to a public timeline. Nothing is ever written to Ethereum. This server reads files locally; only digests and slot records ever leave the machine. File contents are never uploaded, and files are never modified.
7
+ BitGraph gives bytes a causal position in a forward-only ledger. Ethereum serves as a public clock, not a storage layer: the ledger periodically records the hash of a recent Ethereum block, and since a block's hash cannot be known before the block exists, the sequence is pinned to a public timeline. Nothing is ever written to Ethereum. This server reads files locally; only digests, the set's committed artifact and slot records ever leave the machine. File contents are never uploaded, and files are never modified.
8
8
 
9
9
  ```sh
10
10
  claude mcp add bitgraph -- npx -y @mikeargento/bitgraph-mcp
@@ -12,13 +12,13 @@ claude mcp add bitgraph -- npx -y @mikeargento/bitgraph-mcp
12
12
 
13
13
  Tools:
14
14
 
15
- - `bitgraph_record` makes a BitGraph of each file: a new fused artifact under its own slot. Files already on record, as a recording or as the origin of a fused artifact, are returned as-is instead of being BitGraphed again; pass `again: true` to deliberately make a new fused artifact from such a file. BitGraphs are permanent.
16
- - `bitgraph_check` reports whether files or digests are on record (`on_record`: a recording of the exact bytes) and which fused artifacts name them as origin (`fused_descendants`), with every position each one occupies. Read-only.
17
- - `bitgraph_get_proof` fetches a proof by digest, BitGraph number, or file path, including the anchor window, the two Ethereum block times that bracket when it was BitGraphed. Read-only.
15
+ - `bitgraph_record` makes a BitGraph of files and folders: one file on its own, two or more as one set with one position. A directory is every regular file under it, recursively, with hidden entries and symbolic links left out. Files already on record, as a recording, as the origin of a fused file or as a member of a set, are returned as-is instead of being made again; pass `again: true` to deliberately make a new BitGraph of them. Each file comes back with its row in the set (one of N), and the set with its position and proof page. Above 2,000 files the set commits a Merkle root and each member's evidence is indexed on the site afterwards; evidence the site could not take is sent again before the next set is made. BitGraphs are permanent.
16
+ - `bitgraph_check` reports whether files, folders or digests are on record (`on_record`: a recording of the exact bytes, a fused file made from them, or a set they are a member of), with every position each one occupies and, for a set member, its row. Read-only.
17
+ - `bitgraph_get_proof` fetches a proof by digest, BitGraph number, or file path, including the anchor window, the two Ethereum block times that bracket when it was BitGraphed, and the row a set member holds. Read-only.
18
18
 
19
- Configuration is by environment variable. `BITGRAPH_API_URL` overrides the endpoint (default `https://bitgraph.ing`). `BITGRAPH_API_KEY`, when set, is sent as a Bearer token on recordings.
19
+ Configuration is by environment variable. `BITGRAPH_API_URL` overrides the endpoint (default `https://bitgraph.ing`). `BITGRAPH_API_KEY`, when set, is sent as a Bearer token on recordings. A client that passes a progress token gets progress notifications through the scan, the commit and the indexing.
20
20
 
21
- Proofs are the bitgraph/1 schema and can be verified offline, without this server or any service, using `@mikeargento/bitgraph-verify`: `verifyFuse` checks a fused artifact directly or rebuilds it from the original. The fuse pipeline itself comes from `@mikeargento/bitgraph`, the licensed package; this client is MIT.
21
+ Proofs are the bitgraph/1 schema and can be verified offline, without this server or any service, using `@mikeargento/bitgraph-verify`: `verifyFuse` checks the set proof against its committed artifact, and `verifyFuseMember` checks any member from its original or from its new bytes. The set pipeline itself comes from `@mikeargento/bitgraph`, the licensed package; this client is MIT.
22
22
 
23
23
  BitGraph itself lives at [bitgraph.ing](https://bitgraph.ing). The protocol, this package's source, and the rest of the documentation are in the main repository: [github.com/mikeargento/bitgraph](https://github.com/mikeargento/bitgraph).
24
24
 
@@ -24,15 +24,36 @@ test("record markdown mentions again=true only when something was already on rec
24
24
  total_positions: 1,
25
25
  proof_url: "https://bitgraph.ing/proof/x",
26
26
  };
27
- const fresh = renderRecordMarkdown([{ ...base, path: "/a", outcome: "fused", artifact_digest: "ZnVzZWQ", placement: "container/1" }]);
27
+ const fresh = renderRecordMarkdown([{ ...base, path: "/a", outcome: "fused", artifact_digest: "ZnVzZWQ", placement: "container/2", member: 1, member_count: 1 }]);
28
28
  assert.ok(!fresh.includes("again=true"));
29
29
  const mixed = renderRecordMarkdown([
30
- { ...base, path: "/a", outcome: "fused", artifact_digest: "ZnVzZWQ", placement: "container/1" },
31
- { ...base, path: "/b", outcome: "on record", artifact_digest: null, placement: null },
30
+ { ...base, path: "/a", outcome: "fused", artifact_digest: "ZnVzZWQ", placement: "container/2", member: 1, member_count: 1 },
31
+ { ...base, path: "/b", outcome: "on record", artifact_digest: null, placement: null, member: null, member_count: null },
32
32
  ]);
33
33
  assert.ok(mixed.includes("again=true"));
34
34
  assert.ok(mixed.startsWith("1 fused, 1 already on record."));
35
35
  });
36
+ test("record markdown names the set once and its members by row", () => {
37
+ const set = {
38
+ set: "set/1",
39
+ count: 2,
40
+ counter: "1386",
41
+ epoch: toUrlSafeB64(EPOCH),
42
+ artifact_digest: "c2V0",
43
+ proof_url: "https://bitgraph.ing/proof/c2V0?counter=1386",
44
+ manifest_echoed: true,
45
+ recovered: false,
46
+ index: null,
47
+ };
48
+ const row = { digest: toUrlSafeB64(DIGEST), counter: "1386", epoch: toUrlSafeB64(EPOCH), total_positions: 1, proof_url: "https://bitgraph.ing/proof/x", artifact_digest: "ZnVzZWQ", outcome: "fused", member_count: 2 };
49
+ const md = renderRecordMarkdown([{ ...row, path: "/a.png", placement: "trailer/1", member: 2 }, { ...row, path: "/b.txt", placement: "container/2", member: 1 }], set);
50
+ assert.ok(md.startsWith("2 files BitGraphed as one set at #1386 (set of 2), 0 already on record."), md);
51
+ assert.ok(md.includes("- #1386 · set of 2 · https://bitgraph.ing/proof/c2V0?counter=1386"), md);
52
+ assert.ok(md.includes("- fused · /a.png (2 of 2, trailer/1)"), md);
53
+ assert.ok(md.includes("- fused · /b.txt (1 of 2, container/2)"), md);
54
+ const waiting = renderRecordMarkdown([{ ...row, path: "/a.png", placement: "trailer/1", member: 1 }], { ...set, set: "set/2", index: { written: 0, pending: 2 } });
55
+ assert.ok(waiting.includes("not findable by hash"), waiting);
56
+ });
36
57
  test("proof markdown states the window as between lower and upper", () => {
37
58
  const detail = {
38
59
  proofs: [{ proof: { artifact: { digestB64: DIGEST }, commit: { counter: "42", epochId: EPOCH } } }],
@@ -1 +1 @@
1
- {"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scan.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/scan.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ // Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
2
+ /**
3
+ * The scan's two promises: the digest is the file's own, and a state
4
+ * finished with the placement's suffix for a slot is the hash of the
5
+ * placement's own build for that slot. Both are pinned across sizes on both
6
+ * sides of every boundary that matters (the 64-byte sniff, the tar block,
7
+ * the 64 KB stream chunk) for both placements the scan makes.
8
+ */
9
+ import { test, before } from "node:test";
10
+ import { strict as assert } from "node:assert";
11
+ import { createHash, randomBytes } from "node:crypto";
12
+ import { mkdtemp, mkdir, symlink, writeFile } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { placementForBytes } from "@mikeargento/bitgraph";
16
+ import { getPlacement } from "@mikeargento/bitgraph-verify";
17
+ import { expandPaths, fusedDigestFor, scanFile } from "../scan.js";
18
+ let root = "";
19
+ before(async () => {
20
+ root = await mkdtemp(join(tmpdir(), "bitgraph-mcp-scan-"));
21
+ });
22
+ const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
23
+ const SIZES = [0, 1, 8, 63, 64, 65, 511, 512, 513, 1024, 65_535, 65_536, 65_537, 200_000];
24
+ test("a scan's digest is the file's own and its state finishes to the placement's own build", async () => {
25
+ let checked = 0;
26
+ for (const size of SIZES) {
27
+ for (const kind of ["png", "txt"]) {
28
+ const lead = kind === "png" ? PNG : Buffer.from("txt:");
29
+ const bytes = Buffer.concat([lead, randomBytes(Math.max(0, size - lead.length))]).subarray(0, size);
30
+ const path = join(root, `${kind}-${size}.bin`);
31
+ await writeFile(path, bytes);
32
+ const s = await scanFile(path);
33
+ assert.equal(s.size, size);
34
+ assert.equal(s.digestB64, createHash("sha256").update(bytes).digest("base64"), `${kind} ${size}: digest`);
35
+ assert.equal(s.placement, placementForBytes(new Uint8Array(bytes)), `${kind} ${size}: the core's own placement choice`);
36
+ assert.ok(s.state !== null, "a file that did not change keeps its state");
37
+ const placement = getPlacement(s.placement);
38
+ if (placement === undefined)
39
+ throw new Error("placement missing");
40
+ // Two slots, both finished from the same saved state: the copy leaves the state intact.
41
+ for (const commitment of [randomBytes(32), randomBytes(32)]) {
42
+ const built = placement.build({ original: new Uint8Array(bytes), originDigest: s.originDigest, commitment: new Uint8Array(commitment) });
43
+ const expected = createHash("sha256").update(built).digest();
44
+ assert.deepEqual(Buffer.from(fusedDigestFor(s, new Uint8Array(commitment))), expected, `${kind} ${size}: state finished == sha256(build)`);
45
+ checked += 1;
46
+ }
47
+ }
48
+ }
49
+ assert.equal(checked, SIZES.length * 4);
50
+ });
51
+ test("expandPaths: files, directories, hidden entries, links, duplicates, limits, missing paths", async () => {
52
+ const lone = join(root, "lone.txt");
53
+ await writeFile(lone, "lone");
54
+ const dir = join(root, "tree");
55
+ await mkdir(join(dir, "sub", ".secret"), { recursive: true });
56
+ await writeFile(join(dir, "a.txt"), "a");
57
+ await writeFile(join(dir, ".DS_Store"), "junk");
58
+ await writeFile(join(dir, "sub", "b.txt"), "b");
59
+ await writeFile(join(dir, "sub", ".secret", "c.txt"), "c");
60
+ await symlink(join(dir, "a.txt"), join(dir, "link.txt"));
61
+ const e = await expandPaths([lone, dir], 100);
62
+ assert.deepEqual(e.files, [lone, join(dir, "a.txt"), join(dir, "sub", "b.txt")]);
63
+ assert.equal(e.directories, 1);
64
+ const twice = await expandPaths([lone, lone, dir, join(dir, "a.txt")], 100);
65
+ assert.deepEqual(twice.files, [lone, join(dir, "a.txt"), join(dir, "sub", "b.txt")], "the same file twice is one file");
66
+ await assert.rejects(expandPaths([dir], 1), /more than 1 files/);
67
+ await assert.rejects(expandPaths([lone, join(root, "missing.txt")], 100), (err) => {
68
+ assert.ok(err.message.includes("nothing was BitGraphed"));
69
+ assert.ok(err.message.includes(join(root, "missing.txt")));
70
+ return true;
71
+ });
72
+ });
73
+ //# sourceMappingURL=scan.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.test.js","sourceRoot":"","sources":["../../src/__tests__/scan.test.ts"],"names":[],"mappings":"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"}