@agentauditai/mcp-server 0.0.0-stage → 3.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,541 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.optionsFromEnv = optionsFromEnv;
38
+ exports.createServer = createServer;
39
+ require("dotenv/config");
40
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
41
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
42
+ const zod_1 = require("zod");
43
+ const ethers_1 = require("ethers");
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const index_1 = require("../sdk/src/index");
47
+ const verify_1 = require("../tools/verify/src/verify");
48
+ const evidence_1 = require("../tools/verify/src/evidence");
49
+ // ─── Deployment Addresses ────────────────────────────────────────────────────
50
+ const DEPLOYMENTS_DIR = path.join(__dirname, "..", "deployments");
51
+ const NETWORKS = ["mantle", "base", "arbitrum", "optimism", "polygon"];
52
+ const DEPLOYMENT_FILES = {
53
+ mantle: "phase7-mantle.json",
54
+ base: "phase7-base.json",
55
+ arbitrum: "phase7-arbitrumOne.json",
56
+ optimism: "phase7-optimism.json",
57
+ polygon: "phase7-polygon.json",
58
+ };
59
+ function loadDeployment(network) {
60
+ const filePath = path.join(DEPLOYMENTS_DIR, DEPLOYMENT_FILES[network]);
61
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
62
+ return raw.contracts;
63
+ }
64
+ const DEFAULT_RPC_URLS = {
65
+ mantle: "https://rpc.mantle.xyz",
66
+ base: "https://mainnet.base.org",
67
+ arbitrum: "https://arb1.arbitrum.io/rpc",
68
+ optimism: "https://mainnet.optimism.io",
69
+ polygon: "https://polygon-bor-rpc.publicnode.com",
70
+ };
71
+ /** Options read from the environment — what the stdio entry point uses */
72
+ function optionsFromEnv(env = process.env) {
73
+ const rpcUrls = {};
74
+ const auditVaults = {};
75
+ for (const net of NETWORKS) {
76
+ const rpc = env[`RPC_${net.toUpperCase()}`];
77
+ if (rpc)
78
+ rpcUrls[net] = rpc;
79
+ const vault = env[`AUDIT_VAULT_${net.toUpperCase()}`];
80
+ if (vault)
81
+ auditVaults[net] = vault;
82
+ }
83
+ // unset or "" → default code; "false" / "off" / "0" → off; anything else → custom code
84
+ return { privateKey: env.PRIVATE_KEY, rpcUrls, auditVaults, builderCode: (0, index_1.builderCodeFromEnv)(env.AGENTAUDIT_BUILDER_CODE) };
85
+ }
86
+ // ─── ABIs ────────────────────────────────────────────────────────────────────
87
+ const VAULT_ABI = [...verify_1.AUDIT_VAULT_V2_ABI, "function getBatchCount(address agentId) view returns (uint256)"];
88
+ const RISK_ABI = [
89
+ "function openRiskCount(bytes32 agentId) external view returns (uint256)",
90
+ "function risksAboveSeverity(bytes32 agentId, uint8 minSeverity) external view returns (uint256)",
91
+ "function getAgentRisks(bytes32 agentId) external view returns (uint256[])",
92
+ "function risks(uint256 id) external view returns (uint256 id, bytes32 agentId, uint8 category, uint8 severity, uint8 status, string description, string evidenceUri, uint16 likelihood, uint16 impact, address identifiedBy, uint256 identifiedAt, uint256 updatedAt)",
93
+ ];
94
+ const INCIDENT_ABI = [
95
+ "function registerIncident(bytes32 agentId, uint8 severity, uint8 harmType, string description, string evidenceHash, uint256 affectedPersons, uint256 occurredAt) external returns (uint256 id)",
96
+ "function getAgentIncidents(bytes32 agentId) external view returns (uint256[])",
97
+ "function incidents(uint256 id) external view returns (uint256 id, bytes32 agentId, uint8 severity, uint8 harmType, uint8 status, string description, string evidenceHash, uint256 affectedPersons, address reportedBy, uint256 occurredAt, uint256 registeredAt, uint256 reportedToAuthorityAt, address authorityAddress, string authorityRef, bool withinDeadline, string rootCauseUri, string correctionUri)",
98
+ "event IncidentRegistered(uint256 indexed id, bytes32 indexed agentId, uint8 severity, uint8 harmType, uint256 occurredAt, uint256 timestamp)",
99
+ ];
100
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
101
+ const SEVERITY_LABELS = ["NEGLIGIBLE", "LOW", "MEDIUM", "HIGH", "CRITICAL"];
102
+ const RISK_STATUS_LABELS = ["IDENTIFIED", "ASSESSED", "MITIGATED", "TESTED", "RESIDUAL", "CLOSED"];
103
+ // ─── Output Notices ──────────────────────────────────────────────────────────
104
+ const EVIDENCE_DISCLAIMER = "This output lists on-chain records found by the checks above. " +
105
+ "It does not determine whether any obligation under the EU AI Act or any other law is satisfied. " +
106
+ "Not legal advice.";
107
+ const INCIDENT_RECORD_NOTE = "This is an on-chain record, not a report to any authority. " +
108
+ "This tool does not calculate reporting deadlines: they depend on the type of incident, so confirm them with your legal team. Not legal advice.";
109
+ const INCIDENT_SEVERITY_LABELS = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
110
+ const HARM_TYPE_LABELS = ["DEATH", "SERIOUS_HEALTH_HARM", "SIGNIFICANT_PROPERTY_DAMAGE", "FUNDAMENTAL_RIGHTS_VIOLATION", "OTHER"];
111
+ /** Integration tag stored in each audit_action record (EVIDENCE_SCHEMA §2.5) */
112
+ const MCP_PLUGIN = "agentaudit-mcp/2.0";
113
+ const BASE_VAULT = index_1.NETWORKS.base.auditVault;
114
+ const networkEnum = zod_1.z.enum(NETWORKS);
115
+ const bytes32Hex = zod_1.z.string().regex(/^0x[0-9a-fA-F]{64}$/);
116
+ const addressHex = zod_1.z.string().regex(/^0x[0-9a-fA-F]{40}$/);
117
+ function json(value) {
118
+ return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
119
+ }
120
+ function errorMessage(err) {
121
+ if (err && typeof err === "object" && "shortMessage" in err)
122
+ return String(err.shortMessage);
123
+ return err instanceof Error ? err.message : String(err);
124
+ }
125
+ // ─── MCP Server ──────────────────────────────────────────────────────────────
126
+ function createServer(options = optionsFromEnv()) {
127
+ const rpcUrl = (net) => options.rpcUrls?.[net] ?? DEFAULT_RPC_URLS[net];
128
+ // Validated at startup so a bad AGENTAUDIT_BUILDER_CODE fails fast
129
+ const builderCode = (0, index_1.resolveBuilderCode)(options.builderCode);
130
+ const providers = new Map();
131
+ // cacheTimeout -1: back-to-back writes must not reuse a cached nonce
132
+ const provider = (net) => {
133
+ if (!providers.has(net)) {
134
+ providers.set(net, new ethers_1.ethers.JsonRpcProvider(rpcUrl(net), undefined, { staticNetwork: true, cacheTimeout: -1 }));
135
+ }
136
+ return providers.get(net);
137
+ };
138
+ function privateKey() {
139
+ if (!options.privateKey)
140
+ throw new Error("PRIVATE_KEY env var not set");
141
+ return options.privateKey;
142
+ }
143
+ const phase7Defaults = new Map();
144
+ function phase7Address(net, name) {
145
+ const override = options.phase7?.[net]?.[name];
146
+ if (override)
147
+ return override;
148
+ if (!phase7Defaults.has(net))
149
+ phase7Defaults.set(net, loadDeployment(net));
150
+ return phase7Defaults.get(net)[name];
151
+ }
152
+ /** Throws a clear error instead of letting a call to an empty address decode as BAD_DATA */
153
+ async function requireCode(net, address, name) {
154
+ const code = await provider(net).getCode(address);
155
+ if (code === "0x")
156
+ throw new Error(`${name} has no contract code on ${net} at ${address}`);
157
+ }
158
+ /** AuditVault v2 for a network, or a clear error — never a silent skip */
159
+ function auditVault(net) {
160
+ const override = options.auditVaults?.[net];
161
+ if (override)
162
+ return ethers_1.ethers.getAddress(override);
163
+ const sdkNet = index_1.NETWORKS[net];
164
+ if (sdkNet.vaultVersion === 2)
165
+ return sdkNet.auditVault;
166
+ const what = sdkNet.vaultVersion === 1 ? "is AuditVault v1" : "is not AuditVault v2";
167
+ throw new Error(`Audit logging is live on Base only: ${net} has no AuditVault v2 (default ${sdkNet.auditVault} ${what}, no commitBatch). ` +
168
+ `Use network "base" or set AUDIT_VAULT_${net.toUpperCase()} to an AuditVault v2 deployment.`);
169
+ }
170
+ const server = new mcp_js_1.McpServer({ name: "agentaudit", version: "3.0.0" }, { capabilities: { tools: {} } });
171
+ // ─── Tool 1: audit_action ────────────────────────────────────────────────────
172
+ server.tool("audit_action", `Anchor one or more agent actions on AuditVault v2 in one commitBatch transaction (EU AI Act Art. 12, 19). ` +
173
+ `Live on Base only (AuditVault v2 ${BASE_VAULT}); any other network returns an error unless AUDIT_VAULT_<NETWORK> points at an AuditVault v2. ` +
174
+ `Each call commits its entries immediately — nothing is buffered between calls. Payloads are SHA-256 hashed; raw content never leaves this process. ` +
175
+ `Returns txHash, batchIndex, merkle_root and each entry's action_ref + proof, plus the evidence bundle: keep it, the batch payload is not stored anywhere else (contentURI is memory://).`, {
176
+ agent_id: zod_1.z.union([zod_1.z.string().min(1), zod_1.z.number().int().nonnegative()]).describe("Agent identifier recorded in each preimage (preimage.agent_id)"),
177
+ entries: zod_1.z.array(zod_1.z.object({
178
+ action_type: zod_1.z.string().min(1).describe("Action type label, e.g. LLM_DECISION"),
179
+ payload: zod_1.z.union([zod_1.z.string().min(1), zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())]).describe("Raw payload — only its SHA-256 (JCS for objects) is recorded"),
180
+ risk_level: zod_1.z.enum(["LOW", "MEDIUM", "HIGH"]).default("HIGH"),
181
+ timestamp_ms: zod_1.z.number().int().nonnegative().optional().describe("Unix ms; defaults to now"),
182
+ })).min(1).max(256).describe("Actions to commit as one batch, in leaf order"),
183
+ agent_address: addressHex.optional().describe("AuditVault agentId the batch is committed under; defaults to the signer address"),
184
+ compliance_score: zod_1.z.number().int().min(0).max(100).default(0),
185
+ network: networkEnum.default("base").describe("Default base — the only network with AuditVault v2"),
186
+ }, async ({ agent_id, entries, agent_address, compliance_score, network }) => {
187
+ const vault = auditVault(network);
188
+ await requireCode(network, vault, "AuditVault v2");
189
+ const sdk = new index_1.AgentAudit({
190
+ privateKey: privateKey(),
191
+ network,
192
+ rpcUrl: rpcUrl(network),
193
+ auditVaultAddress: vault,
194
+ builderCode: builderCode ?? false,
195
+ });
196
+ const agentAddress = agent_address ? ethers_1.ethers.getAddress(agent_address) : sdk.getAddress();
197
+ // One buffer per call, flushed once. The committer is one-shot so the
198
+ // buffer's retry timer can never resend a batch after this call returned.
199
+ let committed = false;
200
+ const committer = {
201
+ commitBatch: (params) => {
202
+ if (committed)
203
+ return Promise.reject(new Error("audit_action batch already attempted"));
204
+ committed = true;
205
+ return sdk.commitBatch(params);
206
+ },
207
+ };
208
+ const store = new index_1.MemoryBatchStore();
209
+ const buffer = new index_1.BatchBuffer({
210
+ committer,
211
+ agentAddress,
212
+ store,
213
+ maxEntries: Number.MAX_SAFE_INTEGER, // never auto-flush; flush() below commits everything
214
+ complianceScore: compliance_score,
215
+ onError: () => undefined,
216
+ });
217
+ const now = Date.now();
218
+ const refs = [];
219
+ for (const e of entries) {
220
+ const payloadDigest = (0, index_1.sha256Hex)(typeof e.payload === "string" ? e.payload : (0, index_1.canonicalize)(e.payload));
221
+ const record = { plugin: MCP_PLUGIN, action_type: e.action_type, risk_level: e.risk_level, payload_hash: `sha256:${payloadDigest}` };
222
+ const preimage = {
223
+ action_type: e.action_type,
224
+ agent_id: String(agent_id),
225
+ scope: `agent_action:mcp:record_hash:${(0, index_1.sha256Hex)((0, index_1.canonicalize)(record))}`,
226
+ timestamp_ms: e.timestamp_ms ?? now,
227
+ };
228
+ refs.push((await buffer.add(preimage, record)).actionRef);
229
+ }
230
+ const result = (await buffer.flush());
231
+ if (result.batchIndex === null) {
232
+ throw new Error(`commitBatch tx ${result.txHash} mined on ${network} but emitted no LogBatchCommitted for root ${result.merkleRoot}`);
233
+ }
234
+ const bundle = (await store.get(result.batchId));
235
+ return json({
236
+ network,
237
+ vault,
238
+ txHash: result.txHash,
239
+ batchIndex: result.batchIndex,
240
+ merkle_root: result.merkleRoot,
241
+ agent_address: agentAddress,
242
+ contentURI: result.contentURI,
243
+ entries: bundle.entries.map((entry, i) => ({
244
+ action_ref: entry.action_ref,
245
+ proof: buffer.getInclusionProof(refs[i]).proof,
246
+ preimage: entry.preimage,
247
+ record: entry.record,
248
+ })),
249
+ evidence: { ...bundle, meta: { anchor: { chain: network, txHash: result.txHash, rootCommitment: result.merkleRoot } } },
250
+ });
251
+ });
252
+ // ─── Tool 2: get_audit_log ───────────────────────────────────────────────────
253
+ server.tool("get_audit_log", `Read committed AuditVault v2 batches, by commitBatch tx hash or by agent_address + batch_index (Art. 12, 19). ` +
254
+ `Live on Base only (AuditVault v2 ${BASE_VAULT}); other networks return an error unless AUDIT_VAULT_<NETWORK> is set.`, {
255
+ tx_hash: bytes32Hex.optional().describe("commitBatch transaction hash"),
256
+ agent_address: addressHex.optional().describe("AuditVault agentId (with batch_index)"),
257
+ batch_index: zod_1.z.number().int().nonnegative().optional(),
258
+ network: networkEnum.default("base"),
259
+ }, async ({ tx_hash, agent_address, batch_index, network }) => {
260
+ const byIndex = agent_address !== undefined && batch_index !== undefined;
261
+ if (!!tx_hash === byIndex)
262
+ throw new Error("Pass either tx_hash, or agent_address + batch_index");
263
+ const vault = auditVault(network);
264
+ await requireCode(network, vault, "AuditVault v2");
265
+ const contract = new ethers_1.ethers.Contract(vault, VAULT_ABI, provider(network));
266
+ async function readBatch(agent, index) {
267
+ const b = await contract.getBatch(agent, index);
268
+ return {
269
+ agent_address: agent,
270
+ batch_index: Number(index),
271
+ merkle_root: b.merkleRoot,
272
+ contentURI: b.contentURI,
273
+ event_count: Number(b.eventCount),
274
+ compliance_score: Number(b.complianceScore),
275
+ submitter: b.submitter,
276
+ timestamp: Number(b.timestamp),
277
+ block_number: Number(b.blockNumber),
278
+ };
279
+ }
280
+ if (byIndex) {
281
+ const agent = ethers_1.ethers.getAddress(agent_address);
282
+ const count = await contract.getBatchCount(agent);
283
+ if (BigInt(batch_index) >= count) {
284
+ throw new Error(`Batch ${batch_index} not found for ${agent} on ${network} (agent has ${count} batch${count === 1n ? "" : "es"})`);
285
+ }
286
+ return json({ network, vault, batches: [await readBatch(agent, BigInt(batch_index))] });
287
+ }
288
+ const receipt = await provider(network).getTransactionReceipt(tx_hash);
289
+ if (!receipt)
290
+ throw new Error(`Transaction ${tx_hash} not found on ${network}`);
291
+ if (receipt.status !== 1)
292
+ throw new Error(`Transaction ${tx_hash} reverted on ${network} (block ${receipt.blockNumber})`);
293
+ const events = receipt.logs
294
+ .filter((l) => ethers_1.ethers.getAddress(l.address) === vault)
295
+ .map((l) => { try {
296
+ return contract.interface.parseLog(l);
297
+ }
298
+ catch {
299
+ return null;
300
+ } })
301
+ .filter((p) => p?.name === "LogBatchCommitted");
302
+ if (events.length === 0)
303
+ throw new Error(`Transaction ${tx_hash} emitted no LogBatchCommitted from ${vault} on ${network}`);
304
+ const batches = [];
305
+ for (const ev of events)
306
+ batches.push({ ...(await readBatch(ethers_1.ethers.getAddress(ev.args.agentId), ev.args.batchIndex)), tx_hash });
307
+ return json({ network, vault, batches });
308
+ });
309
+ // ─── Tool 3: verify_proof ────────────────────────────────────────────────────
310
+ server.tool("verify_proof", `Check an audit entry's inclusion proof on-chain with AuditVault v2 verifyLog. The leaf is always recomputed from the preimage (never trusted from the caller); ` +
311
+ `the batch submitter is reported and can be pinned (docs/THREAT_MODEL.md KI-1, KI-2). ` +
312
+ `Live on Base only (AuditVault v2 ${BASE_VAULT}); other networks return an error unless AUDIT_VAULT_<NETWORK> is set.`, {
313
+ agent_address: addressHex.describe("AuditVault agentId the batch was committed under"),
314
+ batch_index: zod_1.z.number().int().nonnegative(),
315
+ entry: zod_1.z.object({
316
+ preimage: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).describe("{ action_type, agent_id, scope, timestamp_ms }"),
317
+ action_ref: bytes32Hex.optional().describe("Presented leaf; must match sha256(jcs(preimage)) if given"),
318
+ }),
319
+ proof: zod_1.z.array(bytes32Hex).describe("Merkle proof (sibling hashes)"),
320
+ expected_submitter: addressHex.optional().describe("If set, the batch submitter must equal it"),
321
+ network: networkEnum.default("base"),
322
+ }, async ({ agent_address, batch_index, entry, proof, expected_submitter, network }) => {
323
+ const problem = (0, evidence_1.preimageProblem)(entry.preimage);
324
+ if (problem)
325
+ throw new Error(`Invalid preimage: ${problem}`);
326
+ const leaf = (0, evidence_1.actionRef)(entry.preimage);
327
+ const vault = auditVault(network);
328
+ const reader = new verify_1.EthersChainReader(rpcUrl(network));
329
+ try {
330
+ if (await reader.getCode(vault) === "0x")
331
+ throw new Error(`AuditVault v2 has no contract code on ${network} at ${vault}`);
332
+ const agent = ethers_1.ethers.getAddress(agent_address);
333
+ const count = await new ethers_1.ethers.Contract(vault, VAULT_ABI, provider(network)).getBatchCount(agent);
334
+ if (BigInt(batch_index) >= count) {
335
+ throw new Error(`Batch ${batch_index} not found for ${agent} on ${network} (agent has ${count} batch${count === 1n ? "" : "es"})`);
336
+ }
337
+ const checks = {};
338
+ const reasons = [];
339
+ if (entry.action_ref !== undefined) {
340
+ checks.action_ref_matches = entry.action_ref.toLowerCase() === leaf;
341
+ if (!checks.action_ref_matches)
342
+ reasons.push(`action_ref ${entry.action_ref} ≠ sha256(jcs(preimage)) ${leaf}`);
343
+ }
344
+ checks.verify_log = await reader.verifyLog(vault, agent, BigInt(batch_index), leaf, proof);
345
+ if (!checks.verify_log)
346
+ reasons.push("AuditVault.verifyLog returned false");
347
+ const submitter = ethers_1.ethers.getAddress(await reader.getBatchSubmitter(vault, agent, BigInt(batch_index)));
348
+ if (expected_submitter !== undefined) {
349
+ checks.submitter_matches = ethers_1.ethers.getAddress(expected_submitter) === submitter;
350
+ if (!checks.submitter_matches)
351
+ reasons.push(`batch submitter ${submitter} ≠ expected ${ethers_1.ethers.getAddress(expected_submitter)}`);
352
+ }
353
+ return json({
354
+ valid: Object.values(checks).every(Boolean),
355
+ network,
356
+ vault,
357
+ agent_address: agent,
358
+ batch_index,
359
+ leaf,
360
+ submitter,
361
+ checks,
362
+ ...(reasons.length ? { reasons } : {}),
363
+ });
364
+ }
365
+ finally {
366
+ reader.destroy();
367
+ }
368
+ });
369
+ // ─── Tool 4: get_risk_score ──────────────────────────────────────────────────
370
+ server.tool("get_risk_score", "Query the RiskManagementSystem contract for an agent's risk profile (EU AI Act Art. 9). Deployed on mantle, base, arbitrum, optimism, polygon.", {
371
+ agent_id: zod_1.z.string().regex(/^0x[0-9a-fA-F]{1,64}$/).describe("Agent ID as a hex bytes32 string"),
372
+ network: networkEnum.default("mantle"),
373
+ }, async ({ agent_id, network }) => {
374
+ const agentId = ethers_1.ethers.zeroPadValue(agent_id, 32);
375
+ const addr = phase7Address(network, "RiskManagementSystem");
376
+ await requireCode(network, addr, "RiskManagementSystem");
377
+ const contract = new ethers_1.ethers.Contract(addr, RISK_ABI, provider(network));
378
+ const [riskIds, openCount, highCount, criticalCount] = await Promise.all([
379
+ contract.getAgentRisks(agentId),
380
+ contract.openRiskCount(agentId),
381
+ contract.risksAboveSeverity(agentId, 3), // HIGH = 3
382
+ contract.risksAboveSeverity(agentId, 4), // CRITICAL = 4
383
+ ]);
384
+ const totalRisks = riskIds.length;
385
+ const lines = [
386
+ `get_risk_score — network=${network} agentId=${agent_id}`,
387
+ `contract=${addr}`,
388
+ "",
389
+ `Total risks : ${totalRisks}`,
390
+ `Open risks : ${openCount.toString()}`,
391
+ `HIGH+ : ${highCount.toString()}`,
392
+ `CRITICAL : ${criticalCount.toString()}`,
393
+ ];
394
+ if (totalRisks > 0 && totalRisks <= 10) {
395
+ lines.push("", "Risk breakdown:");
396
+ for (const id of riskIds) {
397
+ const r = await contract.risks(id);
398
+ lines.push(` [${id}] ${SEVERITY_LABELS[Number(r.severity)].padEnd(10)} ` +
399
+ `${RISK_STATUS_LABELS[Number(r.status)].padEnd(12)} ` +
400
+ `likelihood=${(Number(r.likelihood) / 100).toFixed(0)}% ` +
401
+ r.description.slice(0, 60));
402
+ }
403
+ }
404
+ return { content: [{ type: "text", text: lines.join("\n") }] };
405
+ });
406
+ // ─── Tool 5: report_incident ─────────────────────────────────────────────────
407
+ server.tool("report_incident", "Record a serious AI incident on IncidentRegistry v2 (related to EU AI Act Art. 73). Live on mantle, base, arbitrum, optimism, polygon. " +
408
+ "This is an on-chain record only, not a report to any authority. " +
409
+ "Fails unless the registry has code and the transaction emits IncidentRegistered.", {
410
+ agent_id: zod_1.z.string().regex(/^0x[0-9a-fA-F]{1,64}$/).describe("Agent ID as a hex bytes32 string"),
411
+ severity: zod_1.z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]).describe("Incident severity"),
412
+ harm_type: zod_1.z.enum(["DEATH", "SERIOUS_HEALTH_HARM", "SIGNIFICANT_PROPERTY_DAMAGE", "FUNDAMENTAL_RIGHTS_VIOLATION", "OTHER"]),
413
+ description: zod_1.z.string().min(1).describe("Human-readable incident description"),
414
+ evidence_hash: zod_1.z.string().describe("IPFS CID or hash of incident evidence"),
415
+ affected_persons: zod_1.z.number().int().nonnegative().describe("Number of persons affected"),
416
+ occurred_at: zod_1.z.number().int().positive().describe("Unix timestamp when incident occurred"),
417
+ network: networkEnum.default("mantle"),
418
+ }, async ({ agent_id, severity, harm_type, description, evidence_hash, affected_persons, occurred_at, network }) => {
419
+ const agentId = ethers_1.ethers.zeroPadValue(agent_id, 32);
420
+ const severityIdx = INCIDENT_SEVERITY_LABELS.indexOf(severity);
421
+ const harmTypeIdx = HARM_TYPE_LABELS.indexOf(harm_type);
422
+ const addr = ethers_1.ethers.getAddress(phase7Address(network, "IncidentRegistry"));
423
+ await requireCode(network, addr, "IncidentRegistry");
424
+ const signer = new ethers_1.ethers.Wallet(privateKey(), provider(network));
425
+ const contract = new ethers_1.ethers.Contract(addr, INCIDENT_ABI, signer);
426
+ const tx = await (0, index_1.sendAttributed)(contract, "registerIncident", [
427
+ agentId,
428
+ severityIdx,
429
+ harmTypeIdx,
430
+ description,
431
+ evidence_hash,
432
+ affected_persons,
433
+ occurred_at,
434
+ ], builderCode);
435
+ const receipt = await tx.wait();
436
+ // Only an IncidentRegistered from the registry itself counts as registration
437
+ let incidentId = null;
438
+ for (const log of receipt?.logs ?? []) {
439
+ if (ethers_1.ethers.getAddress(log.address) !== addr)
440
+ continue;
441
+ const parsed = (() => { try {
442
+ return contract.interface.parseLog(log);
443
+ }
444
+ catch {
445
+ return null;
446
+ } })();
447
+ if (parsed?.name === "IncidentRegistered") {
448
+ incidentId = parsed.args.id.toString();
449
+ break;
450
+ }
451
+ }
452
+ if (incidentId === null) {
453
+ throw new Error(`Incident NOT registered: tx ${tx.hash} on ${network} emitted no IncidentRegistered from ${addr}`);
454
+ }
455
+ return {
456
+ content: [{
457
+ type: "text",
458
+ text: [
459
+ `report_incident — IncidentRegistry v2 (related: Art. 73)`,
460
+ `network=${network} contract=${addr}`,
461
+ "",
462
+ `Incident ID : ${incidentId}`,
463
+ `Tx hash : ${tx.hash}`,
464
+ `Severity : ${severity}`,
465
+ `Harm type : ${harm_type}`,
466
+ `Affected persons : ${affected_persons}`,
467
+ "",
468
+ INCIDENT_RECORD_NOTE,
469
+ ].join("\n"),
470
+ }],
471
+ };
472
+ });
473
+ // ─── Tool 6: get_evidence_report (+ deprecated get_compliance_report alias) ──
474
+ const evidenceReportSchema = {
475
+ agent_id: zod_1.z.string().regex(/^0x[0-9a-fA-F]{1,64}$/).describe("Agent ID as a hex bytes32 string"),
476
+ network: zod_1.z.enum([...NETWORKS, "all"]).default("all"),
477
+ };
478
+ const evidenceReportDescription = "List the on-chain records found for an agent in the Phase 7 contracts (RiskManagementSystem, IncidentRegistry) " +
479
+ "on one or all of mantle, base, arbitrum, optimism, polygon. Read-only. " +
480
+ "It does not determine whether any legal obligation is satisfied. Not legal advice.";
481
+ const evidenceReport = async ({ agent_id, network }) => {
482
+ const agentId = ethers_1.ethers.zeroPadValue(agent_id, 32);
483
+ const targets = network === "all" ? [...NETWORKS] : [network];
484
+ const lines = [
485
+ "═══════════════════════════════════════════════════════════",
486
+ " AgentAudit — On-chain Evidence Report",
487
+ ` Agent : ${agent_id}`,
488
+ ` Date : ${new Date().toISOString()}`,
489
+ "═══════════════════════════════════════════════════════════",
490
+ ];
491
+ for (const net of targets) {
492
+ const p = provider(net);
493
+ lines.push("", `── Network: ${net.toUpperCase()} ──────────────────────────────────`);
494
+ // RiskManagementSystem
495
+ try {
496
+ const addr = phase7Address(net, "RiskManagementSystem");
497
+ await requireCode(net, addr, "RiskManagementSystem");
498
+ const risk = new ethers_1.ethers.Contract(addr, RISK_ABI, p);
499
+ const [openCount, highCount, criticalCount] = await Promise.all([
500
+ risk.openRiskCount(agentId),
501
+ risk.risksAboveSeverity(agentId, 3),
502
+ risk.risksAboveSeverity(agentId, 4),
503
+ ]);
504
+ lines.push(` RiskManagementSystem (related: Art. 9): open=${openCount} HIGH+=${highCount} CRITICAL=${criticalCount}`);
505
+ }
506
+ catch (e) {
507
+ lines.push(` RiskManagementSystem (related: Art. 9): error — ${errorMessage(e)}`);
508
+ }
509
+ // IncidentRegistry
510
+ try {
511
+ const addr = phase7Address(net, "IncidentRegistry");
512
+ await requireCode(net, addr, "IncidentRegistry");
513
+ const incident = new ethers_1.ethers.Contract(addr, INCIDENT_ABI, p);
514
+ const ids = await incident.getAgentIncidents(agentId);
515
+ let openIncidents = 0;
516
+ for (const id of ids) {
517
+ const inc = await incident.incidents(id);
518
+ if (Number(inc.status) < 3)
519
+ openIncidents++; // < RESOLVED
520
+ }
521
+ lines.push(` IncidentRegistry (related: Art. 73): total=${ids.length} open=${openIncidents}`);
522
+ }
523
+ catch (e) {
524
+ lines.push(` IncidentRegistry (related: Art. 73): error — ${errorMessage(e)}`);
525
+ }
526
+ }
527
+ lines.push("", "═══════════════════════════════════════════════════════════", " Contracts queried: RiskManagementSystem, IncidentRegistry", " (Phase 7 deployment)", "", ` ${EVIDENCE_DISCLAIMER}`, "═══════════════════════════════════════════════════════════");
528
+ return { content: [{ type: "text", text: lines.join("\n") }] };
529
+ };
530
+ server.tool("get_evidence_report", evidenceReportDescription, evidenceReportSchema, evidenceReport);
531
+ server.tool("get_compliance_report", `Deprecated alias of get_evidence_report; it will be removed in a future major version. ${evidenceReportDescription}`, evidenceReportSchema, evidenceReport);
532
+ return server;
533
+ }
534
+ // ─── Start ───────────────────────────────────────────────────────────────────
535
+ if (require.main === module) {
536
+ const transport = new stdio_js_1.StdioServerTransport();
537
+ createServer().connect(transport).catch((err) => {
538
+ console.error("MCP server error:", err);
539
+ process.exit(1);
540
+ });
541
+ }
@@ -0,0 +1,23 @@
1
+ export declare const SCHEMA = "agentaudit-evidence-v1";
2
+ export declare const LEAF_HASH = "sha256(jcs(preimage))";
3
+ export declare const NODE_HASH = "keccak256(sorted_pair)";
4
+ export declare const ODD_NODE = "promote";
5
+ /** The only fields that enter the preimage (docs/EVIDENCE_SCHEMA.md §1.1) */
6
+ export declare const PREIMAGE_FIELDS: readonly ["action_type", "agent_id", "scope", "timestamp_ms"];
7
+ /**
8
+ * RFC 8785 canonical JSON. Numbers use ECMAScript Number serialization (what
9
+ * JSON.stringify emits for finite numbers, -0 → 0); object keys are sorted by
10
+ * UTF-16 code units, which is Array.prototype.sort's default order.
11
+ */
12
+ export declare function jcs(value: unknown): string;
13
+ /** Why a preimage is not a valid Evidence Schema v1 preimage, or null */
14
+ export declare function preimageProblem(preimage: unknown): string | null;
15
+ /** 0x-prefixed lowercase SHA-256 of the UTF-8 JCS bytes */
16
+ export declare function actionRef(preimage: unknown): string;
17
+ /** keccak256(abi.encodePacked(min(a, b), max(a, b))) — AuditVault._verifyMerkle's step */
18
+ export declare function hashPair(a: string, b: string): string;
19
+ export declare function merkleRoot(leaves: string[]): string;
20
+ /** Sibling path for leaves[index]; promoted levels contribute no element */
21
+ export declare function merkleProof(leaves: string[], index: number): string[];
22
+ /** Local mirror of AuditVault._verifyMerkle */
23
+ export declare function verifyProof(leaf: string, proof: string[], root: string): boolean;