@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,557 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// AgentAudit AI — TypeScript SDK
|
|
3
|
+
// @agentauditai/sdk — a RunLockAI product
|
|
4
|
+
// EU AI Act compliance: Articles 9, 11, 12, 13, 14, 19, 26, 50, 72, 73
|
|
5
|
+
// Networks: Mantle, Base, Arbitrum One, Optimism, Polygon
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.AgentAudit = exports.NETWORKS = void 0;
|
|
22
|
+
exports.createAgentAudit = createAgentAudit;
|
|
23
|
+
const ethers_1 = require("ethers");
|
|
24
|
+
__exportStar(require("./batch"), exports);
|
|
25
|
+
// ─── Network Config ───────────────────────────────────────────────────────────
|
|
26
|
+
//
|
|
27
|
+
// vaultVersion: 1 = AuditVault v1, 2 = AuditVault v2 (verified bytecode),
|
|
28
|
+
// null = a contract that is not AuditVault v2 (no commitBatch). Batch
|
|
29
|
+
// anchoring works only where vaultVersion is 2, or with auditVaultAddress.
|
|
30
|
+
//
|
|
31
|
+
// incidentRegistry: IncidentRegistry v2 (contracts/v2/IncidentRegistry.sol,
|
|
32
|
+
// Art. 73) from deployments/phase7-<network>.json — runtime bytecode verified
|
|
33
|
+
// against the source on all five networks. Optimism has its own address.
|
|
34
|
+
exports.NETWORKS = {
|
|
35
|
+
mantle: {
|
|
36
|
+
rpc: "https://rpc.mantle.xyz",
|
|
37
|
+
chainId: 5000,
|
|
38
|
+
auditVault: "0xD0086f19eDb500fB9d3382f6f5EAE1C015be054b",
|
|
39
|
+
vaultVersion: 1,
|
|
40
|
+
incidentRegistry: "0x6ad45a9B32e695e95492dC3796a5aC8543B2EA6F",
|
|
41
|
+
explorer: "https://explorer.mantle.xyz",
|
|
42
|
+
name: "Mantle Mainnet",
|
|
43
|
+
},
|
|
44
|
+
base: {
|
|
45
|
+
rpc: "https://mainnet.base.org",
|
|
46
|
+
chainId: 8453,
|
|
47
|
+
auditVault: "0xCeE831070aa9081422f30df5559c125aa47A75DB",
|
|
48
|
+
vaultVersion: 2,
|
|
49
|
+
incidentRegistry: "0x6ad45a9B32e695e95492dC3796a5aC8543B2EA6F",
|
|
50
|
+
explorer: "https://basescan.org",
|
|
51
|
+
name: "Base Mainnet",
|
|
52
|
+
},
|
|
53
|
+
arbitrum: {
|
|
54
|
+
rpc: "https://arb1.arbitrum.io/rpc",
|
|
55
|
+
chainId: 42161,
|
|
56
|
+
auditVault: "0x30579c6bFe4401A4b07062f0cc13C08FF2D9450C",
|
|
57
|
+
vaultVersion: null, // not AuditVault v2 — no commitBatch (see README "Supported Networks")
|
|
58
|
+
incidentRegistry: "0x6ad45a9B32e695e95492dC3796a5aC8543B2EA6F",
|
|
59
|
+
explorer: "https://arbiscan.io",
|
|
60
|
+
name: "Arbitrum One",
|
|
61
|
+
},
|
|
62
|
+
optimism: {
|
|
63
|
+
rpc: "https://mainnet.optimism.io",
|
|
64
|
+
chainId: 10,
|
|
65
|
+
auditVault: "0x30579c6bFe4401A4b07062f0cc13C08FF2D9450C",
|
|
66
|
+
vaultVersion: null, // not AuditVault v2 — no commitBatch (see README "Supported Networks")
|
|
67
|
+
incidentRegistry: "0x976b03a2417cf7926813605F427ED499fdF61e10",
|
|
68
|
+
explorer: "https://optimistic.etherscan.io",
|
|
69
|
+
name: "Optimism Mainnet",
|
|
70
|
+
},
|
|
71
|
+
polygon: {
|
|
72
|
+
rpc: "https://polygon-bor-rpc.publicnode.com",
|
|
73
|
+
chainId: 137,
|
|
74
|
+
auditVault: "0x6fC00423Df95a7caf6fFFDD93169b5C01480de02",
|
|
75
|
+
vaultVersion: null, // not AuditVault v2 — no commitBatch (see README "Supported Networks")
|
|
76
|
+
incidentRegistry: "0x6ad45a9B32e695e95492dC3796a5aC8543B2EA6F",
|
|
77
|
+
explorer: "https://polygonscan.com",
|
|
78
|
+
name: "Polygon Mainnet",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
// ─── ABIs ─────────────────────────────────────────────────────────────────────
|
|
82
|
+
const AUDIT_VAULT_ABI = [
|
|
83
|
+
"function logAction(uint256 agentId, string actionType, bytes32 payloadHash, uint8 riskLevel) external returns (uint256 logIndex)",
|
|
84
|
+
"function logActionBatch(uint256 agentId, string[] actionTypes, bytes32[] payloadHashes, uint8[] riskLevels) external returns (uint256[] logIndexes)",
|
|
85
|
+
"function getLogCount(uint256 agentId) external view returns (uint256)",
|
|
86
|
+
];
|
|
87
|
+
// AuditVault v2 (contracts/v2/AuditVault.sol) — Merkle batch anchoring
|
|
88
|
+
const AUDIT_VAULT_V2_ABI = [
|
|
89
|
+
"function commitBatch(address agentId, bytes32 merkleRoot, string contentURI, uint256 eventCount, uint8 complianceScore) external",
|
|
90
|
+
"function verifyLog(address agentId, uint256 batchIndex, bytes32 logHash, bytes32[] proof) external view returns (bool)",
|
|
91
|
+
"function getBatchCount(address agentId) external view returns (uint256)",
|
|
92
|
+
"event LogBatchCommitted(address indexed agentId, uint256 indexed batchIndex, bytes32 merkleRoot, string contentURI, uint256 eventCount, uint8 complianceScore, uint256 timestamp)",
|
|
93
|
+
];
|
|
94
|
+
const AGENT_REGISTRATION_ABI = [
|
|
95
|
+
"function registerAgent(string name, string complianceLevel, uint256 spendLimit, address auditVault) external returns (uint256 agentId)",
|
|
96
|
+
"function revokeAgent(uint256 agentId) external",
|
|
97
|
+
"function isActive(uint256 agentId) external view returns (bool)",
|
|
98
|
+
"function getOperatorAgents(address operator) external view returns (uint256[])",
|
|
99
|
+
"function agents(uint256) external view returns (string name, address operator, uint256 createdAt, string complianceLevel, uint256 spendLimit, address auditVault, bool revoked)",
|
|
100
|
+
];
|
|
101
|
+
// IncidentRegistry v2 (contracts/v2/IncidentRegistry.sol) — Art. 73
|
|
102
|
+
const INCIDENT_REGISTRY_ABI = [
|
|
103
|
+
"function registerIncident(bytes32 agentId, uint8 severity, uint8 harmType, string description, string evidenceHash, uint256 affectedPersons, uint256 occurredAt) external returns (uint256 id)",
|
|
104
|
+
"function markReportedToAuthority(uint256 id, address authority, string authorityRef) external",
|
|
105
|
+
"function isWithinDeadline(uint256 id) external view returns (bool)",
|
|
106
|
+
"function reportingDeadline(uint256 id) external view returns (uint256)",
|
|
107
|
+
"function getAgentIncidents(bytes32 agentId) external view returns (uint256[])",
|
|
108
|
+
"function incidents(uint256) 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)",
|
|
109
|
+
"event IncidentRegistered(uint256 indexed id, bytes32 indexed agentId, uint8 severity, uint8 harmType, uint256 occurredAt, uint256 timestamp)",
|
|
110
|
+
"event IncidentReported(uint256 indexed id, address authority, string authorityRef, bool withinDeadline, uint256 timestamp)",
|
|
111
|
+
];
|
|
112
|
+
// Functions an address must implement to count as IncidentRegistry v2
|
|
113
|
+
const INCIDENT_REGISTRY_REQUIRED = ["registerIncident", "markReportedToAuthority", "incidents"];
|
|
114
|
+
const POST_MARKET_MONITOR_ABI = [
|
|
115
|
+
"function enroll(address agent, string systemName, string riskCategory, uint256 reviewIntervalDays) external",
|
|
116
|
+
"function recordMetric(address agent, uint8 metricType, string metricName, int256 value, int256 threshold, string context, bytes32 txRef) external",
|
|
117
|
+
"function recordReview(address agent, int256 complianceScore, string notes) external",
|
|
118
|
+
"function isReviewDue(address agent) external view returns (bool due, uint256 overdueBySeconds)",
|
|
119
|
+
"function summaries(address) external view returns (uint256 totalMetrics, uint256 alertsLow, uint256 alertsMedium, uint256 alertsHigh, uint256 alertsCritical, int256 lastComplianceScore, uint256 lastUpdatedAt)",
|
|
120
|
+
];
|
|
121
|
+
const SEVERITIES = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
122
|
+
const HARM_TYPES = [
|
|
123
|
+
"DEATH", "SERIOUS_HEALTH_HARM", "SIGNIFICANT_PROPERTY_DAMAGE", "FUNDAMENTAL_RIGHTS_VIOLATION", "OTHER",
|
|
124
|
+
];
|
|
125
|
+
const INCIDENT_STATUSES = ["OPEN", "REPORTED", "UNDER_INVESTIGATION", "RESOLVED", "CLOSED"];
|
|
126
|
+
// ─── Main SDK Class ───────────────────────────────────────────────────────────
|
|
127
|
+
class AgentAudit {
|
|
128
|
+
constructor(config) {
|
|
129
|
+
this.config = config;
|
|
130
|
+
this.networkName = config.network ?? "base";
|
|
131
|
+
this.network = exports.NETWORKS[this.networkName];
|
|
132
|
+
const rpc = config.rpcUrl ?? this.network.rpc;
|
|
133
|
+
// cacheTimeout -1: ethers caches identical calls (incl. eth_getTransactionCount)
|
|
134
|
+
// for 250ms by default, which hands back-to-back commits the same nonce.
|
|
135
|
+
this.provider = new ethers_1.ethers.JsonRpcProvider(rpc, undefined, { cacheTimeout: -1 });
|
|
136
|
+
this.wallet = new ethers_1.ethers.Wallet(config.privateKey, this.provider);
|
|
137
|
+
this.auditVault = new ethers_1.ethers.Contract(this.network.auditVault, AUDIT_VAULT_ABI, this.wallet);
|
|
138
|
+
}
|
|
139
|
+
// ─── Lazy contract getters ──────────────────────────────────────────────────
|
|
140
|
+
get registration() {
|
|
141
|
+
if (!this._registration) {
|
|
142
|
+
const addr = this.config.registrationAddress
|
|
143
|
+
?? "0x68769980879414e8f264Ac15a87813E2ABaBaD6e";
|
|
144
|
+
this._registration = new ethers_1.ethers.Contract(addr, AGENT_REGISTRATION_ABI, this.wallet);
|
|
145
|
+
}
|
|
146
|
+
return this._registration;
|
|
147
|
+
}
|
|
148
|
+
get auditVaultV2() {
|
|
149
|
+
if (!this._auditVaultV2) {
|
|
150
|
+
const addr = this.config.auditVaultAddress
|
|
151
|
+
?? (this.network.vaultVersion === 2 ? this.network.auditVault : undefined);
|
|
152
|
+
if (!addr) {
|
|
153
|
+
const what = this.network.vaultVersion === 1
|
|
154
|
+
? "is AuditVault v1"
|
|
155
|
+
: "is not AuditVault v2";
|
|
156
|
+
throw new Error(`Batch anchoring is not available on ${this.network.name}: the default AuditVault ` +
|
|
157
|
+
`(${this.network.auditVault}) ${what} and has no commitBatch. ` +
|
|
158
|
+
"Pass auditVaultAddress pointing at an AuditVault v2 deployment, or use network \"base\".");
|
|
159
|
+
}
|
|
160
|
+
this._auditVaultV2 = new ethers_1.ethers.Contract(addr, AUDIT_VAULT_V2_ABI, this.wallet);
|
|
161
|
+
}
|
|
162
|
+
return this._auditVaultV2;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The IncidentRegistry v2 contract, after checking once per instance that the
|
|
166
|
+
* address has code implementing the v2 functions. A mismatch is cached (it
|
|
167
|
+
* will not fix itself); an RPC failure is not, so the next call retries.
|
|
168
|
+
*/
|
|
169
|
+
incidentRegistry() {
|
|
170
|
+
this._incidentRegistry ?? (this._incidentRegistry = this.verifyIncidentRegistry().catch((err) => {
|
|
171
|
+
if (!err.incidentRegistryMismatch)
|
|
172
|
+
this._incidentRegistry = undefined;
|
|
173
|
+
throw err;
|
|
174
|
+
}));
|
|
175
|
+
return this._incidentRegistry;
|
|
176
|
+
}
|
|
177
|
+
async verifyIncidentRegistry() {
|
|
178
|
+
const addr = this.config.incidentRegistryAddress ?? this.network.incidentRegistry;
|
|
179
|
+
const contract = new ethers_1.ethers.Contract(addr, INCIDENT_REGISTRY_ABI, this.wallet);
|
|
180
|
+
const code = (await this.provider.getCode(addr)).toLowerCase();
|
|
181
|
+
// Selectors appear in the dispatcher as PUSH4 (0x63) immediates
|
|
182
|
+
const missing = INCIDENT_REGISTRY_REQUIRED
|
|
183
|
+
.map((name) => contract.interface.getFunction(name))
|
|
184
|
+
.filter((fn) => !code.includes("63" + fn.selector.slice(2)))
|
|
185
|
+
.map((fn) => fn.format("sighash"));
|
|
186
|
+
if (code === "0x" || missing.length) {
|
|
187
|
+
const what = code === "0x" ? "has no contract code" : `does not implement ${missing.join(", ")}`;
|
|
188
|
+
const err = new Error(`IncidentRegistry v2 is not available on ${this.network.name}: ${addr} ${what}. ` +
|
|
189
|
+
"Pass incidentRegistryAddress pointing at an IncidentRegistry v2 deployment (contracts/v2/IncidentRegistry.sol).");
|
|
190
|
+
Object.assign(err, { incidentRegistryMismatch: true });
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
return contract;
|
|
194
|
+
}
|
|
195
|
+
get postMarketMonitor() {
|
|
196
|
+
if (!this.config.postMarketMonitorAddress) {
|
|
197
|
+
throw new Error("postMarketMonitorAddress not configured. Pass it in AgentAuditConfig.");
|
|
198
|
+
}
|
|
199
|
+
if (!this._postMarketMonitor) {
|
|
200
|
+
this._postMarketMonitor = new ethers_1.ethers.Contract(this.config.postMarketMonitorAddress, POST_MARKET_MONITOR_ABI, this.wallet);
|
|
201
|
+
}
|
|
202
|
+
return this._postMarketMonitor;
|
|
203
|
+
}
|
|
204
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
205
|
+
hashPayload(payload) {
|
|
206
|
+
const str = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
207
|
+
return ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(str));
|
|
208
|
+
}
|
|
209
|
+
riskToUint(risk = "LOW") {
|
|
210
|
+
return { LOW: 0, MEDIUM: 1, HIGH: 2 }[risk];
|
|
211
|
+
}
|
|
212
|
+
enumIndex(labels, value, name) {
|
|
213
|
+
const i = labels.indexOf(value);
|
|
214
|
+
if (i < 0)
|
|
215
|
+
throw new Error(`Invalid ${name}: ${String(value)}. Use one of ${labels.join(", ")}`);
|
|
216
|
+
return i;
|
|
217
|
+
}
|
|
218
|
+
/** bytes32 hex is used as-is; anything else is keccak256(String(agentId)) */
|
|
219
|
+
incidentAgentId(agentId) {
|
|
220
|
+
const s = String(agentId);
|
|
221
|
+
return /^0x[0-9a-fA-F]{64}$/.test(s) ? s : ethers_1.ethers.id(s);
|
|
222
|
+
}
|
|
223
|
+
metricTypeToUint(type = "COMPLIANCE_SCORE") {
|
|
224
|
+
return { ERROR_RATE: 0, DRIFT_SCORE: 1, LATENCY_MS: 2, COMPLIANCE_SCORE: 3, CUSTOM: 4 }[type];
|
|
225
|
+
}
|
|
226
|
+
explorerTx(txHash) {
|
|
227
|
+
return `${this.network.explorer}/tx/${txHash}`;
|
|
228
|
+
}
|
|
229
|
+
// ─── Core: Audit Logging (Art. 12, 19, 50) ──────────────────────────────────
|
|
230
|
+
/**
|
|
231
|
+
* Log a single agent action on-chain (Art. 12, 19, 50)
|
|
232
|
+
*/
|
|
233
|
+
async log(params) {
|
|
234
|
+
const payloadHash = this.hashPayload(params.payload);
|
|
235
|
+
const risk = this.riskToUint(params.riskLevel);
|
|
236
|
+
const agentId = String(params.agentId);
|
|
237
|
+
const tx = await this.auditVault.logAction(agentId, params.actionType, payloadHash, risk);
|
|
238
|
+
await tx.wait();
|
|
239
|
+
return {
|
|
240
|
+
txHash: tx.hash,
|
|
241
|
+
network: this.networkName,
|
|
242
|
+
explorerUrl: this.explorerTx(tx.hash),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Alias for log() — the name the LLM wrappers call in per_action mode
|
|
247
|
+
*/
|
|
248
|
+
async logAction(params) {
|
|
249
|
+
return this.log(params);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Log multiple actions in one transaction (gas efficient)
|
|
253
|
+
*/
|
|
254
|
+
async logBatch(agentId, actions) {
|
|
255
|
+
const actionTypes = actions.map(a => a.actionType);
|
|
256
|
+
const payloadHashes = actions.map(a => this.hashPayload(a.payload));
|
|
257
|
+
const riskLevels = actions.map(a => this.riskToUint(a.riskLevel));
|
|
258
|
+
const tx = await this.auditVault.logActionBatch(String(agentId), actionTypes, payloadHashes, riskLevels);
|
|
259
|
+
await tx.wait();
|
|
260
|
+
return {
|
|
261
|
+
txHash: tx.hash,
|
|
262
|
+
network: this.networkName,
|
|
263
|
+
explorerUrl: this.explorerTx(tx.hash),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get total on-chain log count for an agent
|
|
268
|
+
*/
|
|
269
|
+
async getLogCount(agentId) {
|
|
270
|
+
return Number(await this.auditVault.getLogCount(String(agentId)));
|
|
271
|
+
}
|
|
272
|
+
// ─── Batch Anchoring (AuditVault v2 — Art. 12, 19) ──────────────────────────
|
|
273
|
+
/**
|
|
274
|
+
* Anchor a Merkle root of action_refs (docs/EVIDENCE_SCHEMA.md) in one tx.
|
|
275
|
+
* Usually called by BatchBuffer rather than directly.
|
|
276
|
+
*/
|
|
277
|
+
async commitBatch(params) {
|
|
278
|
+
const vault = this.auditVaultV2;
|
|
279
|
+
const tx = await vault.commitBatch(params.agentAddress, params.merkleRoot, params.contentURI, params.eventCount, params.complianceScore);
|
|
280
|
+
const receipt = await tx.wait();
|
|
281
|
+
let batchIndex = null;
|
|
282
|
+
for (const log of receipt?.logs ?? []) {
|
|
283
|
+
const parsed = (() => { try {
|
|
284
|
+
return vault.interface.parseLog(log);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return null;
|
|
288
|
+
} })();
|
|
289
|
+
if (parsed?.name === "LogBatchCommitted" && parsed.args.merkleRoot === params.merkleRoot) {
|
|
290
|
+
batchIndex = Number(parsed.args.batchIndex);
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return { txHash: tx.hash, batchIndex };
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Throw a descriptive error if this network/config cannot batch-anchor.
|
|
298
|
+
* Wrappers call it at construction so batch mode fails fast instead of on
|
|
299
|
+
* the first flush.
|
|
300
|
+
*/
|
|
301
|
+
assertBatchAnchoring() {
|
|
302
|
+
void this.auditVaultV2;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Check an inclusion proof against a committed batch (AuditVault v2 verifyLog)
|
|
306
|
+
*/
|
|
307
|
+
async verifyLog(agentAddress, batchIndex, leaf, proof) {
|
|
308
|
+
return this.auditVaultV2.verifyLog(agentAddress, batchIndex, leaf, proof);
|
|
309
|
+
}
|
|
310
|
+
// ─── Agent Registration (Art. 13, 26 — KYA Standard) ─────────────────────
|
|
311
|
+
/**
|
|
312
|
+
* Register a new AI agent on-chain (KYA standard, Art. 13, 26)
|
|
313
|
+
*/
|
|
314
|
+
async registerAgent(params) {
|
|
315
|
+
const tx = await this.registration.registerAgent(params.name, params.complianceLevel ?? "limited", params.spendLimit ?? ethers_1.ethers.parseEther("100"), params.auditVault ?? this.network.auditVault);
|
|
316
|
+
await tx.wait();
|
|
317
|
+
// Get agent ID from event or return 0 (caller can query separately)
|
|
318
|
+
return { agentId: 0, txHash: tx.hash, network: this.network.name };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Revoke an agent
|
|
322
|
+
*/
|
|
323
|
+
async revokeAgent(agentId) {
|
|
324
|
+
const tx = await this.registration.revokeAgent(agentId);
|
|
325
|
+
await tx.wait();
|
|
326
|
+
return { txHash: tx.hash };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Check if agent is active
|
|
330
|
+
*/
|
|
331
|
+
async isActive(agentId) {
|
|
332
|
+
return this.registration.isActive(agentId);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get all agent IDs for the current wallet
|
|
336
|
+
*/
|
|
337
|
+
async getMyAgents() {
|
|
338
|
+
const ids = await this.registration.getOperatorAgents(this.wallet.address);
|
|
339
|
+
return ids.map(Number);
|
|
340
|
+
}
|
|
341
|
+
// ─── Incident Reporting (Art. 73) ─────────────────────────────────────────
|
|
342
|
+
/**
|
|
343
|
+
* Register an incident on-chain (Art. 73)
|
|
344
|
+
* The registry enforces the 15/10/2 day reporting timelines (Art. 73§2)
|
|
345
|
+
*/
|
|
346
|
+
async reportIncident(params) {
|
|
347
|
+
const severity = this.enumIndex(SEVERITIES, params.severity, "severity");
|
|
348
|
+
const harmType = this.enumIndex(HARM_TYPES, params.harmType ?? "OTHER", "harmType");
|
|
349
|
+
const affectedPersons = BigInt(params.affectedPersons ?? 0);
|
|
350
|
+
if (affectedPersons < 0n)
|
|
351
|
+
throw new Error(`Invalid affectedPersons: ${affectedPersons}`);
|
|
352
|
+
const registry = await this.incidentRegistry();
|
|
353
|
+
const evidenceHash = params.evidenceHash ?? this.hashPayload(params.description);
|
|
354
|
+
// Block time, not Date.now(): the registry rejects occurredAt > block.timestamp
|
|
355
|
+
const occurredAt = params.occurredAt ?? (await this.provider.getBlock("latest")).timestamp;
|
|
356
|
+
const tx = await registry.registerIncident(this.incidentAgentId(params.agentId), severity, harmType, params.description, evidenceHash, affectedPersons, occurredAt);
|
|
357
|
+
const receipt = await tx.wait();
|
|
358
|
+
let incidentId = 0;
|
|
359
|
+
for (const log of receipt?.logs ?? []) {
|
|
360
|
+
const parsed = (() => { try {
|
|
361
|
+
return registry.interface.parseLog(log);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return null;
|
|
365
|
+
} })();
|
|
366
|
+
if (parsed?.name === "IncidentRegistered") {
|
|
367
|
+
incidentId = Number(parsed.args.id);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { incidentId, txHash: tx.hash };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Mark an incident as reported to the national market surveillance authority
|
|
375
|
+
* (Art. 73§2). Only the original reporter or the registry deployer may call it.
|
|
376
|
+
* @param params.authority Address (or identifier) of the notified authority
|
|
377
|
+
* @param params.authorityRef Authority-issued case reference
|
|
378
|
+
*/
|
|
379
|
+
async markReportedToAuthority(incidentId, params) {
|
|
380
|
+
const registry = await this.incidentRegistry();
|
|
381
|
+
const tx = await registry.markReportedToAuthority(incidentId, ethers_1.ethers.getAddress(params.authority), params.authorityRef);
|
|
382
|
+
const receipt = await tx.wait();
|
|
383
|
+
let withinDeadline = false;
|
|
384
|
+
for (const log of receipt?.logs ?? []) {
|
|
385
|
+
const parsed = (() => { try {
|
|
386
|
+
return registry.interface.parseLog(log);
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return null;
|
|
390
|
+
} })();
|
|
391
|
+
if (parsed?.name === "IncidentReported") {
|
|
392
|
+
withinDeadline = parsed.args.withinDeadline;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return { txHash: tx.hash, withinDeadline };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Read an incident record (IncidentRegistry v2 `incidents(id)`)
|
|
400
|
+
*/
|
|
401
|
+
async getIncident(incidentId) {
|
|
402
|
+
const r = await (await this.incidentRegistry()).incidents(incidentId);
|
|
403
|
+
if (r.id === 0n)
|
|
404
|
+
throw new Error(`Incident ${incidentId} not found`);
|
|
405
|
+
return {
|
|
406
|
+
id: Number(r.id),
|
|
407
|
+
agentId: r.agentId,
|
|
408
|
+
severity: SEVERITIES[Number(r.severity)],
|
|
409
|
+
harmType: HARM_TYPES[Number(r.harmType)],
|
|
410
|
+
status: INCIDENT_STATUSES[Number(r.status)],
|
|
411
|
+
description: r.description,
|
|
412
|
+
evidenceHash: r.evidenceHash,
|
|
413
|
+
affectedPersons: r.affectedPersons,
|
|
414
|
+
reportedBy: r.reportedBy,
|
|
415
|
+
occurredAt: Number(r.occurredAt),
|
|
416
|
+
registeredAt: Number(r.registeredAt),
|
|
417
|
+
reportedToAuthorityAt: Number(r.reportedToAuthorityAt),
|
|
418
|
+
authorityAddress: r.authorityAddress,
|
|
419
|
+
authorityRef: r.authorityRef,
|
|
420
|
+
withinDeadline: r.withinDeadline,
|
|
421
|
+
rootCauseUri: r.rootCauseUri,
|
|
422
|
+
correctionUri: r.correctionUri,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Incident IDs registered for an agent (same agentId rules as reportIncident)
|
|
427
|
+
*/
|
|
428
|
+
async getAgentIncidents(agentId) {
|
|
429
|
+
const ids = await (await this.incidentRegistry()).getAgentIncidents(this.incidentAgentId(agentId));
|
|
430
|
+
return ids.map(Number);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Whether an incident is still within its Art. 73§2 reporting deadline
|
|
434
|
+
*/
|
|
435
|
+
async isWithinDeadline(incidentId) {
|
|
436
|
+
return (await this.incidentRegistry()).isWithinDeadline(incidentId);
|
|
437
|
+
}
|
|
438
|
+
// ─── Post-Market Monitoring (Art. 72) ─────────────────────────────────────
|
|
439
|
+
/**
|
|
440
|
+
* Enroll an agent in post-market monitoring (Art. 72)
|
|
441
|
+
*/
|
|
442
|
+
async enrollMonitoring(params) {
|
|
443
|
+
const tx = await this.postMarketMonitor.enroll(params.agentAddress, params.systemName, params.riskCategory, params.reviewIntervalDays);
|
|
444
|
+
await tx.wait();
|
|
445
|
+
return { txHash: tx.hash };
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Record a performance metric (Art. 72)
|
|
449
|
+
*/
|
|
450
|
+
async recordMetric(params) {
|
|
451
|
+
const tx = await this.postMarketMonitor.recordMetric(params.agentAddress, this.metricTypeToUint(params.metricType), params.metricName, params.value, params.threshold, params.context ?? "sdk", params.txRef ? ethers_1.ethers.encodeBytes32String(params.txRef.slice(0, 31)) : ethers_1.ethers.ZeroHash);
|
|
452
|
+
await tx.wait();
|
|
453
|
+
return { txHash: tx.hash };
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Record periodic compliance review (Art. 72)
|
|
457
|
+
* @param complianceScore 0-10000 (e.g. 9500 = 95.00%)
|
|
458
|
+
*/
|
|
459
|
+
async recordReview(params) {
|
|
460
|
+
const tx = await this.postMarketMonitor.recordReview(params.agentAddress, params.complianceScore, params.notes);
|
|
461
|
+
await tx.wait();
|
|
462
|
+
return { txHash: tx.hash };
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Check if agent is overdue for review
|
|
466
|
+
*/
|
|
467
|
+
async isReviewDue(agentAddress) {
|
|
468
|
+
const [due, overdue] = await this.postMarketMonitor.isReviewDue(agentAddress);
|
|
469
|
+
return { due, overdueBySeconds: Number(overdue) };
|
|
470
|
+
}
|
|
471
|
+
// ─── Utils ──────────────────────────────────────────────────────────────────
|
|
472
|
+
/**
|
|
473
|
+
* Get current network info
|
|
474
|
+
*/
|
|
475
|
+
getNetworkInfo() {
|
|
476
|
+
return { name: this.networkName, ...this.network };
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get connected wallet address
|
|
480
|
+
*/
|
|
481
|
+
getAddress() {
|
|
482
|
+
return this.wallet.address;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Hash any payload for on-chain storage
|
|
486
|
+
*/
|
|
487
|
+
hash(payload) {
|
|
488
|
+
return this.hashPayload(payload);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
exports.AgentAudit = AgentAudit;
|
|
492
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
493
|
+
/**
|
|
494
|
+
* Create AgentAudit from environment variables
|
|
495
|
+
*/
|
|
496
|
+
function createAgentAudit(network = "base") {
|
|
497
|
+
// AGENT_AUDIT_PRIVATE_KEY: deprecated name from the 1.0.x README, still accepted
|
|
498
|
+
const privateKey = process.env.AUDIT_PRIVATE_KEY ?? process.env.AGENT_AUDIT_PRIVATE_KEY;
|
|
499
|
+
if (!privateKey)
|
|
500
|
+
throw new Error("AUDIT_PRIVATE_KEY env var required");
|
|
501
|
+
return new AgentAudit({
|
|
502
|
+
privateKey,
|
|
503
|
+
network,
|
|
504
|
+
incidentRegistryAddress: process.env.AUDIT_INCIDENT_REGISTRY || undefined,
|
|
505
|
+
postMarketMonitorAddress: process.env.AUDIT_MONITOR_ADDRESS,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
exports.default = AgentAudit;
|
|
509
|
+
// ─── Usage Examples ───────────────────────────────────────────────────────────
|
|
510
|
+
//
|
|
511
|
+
// import AgentAudit, { createAgentAudit } from "@agentauditai/sdk";
|
|
512
|
+
//
|
|
513
|
+
// // Option 1: from env vars
|
|
514
|
+
// const audit = createAgentAudit("base");
|
|
515
|
+
//
|
|
516
|
+
// // Option 2: explicit config
|
|
517
|
+
// const audit = new AgentAudit({
|
|
518
|
+
// privateKey: process.env.AUDIT_PRIVATE_KEY!,
|
|
519
|
+
// network: "base",
|
|
520
|
+
// });
|
|
521
|
+
//
|
|
522
|
+
// // Log an action (Art. 12) — 2 lines to compliance
|
|
523
|
+
// const result = await audit.log({
|
|
524
|
+
// agentId: 1,
|
|
525
|
+
// actionType: "TRANSFER_APPROVED",
|
|
526
|
+
// payload: { amount: "100 USDC", recipient: "0x..." },
|
|
527
|
+
// riskLevel: "HIGH",
|
|
528
|
+
// });
|
|
529
|
+
// console.log(result.explorerUrl); // on-chain proof ✅
|
|
530
|
+
//
|
|
531
|
+
// // Batch log (gas efficient)
|
|
532
|
+
// await audit.logBatch(1, [
|
|
533
|
+
// { actionType: "POLICY_CHECK", payload: "...", riskLevel: "LOW" },
|
|
534
|
+
// { actionType: "TRANSFER", payload: "...", riskLevel: "HIGH" },
|
|
535
|
+
// ]);
|
|
536
|
+
//
|
|
537
|
+
// // Report incident (Art. 73)
|
|
538
|
+
// const incident = await audit.reportIncident({
|
|
539
|
+
// agentId: 1,
|
|
540
|
+
// severity: "HIGH",
|
|
541
|
+
// harmType: "SIGNIFICANT_PROPERTY_DAMAGE",
|
|
542
|
+
// affectedPersons: 12,
|
|
543
|
+
// description: "Unexpected behavior detected in financial agent",
|
|
544
|
+
// });
|
|
545
|
+
// await audit.markReportedToAuthority(incident.incidentId, {
|
|
546
|
+
// authority: "0x...", // notified market surveillance authority
|
|
547
|
+
// authorityRef: "MSA-2026-0042",
|
|
548
|
+
// });
|
|
549
|
+
//
|
|
550
|
+
// // Record monitoring metric (Art. 72)
|
|
551
|
+
// await audit.recordMetric({
|
|
552
|
+
// agentAddress: "0x...",
|
|
553
|
+
// metricType: "COMPLIANCE_SCORE",
|
|
554
|
+
// metricName: "weekly_compliance",
|
|
555
|
+
// value: 9500, // 95.00%
|
|
556
|
+
// threshold: 8000, // 80.00% minimum
|
|
557
|
+
// });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
type NetworkKey = "mantle" | "base" | "arbitrum" | "optimism" | "polygon";
|
|
5
|
+
type Phase7Contract = "RiskManagementSystem" | "ConformityAssessment" | "IncidentRegistry";
|
|
6
|
+
export interface McpServerOptions {
|
|
7
|
+
/** Signer key for audit_action and report_incident (env PRIVATE_KEY) */
|
|
8
|
+
privateKey?: string;
|
|
9
|
+
/** Per-network RPC overrides (env RPC_<NETWORK>) */
|
|
10
|
+
rpcUrls?: Partial<Record<NetworkKey, string>>;
|
|
11
|
+
/** AuditVault v2 overrides (env AUDIT_VAULT_<NETWORK>); the SDK default is used only where it is v2 */
|
|
12
|
+
auditVaults?: Partial<Record<NetworkKey, string>>;
|
|
13
|
+
/** Phase 7 address overrides; defaults come from deployments/phase7-<network>.json */
|
|
14
|
+
phase7?: Partial<Record<NetworkKey, Partial<Record<Phase7Contract, string>>>>;
|
|
15
|
+
}
|
|
16
|
+
/** Options read from the environment — what the stdio entry point uses */
|
|
17
|
+
export declare function optionsFromEnv(env?: NodeJS.ProcessEnv): McpServerOptions;
|
|
18
|
+
export declare function createServer(options?: McpServerOptions): McpServer;
|
|
19
|
+
export {};
|