@mikeargento/bitgraph-mcp 0.1.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/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/__tests__/encoding.test.d.ts +2 -0
- package/dist/__tests__/encoding.test.d.ts.map +1 -0
- package/dist/__tests__/encoding.test.js +51 -0
- package/dist/__tests__/encoding.test.js.map +1 -0
- package/dist/__tests__/format.test.d.ts +2 -0
- package/dist/__tests__/format.test.d.ts.map +1 -0
- package/dist/__tests__/format.test.js +59 -0
- package/dist/__tests__/format.test.js.map +1 -0
- package/dist/__tests__/tools.test.d.ts +2 -0
- package/dist/__tests__/tools.test.d.ts.map +1 -0
- package/dist/__tests__/tools.test.js +304 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/api.d.ts +54 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +144 -0
- package/dist/api.js.map +1 -0
- package/dist/encoding.d.ts +19 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +64 -0
- package/dist/encoding.js.map +1 -0
- package/dist/format.d.ts +44 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +164 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +375 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
- package/src/__tests__/encoding.test.ts +64 -0
- package/src/__tests__/format.test.ts +69 -0
- package/src/__tests__/tools.test.ts +334 -0
- package/src/api.ts +211 -0
- package/src/encoding.ts +72 -0
- package/src/format.ts +201 -0
- package/src/index.ts +30 -0
- package/src/server.ts +463 -0
- package/src/types.ts +74 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
/** Read configuration from the environment at call time (testable via env). */
|
|
3
|
+
export function configFromEnv() {
|
|
4
|
+
const baseUrl = (process.env["BITGRAPH_API_URL"] ?? "https://bitgraph.ing").replace(/\/+$/, "");
|
|
5
|
+
const apiKey = process.env["BITGRAPH_API_KEY"];
|
|
6
|
+
return apiKey ? { baseUrl, apiKey } : { baseUrl };
|
|
7
|
+
}
|
|
8
|
+
/** Matches the website client: 50 digests per commit request (~1s of TEE work each). */
|
|
9
|
+
export const COMMIT_CHUNK_SIZE = 50;
|
|
10
|
+
/** Matches the batch endpoint's MAX_DIGESTS. */
|
|
11
|
+
export const BATCH_CHECK_LIMIT = 500;
|
|
12
|
+
const CHECK_TIMEOUT_MS = 30_000;
|
|
13
|
+
const COMMIT_TIMEOUT_MS = 120_000;
|
|
14
|
+
export class ApiError extends Error {
|
|
15
|
+
status;
|
|
16
|
+
retryAfterSec;
|
|
17
|
+
constructor(status, message, retryAfterSec = null) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.retryAfterSec = retryAfterSec;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function parseError(res) {
|
|
24
|
+
let message = res.statusText || `HTTP ${res.status}`;
|
|
25
|
+
try {
|
|
26
|
+
const body = (await res.json());
|
|
27
|
+
if (typeof body.error === "string" && body.error.length > 0)
|
|
28
|
+
message = body.error;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* non-JSON error body */
|
|
32
|
+
}
|
|
33
|
+
const retryAfterRaw = res.headers.get("retry-after");
|
|
34
|
+
const retryAfterSec = retryAfterRaw !== null ? Number.parseInt(retryAfterRaw, 10) : null;
|
|
35
|
+
return new ApiError(res.status, message, Number.isFinite(retryAfterSec) ? retryAfterSec : null);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Redirects are refused everywhere: an Authorization header must never follow
|
|
39
|
+
* one to a different host, and the canonical endpoint serves without them.
|
|
40
|
+
*/
|
|
41
|
+
function redirectHint(err) {
|
|
42
|
+
if (err instanceof TypeError && /redirect/i.test(err.message)) {
|
|
43
|
+
throw new Error("The BitGraph endpoint redirected. Set BITGRAPH_API_URL to the canonical https://bitgraph.ing (apex, no www).");
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
async function getJson(config, path, timeoutMs) {
|
|
48
|
+
const res = await fetch(`${config.baseUrl}${path}`, {
|
|
49
|
+
method: "GET",
|
|
50
|
+
redirect: "error",
|
|
51
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
52
|
+
}).catch(redirectHint);
|
|
53
|
+
if (!res.ok)
|
|
54
|
+
throw await parseError(res);
|
|
55
|
+
return (await res.json());
|
|
56
|
+
}
|
|
57
|
+
async function postJson(config, path, body, timeoutMs, authenticated) {
|
|
58
|
+
const headers = { "Content-Type": "application/json" };
|
|
59
|
+
if (authenticated && config.apiKey)
|
|
60
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
61
|
+
const res = await fetch(`${config.baseUrl}${path}`, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers,
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
redirect: "error",
|
|
66
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
67
|
+
}).catch(redirectHint);
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw await parseError(res);
|
|
70
|
+
return (await res.json());
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Look up which digests are on record. Input digests must be URL-safe base64;
|
|
74
|
+
* the response is keyed by the exact strings sent. Batches of up to 500.
|
|
75
|
+
*/
|
|
76
|
+
export async function batchCheck(config, urlSafeDigests) {
|
|
77
|
+
const merged = { results: {} };
|
|
78
|
+
for (let offset = 0; offset < urlSafeDigests.length; offset += BATCH_CHECK_LIMIT) {
|
|
79
|
+
const chunk = urlSafeDigests.slice(offset, offset + BATCH_CHECK_LIMIT);
|
|
80
|
+
const page = await postJson(config, "/api/proofs/batch", { digests: chunk }, CHECK_TIMEOUT_MS, false);
|
|
81
|
+
Object.assign(merged.results, page.results);
|
|
82
|
+
}
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Record digests at new causal positions. Input digests must be STANDARD
|
|
87
|
+
* base64 (as stored in proofs). Commits sequentially in chunks of 50, matching
|
|
88
|
+
* the website client; the TEE serializes commits anyway.
|
|
89
|
+
*
|
|
90
|
+
* On a mid-batch failure the error carries how many proofs were already
|
|
91
|
+
* minted (those are permanent); the caller must report partial results
|
|
92
|
+
* honestly rather than pretending all-or-nothing.
|
|
93
|
+
*/
|
|
94
|
+
export async function commitDigests(config, standardDigests, attribution) {
|
|
95
|
+
const proofs = [];
|
|
96
|
+
for (let offset = 0; offset < standardDigests.length; offset += COMMIT_CHUNK_SIZE) {
|
|
97
|
+
const chunk = standardDigests.slice(offset, offset + COMMIT_CHUNK_SIZE);
|
|
98
|
+
const body = {
|
|
99
|
+
digests: chunk.map((digestB64) => ({ digestB64, hashAlg: "sha256" })),
|
|
100
|
+
chainId: "bitgraph:main",
|
|
101
|
+
};
|
|
102
|
+
if (attribution)
|
|
103
|
+
body["attribution"] = attribution;
|
|
104
|
+
try {
|
|
105
|
+
const raw = await postJson(config, "/api/commit", body, COMMIT_TIMEOUT_MS, true);
|
|
106
|
+
proofs.push(...(Array.isArray(raw) ? raw : [raw]));
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new PartialCommitError(proofs, standardDigests.length, err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return proofs;
|
|
113
|
+
}
|
|
114
|
+
/** A commit batch failed partway: `minted` proofs are already permanent. */
|
|
115
|
+
export class PartialCommitError extends Error {
|
|
116
|
+
minted;
|
|
117
|
+
requested;
|
|
118
|
+
cause2;
|
|
119
|
+
constructor(minted, requested, cause) {
|
|
120
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
121
|
+
super(`Recording stopped after ${minted.length} of ${requested} digests: ${reason}` +
|
|
122
|
+
(cause instanceof ApiError && cause.retryAfterSec !== null
|
|
123
|
+
? ` (retry after ${cause.retryAfterSec}s)`
|
|
124
|
+
: ""));
|
|
125
|
+
this.minted = minted;
|
|
126
|
+
this.requested = requested;
|
|
127
|
+
this.cause2 = cause;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Full detail for one digest: proof, all causal positions, anchor window. */
|
|
131
|
+
export async function getProofDetail(config, urlSafeDigest, counter, urlSafeEpoch) {
|
|
132
|
+
const params = new URLSearchParams();
|
|
133
|
+
if (counter !== undefined)
|
|
134
|
+
params.set("counter", counter);
|
|
135
|
+
if (urlSafeEpoch !== undefined)
|
|
136
|
+
params.set("epoch", urlSafeEpoch);
|
|
137
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
138
|
+
return getJson(config, `/api/proofs/digest/${encodeURIComponent(urlSafeDigest)}${query}`, CHECK_TIMEOUT_MS);
|
|
139
|
+
}
|
|
140
|
+
/** Resolve a BitGraph number (or digest string) to a digest via /api/search. */
|
|
141
|
+
export async function search(config, q) {
|
|
142
|
+
return getJson(config, `/api/search?q=${encodeURIComponent(q)}`, CHECK_TIMEOUT_MS);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Standard base64 → URL-safe unpadded base64. */
|
|
2
|
+
export declare function toUrlSafeB64(b64: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* URL-safe base64 → standard base64 with padding.
|
|
5
|
+
* A no-op (apart from re-padding) when the input is already standard base64,
|
|
6
|
+
* so it is safe to call on either form.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fromUrlSafeB64(urlSafe: string): string;
|
|
9
|
+
/** True if the string plausibly encodes 32 bytes of SHA-256 in either base64 form. */
|
|
10
|
+
export declare function looksLikeDigest(s: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* SHA-256 of a file's bytes as standard base64.
|
|
13
|
+
* Streams from disk, so file size is not a memory concern. Only this digest
|
|
14
|
+
* ever leaves the machine; file contents are never uploaded.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sha256FileB64(path: string): Promise<string>;
|
|
17
|
+
/** Run an async mapper over items with bounded concurrency, preserving order. */
|
|
18
|
+
export declare function mapConcurrent<T, R>(items: readonly T[], concurrency: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
19
|
+
//# sourceMappingURL=encoding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAcA,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAItD;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAIlD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUjE;AAED,iFAAiF;AACjF,wBAAsB,aAAa,CAAC,CAAC,EAAE,CAAC,EACtC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GACzC,OAAO,CAAC,CAAC,EAAE,CAAC,CAYd"}
|
package/dist/encoding.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
/**
|
|
3
|
+
* @mikeargento/bitgraph-mcp: digest encoding and file hashing.
|
|
4
|
+
*
|
|
5
|
+
* BitGraph uses two base64 forms for the same SHA-256 digest:
|
|
6
|
+
* standard base64 with padding inside proof objects and commit requests,
|
|
7
|
+
* URL-safe unpadded base64 in API lookups, S3 keys, and proof page URLs.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { createReadStream } from "node:fs";
|
|
11
|
+
import { stat } from "node:fs/promises";
|
|
12
|
+
/** Standard base64 → URL-safe unpadded base64. */
|
|
13
|
+
export function toUrlSafeB64(b64) {
|
|
14
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* URL-safe base64 → standard base64 with padding.
|
|
18
|
+
* A no-op (apart from re-padding) when the input is already standard base64,
|
|
19
|
+
* so it is safe to call on either form.
|
|
20
|
+
*/
|
|
21
|
+
export function fromUrlSafeB64(urlSafe) {
|
|
22
|
+
let b64 = urlSafe.replace(/-/g, "+").replace(/_/g, "/");
|
|
23
|
+
while (b64.length % 4 !== 0)
|
|
24
|
+
b64 += "=";
|
|
25
|
+
return b64;
|
|
26
|
+
}
|
|
27
|
+
/** True if the string plausibly encodes 32 bytes of SHA-256 in either base64 form. */
|
|
28
|
+
export function looksLikeDigest(s) {
|
|
29
|
+
const normalized = fromUrlSafeB64(s.trim());
|
|
30
|
+
if (!/^[A-Za-z0-9+/]{43}=$/.test(normalized))
|
|
31
|
+
return false;
|
|
32
|
+
return Buffer.from(normalized, "base64").length === 32;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SHA-256 of a file's bytes as standard base64.
|
|
36
|
+
* Streams from disk, so file size is not a memory concern. Only this digest
|
|
37
|
+
* ever leaves the machine; file contents are never uploaded.
|
|
38
|
+
*/
|
|
39
|
+
export async function sha256FileB64(path) {
|
|
40
|
+
const info = await stat(path);
|
|
41
|
+
if (!info.isFile()) {
|
|
42
|
+
throw new Error(`Not a regular file: ${path}`);
|
|
43
|
+
}
|
|
44
|
+
const hash = createHash("sha256");
|
|
45
|
+
for await (const chunk of createReadStream(path)) {
|
|
46
|
+
hash.update(chunk);
|
|
47
|
+
}
|
|
48
|
+
return hash.digest("base64");
|
|
49
|
+
}
|
|
50
|
+
/** Run an async mapper over items with bounded concurrency, preserving order. */
|
|
51
|
+
export async function mapConcurrent(items, concurrency, fn) {
|
|
52
|
+
const results = new Array(items.length);
|
|
53
|
+
let next = 0;
|
|
54
|
+
async function worker() {
|
|
55
|
+
while (next < items.length) {
|
|
56
|
+
const index = next++;
|
|
57
|
+
results[index] = await fn(items[index], index);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
|
|
61
|
+
await Promise.all(workers);
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=encoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,kDAAkD;AAClD,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC;IACxC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,MAAM,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3D,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,KAAe,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAmB,EACnB,WAAmB,EACnB,EAA0C;IAE1C,MAAM,OAAO,GAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,UAAU,MAAM;QACnB,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,KAAK,CAAM,EAAE,KAAK,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BitGraphProof, ProofDetailResponse } from "./types.js";
|
|
2
|
+
export declare const CHARACTER_LIMIT = 25000;
|
|
3
|
+
/** Public proof page URL for a digest, optionally pinned to one causal position. */
|
|
4
|
+
export declare function proofUrl(baseUrl: string, standardDigest: string, counter?: string, standardEpochId?: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* One recording outcome, in the product's own vocabulary. "not recorded" is
|
|
7
|
+
* the honest label for a file lost to a partial commit failure: never claim
|
|
8
|
+
* "on record" for a digest that has no proof.
|
|
9
|
+
*/
|
|
10
|
+
export interface RecordOutcome {
|
|
11
|
+
path: string;
|
|
12
|
+
digest: string;
|
|
13
|
+
outcome: "recorded" | "on record" | "not recorded";
|
|
14
|
+
counter: string | null;
|
|
15
|
+
epoch: string | null;
|
|
16
|
+
total_positions: number;
|
|
17
|
+
proof_url: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface CheckOutcome {
|
|
20
|
+
input: string;
|
|
21
|
+
digest: string;
|
|
22
|
+
on_record: boolean;
|
|
23
|
+
positions: Array<{
|
|
24
|
+
counter: string | null;
|
|
25
|
+
epoch: string | null;
|
|
26
|
+
}>;
|
|
27
|
+
proof_url: string | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function positionOf(proof: BitGraphProof): {
|
|
30
|
+
counter: string | null;
|
|
31
|
+
epoch: string | null;
|
|
32
|
+
};
|
|
33
|
+
export declare function renderRecordMarkdown(outcomes: readonly RecordOutcome[]): string;
|
|
34
|
+
export declare function renderCheckMarkdown(outcomes: readonly CheckOutcome[]): string;
|
|
35
|
+
export declare function renderProofMarkdown(detail: ProofDetailResponse, baseUrl: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Cap a JSON payload at CHARACTER_LIMIT. Attestation reports are the usual
|
|
38
|
+
* culprit; elide them first, then fall back to hard truncation with a notice.
|
|
39
|
+
*/
|
|
40
|
+
export declare function capJson(value: unknown): {
|
|
41
|
+
text: string;
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAgB,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEnF,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,oFAAoF;AACpF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,CASR;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC;IACnD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACnE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAIjG;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,CA+B/E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAa7E;AAiBD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,MAAM,GACd,MAAM,CAuCR;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAW5E"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
/**
|
|
3
|
+
* @mikeargento/bitgraph-mcp: output shaping.
|
|
4
|
+
*
|
|
5
|
+
* Markdown for human-facing summaries, JSON for complete structured data.
|
|
6
|
+
* Time statements come from the Ethereum anchor bracket ("BitGraphed between
|
|
7
|
+
* X and Y"), never from advisory clock fields.
|
|
8
|
+
*/
|
|
9
|
+
import { toUrlSafeB64 } from "./encoding.js";
|
|
10
|
+
export const CHARACTER_LIMIT = 25_000;
|
|
11
|
+
/** Public proof page URL for a digest, optionally pinned to one causal position. */
|
|
12
|
+
export function proofUrl(baseUrl, standardDigest, counter, standardEpochId) {
|
|
13
|
+
let url = `${baseUrl}/proof/${encodeURIComponent(toUrlSafeB64(standardDigest))}`;
|
|
14
|
+
if (counter !== undefined) {
|
|
15
|
+
url += `?counter=${encodeURIComponent(counter)}`;
|
|
16
|
+
if (standardEpochId !== undefined) {
|
|
17
|
+
url += `&epoch=${encodeURIComponent(toUrlSafeB64(standardEpochId))}`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return url;
|
|
21
|
+
}
|
|
22
|
+
export function positionOf(proof) {
|
|
23
|
+
const counter = proof.commit?.counter ?? null;
|
|
24
|
+
const epochId = proof.commit?.epochId;
|
|
25
|
+
return { counter, epoch: epochId !== undefined ? toUrlSafeB64(epochId) : null };
|
|
26
|
+
}
|
|
27
|
+
export function renderRecordMarkdown(outcomes) {
|
|
28
|
+
const recorded = outcomes.filter((o) => o.outcome === "recorded");
|
|
29
|
+
const onRecord = outcomes.filter((o) => o.outcome === "on record");
|
|
30
|
+
const notRecorded = outcomes.filter((o) => o.outcome === "not recorded");
|
|
31
|
+
const lines = [];
|
|
32
|
+
let headline = `${recorded.length} recorded, ${onRecord.length} already on record.`;
|
|
33
|
+
if (notRecorded.length > 0) {
|
|
34
|
+
headline = `${recorded.length} recorded, ${onRecord.length} already on record, ${notRecorded.length} NOT recorded.`;
|
|
35
|
+
}
|
|
36
|
+
lines.push(headline);
|
|
37
|
+
for (const o of outcomes) {
|
|
38
|
+
if (o.outcome === "not recorded") {
|
|
39
|
+
lines.push(`- not recorded (commit failed) · ${o.path}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const positionNote = o.outcome === "on record"
|
|
43
|
+
? o.total_positions > 1
|
|
44
|
+
? ` (${o.total_positions} causal positions, earliest shown)`
|
|
45
|
+
: ""
|
|
46
|
+
: "";
|
|
47
|
+
lines.push(`- ${o.outcome} · #${o.counter ?? "?"} · ${o.path}${positionNote}\n ${o.proof_url}`);
|
|
48
|
+
}
|
|
49
|
+
if (onRecord.length > 0) {
|
|
50
|
+
lines.push(`\nAlready-recorded files were not re-recorded. To record one of them at a new causal position deliberately, call bitgraph_record with again=true.`);
|
|
51
|
+
}
|
|
52
|
+
return lines.join("\n");
|
|
53
|
+
}
|
|
54
|
+
export function renderCheckMarkdown(outcomes) {
|
|
55
|
+
const found = outcomes.filter((o) => o.on_record).length;
|
|
56
|
+
const lines = [`${found} of ${outcomes.length} on record.`];
|
|
57
|
+
for (const o of outcomes) {
|
|
58
|
+
if (o.on_record) {
|
|
59
|
+
const first = o.positions[0];
|
|
60
|
+
const extra = o.positions.length > 1 ? ` and ${o.positions.length - 1} more position(s)` : "";
|
|
61
|
+
lines.push(`- on record · #${first?.counter ?? "?"}${extra} · ${o.input}\n ${o.proof_url}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
lines.push(`- not on record · ${o.input}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
}
|
|
69
|
+
function renderWindow(detail) {
|
|
70
|
+
const w = detail.causalWindow;
|
|
71
|
+
if (!w)
|
|
72
|
+
return null;
|
|
73
|
+
const lower = w.anchorBefore?.blockTime ?? null; // earlier block: BitGraphed after it
|
|
74
|
+
const upper = w.anchorAfter?.blockTime ?? null; // later block: BitGraphed before it
|
|
75
|
+
const lowerBlock = w.anchorBefore?.blockNumber ?? null;
|
|
76
|
+
const upperBlock = w.anchorAfter?.blockNumber ?? null;
|
|
77
|
+
if (lower && upper) {
|
|
78
|
+
return `BitGraphed between ${lower} (Ethereum block ${lowerBlock ?? "?"}) and ${upper} (block ${upperBlock ?? "?"}).`;
|
|
79
|
+
}
|
|
80
|
+
if (upper)
|
|
81
|
+
return `BitGraphed before ${upper} (Ethereum block ${upperBlock ?? "?"}).`;
|
|
82
|
+
if (lower)
|
|
83
|
+
return `BitGraphed after ${lower} (Ethereum block ${lowerBlock ?? "?"}).`;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
export function renderProofMarkdown(detail, baseUrl) {
|
|
87
|
+
const proof = detail.proofs[0]?.proof;
|
|
88
|
+
if (!proof)
|
|
89
|
+
return "No proof found for that digest.";
|
|
90
|
+
const digest = proof.artifact?.digestB64 ?? "";
|
|
91
|
+
const { counter, epoch } = positionOf(proof);
|
|
92
|
+
const lines = [];
|
|
93
|
+
lines.push(`# BitGraph #${counter ?? "?"}`);
|
|
94
|
+
lines.push("");
|
|
95
|
+
lines.push(`- Digest (SHA-256): ${toUrlSafeB64(digest)}`);
|
|
96
|
+
if (epoch)
|
|
97
|
+
lines.push(`- Position: counter ${counter ?? "?"} in epoch ${epoch.slice(0, 12)}…`);
|
|
98
|
+
const window = renderWindow(detail);
|
|
99
|
+
if (window)
|
|
100
|
+
lines.push(`- ${window}`);
|
|
101
|
+
const etherscan = detail.causalWindow?.anchorAfter?.etherscanUrl ??
|
|
102
|
+
detail.causalWindow?.anchorBefore?.etherscanUrl;
|
|
103
|
+
if (etherscan)
|
|
104
|
+
lines.push(`- Anchor block on Etherscan: ${etherscan}`);
|
|
105
|
+
if (proof.environment?.enforcement) {
|
|
106
|
+
lines.push(`- Enforcement: ${proof.environment.enforcement}`);
|
|
107
|
+
}
|
|
108
|
+
if (proof.attribution?.name || proof.attribution?.message) {
|
|
109
|
+
const note = [proof.attribution.name, proof.attribution.message]
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.join(": ");
|
|
112
|
+
lines.push(`- Submitter's note (self-attributed, not verified): ${note}`);
|
|
113
|
+
}
|
|
114
|
+
const positions = detail.positions ?? [];
|
|
115
|
+
if (positions.length > 1) {
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(`## Causal positions (${positions.length})`);
|
|
118
|
+
positions.forEach((p, i) => {
|
|
119
|
+
const label = i === 0 ? " · original" : "";
|
|
120
|
+
const bracket = p.lowerTime && p.upperTime ? ` · between ${p.lowerTime} and ${p.upperTime}` : "";
|
|
121
|
+
lines.push(`- #${p.counter ?? "?"}${label}${bracket}`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
lines.push("");
|
|
125
|
+
lines.push(`Proof page: ${proofUrl(baseUrl, digest, counter ?? undefined, proof.commit?.epochId)}`);
|
|
126
|
+
return lines.join("\n");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Cap a JSON payload at CHARACTER_LIMIT. Attestation reports are the usual
|
|
130
|
+
* culprit; elide them first, then fall back to hard truncation with a notice.
|
|
131
|
+
*/
|
|
132
|
+
export function capJson(value) {
|
|
133
|
+
let text = JSON.stringify(value, null, 2);
|
|
134
|
+
if (text.length <= CHARACTER_LIMIT)
|
|
135
|
+
return { text, truncated: false };
|
|
136
|
+
const elided = JSON.parse(JSON.stringify(value));
|
|
137
|
+
elideReports(elided);
|
|
138
|
+
text = JSON.stringify(elided, null, 2);
|
|
139
|
+
if (text.length <= CHARACTER_LIMIT)
|
|
140
|
+
return { text, truncated: true };
|
|
141
|
+
return {
|
|
142
|
+
text: `${text.slice(0, CHARACTER_LIMIT)}\n… truncated at ${CHARACTER_LIMIT} characters. Request a single item or use markdown format for a summary.`,
|
|
143
|
+
truncated: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function elideReports(value) {
|
|
147
|
+
if (Array.isArray(value)) {
|
|
148
|
+
for (const item of value)
|
|
149
|
+
elideReports(item);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (value !== null && typeof value === "object") {
|
|
153
|
+
const obj = value;
|
|
154
|
+
for (const [key, v] of Object.entries(obj)) {
|
|
155
|
+
if (key === "reportB64" && typeof v === "string" && v.length > 256) {
|
|
156
|
+
obj[key] = `<elided ${v.length} base64 chars; fetch the proof page for the full attestation>`;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
elideReports(v);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
3
|
+
/**
|
|
4
|
+
* @mikeargento/bitgraph-mcp: stdio entry point.
|
|
5
|
+
*
|
|
6
|
+
* Environment:
|
|
7
|
+
* BITGRAPH_API_URL optional, defaults to https://bitgraph.ing
|
|
8
|
+
* BITGRAPH_API_KEY optional; sent as a Bearer token on recordings
|
|
9
|
+
*/
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { configFromEnv } from "./api.js";
|
|
12
|
+
import { buildServer, SERVER_VERSION } from "./server.js";
|
|
13
|
+
async function main() {
|
|
14
|
+
const config = configFromEnv();
|
|
15
|
+
const server = buildServer();
|
|
16
|
+
const transport = new StdioServerTransport();
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
// stdio servers must never write to stdout; stderr only.
|
|
19
|
+
console.error(`bitgraph-mcp ${SERVER_VERSION} running (endpoint: ${config.baseUrl}, api key: ${config.apiKey ? "set" : "not set"})`);
|
|
20
|
+
}
|
|
21
|
+
main().catch((err) => {
|
|
22
|
+
console.error("bitgraph-mcp failed to start:", err);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,qFAAqF;AAErF;;;;;;GAMG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1D,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,yDAAyD;IACzD,OAAO,CAAC,KAAK,CACX,gBAAgB,cAAc,uBAAuB,MAAM,CAAC,OAAO,cAAc,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,CACtH,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mikeargento/bitgraph-mcp: tool definitions.
|
|
3
|
+
*
|
|
4
|
+
* Three gestures, the same three the website has: record a file (take a
|
|
5
|
+
* BitGraph), check whether bytes are on record, fetch a proof. Only SHA-256
|
|
6
|
+
* digests ever leave the machine; file contents are never uploaded.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
export declare const SERVER_VERSION = "0.1.1";
|
|
10
|
+
export declare function buildServer(): McpServer;
|
|
11
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8BpE,eAAO,MAAM,cAAc,UAAU,CAAC;AAqDtC,wBAAgB,WAAW,IAAI,SAAS,CAiXvC"}
|