@mikeargento/bitgraph-mcp 0.2.0 → 0.4.0
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 +8 -8
- package/dist/__tests__/format.test.js +24 -3
- package/dist/__tests__/format.test.js.map +1 -1
- package/dist/__tests__/scan.test.d.ts +2 -0
- package/dist/__tests__/scan.test.d.ts.map +1 -0
- package/dist/__tests__/scan.test.js +73 -0
- package/dist/__tests__/scan.test.js.map +1 -0
- package/dist/__tests__/tools.test.js +265 -83
- package/dist/__tests__/tools.test.js.map +1 -1
- package/dist/api.d.ts +18 -30
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +27 -50
- package/dist/api.js.map +1 -1
- package/dist/format.d.ts +36 -9
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +56 -26
- package/dist/format.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/scan.d.ts +56 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +158 -0
- package/dist/scan.js.map +1 -0
- package/dist/server.d.ts +45 -14
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +289 -125
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/__tests__/format.test.ts +25 -3
- package/src/__tests__/scan.test.ts +80 -0
- package/src/__tests__/tools.test.ts +280 -96
- package/src/api.ts +36 -77
- package/src/format.ts +96 -36
- package/src/scan.ts +173 -0
- package/src/server.ts +358 -137
- package/src/types.ts +23 -1
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:
|
|
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
|
-
|
|
5
|
+
Making a BitGraph is one call for any number of files or folders, and everything in the call becomes ONE BitGraph: a set under one slot, one position for all of it, the way a drop on the site works. 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`
|
|
16
|
-
- `bitgraph_check` reports whether files or digests are on record (`on_record`: a recording of the exact bytes
|
|
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 set, 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
|
|
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/
|
|
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/
|
|
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":"AAAA,qFAAqF;AAErF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAG5F,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACxE,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEpE,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAC5D,MAAM,GAAG,GAAG,QAAQ,CAAC,sBAAsB,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,8BAA8B,kBAAkB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpG,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,kBAAkB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,wCAAwC,CAAC,CAAC;AACjG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC3D,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+EAA+E,EAAE,GAAG,EAAE;IACzF,MAAM,IAAI,GAAG;QACX,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;QAC5B,OAAO,EAAE,GAAG;QACZ,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC;QAC1B,eAAe,EAAE,CAAC;QAClB,SAAS,EAAE,8BAA8B;KAC1C,CAAC;IACF,MAAM,KAAK,GAAG,oBAAoB,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|