@agentauditai/mcp-server 0.0.0-stage → 2.0.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/LICENSE +21 -0
- package/README.md +79 -2
- package/dist/deployments/phase7-arbitrumOne.json +15 -0
- package/dist/deployments/phase7-base.json +15 -0
- package/dist/deployments/phase7-mantle.json +15 -0
- package/dist/deployments/phase7-optimism.json +15 -0
- package/dist/deployments/phase7-polygon.json +15 -0
- package/dist/sdk/src/batch.d.ts +175 -0
- package/dist/sdk/src/batch.js +370 -0
- package/dist/sdk/src/index.d.ts +347 -0
- package/dist/sdk/src/index.js +557 -0
- package/dist/src/mcp-server.d.ts +19 -0
- package/dist/src/mcp-server.js +575 -0
- package/dist/tools/verify/src/evidence.d.ts +23 -0
- package/dist/tools/verify/src/evidence.js +152 -0
- package/dist/tools/verify/src/verify.d.ts +58 -0
- package/dist/tools/verify/src/verify.js +312 -0
- package/package.json +35 -4
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// AgentAudit AI — Batch anchoring
|
|
3
|
+
// Buffers action_refs (docs/EVIDENCE_SCHEMA.md) and anchors them on-chain as a
|
|
4
|
+
// single Merkle root via AuditVault v2 commitBatch.
|
|
5
|
+
// EU AI Act: Art. 12 (record-keeping), Art. 19 (log retention)
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.BatchBuffer = exports.FileBatchStore = exports.MemoryBatchStore = exports.EVIDENCE_SCHEMA = void 0;
|
|
41
|
+
exports.canonicalize = canonicalize;
|
|
42
|
+
exports.sha256Hex = sha256Hex;
|
|
43
|
+
exports.computeActionRef = computeActionRef;
|
|
44
|
+
exports.encodeScopeSegment = encodeScopeSegment;
|
|
45
|
+
exports.toolCallScope = toolCallScope;
|
|
46
|
+
exports.dataReadScope = dataReadScope;
|
|
47
|
+
exports.llmCallScope = llmCallScope;
|
|
48
|
+
exports.llmDecisionPreimage = llmDecisionPreimage;
|
|
49
|
+
exports.hashPair = hashPair;
|
|
50
|
+
exports.buildMerkleLevels = buildMerkleLevels;
|
|
51
|
+
exports.merkleRoot = merkleRoot;
|
|
52
|
+
exports.merkleProof = merkleProof;
|
|
53
|
+
exports.verifyMerkleProof = verifyMerkleProof;
|
|
54
|
+
exports.resolveBatchBuffer = resolveBatchBuffer;
|
|
55
|
+
const crypto_1 = require("crypto");
|
|
56
|
+
const fs_1 = require("fs");
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const ethers_1 = require("ethers");
|
|
59
|
+
// ─── JCS (RFC 8785) ───────────────────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* RFC 8785 JSON Canonicalization Scheme.
|
|
62
|
+
* Strings and numbers use ECMAScript JSON.stringify serialization (as RFC 8785
|
|
63
|
+
* specifies); object keys are sorted by UTF-16 code units (Array#sort default).
|
|
64
|
+
*/
|
|
65
|
+
function canonicalize(value) {
|
|
66
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
|
67
|
+
return JSON.stringify(value);
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === "number") {
|
|
70
|
+
if (!Number.isFinite(value))
|
|
71
|
+
throw new Error(`JCS: non-finite number ${value}`);
|
|
72
|
+
return JSON.stringify(value);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(value)) {
|
|
75
|
+
return "[" + value.map(canonicalize).join(",") + "]";
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === "object") {
|
|
78
|
+
const obj = value;
|
|
79
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== undefined).sort();
|
|
80
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k])).join(",") + "}";
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`JCS: unsupported type ${typeof value}`);
|
|
83
|
+
}
|
|
84
|
+
/** Lowercase hex SHA-256 of the UTF-8 bytes of `value`, no prefix */
|
|
85
|
+
function sha256Hex(value) {
|
|
86
|
+
return (0, crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
|
|
87
|
+
}
|
|
88
|
+
// ─── Evidence Schema v1 ───────────────────────────────────────────────────────
|
|
89
|
+
exports.EVIDENCE_SCHEMA = "agentaudit-evidence-v1";
|
|
90
|
+
/** Copy only the four preimage fields — envelope fields must never leak in */
|
|
91
|
+
function pickPreimage(p) {
|
|
92
|
+
return {
|
|
93
|
+
action_type: p.action_type,
|
|
94
|
+
agent_id: p.agent_id,
|
|
95
|
+
scope: p.scope,
|
|
96
|
+
timestamp_ms: p.timestamp_ms,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** action_ref = SHA-256(JCS(preimage)), as 0x-prefixed bytes32 hex */
|
|
100
|
+
function computeActionRef(preimage) {
|
|
101
|
+
if (!Number.isSafeInteger(preimage.timestamp_ms)) {
|
|
102
|
+
throw new Error("timestamp_ms must be an integer (Unix ms)");
|
|
103
|
+
}
|
|
104
|
+
return "0x" + sha256Hex(canonicalize(pickPreimage(preimage)));
|
|
105
|
+
}
|
|
106
|
+
/** Percent-encode the characters that would break a scope segment */
|
|
107
|
+
function encodeScopeSegment(segment) {
|
|
108
|
+
return segment.replace(/%/g, "%25").replace(/:/g, "%3A");
|
|
109
|
+
}
|
|
110
|
+
/** §2.1 — tool_call:<tool>:result_hash:<sha256> */
|
|
111
|
+
function toolCallScope(tool, resultHash) {
|
|
112
|
+
return `tool_call:${encodeScopeSegment(tool)}:result_hash:${resultHash}`;
|
|
113
|
+
}
|
|
114
|
+
/** §2.2 — data_read:<endpoint>:<dataset_version>:<result_hash> */
|
|
115
|
+
function dataReadScope(endpoint, datasetVersion, resultHash) {
|
|
116
|
+
return `data_read:${encodeScopeSegment(endpoint)}:${encodeScopeSegment(datasetVersion)}:${resultHash}`;
|
|
117
|
+
}
|
|
118
|
+
/** §2.3 — llm_call:<model>:record_hash:<sha256(JCS(record))> */
|
|
119
|
+
function llmCallScope(model, record) {
|
|
120
|
+
return `llm_call:${encodeScopeSegment(model)}:record_hash:${sha256Hex(canonicalize(record))}`;
|
|
121
|
+
}
|
|
122
|
+
/** Preimage for an LLM decision logged by the SDK wrappers */
|
|
123
|
+
function llmDecisionPreimage(agentId, model, record, timestampMs = Date.now()) {
|
|
124
|
+
return {
|
|
125
|
+
action_type: "LLM_DECISION",
|
|
126
|
+
agent_id: String(agentId),
|
|
127
|
+
scope: llmCallScope(model, record),
|
|
128
|
+
timestamp_ms: timestampMs,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// ─── Merkle tree (matches AuditVault v2 _verifyMerkle) ───────────────────────
|
|
132
|
+
//
|
|
133
|
+
// Leaves: action_ref values as-is (the contract does not re-hash leaves).
|
|
134
|
+
// Nodes: keccak256(abi.encodePacked(min(a, b), max(a, b))) — sorted pairs.
|
|
135
|
+
// Odd: an unpaired last node is promoted unchanged to the next level.
|
|
136
|
+
const BYTES32 = /^0x[0-9a-f]{64}$/;
|
|
137
|
+
function assertBytes32(value, label) {
|
|
138
|
+
const v = value.toLowerCase();
|
|
139
|
+
if (!BYTES32.test(v))
|
|
140
|
+
throw new Error(`${label} is not a bytes32 hex string: ${value}`);
|
|
141
|
+
return v;
|
|
142
|
+
}
|
|
143
|
+
function hashPair(a, b) {
|
|
144
|
+
const [lo, hi] = BigInt(a) <= BigInt(b) ? [a, b] : [b, a];
|
|
145
|
+
return ethers_1.ethers.keccak256(ethers_1.ethers.concat([lo, hi]));
|
|
146
|
+
}
|
|
147
|
+
/** All tree levels, leaves first, root level last */
|
|
148
|
+
function buildMerkleLevels(leaves) {
|
|
149
|
+
if (leaves.length === 0)
|
|
150
|
+
throw new Error("Merkle tree needs at least one leaf");
|
|
151
|
+
const levels = [leaves.map((l) => assertBytes32(l, "leaf"))];
|
|
152
|
+
while (levels[levels.length - 1].length > 1) {
|
|
153
|
+
const cur = levels[levels.length - 1];
|
|
154
|
+
const next = [];
|
|
155
|
+
for (let i = 0; i < cur.length; i += 2) {
|
|
156
|
+
next.push(i + 1 < cur.length ? hashPair(cur[i], cur[i + 1]) : cur[i]);
|
|
157
|
+
}
|
|
158
|
+
levels.push(next);
|
|
159
|
+
}
|
|
160
|
+
return levels;
|
|
161
|
+
}
|
|
162
|
+
function merkleRoot(leaves) {
|
|
163
|
+
const levels = buildMerkleLevels(leaves);
|
|
164
|
+
return levels[levels.length - 1][0];
|
|
165
|
+
}
|
|
166
|
+
/** Sibling path for leaf `index`; levels where the node was promoted are skipped */
|
|
167
|
+
function merkleProof(levels, index) {
|
|
168
|
+
if (index < 0 || index >= levels[0].length)
|
|
169
|
+
throw new Error(`leaf index ${index} out of range`);
|
|
170
|
+
const proof = [];
|
|
171
|
+
let idx = index;
|
|
172
|
+
for (let l = 0; l < levels.length - 1; l++) {
|
|
173
|
+
const sibling = idx ^ 1;
|
|
174
|
+
if (sibling < levels[l].length)
|
|
175
|
+
proof.push(levels[l][sibling]);
|
|
176
|
+
idx >>= 1;
|
|
177
|
+
}
|
|
178
|
+
return proof;
|
|
179
|
+
}
|
|
180
|
+
/** Off-chain mirror of AuditVault._verifyMerkle */
|
|
181
|
+
function verifyMerkleProof(leaf, proof, root) {
|
|
182
|
+
let computed = leaf.toLowerCase();
|
|
183
|
+
for (const sibling of proof)
|
|
184
|
+
computed = hashPair(computed, sibling.toLowerCase());
|
|
185
|
+
return computed === root.toLowerCase();
|
|
186
|
+
}
|
|
187
|
+
class MemoryBatchStore {
|
|
188
|
+
constructor() {
|
|
189
|
+
this.records = new Map();
|
|
190
|
+
}
|
|
191
|
+
async put(record) {
|
|
192
|
+
this.records.set(record.batch_id, record);
|
|
193
|
+
return `memory://${record.batch_id}`;
|
|
194
|
+
}
|
|
195
|
+
async get(batchId) {
|
|
196
|
+
return this.records.get(batchId) ?? null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.MemoryBatchStore = MemoryBatchStore;
|
|
200
|
+
/** Writes one JSON file per batch: <dir>/<batch_id>.json */
|
|
201
|
+
class FileBatchStore {
|
|
202
|
+
constructor(dir) {
|
|
203
|
+
this.dir = dir;
|
|
204
|
+
}
|
|
205
|
+
file(batchId) {
|
|
206
|
+
return path.resolve(this.dir, `${batchId}.json`);
|
|
207
|
+
}
|
|
208
|
+
async put(record) {
|
|
209
|
+
await fs_1.promises.mkdir(this.dir, { recursive: true });
|
|
210
|
+
const file = this.file(record.batch_id);
|
|
211
|
+
await fs_1.promises.writeFile(file, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
212
|
+
return `file://${file}`;
|
|
213
|
+
}
|
|
214
|
+
async get(batchId) {
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(await fs_1.promises.readFile(this.file(batchId), "utf8"));
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (err.code === "ENOENT")
|
|
220
|
+
return null;
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.FileBatchStore = FileBatchStore;
|
|
226
|
+
/**
|
|
227
|
+
* Collects action_refs and anchors them as one Merkle root per batch.
|
|
228
|
+
* Flushes on maxEntries, on maxAgeSeconds after the first entry, or on flush().
|
|
229
|
+
* Flushes are serialized; a failed flush re-queues its entries for retry.
|
|
230
|
+
*/
|
|
231
|
+
class BatchBuffer {
|
|
232
|
+
constructor(config) {
|
|
233
|
+
this.pending = [];
|
|
234
|
+
this.flushChain = Promise.resolve();
|
|
235
|
+
this.index = new Map();
|
|
236
|
+
this.batches = new Map();
|
|
237
|
+
this.committer = config.committer;
|
|
238
|
+
this.agentAddress = ethers_1.ethers.getAddress(config.agentAddress);
|
|
239
|
+
this.store = config.store ?? new MemoryBatchStore();
|
|
240
|
+
this.maxEntries = config.maxEntries ?? 50;
|
|
241
|
+
this.maxAgeMs = (config.maxAgeSeconds ?? 60) * 1000;
|
|
242
|
+
this.complianceScore = config.complianceScore ?? 0;
|
|
243
|
+
this.onError = config.onError ?? ((err) => console.error("[BatchBuffer] flush failed:", err.message));
|
|
244
|
+
if (!Number.isInteger(this.maxEntries) || this.maxEntries < 1) {
|
|
245
|
+
throw new Error("maxEntries must be a positive integer");
|
|
246
|
+
}
|
|
247
|
+
if (!(this.maxAgeMs > 0))
|
|
248
|
+
throw new Error("maxAgeSeconds must be positive");
|
|
249
|
+
if (!Number.isInteger(this.complianceScore) || this.complianceScore < 0 || this.complianceScore > 100) {
|
|
250
|
+
throw new Error("complianceScore must be an integer 0–100");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** Number of entries waiting for the next flush */
|
|
254
|
+
get size() {
|
|
255
|
+
return this.pending.length;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Buffer one action. Resolves once buffered — or, if this entry fills the
|
|
259
|
+
* batch, once that flush completes (so commit errors reach the caller).
|
|
260
|
+
*/
|
|
261
|
+
async add(preimage, record) {
|
|
262
|
+
const actionRef = computeActionRef(preimage);
|
|
263
|
+
this.pending.push({ action_ref: actionRef, preimage: pickPreimage(preimage), record });
|
|
264
|
+
if (this.pending.length >= this.maxEntries) {
|
|
265
|
+
await this.flush();
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
this._armTimer();
|
|
269
|
+
}
|
|
270
|
+
return { actionRef };
|
|
271
|
+
}
|
|
272
|
+
/** Anchor everything buffered now. Resolves null if there was nothing to flush. */
|
|
273
|
+
flush() {
|
|
274
|
+
const run = this.flushChain.then(() => this._flush());
|
|
275
|
+
this.flushChain = run.catch(() => undefined);
|
|
276
|
+
return run;
|
|
277
|
+
}
|
|
278
|
+
/** Stop the timer and flush whatever is left */
|
|
279
|
+
async close() {
|
|
280
|
+
this._clearTimer();
|
|
281
|
+
return this.flush();
|
|
282
|
+
}
|
|
283
|
+
/** Proof for a committed action_ref; null if unknown or still pending */
|
|
284
|
+
getInclusionProof(actionRef) {
|
|
285
|
+
const loc = this.index.get(actionRef.toLowerCase());
|
|
286
|
+
if (!loc)
|
|
287
|
+
return null;
|
|
288
|
+
const batch = this.batches.get(loc.batchId);
|
|
289
|
+
return {
|
|
290
|
+
leaf: batch.levels[0][loc.leafIndex],
|
|
291
|
+
proof: merkleProof(batch.levels, loc.leafIndex),
|
|
292
|
+
root: loc.batchId,
|
|
293
|
+
batchId: loc.batchId,
|
|
294
|
+
batchIndex: batch.batchIndex,
|
|
295
|
+
agentAddress: this.agentAddress,
|
|
296
|
+
txHash: batch.txHash,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
300
|
+
async _flush() {
|
|
301
|
+
this._clearTimer();
|
|
302
|
+
if (this.pending.length === 0)
|
|
303
|
+
return null;
|
|
304
|
+
const entries = this.pending;
|
|
305
|
+
this.pending = [];
|
|
306
|
+
try {
|
|
307
|
+
const levels = buildMerkleLevels(entries.map((e) => e.action_ref));
|
|
308
|
+
const root = levels[levels.length - 1][0];
|
|
309
|
+
const record = {
|
|
310
|
+
schema: exports.EVIDENCE_SCHEMA,
|
|
311
|
+
batch_id: root,
|
|
312
|
+
agent_address: this.agentAddress,
|
|
313
|
+
merkle_root: root,
|
|
314
|
+
leaf_hash: "sha256(jcs(preimage))",
|
|
315
|
+
node_hash: "keccak256(sorted_pair)",
|
|
316
|
+
odd_node: "promote",
|
|
317
|
+
created_at_ms: Date.now(),
|
|
318
|
+
entries,
|
|
319
|
+
};
|
|
320
|
+
const contentURI = await this.store.put(record);
|
|
321
|
+
const { txHash, batchIndex } = await this.committer.commitBatch({
|
|
322
|
+
agentAddress: this.agentAddress,
|
|
323
|
+
merkleRoot: root,
|
|
324
|
+
contentURI,
|
|
325
|
+
eventCount: entries.length,
|
|
326
|
+
complianceScore: this.complianceScore,
|
|
327
|
+
});
|
|
328
|
+
this.batches.set(root, { levels, batchIndex, txHash });
|
|
329
|
+
entries.forEach((e, i) => this.index.set(e.action_ref, { batchId: root, leafIndex: i }));
|
|
330
|
+
return { batchId: root, merkleRoot: root, contentURI, eventCount: entries.length, txHash, batchIndex };
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
// Keep the evidence: put the entries back in front of anything added meanwhile
|
|
334
|
+
this.pending = entries.concat(this.pending);
|
|
335
|
+
this._armTimer();
|
|
336
|
+
throw err;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
_armTimer() {
|
|
340
|
+
if (this.timer)
|
|
341
|
+
return;
|
|
342
|
+
this.timer = setTimeout(() => {
|
|
343
|
+
this.timer = undefined;
|
|
344
|
+
this.flush().catch((err) => this.onError(err));
|
|
345
|
+
}, this.maxAgeMs);
|
|
346
|
+
// Never keep the process alive just to flush
|
|
347
|
+
this.timer.unref?.();
|
|
348
|
+
}
|
|
349
|
+
_clearTimer() {
|
|
350
|
+
if (this.timer)
|
|
351
|
+
clearTimeout(this.timer);
|
|
352
|
+
this.timer = undefined;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
exports.BatchBuffer = BatchBuffer;
|
|
356
|
+
/** Build (or reuse) the buffer a wrapper logs into */
|
|
357
|
+
function resolveBatchBuffer(sdk, options = {}) {
|
|
358
|
+
if (options.buffer)
|
|
359
|
+
return options.buffer;
|
|
360
|
+
sdk.assertBatchAnchoring?.();
|
|
361
|
+
return new BatchBuffer({
|
|
362
|
+
committer: sdk,
|
|
363
|
+
agentAddress: options.agentAddress ?? sdk.getAddress(),
|
|
364
|
+
store: options.store,
|
|
365
|
+
maxEntries: options.maxEntries,
|
|
366
|
+
maxAgeSeconds: options.maxAgeSeconds,
|
|
367
|
+
complianceScore: options.complianceScore,
|
|
368
|
+
onError: options.onError,
|
|
369
|
+
});
|
|
370
|
+
}
|