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,421 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Server: Repair Validator
4
+ Multi-round repair verification: audit → fix → re-audit → compare.
5
+ """
6
+ import sys
7
+ import json
8
+ import asyncio
9
+ import logging
10
+
11
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
12
+ logger = logging.getLogger("repair_validator")
13
+
14
+ TOOL_NAME = "repair_validator"
15
+ TOOL_VERSION = "1.0.0"
16
+
17
+
18
+ def build_tool_definitions() -> list:
19
+ return [
20
+ {
21
+ "name": "compare_audit_results",
22
+ "description": "Compare before/after audit results to measure repair effectiveness",
23
+ "inputSchema": {
24
+ "type": "object",
25
+ "properties": {
26
+ "before_findings": {
27
+ "type": "array",
28
+ "items": {"type": "object"},
29
+ "description": "Findings from initial audit"
30
+ },
31
+ "after_findings": {
32
+ "type": "array",
33
+ "items": {"type": "object"},
34
+ "description": "Findings from re-audit after fix"
35
+ }
36
+ },
37
+ "required": ["before_findings", "after_findings"]
38
+ }
39
+ },
40
+ {
41
+ "name": "generate_repair_report",
42
+ "description": "Generate a repair verification report",
43
+ "inputSchema": {
44
+ "type": "object",
45
+ "properties": {
46
+ "contract_name": {"type": "string"},
47
+ "before_findings": {
48
+ "type": "array",
49
+ "items": {"type": "object"}
50
+ },
51
+ "after_findings": {
52
+ "type": "array",
53
+ "items": {"type": "object"}
54
+ },
55
+ "fixes_applied": {
56
+ "type": "array",
57
+ "items": {"type": "string"},
58
+ "description": "List of fixes applied"
59
+ }
60
+ },
61
+ "required": ["contract_name", "before_findings", "after_findings"]
62
+ }
63
+ },
64
+ {
65
+ "name": "suggest_fixes",
66
+ "description": "Suggest fixes for audit findings",
67
+ "inputSchema": {
68
+ "type": "object",
69
+ "properties": {
70
+ "findings": {
71
+ "type": "array",
72
+ "items": {"type": "object"},
73
+ "description": "Audit findings to fix"
74
+ },
75
+ "contract_code": {
76
+ "type": "string",
77
+ "description": "Original contract code"
78
+ }
79
+ },
80
+ "required": ["findings"]
81
+ }
82
+ }
83
+ ]
84
+
85
+
86
+ async def compare_audit_results(before_findings: list, after_findings: list) -> dict:
87
+ """Compare before/after audit results."""
88
+
89
+ # Categorize findings
90
+ before_by_severity = _categorize_by_severity(before_findings)
91
+ after_by_severity = _categorize_by_severity(after_findings)
92
+
93
+ # Find fixed findings
94
+ fixed = []
95
+ for bf in before_findings:
96
+ found_in_after = False
97
+ for af in after_findings:
98
+ if _findings_match(bf, af):
99
+ found_in_after = True
100
+ break
101
+ if not found_in_after:
102
+ fixed.append(bf)
103
+
104
+ # Find new findings
105
+ new = []
106
+ for af in after_findings:
107
+ found_in_before = False
108
+ for bf in before_findings:
109
+ if _findings_match(bf, af):
110
+ found_in_before = True
111
+ break
112
+ if not found_in_before:
113
+ new.append(af)
114
+
115
+ # Find remaining findings
116
+ remaining = []
117
+ for af in after_findings:
118
+ for bf in before_findings:
119
+ if _findings_match(bf, af):
120
+ remaining.append(af)
121
+ break
122
+
123
+ # Calculate metrics
124
+ total_before = len(before_findings)
125
+ total_after = len(after_findings)
126
+ fixed_count = len(fixed)
127
+ new_count = len(new)
128
+ remaining_count = len(remaining)
129
+
130
+ # Calculate severity reduction
131
+ severity_reduction = {}
132
+ for sev in ["critical", "high", "medium", "low"]:
133
+ before_count = before_by_severity.get(sev, 0)
134
+ after_count = after_by_severity.get(sev, 0)
135
+ severity_reduction[sev] = {
136
+ "before": before_count,
137
+ "after": after_count,
138
+ "reduced": before_count - after_count
139
+ }
140
+
141
+ # Calculate effectiveness score (0-100)
142
+ if total_before == 0:
143
+ effectiveness = 100
144
+ else:
145
+ effectiveness = int((fixed_count / total_before) * 100)
146
+
147
+ return {
148
+ "success": True,
149
+ "summary": {
150
+ "total_before": total_before,
151
+ "total_after": total_after,
152
+ "fixed": fixed_count,
153
+ "new": new_count,
154
+ "remaining": remaining_count,
155
+ "effectiveness": effectiveness
156
+ },
157
+ "severity_reduction": severity_reduction,
158
+ "fixed_findings": fixed,
159
+ "new_findings": new,
160
+ "remaining_findings": remaining,
161
+ "verdict": _get_verdict(effectiveness, new_count)
162
+ }
163
+
164
+
165
+ def _categorize_by_severity(findings: list) -> dict:
166
+ """Categorize findings by severity."""
167
+ categories = {}
168
+ for f in findings:
169
+ sev = f.get("severity", "unknown").lower()
170
+ categories[sev] = categories.get(sev, 0) + 1
171
+ return categories
172
+
173
+
174
+ def _findings_match(f1: dict, f2: dict) -> bool:
175
+ """Check if two findings match (same vulnerability)."""
176
+ # Match by contract + function + type
177
+ return (
178
+ f1.get("contract") == f2.get("contract") and
179
+ f1.get("function") == f2.get("function") and
180
+ f1.get("type") == f2.get("type")
181
+ )
182
+
183
+
184
+ def _get_verdict(effectiveness: int, new_findings: int) -> str:
185
+ """Get overall verdict."""
186
+ if effectiveness >= 90 and new_findings == 0:
187
+ return "EXCELLENT - Nearly all issues fixed, no new issues"
188
+ elif effectiveness >= 70 and new_findings == 0:
189
+ return "GOOD - Most issues fixed, no new issues"
190
+ elif effectiveness >= 50:
191
+ return "FAIR - Some issues fixed"
192
+ elif new_findings > 0:
193
+ return "CONCERNING - New issues introduced"
194
+ else:
195
+ return "POOR - Few issues fixed"
196
+
197
+
198
+ async def generate_repair_report(contract_name: str, before_findings: list, after_findings: list, fixes_applied: list = None) -> dict:
199
+ """Generate a repair verification report."""
200
+
201
+ comparison = await compare_audit_results(before_findings, after_findings)
202
+
203
+ report = f"""# Repair Verification Report
204
+
205
+ ## Contract: {contract_name}
206
+
207
+ ---
208
+
209
+ ## Summary
210
+
211
+ | Metric | Value |
212
+ |--------|-------|
213
+ | Findings Before | {comparison['summary']['total_before']} |
214
+ | Findings After | {comparison['summary']['total_after']} |
215
+ | Fixed | {comparison['summary']['fixed']} |
216
+ | New | {comparison['summary']['new']} |
217
+ | Remaining | {comparison['summary']['remaining']} |
218
+ | Effectiveness | {comparison['summary']['effectiveness']}% |
219
+ | Verdict | {comparison['verdict']} |
220
+
221
+ ---
222
+
223
+ ## Severity Reduction
224
+
225
+ | Severity | Before | After | Reduced |
226
+ |----------|--------|-------|---------|
227
+ """
228
+
229
+ for sev, data in comparison['severity_reduction'].items():
230
+ report += f"| {sev.capitalize()} | {data['before']} | {data['after']} | {data['reduced']} |\n"
231
+
232
+ if fixes_applied:
233
+ report += "\n---\n\n## Fixes Applied\n\n"
234
+ for i, fix in enumerate(fixes_applied, 1):
235
+ report += f"{i}. {fix}\n"
236
+
237
+ if comparison['fixed_findings']:
238
+ report += "\n---\n\n## Fixed Findings\n\n"
239
+ for f in comparison['fixed_findings']:
240
+ report += f"- **{f.get('type', 'unknown')}** in {f.get('contract', '?')}.{f.get('function', '?')}\n"
241
+
242
+ if comparison['new_findings']:
243
+ report += "\n---\n\n## New Findings (Introduced by Fix)\n\n"
244
+ for f in comparison['new_findings']:
245
+ report += f"- **{f.get('type', 'unknown')}** in {f.get('contract', '?')}.{f.get('function', '?')}\n"
246
+
247
+ if comparison['remaining_findings']:
248
+ report += "\n---\n\n## Remaining Findings\n\n"
249
+ for f in comparison['remaining_findings']:
250
+ report += f"- **{f.get('type', 'unknown')}** in {f.get('contract', '?')}.{f.get('function', '?')}\n"
251
+
252
+ return {
253
+ "success": True,
254
+ "report": report,
255
+ "comparison": comparison
256
+ }
257
+
258
+
259
+ async def suggest_fixes(findings: list, contract_code: str = None) -> dict:
260
+ """Suggest fixes for audit findings."""
261
+ suggestions = []
262
+
263
+ for finding in findings:
264
+ finding_type = finding.get("type", "").lower()
265
+ severity = finding.get("severity", "unknown")
266
+
267
+ suggestion = {
268
+ "finding": finding,
269
+ "fixes": []
270
+ }
271
+
272
+ # Reentrancy fix
273
+ if "reentrancy" in finding_type:
274
+ suggestion["fixes"].append({
275
+ "approach": "checks-effects-interactions",
276
+ "description": "Update state before external call",
277
+ "diff": "- (bool success, ) = msg.sender.call{value: balance}(\"\");\n- balances[msg.sender] = 0;\n+ balances[msg.sender] = 0;\n+ (bool success, ) = msg.sender.call{value: balance}(\"\");"
278
+ })
279
+ suggestion["fixes"].append({
280
+ "approach": "reentrancy-guard",
281
+ "description": "Use OpenZeppelin ReentrancyGuard",
282
+ "diff": "+ import \"@openzeppelin/contracts/security/ReentrancyGuard.sol\";\n+ contract Vault is ReentrancyGuard {\n function withdraw() public nonReentrant {"
283
+ })
284
+
285
+ # Access control fix
286
+ elif "access_control" in finding_type or "missing_access" in finding_type:
287
+ suggestion["fixes"].append({
288
+ "approach": "onlyOwner-modifier",
289
+ "description": "Add onlyOwner modifier",
290
+ "diff": "+ modifier onlyOwner() {\n+ require(msg.sender == owner, \"Not owner\");\n+ _;\n+ }\n function adminFunction() public onlyOwner {"
291
+ })
292
+
293
+ # Oracle manipulation fix
294
+ elif "oracle" in finding_type:
295
+ suggestion["fixes"].append({
296
+ "approach": "twap-oracle",
297
+ "description": "Use TWAP instead of spot price",
298
+ "diff": "- uint256 price = oracle.getPrice(token);\n+ uint256 price = oracle.getTWAPPrice(token, 3600); // 1 hour TWAP"
299
+ })
300
+
301
+ # Flash loan fix
302
+ elif "flash_loan" in finding_type:
303
+ suggestion["fixes"].append({
304
+ "approach": "timelock",
305
+ "description": "Add timelock for governance actions",
306
+ "diff": "+ require(block.timestamp >= proposal.timestamp + TIMELOCK_DELAY, \"Timelock not expired\");"
307
+ })
308
+
309
+ # Overflow fix
310
+ elif "overflow" in finding_type:
311
+ suggestion["fixes"].append({
312
+ "approach": "checked-arithmetic",
313
+ "description": "Use Solidity 0.8+ checked arithmetic",
314
+ "diff": "- unchecked { total += amount; }\n+ total += amount; // Solidity 0.8+ checks automatically"
315
+ })
316
+
317
+ # Generic fix
318
+ else:
319
+ suggestion["fixes"].append({
320
+ "approach": "review-and-fix",
321
+ "description": "Manual review required",
322
+ "diff": "See finding details for specific fix recommendations"
323
+ })
324
+
325
+ suggestions.append(suggestion)
326
+
327
+ return {
328
+ "success": True,
329
+ "suggestions": suggestions,
330
+ "total_suggestions": len(suggestions)
331
+ }
332
+
333
+
334
+ async def execute_tool(tool_name: str, arguments: dict) -> dict:
335
+ if tool_name == "compare_audit_results":
336
+ return await compare_audit_results(
337
+ arguments.get("before_findings", []),
338
+ arguments.get("after_findings", [])
339
+ )
340
+ elif tool_name == "generate_repair_report":
341
+ return await generate_repair_report(
342
+ arguments.get("contract_name", ""),
343
+ arguments.get("before_findings", []),
344
+ arguments.get("after_findings", []),
345
+ arguments.get("fixes_applied")
346
+ )
347
+ elif tool_name == "suggest_fixes":
348
+ return await suggest_fixes(
349
+ arguments.get("findings", []),
350
+ arguments.get("contract_code")
351
+ )
352
+ return {"success": False, "error": f"Unknown tool: {tool_name}"}
353
+
354
+
355
+ async def handle_request(request: dict) -> dict:
356
+ method = request.get("method")
357
+ params = request.get("params", {})
358
+
359
+ if method == "initialize":
360
+ return {
361
+ "protocolVersion": "2024-11-05",
362
+ "capabilities": {"tools": {}},
363
+ "serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
364
+ }
365
+ elif method == "tools/list":
366
+ return {"tools": build_tool_definitions()}
367
+ elif method == "tools/call":
368
+ tool_name = params.get("name")
369
+ arguments = params.get("arguments", {})
370
+ result = await execute_tool(tool_name, arguments)
371
+ return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
372
+ elif method == "ping":
373
+ return {}
374
+ return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
375
+
376
+
377
+ async def main():
378
+ reader = asyncio.StreamReader()
379
+ protocol = asyncio.StreamReaderProtocol(reader)
380
+ loop = asyncio.get_event_loop()
381
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
382
+
383
+ logger.info(f"{TOOL_NAME} MCP server started")
384
+
385
+ while True:
386
+ line = await reader.readline()
387
+ if not line:
388
+ break
389
+
390
+ line_str = line.decode("utf-8").strip()
391
+ if not line_str:
392
+ continue
393
+
394
+ try:
395
+ request = json.loads(line_str)
396
+ response = await handle_request(request)
397
+ response["jsonrpc"] = "2.0"
398
+ response["id"] = request.get("id")
399
+ sys.stdout.write(json.dumps(response) + "\n")
400
+ sys.stdout.flush()
401
+ except json.JSONDecodeError:
402
+ error_resp = {
403
+ "jsonrpc": "2.0",
404
+ "id": None,
405
+ "error": {"code": -32700, "message": "Parse error"}
406
+ }
407
+ sys.stdout.write(json.dumps(error_resp) + "\n")
408
+ sys.stdout.flush()
409
+ except Exception as e:
410
+ logger.exception("Error handling request")
411
+ error_resp = {
412
+ "jsonrpc": "2.0",
413
+ "id": request.get("id") if "request" in dir() else None,
414
+ "error": {"code": -32603, "message": f"Internal error: {str(e)}"}
415
+ }
416
+ sys.stdout.write(json.dumps(error_resp) + "\n")
417
+ sys.stdout.flush()
418
+
419
+
420
+ if __name__ == "__main__":
421
+ asyncio.run(main())
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Server: Slither Runner
4
+ Wraps Slither static analysis tool for Solidity smart contracts.
5
+ """
6
+ import sys
7
+ import json
8
+ import asyncio
9
+ import tempfile
10
+ import subprocess
11
+ import logging
12
+ import os
13
+ from pathlib import Path
14
+
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
16
+ logger = logging.getLogger("slither_runner")
17
+
18
+ TOOL_NAME = "slither_runner"
19
+ TOOL_VERSION = "1.0.0"
20
+
21
+
22
+ def build_tool_definitions() -> list:
23
+ return [
24
+ {
25
+ "name": "slither_analyze",
26
+ "description": "Run Slither static analysis on a Solidity smart contract. Returns detected vulnerabilities with impact, confidence, and location details.",
27
+ "inputSchema": {
28
+ "type": "object",
29
+ "properties": {
30
+ "contract_path": {
31
+ "type": "string",
32
+ "description": "Path to the Solidity contract file to analyze"
33
+ },
34
+ "detectors": {
35
+ "type": "array",
36
+ "items": {"type": "string"},
37
+ "description": "Optional list of specific Slither detector names to run (e.g., ['reentrancy-eth', 'unchecked-transfer']). If omitted, all detectors run."
38
+ }
39
+ },
40
+ "required": ["contract_path"]
41
+ }
42
+ }
43
+ ]
44
+
45
+
46
+ async def run_slither(contract_path: str, detectors: list = None) -> dict:
47
+ """Execute Slither and parse JSON output."""
48
+ contract_path = os.path.abspath(contract_path)
49
+
50
+ if not os.path.isfile(contract_path):
51
+ return {"success": False, "error": f"Contract file not found: {contract_path}"}
52
+
53
+ # Create a temp directory and point at a file inside it (slither refuses to overwrite)
54
+ tmp_dir = tempfile.mkdtemp(prefix="slither_")
55
+ json_output_path = os.path.join(tmp_dir, "slither.json")
56
+
57
+ try:
58
+ cmd = ["slither", contract_path, "--json", json_output_path]
59
+
60
+ # Add specific detectors if provided
61
+ if detectors:
62
+ for detector in detectors:
63
+ cmd.extend(["--detect", detector])
64
+
65
+ logger.info(f"Running slither: {' '.join(cmd)}")
66
+
67
+ proc = await asyncio.create_subprocess_exec(
68
+ *cmd,
69
+ stdout=asyncio.subprocess.PIPE,
70
+ stderr=asyncio.subprocess.PIPE
71
+ )
72
+ stdout, stderr = await proc.communicate()
73
+
74
+ # Slither returns various non-zero codes when it finds issues
75
+ # 0 = no issues, 1 = issues found, 2 = error, 255 = issues found (newer versions)
76
+ if proc.returncode not in (0, 1, 2, 255):
77
+ return {
78
+ "success": False,
79
+ "error": f"Slither exited with code {proc.returncode}",
80
+ "stderr": stderr.decode("utf-8", errors="replace").strip()
81
+ }
82
+
83
+ # Parse JSON output
84
+ if not os.path.isfile(json_output_path):
85
+ return {
86
+ "success": False,
87
+ "error": "Slither did not produce JSON output",
88
+ "stderr": stderr.decode("utf-8", errors="replace").strip()
89
+ }
90
+
91
+ with open(json_output_path, "r") as f:
92
+ raw_output = json.load(f)
93
+
94
+ # Extract and normalize findings
95
+ findings = []
96
+ for detector_result in raw_output.get("results", {}).get("detectors", []):
97
+ finding = {
98
+ "check": detector_result.get("check", "unknown"),
99
+ "impact": detector_result.get("impact", "Unknown"),
100
+ "confidence": detector_result.get("confidence", "Unknown"),
101
+ "description": detector_result.get("description", ""),
102
+ "elements": []
103
+ }
104
+
105
+ # Extract source locations
106
+ for element in detector_result.get("elements", []):
107
+ source = element.get("source_mapping", {})
108
+ elem_info = {
109
+ "type": element.get("type", ""),
110
+ "name": element.get("name", ""),
111
+ "file": source.get("filename_relative", ""),
112
+ "lines": source.get("lines", []),
113
+ "start": source.get("start", 0),
114
+ "length": source.get("length", 0)
115
+ }
116
+ finding["elements"].append(elem_info)
117
+
118
+ findings.append(finding)
119
+
120
+ return {
121
+ "success": True,
122
+ "contract": contract_path,
123
+ "findings_count": len(findings),
124
+ "findings": findings,
125
+ "slither_version": raw_output.get("meta", {}).get("slither_version", "unknown"),
126
+ "compiler": raw_output.get("meta", {}).get("solc_version", "unknown")
127
+ }
128
+
129
+ except FileNotFoundError:
130
+ return {"success": False, "error": "Slither not found. Install with: pip3 install slither-analyzer"}
131
+ except json.JSONDecodeError as e:
132
+ return {"success": False, "error": f"Failed to parse Slither JSON output: {str(e)}"}
133
+ except Exception as e:
134
+ logger.exception("Unexpected error in slither_runner")
135
+ return {"success": False, "error": f"Unexpected error: {str(e)}"}
136
+ finally:
137
+ # Cleanup temp directory
138
+ import shutil
139
+ if os.path.exists(json_output_path):
140
+ os.unlink(json_output_path)
141
+ try:
142
+ os.rmdir(tmp_dir)
143
+ except OSError:
144
+ pass
145
+
146
+
147
+ async def execute_tool(tool_name: str, arguments: dict) -> dict:
148
+ if tool_name == "slither_analyze":
149
+ contract_path = arguments.get("contract_path", "")
150
+ detectors = arguments.get("detectors", None)
151
+ return await run_slither(contract_path, detectors)
152
+ return {"success": False, "error": f"Unknown tool: {tool_name}"}
153
+
154
+
155
+ async def handle_request(request: dict) -> dict:
156
+ method = request.get("method")
157
+ params = request.get("params", {})
158
+
159
+ if method == "initialize":
160
+ return {
161
+ "protocolVersion": "2024-11-05",
162
+ "capabilities": {"tools": {}},
163
+ "serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
164
+ }
165
+ elif method == "tools/list":
166
+ return {"tools": build_tool_definitions()}
167
+ elif method == "tools/call":
168
+ tool_name = params.get("name")
169
+ arguments = params.get("arguments", {})
170
+ result = await execute_tool(tool_name, arguments)
171
+ return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
172
+ elif method == "ping":
173
+ return {}
174
+ return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
175
+
176
+
177
+ async def main():
178
+ reader = asyncio.StreamReader()
179
+ protocol = asyncio.StreamReaderProtocol(reader)
180
+ loop = asyncio.get_event_loop()
181
+ await loop.connect_read_pipe(lambda: protocol, sys.stdin)
182
+
183
+ logger.info(f"{TOOL_NAME} MCP server started")
184
+
185
+ while True:
186
+ line = await reader.readline()
187
+ if not line:
188
+ break
189
+
190
+ line_str = line.decode("utf-8").strip()
191
+ if not line_str:
192
+ continue
193
+
194
+ try:
195
+ request = json.loads(line_str)
196
+ response = await handle_request(request)
197
+ response["jsonrpc"] = "2.0"
198
+ response["id"] = request.get("id")
199
+ sys.stdout.write(json.dumps(response) + "\n")
200
+ sys.stdout.flush()
201
+ except json.JSONDecodeError:
202
+ error_resp = {
203
+ "jsonrpc": "2.0",
204
+ "id": None,
205
+ "error": {"code": -32700, "message": "Parse error"}
206
+ }
207
+ sys.stdout.write(json.dumps(error_resp) + "\n")
208
+ sys.stdout.flush()
209
+ except Exception as e:
210
+ logger.exception("Error handling request")
211
+ error_resp = {
212
+ "jsonrpc": "2.0",
213
+ "id": request.get("id") if "request" in dir() else None,
214
+ "error": {"code": -32603, "message": f"Internal error: {str(e)}"}
215
+ }
216
+ sys.stdout.write(json.dumps(error_resp) + "\n")
217
+ sys.stdout.flush()
218
+
219
+
220
+ if __name__ == "__main__":
221
+ asyncio.run(main())
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "athena-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Athena — Web3 smart contract security audit MCP tools + Skills for Claude Code",
5
+ "main": "install.js",
6
+ "bin": {
7
+ "athena-mcp": "install.js"
8
+ },
9
+ "scripts": {
10
+ "install-tools": "node install.js",
11
+ "postinstall": "echo 'Run npx athena-mcp install to set up Athena tools'"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "smart-contract",
16
+ "security-audit",
17
+ "solidity",
18
+ "web3",
19
+ "claude",
20
+ "slither",
21
+ "aderyn",
22
+ "foundry",
23
+ "eas",
24
+ "ethereum"
25
+ ],
26
+ "author": "tiyadegure",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/tiyadegure/Athena.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/tiyadegure/Athena/issues"
34
+ },
35
+ "homepage": "https://github.com/tiyadegure/Athena#readme",
36
+ "engines": {
37
+ "node": ">=16.0.0"
38
+ },
39
+ "files": [
40
+ "install.js",
41
+ "package.json",
42
+ "README.md",
43
+ "mcp/",
44
+ "skills/",
45
+ "requirements.txt"
46
+ ],
47
+ "preferGlobal": true,
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "registry": "https://registry.npmjs.org/"
51
+ }
52
+ }