@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.
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ // AgentAudit AI — Evidence Schema v1 primitives for the standalone verifier:
3
+ // JCS (RFC 8785) canonicalization, action_ref and the AuditVault v2 Merkle tree.
4
+ // Self-contained on purpose: no import from sdk/ or packages/, so the verifier
5
+ // re-derives everything independently of the code that produced the bundle.
6
+ // EU AI Act: Art. 12 (record-keeping), Art. 19 (log retention)
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.PREIMAGE_FIELDS = exports.ODD_NODE = exports.NODE_HASH = exports.LEAF_HASH = exports.SCHEMA = void 0;
9
+ exports.jcs = jcs;
10
+ exports.preimageProblem = preimageProblem;
11
+ exports.actionRef = actionRef;
12
+ exports.hashPair = hashPair;
13
+ exports.merkleRoot = merkleRoot;
14
+ exports.merkleProof = merkleProof;
15
+ exports.verifyProof = verifyProof;
16
+ const crypto_1 = require("crypto");
17
+ const ethers_1 = require("ethers");
18
+ // ─── Constants ────────────────────────────────────────────────────────────────
19
+ exports.SCHEMA = "agentaudit-evidence-v1";
20
+ exports.LEAF_HASH = "sha256(jcs(preimage))";
21
+ exports.NODE_HASH = "keccak256(sorted_pair)";
22
+ exports.ODD_NODE = "promote";
23
+ /** The only fields that enter the preimage (docs/EVIDENCE_SCHEMA.md §1.1) */
24
+ exports.PREIMAGE_FIELDS = ["action_type", "agent_id", "scope", "timestamp_ms"];
25
+ const BYTES32_RE = /^0x[0-9a-f]{64}$/;
26
+ // ─── JCS (RFC 8785) ───────────────────────────────────────────────────────────
27
+ function hasLoneSurrogate(s) {
28
+ for (let i = 0; i < s.length; i++) {
29
+ const c = s.charCodeAt(i);
30
+ if (c >= 0xd800 && c <= 0xdbff) {
31
+ const next = s.charCodeAt(i + 1);
32
+ if (next >= 0xdc00 && next <= 0xdfff) {
33
+ i++;
34
+ continue;
35
+ }
36
+ return true;
37
+ }
38
+ if (c >= 0xdc00 && c <= 0xdfff)
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+ function jcsString(s) {
44
+ // RFC 8785 §3.2.2.2: strings must be valid Unicode; JSON.stringify's escaping
45
+ // (\b \t \n \f \r \" \\, other C0 as lowercase \u00xx) is exactly JCS's.
46
+ if (hasLoneSurrogate(s))
47
+ throw new Error("JCS: string contains a lone surrogate");
48
+ return JSON.stringify(s);
49
+ }
50
+ /**
51
+ * RFC 8785 canonical JSON. Numbers use ECMAScript Number serialization (what
52
+ * JSON.stringify emits for finite numbers, -0 → 0); object keys are sorted by
53
+ * UTF-16 code units, which is Array.prototype.sort's default order.
54
+ */
55
+ function jcs(value) {
56
+ if (value === null)
57
+ return "null";
58
+ if (typeof value === "boolean")
59
+ return value ? "true" : "false";
60
+ if (typeof value === "number") {
61
+ if (!Number.isFinite(value))
62
+ throw new Error("JCS: non-finite number");
63
+ return JSON.stringify(value);
64
+ }
65
+ if (typeof value === "string")
66
+ return jcsString(value);
67
+ if (Array.isArray(value))
68
+ return "[" + value.map(jcs).join(",") + "]";
69
+ if (typeof value === "object") {
70
+ const obj = value;
71
+ const keys = Object.keys(obj).sort();
72
+ return "{" + keys.map((k) => jcsString(k) + ":" + jcs(obj[k])).join(",") + "}";
73
+ }
74
+ throw new Error(`JCS: unsupported type ${typeof value}`);
75
+ }
76
+ // ─── action_ref ───────────────────────────────────────────────────────────────
77
+ /** Why a preimage is not a valid Evidence Schema v1 preimage, or null */
78
+ function preimageProblem(preimage) {
79
+ if (typeof preimage !== "object" || preimage === null || Array.isArray(preimage)) {
80
+ return "preimage is not an object";
81
+ }
82
+ const p = preimage;
83
+ const extra = Object.keys(p).filter((k) => !exports.PREIMAGE_FIELDS.includes(k));
84
+ if (extra.length > 0)
85
+ return `preimage has non-schema field(s): ${extra.join(", ")}`;
86
+ for (const f of ["action_type", "agent_id", "scope"]) {
87
+ if (typeof p[f] !== "string")
88
+ return `preimage.${f} must be a string`;
89
+ }
90
+ if (!Number.isSafeInteger(p.timestamp_ms))
91
+ return "preimage.timestamp_ms must be a safe integer";
92
+ return null;
93
+ }
94
+ /** 0x-prefixed lowercase SHA-256 of the UTF-8 JCS bytes */
95
+ function actionRef(preimage) {
96
+ return "0x" + (0, crypto_1.createHash)("sha256").update(jcs(preimage), "utf8").digest("hex");
97
+ }
98
+ // ─── Merkle tree (AuditVault v2) ──────────────────────────────────────────────
99
+ /** keccak256(abi.encodePacked(min(a, b), max(a, b))) — AuditVault._verifyMerkle's step */
100
+ function hashPair(a, b) {
101
+ const [lo, hi] = a <= b ? [a, b] : [b, a];
102
+ return ethers_1.ethers.keccak256(ethers_1.ethers.concat([lo, hi]));
103
+ }
104
+ function assertLeaves(leaves) {
105
+ if (leaves.length === 0)
106
+ throw new Error("Merkle tree needs at least one leaf");
107
+ for (const l of leaves) {
108
+ if (!BYTES32_RE.test(l))
109
+ throw new Error(`leaf is not lowercase 0x-bytes32: ${l}`);
110
+ }
111
+ }
112
+ /** Every level, leaves first; an unpaired last node is promoted unchanged */
113
+ function levels(leaves) {
114
+ assertLeaves(leaves);
115
+ const out = [leaves];
116
+ while (out[out.length - 1].length > 1) {
117
+ const cur = out[out.length - 1];
118
+ const next = [];
119
+ for (let i = 0; i < cur.length; i += 2) {
120
+ next.push(i + 1 < cur.length ? hashPair(cur[i], cur[i + 1]) : cur[i]);
121
+ }
122
+ out.push(next);
123
+ }
124
+ return out;
125
+ }
126
+ function merkleRoot(leaves) {
127
+ const lv = levels(leaves);
128
+ return lv[lv.length - 1][0];
129
+ }
130
+ /** Sibling path for leaves[index]; promoted levels contribute no element */
131
+ function merkleProof(leaves, index) {
132
+ const lv = levels(leaves);
133
+ if (!Number.isInteger(index) || index < 0 || index >= leaves.length) {
134
+ throw new Error(`leaf index ${index} out of range`);
135
+ }
136
+ const proof = [];
137
+ let i = index;
138
+ for (let d = 0; d < lv.length - 1; d++) {
139
+ const sibling = i ^ 1;
140
+ if (sibling < lv[d].length)
141
+ proof.push(lv[d][sibling]);
142
+ i >>= 1;
143
+ }
144
+ return proof;
145
+ }
146
+ /** Local mirror of AuditVault._verifyMerkle */
147
+ function verifyProof(leaf, proof, root) {
148
+ let computed = leaf.toLowerCase();
149
+ for (const p of proof)
150
+ computed = hashPair(computed, p.toLowerCase());
151
+ return computed === root.toLowerCase();
152
+ }
@@ -0,0 +1,58 @@
1
+ export type CheckStatus = "PASS" | "FAIL";
2
+ export interface Check {
3
+ name: string;
4
+ status: CheckStatus;
5
+ detail: string;
6
+ }
7
+ export interface VerifyResult {
8
+ ok: boolean;
9
+ checks: Check[];
10
+ /** Context that is reported but not pass/fail (e.g. submitter when not pinned) */
11
+ info: string[];
12
+ }
13
+ export interface LogLike {
14
+ address: string;
15
+ topics: readonly string[];
16
+ data: string;
17
+ }
18
+ export interface ReceiptLike {
19
+ status: number | null;
20
+ blockNumber: number;
21
+ logs: readonly LogLike[];
22
+ }
23
+ /** The chain reads the verifier needs; EthersChainReader is the real one */
24
+ export interface ChainReader {
25
+ chainId(): Promise<bigint>;
26
+ getCode(address: string): Promise<string>;
27
+ getReceipt(txHash: string): Promise<ReceiptLike | null>;
28
+ getBatchSubmitter(vault: string, agentId: string, batchIndex: bigint): Promise<string>;
29
+ verifyLog(vault: string, agentId: string, batchIndex: bigint, leaf: string, proof: string[]): Promise<boolean>;
30
+ }
31
+ export interface VerifyOptions {
32
+ reader: ChainReader;
33
+ /** AuditVault v2 address; defaults to the known deployment for meta.anchor.chain */
34
+ vault?: string;
35
+ /** If set, LogBatch.submitter must equal it (THREAT_MODEL KI-1) */
36
+ submitter?: string;
37
+ }
38
+ export declare const AUDIT_VAULT_V2_ABI: string[];
39
+ /** Chains with a known chain id and, where deployed, AuditVault v2 */
40
+ export declare const KNOWN_CHAINS: Record<string, {
41
+ chainId: bigint;
42
+ vault?: string;
43
+ }>;
44
+ export declare class EthersChainReader implements ChainReader {
45
+ private readonly provider;
46
+ constructor(rpcUrl: string, timeoutMs?: number);
47
+ chainId(): Promise<bigint>;
48
+ getCode(address: string): Promise<string>;
49
+ getReceipt(txHash: string): Promise<ReceiptLike | null>;
50
+ getBatchSubmitter(vault: string, agentId: string, batchIndex: bigint): Promise<string>;
51
+ verifyLog(vault: string, agentId: string, batchIndex: bigint, leaf: string, proof: string[]): Promise<boolean>;
52
+ destroy(): void;
53
+ }
54
+ /**
55
+ * Runs every check it can and never throws: a failed prerequisite turns its
56
+ * dependent checks into FAILs with the reason, so the report is always complete.
57
+ */
58
+ export declare function verifyBundle(bundleInput: unknown, opts: VerifyOptions): Promise<VerifyResult>;
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ // AgentAudit AI — verifies an Evidence Schema v1 bundle end to end: preimages →
3
+ // action_refs → Merkle root → AuditVault v2 LogBatchCommitted → verifyLog.
4
+ // Applies the verifier mitigations in docs/THREAT_MODEL.md (KI-1, KI-2).
5
+ // EU AI Act: Art. 12 (record-keeping), Art. 19 (log retention)
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.EthersChainReader = exports.KNOWN_CHAINS = exports.AUDIT_VAULT_V2_ABI = void 0;
8
+ exports.verifyBundle = verifyBundle;
9
+ const ethers_1 = require("ethers");
10
+ const evidence_1 = require("./evidence");
11
+ // ─── Constants ────────────────────────────────────────────────────────────────
12
+ exports.AUDIT_VAULT_V2_ABI = [
13
+ "event LogBatchCommitted(address indexed agentId, uint256 indexed batchIndex, bytes32 merkleRoot, string contentURI, uint256 eventCount, uint8 complianceScore, uint256 timestamp)",
14
+ "function verifyLog(address agentId, uint256 batchIndex, bytes32 logHash, bytes32[] proof) view returns (bool)",
15
+ "function getBatch(address agentId, uint256 batchIndex) view returns (tuple(bytes32 merkleRoot, string contentURI, uint256 timestamp, uint256 blockNumber, uint256 eventCount, address submitter, uint8 complianceScore))",
16
+ ];
17
+ /** Chains with a known chain id and, where deployed, AuditVault v2 */
18
+ exports.KNOWN_CHAINS = {
19
+ base: { chainId: 8453n, vault: "0xCeE831070aa9081422f30df5559c125aa47A75DB" },
20
+ anvil: { chainId: 31337n },
21
+ };
22
+ const BYTES32_RE = /^0x[0-9a-fA-F]{64}$/;
23
+ const iface = new ethers_1.ethers.Interface(exports.AUDIT_VAULT_V2_ABI);
24
+ const BATCH_TOPIC = iface.getEvent("LogBatchCommitted").topicHash;
25
+ // ─── Chain reader ─────────────────────────────────────────────────────────────
26
+ class EthersChainReader {
27
+ constructor(rpcUrl, timeoutMs = 30000) {
28
+ const req = new ethers_1.ethers.FetchRequest(rpcUrl);
29
+ req.timeout = timeoutMs;
30
+ // staticNetwork: an unreachable RPC fails the call instead of retrying network detection forever
31
+ this.provider = new ethers_1.ethers.JsonRpcProvider(req, undefined, { staticNetwork: true });
32
+ }
33
+ async chainId() {
34
+ return BigInt(await this.provider.send("eth_chainId", []));
35
+ }
36
+ getCode(address) {
37
+ return this.provider.getCode(address);
38
+ }
39
+ async getReceipt(txHash) {
40
+ return this.provider.getTransactionReceipt(txHash);
41
+ }
42
+ async getBatchSubmitter(vault, agentId, batchIndex) {
43
+ const batch = await new ethers_1.ethers.Contract(vault, exports.AUDIT_VAULT_V2_ABI, this.provider).getBatch(agentId, batchIndex);
44
+ return batch.submitter;
45
+ }
46
+ async verifyLog(vault, agentId, batchIndex, leaf, proof) {
47
+ return (await new ethers_1.ethers.Contract(vault, exports.AUDIT_VAULT_V2_ABI, this.provider).verifyLog(agentId, batchIndex, leaf, proof));
48
+ }
49
+ destroy() {
50
+ this.provider.destroy();
51
+ }
52
+ }
53
+ exports.EthersChainReader = EthersChainReader;
54
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
55
+ function errorMessage(err) {
56
+ if (err && typeof err === "object" && "shortMessage" in err)
57
+ return String(err.shortMessage);
58
+ return err instanceof Error ? err.message : String(err);
59
+ }
60
+ function short(hex) {
61
+ return hex.length > 18 ? `${hex.slice(0, 10)}…${hex.slice(-6)}` : hex;
62
+ }
63
+ function asObject(v) {
64
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
65
+ }
66
+ /** Bare or 0x 64-hex → 0x lowercase, else null */
67
+ function normRoot(v) {
68
+ if (typeof v !== "string")
69
+ return null;
70
+ const hex = v.startsWith("0x") ? v : "0x" + v;
71
+ return BYTES32_RE.test(hex) ? hex.toLowerCase() : null;
72
+ }
73
+ // ─── Functions ────────────────────────────────────────────────────────────────
74
+ /**
75
+ * Runs every check it can and never throws: a failed prerequisite turns its
76
+ * dependent checks into FAILs with the reason, so the report is always complete.
77
+ */
78
+ async function verifyBundle(bundleInput, opts) {
79
+ const checks = [];
80
+ const info = [];
81
+ const pass = (name, detail) => checks.push({ name, status: "PASS", detail });
82
+ const fail = (name, detail) => checks.push({ name, status: "FAIL", detail });
83
+ const done = () => ({ ok: checks.length > 0 && checks.every((c) => c.status === "PASS"), checks, info });
84
+ // ── 1. Bundle shape ──
85
+ const bundle = asObject(bundleInput);
86
+ if (!bundle) {
87
+ fail("bundle.schema", "bundle is not a JSON object");
88
+ return done();
89
+ }
90
+ const entries = Array.isArray(bundle.entries) ? bundle.entries : null;
91
+ const shapeProblems = [];
92
+ if (bundle.schema !== evidence_1.SCHEMA)
93
+ shapeProblems.push(`schema is ${JSON.stringify(bundle.schema)}, expected "${evidence_1.SCHEMA}"`);
94
+ if (bundle.leaf_hash !== evidence_1.LEAF_HASH)
95
+ shapeProblems.push(`leaf_hash is ${JSON.stringify(bundle.leaf_hash)}, expected "${evidence_1.LEAF_HASH}"`);
96
+ if (bundle.node_hash !== evidence_1.NODE_HASH)
97
+ shapeProblems.push(`node_hash is ${JSON.stringify(bundle.node_hash)}, expected "${evidence_1.NODE_HASH}"`);
98
+ if (bundle.odd_node !== evidence_1.ODD_NODE)
99
+ shapeProblems.push(`odd_node is ${JSON.stringify(bundle.odd_node)}, expected "${evidence_1.ODD_NODE}"`);
100
+ if (!entries || entries.length === 0)
101
+ shapeProblems.push("entries is missing or empty");
102
+ const claimedRoot = normRoot(bundle.merkle_root);
103
+ if (!claimedRoot)
104
+ shapeProblems.push("merkle_root is not a 32-byte hex string");
105
+ if (shapeProblems.length > 0) {
106
+ // Without the v1 tree rules nothing below is meaningful
107
+ fail("bundle.schema", shapeProblems.join("; "));
108
+ return done();
109
+ }
110
+ pass("bundle.schema", `${evidence_1.SCHEMA}, ${entries.length} entries`);
111
+ // ── 2. action_ref per entry (recomputed — never trust the presented leaf, KI-2) ──
112
+ const leaves = [];
113
+ entries.forEach((raw, i) => {
114
+ const name = `entry[${i}].action_ref`;
115
+ const entry = asObject(raw);
116
+ if (!entry) {
117
+ fail(name, "entry is not an object");
118
+ leaves.push(null);
119
+ return;
120
+ }
121
+ const problem = (0, evidence_1.preimageProblem)(entry.preimage);
122
+ if (problem) {
123
+ fail(name, problem);
124
+ leaves.push(null);
125
+ return;
126
+ }
127
+ let computed;
128
+ try {
129
+ computed = (0, evidence_1.actionRef)(entry.preimage);
130
+ }
131
+ catch (err) {
132
+ fail(name, errorMessage(err));
133
+ leaves.push(null);
134
+ return;
135
+ }
136
+ leaves.push(computed);
137
+ const presented = typeof entry.action_ref === "string" ? entry.action_ref.toLowerCase() : entry.action_ref;
138
+ if (presented === computed)
139
+ pass(name, computed);
140
+ else
141
+ fail(name, `sha256(jcs(preimage)) = ${computed}, bundle says ${String(entry.action_ref)}`);
142
+ });
143
+ // ── 3. Merkle root ──
144
+ const allLeaves = leaves.every((l) => l !== null);
145
+ if (!allLeaves) {
146
+ fail("merkle_root", "cannot rebuild the tree: some preimages are invalid");
147
+ }
148
+ else {
149
+ const rebuilt = (0, evidence_1.merkleRoot)(leaves);
150
+ if (rebuilt === claimedRoot)
151
+ pass("merkle_root", `rebuilt ${rebuilt}`);
152
+ else
153
+ fail("merkle_root", `rebuilt ${rebuilt}, bundle says ${claimedRoot}`);
154
+ }
155
+ // ── 4. Anchor reference ──
156
+ const anchor = asObject(asObject(bundle.meta)?.anchor);
157
+ const txHash = typeof anchor?.txHash === "string" ? anchor.txHash : null;
158
+ const chain = typeof anchor?.chain === "string" ? anchor.chain : null;
159
+ if (!anchor || !txHash || !/^0x[0-9a-fA-F]{64}$/.test(txHash)) {
160
+ fail("anchor.reference", "meta.anchor.txHash is missing or not a 32-byte 0x hex string");
161
+ return done();
162
+ }
163
+ const rootCommitment = normRoot(anchor.rootCommitment);
164
+ if (anchor.rootCommitment !== undefined && rootCommitment !== claimedRoot) {
165
+ fail("anchor.reference", `meta.anchor.rootCommitment ${String(anchor.rootCommitment)} ≠ merkle_root ${claimedRoot}`);
166
+ }
167
+ else {
168
+ pass("anchor.reference", `chain ${chain ?? "(unset)"}, tx ${txHash}`);
169
+ }
170
+ const known = chain ? exports.KNOWN_CHAINS[chain] : undefined;
171
+ const vault = opts.vault ?? known?.vault;
172
+ if (!vault || !ethers_1.ethers.isAddress(vault)) {
173
+ fail("anchor.vault", vault ? `--vault ${vault} is not an address` : `no known AuditVault v2 for chain ${JSON.stringify(chain)}; pass --vault`);
174
+ return done();
175
+ }
176
+ const vaultAddr = ethers_1.ethers.getAddress(vault);
177
+ const { reader } = opts;
178
+ // ── 5. RPC chain + vault code ──
179
+ try {
180
+ const id = await reader.chainId();
181
+ if (known && id !== known.chainId)
182
+ fail("rpc.chain", `RPC is chain ${id}, meta.anchor.chain "${chain}" is ${known.chainId}`);
183
+ else
184
+ pass("rpc.chain", known ? `chain id ${id} = ${chain}` : `chain id ${id} (chain "${chain}" not in the known list, not cross-checked)`);
185
+ const code = await reader.getCode(vaultAddr);
186
+ if (code === "0x") {
187
+ fail("anchor.vault", `no contract code at ${vaultAddr}`);
188
+ return done();
189
+ }
190
+ pass("anchor.vault", `${vaultAddr}${opts.vault ? "" : ` (default for ${chain})`}`);
191
+ }
192
+ catch (err) {
193
+ fail("rpc.chain", `RPC error: ${errorMessage(err)}`);
194
+ return done();
195
+ }
196
+ // ── 6. Receipt + LogBatchCommitted ──
197
+ let receipt;
198
+ try {
199
+ receipt = await reader.getReceipt(txHash);
200
+ }
201
+ catch (err) {
202
+ fail("anchor.receipt", `RPC error: ${errorMessage(err)}`);
203
+ return done();
204
+ }
205
+ if (!receipt) {
206
+ fail("anchor.receipt", `transaction ${txHash} not found (no receipt)`);
207
+ return done();
208
+ }
209
+ if (receipt.status !== 1) {
210
+ fail("anchor.receipt", `transaction reverted (status ${receipt.status}) in block ${receipt.blockNumber}`);
211
+ return done();
212
+ }
213
+ pass("anchor.receipt", `status 1, block ${receipt.blockNumber}`);
214
+ const batchLogs = receipt.logs
215
+ .filter((l) => ethers_1.ethers.getAddress(l.address) === vaultAddr && l.topics[0] === BATCH_TOPIC)
216
+ .map((l) => iface.parseLog({ topics: [...l.topics], data: l.data }));
217
+ if (batchLogs.length === 0) {
218
+ fail("anchor.event", `transaction emitted no LogBatchCommitted from ${vaultAddr}`);
219
+ return done();
220
+ }
221
+ // A tx could commit several batches; prefer the one carrying our root
222
+ const ev = batchLogs.find((l) => l.args.merkleRoot.toLowerCase() === claimedRoot) ?? batchLogs[0];
223
+ const onChainRoot = ev.args.merkleRoot.toLowerCase();
224
+ const agentId = ethers_1.ethers.getAddress(ev.args.agentId);
225
+ const batchIndex = ev.args.batchIndex;
226
+ const eventCount = ev.args.eventCount;
227
+ info.push(`batch agentId ${agentId}, batchIndex ${batchIndex}, contentURI ${String(ev.args.contentURI)}`);
228
+ if (onChainRoot === claimedRoot)
229
+ pass("anchor.event", `LogBatchCommitted root ${onChainRoot}`);
230
+ else
231
+ fail("anchor.event", `on-chain root ${onChainRoot} ≠ merkle_root ${claimedRoot}`);
232
+ if (eventCount === BigInt(entries.length))
233
+ pass("anchor.event_count", `${eventCount}`);
234
+ else
235
+ fail("anchor.event_count", `on-chain eventCount ${eventCount}, bundle has ${entries.length} entries`);
236
+ // ── 7. Submitter (KI-1) ──
237
+ try {
238
+ const submitter = ethers_1.ethers.getAddress(await reader.getBatchSubmitter(vaultAddr, agentId, batchIndex));
239
+ if (!opts.submitter) {
240
+ info.push(`submitter ${submitter} — not checked (commitBatch is permissionless; pin it with --submitter)`);
241
+ }
242
+ else if (ethers_1.ethers.isAddress(opts.submitter) && ethers_1.ethers.getAddress(opts.submitter) === submitter) {
243
+ pass("anchor.submitter", submitter);
244
+ }
245
+ else {
246
+ fail("anchor.submitter", `LogBatch.submitter is ${submitter}, expected ${opts.submitter}`);
247
+ }
248
+ }
249
+ catch (err) {
250
+ if (opts.submitter)
251
+ fail("anchor.submitter", `getBatch failed: ${errorMessage(err)}`);
252
+ else
253
+ info.push(`submitter unavailable: ${errorMessage(err)}`);
254
+ }
255
+ // ── 8. verifyLog per entry ──
256
+ // The leaf is always the recomputed action_ref. Proof siblings are only path
257
+ // data, so besides the recomputed tree we may also try the tree of the
258
+ // presented action_refs: after one tampered preimage, every recomputed path
259
+ // runs through the tampered subtree, and only the presented values let the
260
+ // intact entries still prove inclusion — which localizes the bad one.
261
+ const presented = entries.map((raw) => {
262
+ const ref = asObject(raw)?.action_ref;
263
+ return typeof ref === "string" && BYTES32_RE.test(ref) ? ref.toLowerCase() : null;
264
+ });
265
+ const siblingTrees = [];
266
+ if (allLeaves)
267
+ siblingTrees.push(["derived proof", leaves]);
268
+ if (presented.every((p) => p !== null) && !(allLeaves && presented.every((p, i) => p === leaves[i]))) {
269
+ siblingTrees.push(["proof from bundle action_refs", presented]);
270
+ }
271
+ for (let i = 0; i < entries.length; i++) {
272
+ const name = `entry[${i}].verifyLog`;
273
+ const leaf = leaves[i];
274
+ if (!leaf) {
275
+ fail(name, "skipped: action_ref could not be recomputed");
276
+ continue;
277
+ }
278
+ const entry = asObject(entries[i]);
279
+ let candidates;
280
+ if (entry.proof !== undefined) {
281
+ if (!Array.isArray(entry.proof) || !entry.proof.every((p) => typeof p === "string" && BYTES32_RE.test(p))) {
282
+ fail(name, "entry.proof is not an array of bytes32 hex strings");
283
+ continue;
284
+ }
285
+ candidates = [["bundle proof", entry.proof]];
286
+ }
287
+ else if (siblingTrees.length > 0) {
288
+ candidates = siblingTrees.map(([source, tree]) => [source, (0, evidence_1.merkleProof)(tree, i)]);
289
+ }
290
+ else {
291
+ fail(name, "skipped: no entry.proof and no tree to derive one from");
292
+ continue;
293
+ }
294
+ try {
295
+ let passed;
296
+ for (const [source, proof] of candidates) {
297
+ if (await reader.verifyLog(vaultAddr, agentId, batchIndex, leaf, proof)) {
298
+ passed = [source, proof];
299
+ break;
300
+ }
301
+ }
302
+ if (passed)
303
+ pass(name, `true (${passed[0]}, ${passed[1].length} node${passed[1].length === 1 ? "" : "s"}, leaf ${short(leaf)})`);
304
+ else
305
+ fail(name, `AuditVault.verifyLog returned false (${candidates.map((c) => c[0]).join(", ")}; leaf ${short(leaf)})`);
306
+ }
307
+ catch (err) {
308
+ fail(name, `call failed: ${errorMessage(err)}`);
309
+ }
310
+ }
311
+ return done();
312
+ }
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
2
  "name": "@agentauditai/mcp-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0",
4
+ "description": "MCP server for AgentAudit: EU AI Act audit logging on AuditVault v2 (Base), incident reporting (Art. 73) and compliance reads.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/agentauditAI/AgentAudit.git",
9
+ "directory": "packages/mcp-server"
10
+ },
11
+ "homepage": "https://github.com/agentauditAI/AgentAudit/tree/main/packages/mcp-server#readme",
12
+ "keywords": ["mcp", "model-context-protocol", "eu-ai-act", "audit", "agentaudit", "base"],
13
+ "main": "dist/src/mcp-server.js",
14
+ "types": "dist/src/mcp-server.d.ts",
15
+ "bin": {
16
+ "agentaudit-mcp": "dist/src/mcp-server.js"
17
+ },
18
+ "files": [
19
+ "dist/src",
20
+ "dist/sdk/src",
21
+ "dist/tools/verify/src",
22
+ "dist/deployments",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "node scripts/clean.js && tsc -p tsconfig.build.json && node scripts/copy-deployments.js",
27
+ "prepack": "npm run build",
28
+ "smoke": "node scripts/smoke.js"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.29.0",
32
+ "dotenv": "^16.0.0",
33
+ "ethers": "^6.16.0",
34
+ "zod": "^4.4.3"
35
+ },
36
+ "engines": { "node": ">=18" }
37
+ }