athena-mcp 1.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.
Files changed (37) hide show
  1. package/README.md +477 -0
  2. package/install.js +327 -0
  3. package/mcp/servers.json +100 -0
  4. package/mcp/tools/README.md +64 -0
  5. package/mcp/tools/__init__.py +1 -0
  6. package/mcp/tools/aderyn_runner.py +226 -0
  7. package/mcp/tools/eas_attest.py +404 -0
  8. package/mcp/tools/evidence_chain.py +363 -0
  9. package/mcp/tools/exploit_simulator.py +545 -0
  10. package/mcp/tools/fuzz_runner.py +440 -0
  11. package/mcp/tools/gev_analyzer.py +362 -0
  12. package/mcp/tools/halmos_runner.py +408 -0
  13. package/mcp/tools/incremental_auditor.py +441 -0
  14. package/mcp/tools/knowledge_base.py +378 -0
  15. package/mcp/tools/poc_generator.py +479 -0
  16. package/mcp/tools/protocol_scanner.py +456 -0
  17. package/mcp/tools/repair_validator.py +421 -0
  18. package/mcp/tools/slither_runner.py +221 -0
  19. package/package.json +52 -0
  20. package/requirements.txt +20 -0
  21. package/skills/glm-audit-skill/SKILL.md +73 -0
  22. package/skills/glm-audit-skill/references/audit-agents/access-control-agent.md +42 -0
  23. package/skills/glm-audit-skill/references/audit-agents/asymmetry-agent.md +42 -0
  24. package/skills/glm-audit-skill/references/audit-agents/boundary-agent.md +42 -0
  25. package/skills/glm-audit-skill/references/audit-agents/economic-security-agent.md +42 -0
  26. package/skills/glm-audit-skill/references/audit-agents/execution-trace-agent.md +42 -0
  27. package/skills/glm-audit-skill/references/audit-agents/first-principles-agent.md +42 -0
  28. package/skills/glm-audit-skill/references/audit-agents/flow-gap-agent.md +38 -0
  29. package/skills/glm-audit-skill/references/audit-agents/invariant-agent.md +37 -0
  30. package/skills/glm-audit-skill/references/audit-agents/math-precision-agent.md +37 -0
  31. package/skills/glm-audit-skill/references/audit-agents/numerical-gap-agent.md +37 -0
  32. package/skills/glm-audit-skill/references/audit-agents/periphery-agent.md +37 -0
  33. package/skills/glm-audit-skill/references/audit-agents/shared-rules.md +37 -0
  34. package/skills/glm-audit-skill/references/audit-agents/trust-gap-agent.md +39 -0
  35. package/skills/glm-audit-skill/references/judging.md +45 -0
  36. package/skills/glm-audit-skill/references/report-formatting.md +22 -0
  37. package/skills/glm-audit-skill/references/senior-auditor-sop.md +34 -0
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Server: Incremental Auditor
4
+ Detects code changes and audits only the diff, reusing previous audit context.
5
+ Demonstrates GLM-5.1's 200K context window for long-horizon tasks.
6
+ """
7
+ import sys
8
+ import json
9
+ import asyncio
10
+ import hashlib
11
+ import logging
12
+ import difflib
13
+
14
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
15
+ logger = logging.getLogger("incremental_auditor")
16
+
17
+ TOOL_NAME = "incremental_auditor"
18
+ TOOL_VERSION = "1.0.0"
19
+
20
+
21
+ def build_tool_definitions() -> list:
22
+ return [
23
+ {
24
+ "name": "detect_changes",
25
+ "description": "Detect changes between old and new contract versions",
26
+ "inputSchema": {
27
+ "type": "object",
28
+ "properties": {
29
+ "old_code": {"type": "string", "description": "Original contract code"},
30
+ "new_code": {"type": "string", "description": "Updated contract code"},
31
+ "contract_name": {"type": "string", "description": "Contract name"}
32
+ },
33
+ "required": ["old_code", "new_code"]
34
+ }
35
+ },
36
+ {
37
+ "name": "generate_incremental_report",
38
+ "description": "Generate incremental audit report based on changes",
39
+ "inputSchema": {
40
+ "type": "object",
41
+ "properties": {
42
+ "contract_name": {"type": "string"},
43
+ "old_code": {"type": "string"},
44
+ "new_code": {"type": "string"},
45
+ "previous_findings": {
46
+ "type": "array",
47
+ "items": {"type": "object"},
48
+ "description": "Findings from previous audit"
49
+ }
50
+ },
51
+ "required": ["contract_name", "old_code", "new_code"]
52
+ }
53
+ },
54
+ {
55
+ "name": "build_audit_context",
56
+ "description": "Build audit context for 200K context window utilization",
57
+ "inputSchema": {
58
+ "type": "object",
59
+ "properties": {
60
+ "contracts": {
61
+ "type": "array",
62
+ "items": {"type": "object"},
63
+ "description": "All contracts in the protocol"
64
+ },
65
+ "previous_audits": {
66
+ "type": "array",
67
+ "items": {"type": "object"},
68
+ "description": "Previous audit results"
69
+ }
70
+ },
71
+ "required": ["contracts"]
72
+ }
73
+ }
74
+ ]
75
+
76
+
77
+ async def detect_changes(old_code: str, new_code: str, contract_name: str = None) -> dict:
78
+ """Detect changes between old and new contract versions."""
79
+
80
+ old_lines = old_code.splitlines()
81
+ new_lines = new_code.splitlines()
82
+
83
+ # Generate diff
84
+ diff = list(difflib.unified_diff(old_lines, new_lines, lineterm='', n=3))
85
+
86
+ # Analyze changes
87
+ added_lines = []
88
+ removed_lines = []
89
+ changed_functions = []
90
+
91
+ for line in diff:
92
+ if line.startswith('+') and not line.startswith('+++'):
93
+ added_lines.append(line[1:])
94
+ elif line.startswith('-') and not line.startswith('---'):
95
+ removed_lines.append(line[1:])
96
+
97
+ # Detect changed functions
98
+ old_functions = _extract_functions(old_code)
99
+ new_functions = _extract_functions(new_code)
100
+
101
+ for func_name in set(old_functions.keys()) | set(new_functions.keys()):
102
+ old_func = old_functions.get(func_name)
103
+ new_func = new_functions.get(func_name)
104
+
105
+ if old_func and new_func:
106
+ if old_func != new_func:
107
+ changed_functions.append({
108
+ "name": func_name,
109
+ "change_type": "modified",
110
+ "old_hash": hashlib.sha256(old_func.encode()).hexdigest()[:16],
111
+ "new_hash": hashlib.sha256(new_func.encode()).hexdigest()[:16]
112
+ })
113
+ elif new_func and not old_func:
114
+ changed_functions.append({
115
+ "name": func_name,
116
+ "change_type": "added"
117
+ })
118
+ elif old_func and not new_func:
119
+ changed_functions.append({
120
+ "name": func_name,
121
+ "change_type": "removed"
122
+ })
123
+
124
+ # Calculate change magnitude
125
+ total_lines = max(len(old_lines), len(new_lines))
126
+ changed_lines = len(added_lines) + len(removed_lines)
127
+ change_percentage = (changed_lines / total_lines * 100) if total_lines > 0 else 0
128
+
129
+ return {
130
+ "success": True,
131
+ "contract_name": contract_name,
132
+ "summary": {
133
+ "old_lines": len(old_lines),
134
+ "new_lines": len(new_lines),
135
+ "added_lines": len(added_lines),
136
+ "removed_lines": len(removed_lines),
137
+ "change_percentage": round(change_percentage, 2)
138
+ },
139
+ "changed_functions": changed_functions,
140
+ "diff": diff[:100], # First 100 lines of diff
141
+ "affected_areas": _identify_affected_areas(changed_functions)
142
+ }
143
+
144
+
145
+ def _extract_functions(code: str) -> dict:
146
+ """Extract function bodies from code."""
147
+ functions = {}
148
+
149
+ import re
150
+ pattern = r'function\s+(\w+)\s*\([^)]*\)[^{]*\{'
151
+
152
+ for match in re.finditer(pattern, code):
153
+ func_name = match.group(1)
154
+ start_pos = match.end()
155
+
156
+ # Find function end (simplified)
157
+ brace_count = 1
158
+ pos = start_pos
159
+ while pos < len(code) and brace_count > 0:
160
+ if code[pos] == '{':
161
+ brace_count += 1
162
+ elif code[pos] == '}':
163
+ brace_count -= 1
164
+ pos += 1
165
+
166
+ functions[func_name] = code[match.start():pos]
167
+
168
+ return functions
169
+
170
+
171
+ def _identify_affected_areas(changed_functions: list) -> list:
172
+ """Identify which areas of the contract are affected."""
173
+ areas = set()
174
+
175
+ for func in changed_functions:
176
+ name = func["name"].lower()
177
+
178
+ if "deposit" in name or "withdraw" in name or "transfer" in name:
179
+ areas.add("token_operations")
180
+ if "borrow" in name or "repay" in name or "liquidat" in name:
181
+ areas.add("lending")
182
+ if "swap" in name or "addliquidity" in name or "removeliquidity" in name:
183
+ areas.add("amm")
184
+ if "vote" in name or "propose" in name or "execute" in name:
185
+ areas.add("governance")
186
+ if "oracle" in name or "price" in name:
187
+ areas.add("oracle")
188
+ if "admin" in name or "owner" in name or "set" in name:
189
+ areas.add("admin")
190
+
191
+ return list(areas)
192
+
193
+
194
+ async def generate_incremental_report(contract_name: str, old_code: str, new_code: str, previous_findings: list = None) -> dict:
195
+ """Generate incremental audit report."""
196
+
197
+ changes = await detect_changes(old_code, new_code, contract_name)
198
+
199
+ if previous_findings is None:
200
+ previous_findings = []
201
+
202
+ # Determine what needs re-auditing
203
+ affected_areas = changes.get("affected_areas", [])
204
+ changed_functions = changes.get("changed_functions", [])
205
+
206
+ # Filter previous findings that might be affected
207
+ potentially_affected = []
208
+ for finding in previous_findings:
209
+ finding_func = finding.get("function", "").lower()
210
+ for func in changed_functions:
211
+ if func["name"].lower() in finding_func or finding_func in func["name"].lower():
212
+ potentially_affected.append(finding)
213
+ break
214
+
215
+ # Generate report
216
+ report = f"""# Incremental Audit Report
217
+
218
+ ## Contract: {contract_name}
219
+
220
+ ---
221
+
222
+ ## Change Summary
223
+
224
+ | Metric | Value |
225
+ |--------|-------|
226
+ | Old Lines | {changes['summary']['old_lines']} |
227
+ | New Lines | {changes['summary']['new_lines']} |
228
+ | Added Lines | {changes['summary']['added_lines']} |
229
+ | Removed Lines | {changes['summary']['removed_lines']} |
230
+ | Change % | {changes['summary']['change_percentage']}% |
231
+
232
+ ---
233
+
234
+ ## Changed Functions
235
+
236
+ | Function | Change Type |
237
+ |----------|-------------|
238
+ """
239
+
240
+ for func in changed_functions:
241
+ report += f"| {func['name']} | {func['change_type']} |\n"
242
+
243
+ report += f"""
244
+ ---
245
+
246
+ ## Affected Areas
247
+
248
+ {', '.join(affected_areas) if affected_areas else 'None identified'}
249
+
250
+ ---
251
+
252
+ ## Previous Findings Affected
253
+
254
+ {len(potentially_affected)} findings may be affected by changes:
255
+ """
256
+
257
+ for finding in potentially_affected:
258
+ report += f"- {finding.get('type', 'unknown')} in {finding.get('function', '?')}\n"
259
+
260
+ report += """
261
+ ---
262
+
263
+ ## Recommended Actions
264
+
265
+ 1. Re-audit changed functions
266
+ 2. Verify fixes don't introduce new issues
267
+ 3. Run fuzz tests on affected areas
268
+ 4. Update audit report with incremental findings
269
+ """
270
+
271
+ return {
272
+ "success": True,
273
+ "report": report,
274
+ "changes": changes,
275
+ "affected_findings": potentially_affected,
276
+ "requires_reaudit": len(changed_functions) > 0
277
+ }
278
+
279
+
280
+ async def build_audit_context(contracts: list, previous_audits: list = None) -> dict:
281
+ """Build audit context for 200K context window."""
282
+
283
+ if previous_audits is None:
284
+ previous_audits = []
285
+
286
+ # Build context structure
287
+ context = {
288
+ "protocol_overview": {
289
+ "total_contracts": len(contracts),
290
+ "contract_names": [c.get("name", "unknown") for c in contracts]
291
+ },
292
+ "contract_details": [],
293
+ "audit_history": previous_audits,
294
+ "known_vulnerabilities": [],
295
+ "architecture_notes": []
296
+ }
297
+
298
+ # Add contract details
299
+ total_lines = 0
300
+ for contract in contracts:
301
+ code = contract.get("code", "")
302
+ lines = code.count('\n') + 1
303
+ total_lines += lines
304
+
305
+ context["contract_details"].append({
306
+ "name": contract.get("name", "unknown"),
307
+ "lines": lines,
308
+ "functions": len(contract.get("functions", [])),
309
+ "imports": contract.get("imports", [])
310
+ })
311
+
312
+ # Estimate context usage
313
+ # Rough estimate: 1 token ≈ 4 characters
314
+ total_chars = sum(len(c.get("code", "")) for c in contracts)
315
+ estimated_tokens = total_chars // 4
316
+
317
+ context["context_stats"] = {
318
+ "total_lines": total_lines,
319
+ "estimated_tokens": estimated_tokens,
320
+ "context_window": 200000,
321
+ "utilization": f"{(estimated_tokens / 200000 * 100):.1f}%"
322
+ }
323
+
324
+ # Add known vulnerabilities from previous audits
325
+ for audit in previous_audits:
326
+ for finding in audit.get("findings", []):
327
+ context["known_vulnerabilities"].append({
328
+ "contract": finding.get("contract"),
329
+ "type": finding.get("type"),
330
+ "severity": finding.get("severity"),
331
+ "status": finding.get("status", "open")
332
+ })
333
+
334
+ return {
335
+ "success": True,
336
+ "context": context,
337
+ "recommendation": _get_context_recommendation(estimated_tokens)
338
+ }
339
+
340
+
341
+ def _get_context_recommendation(estimated_tokens: int) -> str:
342
+ """Get recommendation based on context utilization."""
343
+ if estimated_tokens < 50000:
344
+ return "Full protocol audit in single context - ideal"
345
+ elif estimated_tokens < 100000:
346
+ return "Protocol fits in context with room for analysis"
347
+ elif estimated_tokens < 150000:
348
+ return "Protocol requires most of context - prioritize critical contracts"
349
+ else:
350
+ return "Protocol exceeds comfortable context - use incremental auditing"
351
+
352
+
353
+ async def execute_tool(tool_name: str, arguments: dict) -> dict:
354
+ if tool_name == "detect_changes":
355
+ return await detect_changes(
356
+ arguments.get("old_code", ""),
357
+ arguments.get("new_code", ""),
358
+ arguments.get("contract_name")
359
+ )
360
+ elif tool_name == "generate_incremental_report":
361
+ return await generate_incremental_report(
362
+ arguments.get("contract_name", ""),
363
+ arguments.get("old_code", ""),
364
+ arguments.get("new_code", ""),
365
+ arguments.get("previous_findings")
366
+ )
367
+ elif tool_name == "build_audit_context":
368
+ return await build_audit_context(
369
+ arguments.get("contracts", []),
370
+ arguments.get("previous_audits")
371
+ )
372
+ return {"success": False, "error": f"Unknown tool: {tool_name}"}
373
+
374
+
375
+ async def handle_request(request: dict) -> dict:
376
+ method = request.get("method")
377
+ params = request.get("params", {})
378
+
379
+ if method == "initialize":
380
+ return {
381
+ "protocolVersion": "2024-11-05",
382
+ "capabilities": {"tools": {}},
383
+ "serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
384
+ }
385
+ elif method == "tools/list":
386
+ return {"tools": build_tool_definitions()}
387
+ elif method == "tools/call":
388
+ tool_name = params.get("name")
389
+ arguments = params.get("arguments", {})
390
+ result = await execute_tool(tool_name, arguments)
391
+ return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
392
+ elif method == "ping":
393
+ return {}
394
+ return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
395
+
396
+
397
+ async def main():
398
+ reader = asyncio.StreamReader()
399
+ protocol = asyncio.StreamReaderProtocol(reader)
400
+ loop = asyncio.get_event_loop()
401
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
402
+
403
+ logger.info(f"{TOOL_NAME} MCP server started")
404
+
405
+ while True:
406
+ line = await reader.readline()
407
+ if not line:
408
+ break
409
+
410
+ line_str = line.decode("utf-8").strip()
411
+ if not line_str:
412
+ continue
413
+
414
+ try:
415
+ request = json.loads(line_str)
416
+ response = await handle_request(request)
417
+ response["jsonrpc"] = "2.0"
418
+ response["id"] = request.get("id")
419
+ sys.stdout.write(json.dumps(response) + "\n")
420
+ sys.stdout.flush()
421
+ except json.JSONDecodeError:
422
+ error_resp = {
423
+ "jsonrpc": "2.0",
424
+ "id": None,
425
+ "error": {"code": -32700, "message": "Parse error"}
426
+ }
427
+ sys.stdout.write(json.dumps(error_resp) + "\n")
428
+ sys.stdout.flush()
429
+ except Exception as e:
430
+ logger.exception("Error handling request")
431
+ error_resp = {
432
+ "jsonrpc": "2.0",
433
+ "id": request.get("id") if "request" in dir() else None,
434
+ "error": {"code": -32603, "message": f"Internal error: {str(e)}"}
435
+ }
436
+ sys.stdout.write(json.dumps(error_resp) + "\n")
437
+ sys.stdout.flush()
438
+
439
+
440
+ if __name__ == "__main__":
441
+ asyncio.run(main())