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,408 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Server: Halmos Runner
4
+ Symbolic verification tool using Halmos (a16z) for Solidity contracts.
5
+ Verifies contract properties using Z3 SMT solver.
6
+ """
7
+ import sys
8
+ import json
9
+ import asyncio
10
+ import subprocess
11
+ import tempfile
12
+ import logging
13
+ import os
14
+ from pathlib import Path
15
+
16
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
17
+ logger = logging.getLogger("halmos_runner")
18
+
19
+ TOOL_NAME = "halmos_runner"
20
+ TOOL_VERSION = "1.0.0"
21
+
22
+
23
+ def build_tool_definitions() -> list:
24
+ return [
25
+ {
26
+ "name": "halmos_verify",
27
+ "description": "Run symbolic verification on Solidity contracts using Halmos. Verifies contract properties like balance invariants, access control, etc.",
28
+ "inputSchema": {
29
+ "type": "object",
30
+ "properties": {
31
+ "contract_path": {
32
+ "type": "string",
33
+ "description": "Path to Solidity contract file"
34
+ },
35
+ "properties": {
36
+ "type": "array",
37
+ "items": {"type": "string"},
38
+ "description": "Properties to verify (e.g., ['balance >= 0', 'totalSupply == sum(balances)'])"
39
+ },
40
+ "contract_name": {
41
+ "type": "string",
42
+ "description": "Specific contract name to verify (optional, verifies all if omitted)"
43
+ }
44
+ },
45
+ "required": ["contract_path"]
46
+ }
47
+ },
48
+ {
49
+ "name": "halmos_check_invariants",
50
+ "description": "Check common security invariants for a contract (balance >= 0, no overflow, access control)",
51
+ "inputSchema": {
52
+ "type": "object",
53
+ "properties": {
54
+ "contract_path": {
55
+ "type": "string",
56
+ "description": "Path to Solidity contract file"
57
+ }
58
+ },
59
+ "required": ["contract_path"]
60
+ }
61
+ },
62
+ {
63
+ "name": "halmos_generate_tests",
64
+ "description": "Generate Halmos test stubs for contract properties",
65
+ "inputSchema": {
66
+ "type": "object",
67
+ "properties": {
68
+ "contract_path": {
69
+ "type": "string",
70
+ "description": "Path to Solidity contract file"
71
+ },
72
+ "properties": {
73
+ "type": "array",
74
+ "items": {"type": "string"},
75
+ "description": "Properties to generate tests for"
76
+ }
77
+ },
78
+ "required": ["contract_path"]
79
+ }
80
+ }
81
+ ]
82
+
83
+
84
+ async def halmos_verify(contract_path: str, properties: list = None, contract_name: str = None) -> dict:
85
+ """Run Halmos symbolic verification."""
86
+ contract_path = os.path.abspath(contract_path)
87
+
88
+ if not os.path.isfile(contract_path):
89
+ return {"success": False, "error": f"Contract file not found: {contract_path}"}
90
+
91
+ try:
92
+ # Check if halmos is installed
93
+ check_proc = await asyncio.create_subprocess_exec(
94
+ "halmos", "--version",
95
+ stdout=asyncio.subprocess.PIPE,
96
+ stderr=asyncio.subprocess.PIPE
97
+ )
98
+ await check_proc.communicate()
99
+
100
+ if check_proc.returncode != 0:
101
+ # Halmos not installed - return mock result
102
+ return {
103
+ "success": True,
104
+ "mock": True,
105
+ "message": "Halmos not installed. Install: pip install halmos",
106
+ "findings": _generate_mock_findings(contract_path, properties)
107
+ }
108
+
109
+ # Build halmos command
110
+ cmd = ["halmos", "--contract", contract_path]
111
+
112
+ if contract_name:
113
+ cmd.extend(["--contract-name", contract_name])
114
+
115
+ # Run halmos
116
+ logger.info(f"Running halmos: {' '.join(cmd)}")
117
+
118
+ proc = await asyncio.create_subprocess_exec(
119
+ *cmd,
120
+ stdout=asyncio.subprocess.PIPE,
121
+ stderr=asyncio.subprocess.PIPE
122
+ )
123
+
124
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=120)
125
+
126
+ stdout_text = stdout.decode("utf-8", errors="replace")
127
+ stderr_text = stderr.decode("utf-8", errors="replace")
128
+
129
+ # Parse halmos output
130
+ findings = _parse_halmos_output(stdout_text, stderr_text)
131
+
132
+ return {
133
+ "success": True,
134
+ "exit_code": proc.returncode,
135
+ "findings": findings,
136
+ "properties_verified": len([f for f in findings if f["status"] == "verified"]),
137
+ "properties_failed": len([f for f in findings if f["status"] == "failed"]),
138
+ "raw_output": stdout_text[:2000]
139
+ }
140
+
141
+ except asyncio.TimeoutError:
142
+ return {"success": False, "error": "Halmos verification timed out (120s)"}
143
+ except FileNotFoundError:
144
+ return {
145
+ "success": True,
146
+ "mock": True,
147
+ "message": "Halmos not installed. Install: pip install halmos",
148
+ "findings": _generate_mock_findings(contract_path, properties)
149
+ }
150
+ except Exception as e:
151
+ logger.exception("Halmos verification failed")
152
+ return {"success": False, "error": str(e)}
153
+
154
+
155
+ def _parse_halmos_output(stdout: str, stderr: str) -> list:
156
+ """Parse halmos output to extract findings."""
157
+ findings = []
158
+
159
+ # Halmos output format varies, parse common patterns
160
+ lines = stdout.split("\n")
161
+
162
+ for line in lines:
163
+ line = line.strip()
164
+
165
+ # Look for verification results
166
+ if "PASS" in line or "VERIFIED" in line:
167
+ findings.append({
168
+ "property": _extract_property(line),
169
+ "status": "verified",
170
+ "message": line
171
+ })
172
+ elif "FAIL" in line or "COUNTEREXAMPLE" in line:
173
+ findings.append({
174
+ "property": _extract_property(line),
175
+ "status": "failed",
176
+ "message": line,
177
+ "counterexample": _extract_counterexample(line)
178
+ })
179
+ elif "UNKNOWN" in line or "TIMEOUT" in line:
180
+ findings.append({
181
+ "property": _extract_property(line),
182
+ "status": "unknown",
183
+ "message": line
184
+ })
185
+
186
+ # If no findings parsed, create generic result
187
+ if not findings:
188
+ findings.append({
189
+ "property": "general",
190
+ "status": "unknown",
191
+ "message": "Could not parse halmos output"
192
+ })
193
+
194
+ return findings
195
+
196
+
197
+ def _extract_property(line: str) -> str:
198
+ """Extract property name from halmos output line."""
199
+ # Try to extract function/property name
200
+ if "::" in line:
201
+ return line.split("::")[-1].split()[0]
202
+ return "unknown"
203
+
204
+
205
+ def _extract_counterexample(line: str) -> str:
206
+ """Extract counterexample from halmos output."""
207
+ if "counterexample" in line.lower():
208
+ return line
209
+ return ""
210
+
211
+
212
+ def _generate_mock_findings(contract_path: str, properties: list = None) -> list:
213
+ """Generate mock findings when halmos is not installed."""
214
+ findings = []
215
+
216
+ # Common properties to check
217
+ default_properties = [
218
+ "balance >= 0",
219
+ "totalSupply == sum(balances)",
220
+ "only_owner_can_admin",
221
+ "no_overflow"
222
+ ]
223
+
224
+ check_properties = properties or default_properties
225
+
226
+ for prop in check_properties:
227
+ findings.append({
228
+ "property": prop,
229
+ "status": "mock",
230
+ "message": f"Mock verification (halmos not installed): {prop}",
231
+ "recommendation": "Install halmos and run actual verification"
232
+ })
233
+
234
+ return findings
235
+
236
+
237
+ async def halmos_check_invariants(contract_path: str) -> dict:
238
+ """Check common security invariants."""
239
+ # Common invariants to verify
240
+ invariants = [
241
+ "balance >= 0",
242
+ "totalSupply >= 0",
243
+ "no_reentrancy",
244
+ "access_control_valid",
245
+ "no_overflow_underflow"
246
+ ]
247
+
248
+ result = await halmos_verify(contract_path, invariants)
249
+
250
+ if result.get("success"):
251
+ result["invariants_checked"] = invariants
252
+ result["summary"] = f"Checked {len(invariants)} invariants"
253
+
254
+ return result
255
+
256
+
257
+ async def halmos_generate_tests(contract_path: str, properties: list = None) -> dict:
258
+ """Generate Halmos test stubs."""
259
+ contract_path = os.path.abspath(contract_path)
260
+
261
+ if not os.path.isfile(contract_path):
262
+ return {"success": False, "error": f"Contract file not found: {contract_path}"}
263
+
264
+ # Read contract to extract contract name
265
+ try:
266
+ with open(contract_path, 'r') as f:
267
+ content = f.read()
268
+
269
+ import re
270
+ match = re.search(r'contract\s+(\w+)', content)
271
+ contract_name = match.group(1) if match else "Target"
272
+ except Exception:
273
+ contract_name = "Target"
274
+
275
+ # Generate test stubs
276
+ default_properties = properties or [
277
+ "balance_never_negative",
278
+ "total_supply_conserved",
279
+ "only_owner_can_withdraw"
280
+ ]
281
+
282
+ test_code = f'''// SPDX-License-Identifier: MIT
283
+ pragma solidity ^0.8.20;
284
+
285
+ import "forge-std/Test.sol";
286
+ import "{contract_path}";
287
+
288
+ /// @title HalmosTests - Generated symbolic verification tests
289
+ contract HalmosTests is Test {{
290
+ {contract_name} public target;
291
+
292
+ function setUp() public {{
293
+ target = new {contract_name}();
294
+ }}
295
+
296
+ '''
297
+
298
+ for i, prop in enumerate(default_properties):
299
+ test_code += f''' /// @notice Verify: {prop}
300
+ function check_{prop}() public {{
301
+ // TODO: Implement property verification
302
+ // Use symbolic variables for inputs
303
+ // Assert invariant holds for all possible inputs
304
+ }}
305
+
306
+ '''
307
+
308
+ test_code += ''' /// @notice Helper: Check balance invariant
309
+ function check_balance_non_negative() public view {
310
+ // Symbolic verification that balance >= 0
311
+ // This is always true for uint256, but worth documenting
312
+ }
313
+ }
314
+ '''
315
+
316
+ return {
317
+ "success": True,
318
+ "contract_name": contract_name,
319
+ "properties": default_properties,
320
+ "test_code": test_code,
321
+ "usage": "Save to test/HalmosTests.t.sol and run: forge test"
322
+ }
323
+
324
+
325
+ async def execute_tool(tool_name: str, arguments: dict) -> dict:
326
+ if tool_name == "halmos_verify":
327
+ return await halmos_verify(
328
+ arguments.get("contract_path", ""),
329
+ arguments.get("properties"),
330
+ arguments.get("contract_name")
331
+ )
332
+ elif tool_name == "halmos_check_invariants":
333
+ return await halmos_check_invariants(arguments.get("contract_path", ""))
334
+ elif tool_name == "halmos_generate_tests":
335
+ return await halmos_generate_tests(
336
+ arguments.get("contract_path", ""),
337
+ arguments.get("properties")
338
+ )
339
+ return {"success": False, "error": f"Unknown tool: {tool_name}"}
340
+
341
+
342
+ async def handle_request(request: dict) -> dict:
343
+ method = request.get("method")
344
+ params = request.get("params", {})
345
+
346
+ if method == "initialize":
347
+ return {
348
+ "protocolVersion": "2024-11-05",
349
+ "capabilities": {"tools": {}},
350
+ "serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
351
+ }
352
+ elif method == "tools/list":
353
+ return {"tools": build_tool_definitions()}
354
+ elif method == "tools/call":
355
+ tool_name = params.get("name")
356
+ arguments = params.get("arguments", {})
357
+ result = await execute_tool(tool_name, arguments)
358
+ return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
359
+ elif method == "ping":
360
+ return {}
361
+ return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
362
+
363
+
364
+ async def main():
365
+ reader = asyncio.StreamReader()
366
+ protocol = asyncio.StreamReaderProtocol(reader)
367
+ loop = asyncio.get_event_loop()
368
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
369
+
370
+ logger.info(f"{TOOL_NAME} MCP server started")
371
+
372
+ while True:
373
+ line = await reader.readline()
374
+ if not line:
375
+ break
376
+
377
+ line_str = line.decode("utf-8").strip()
378
+ if not line_str:
379
+ continue
380
+
381
+ try:
382
+ request = json.loads(line_str)
383
+ response = await handle_request(request)
384
+ response["jsonrpc"] = "2.0"
385
+ response["id"] = request.get("id")
386
+ sys.stdout.write(json.dumps(response) + "\n")
387
+ sys.stdout.flush()
388
+ except json.JSONDecodeError:
389
+ error_resp = {
390
+ "jsonrpc": "2.0",
391
+ "id": None,
392
+ "error": {"code": -32700, "message": "Parse error"}
393
+ }
394
+ sys.stdout.write(json.dumps(error_resp) + "\n")
395
+ sys.stdout.flush()
396
+ except Exception as e:
397
+ logger.exception("Error handling request")
398
+ error_resp = {
399
+ "jsonrpc": "2.0",
400
+ "id": request.get("id") if "request" in dir() else None,
401
+ "error": {"code": -32603, "message": f"Internal error: {str(e)}"}
402
+ }
403
+ sys.stdout.write(json.dumps(error_resp) + "\n")
404
+ sys.stdout.flush()
405
+
406
+
407
+ if __name__ == "__main__":
408
+ asyncio.run(main())