athena-mcp 1.0.4 → 1.0.5
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/README.md +3 -3
- package/mcp/servers.json +49 -14
- package/mcp/tools/nft_minter.py +224 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -198,7 +198,7 @@ This will:
|
|
|
198
198
|
2. Install Python dependencies (slither, web3, chromadb, etc.)
|
|
199
199
|
3. Install system tools (Slither, Aderyn, Foundry)
|
|
200
200
|
4. Copy the audit skill to `~/.claude/skills/athena-audit-skill/`
|
|
201
|
-
5. Configure
|
|
201
|
+
5. Configure 14 MCP servers in Claude Code
|
|
202
202
|
|
|
203
203
|
### Option B: curl install script
|
|
204
204
|
|
|
@@ -294,7 +294,7 @@ claude "审计 contracts/test-cases/Reentrancy.sol 并铸造 NFT 证书"
|
|
|
294
294
|
claude "用 slither 和 aderyn 扫描 contracts/test-cases/Reentrancy.sol"
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
-
Claude Code 会自动发现并调用
|
|
297
|
+
Claude Code 会自动发现并调用 14 个 MCP 工具,完成从漏洞发现到链上认证的完整闭环。
|
|
298
298
|
|
|
299
299
|
### 方式二:GLM-5.1 Agent
|
|
300
300
|
|
|
@@ -399,7 +399,7 @@ cast send 0x3247d57d37bd1878479f03a077aba807649dbaf5 \
|
|
|
399
399
|
|
|
400
400
|
详见 [smart-contract-audit-agents-comparison.md](./smart-contract-audit-agents-comparison.md)
|
|
401
401
|
|
|
402
|
-
核心优势:**自建
|
|
402
|
+
核心优势:**自建 14 个 MCP 工具 + 12 agent 并行审计方法论 + RAG 知识库 + PoC 生成 + Foundry fuzz + 攻击模拟 + 形式化验证 + EAS 链上认证 + Generative NFT,配合 GLM-5.1 的长程能力驱动从漏洞发现到链上证书的完整闭环。**
|
|
403
403
|
|
|
404
404
|
## 开发工作流
|
|
405
405
|
|
package/mcp/servers.json
CHANGED
|
@@ -3,35 +3,45 @@
|
|
|
3
3
|
{
|
|
4
4
|
"name": "slither",
|
|
5
5
|
"command": "python3",
|
|
6
|
-
"args": [
|
|
6
|
+
"args": [
|
|
7
|
+
"mcp/tools/slither_runner.py"
|
|
8
|
+
],
|
|
7
9
|
"description": "Slither static analysis for Solidity contracts",
|
|
8
10
|
"env": {}
|
|
9
11
|
},
|
|
10
12
|
{
|
|
11
13
|
"name": "aderyn",
|
|
12
14
|
"command": "python3",
|
|
13
|
-
"args": [
|
|
15
|
+
"args": [
|
|
16
|
+
"mcp/tools/aderyn_runner.py"
|
|
17
|
+
],
|
|
14
18
|
"description": "Aderyn static analysis (Rust-based) for Solidity projects",
|
|
15
19
|
"env": {}
|
|
16
20
|
},
|
|
17
21
|
{
|
|
18
22
|
"name": "poc-generator",
|
|
19
23
|
"command": "python3",
|
|
20
|
-
"args": [
|
|
24
|
+
"args": [
|
|
25
|
+
"mcp/tools/poc_generator.py"
|
|
26
|
+
],
|
|
21
27
|
"description": "Generate Foundry PoC exploit tests for vulnerabilities",
|
|
22
28
|
"env": {}
|
|
23
29
|
},
|
|
24
30
|
{
|
|
25
31
|
"name": "fuzz-runner",
|
|
26
32
|
"command": "python3",
|
|
27
|
-
"args": [
|
|
33
|
+
"args": [
|
|
34
|
+
"mcp/tools/fuzz_runner.py"
|
|
35
|
+
],
|
|
28
36
|
"description": "Run Foundry fuzz and invariant tests",
|
|
29
37
|
"env": {}
|
|
30
38
|
},
|
|
31
39
|
{
|
|
32
40
|
"name": "knowledge-base",
|
|
33
41
|
"command": "python3",
|
|
34
|
-
"args": [
|
|
42
|
+
"args": [
|
|
43
|
+
"mcp/tools/knowledge_base.py"
|
|
44
|
+
],
|
|
35
45
|
"description": "Query vulnerability knowledge base (ChromaDB RAG)",
|
|
36
46
|
"env": {
|
|
37
47
|
"KNOWLEDGE_PATH": "data/knowledge"
|
|
@@ -40,7 +50,9 @@
|
|
|
40
50
|
{
|
|
41
51
|
"name": "eas-attest",
|
|
42
52
|
"command": "python3",
|
|
43
|
-
"args": [
|
|
53
|
+
"args": [
|
|
54
|
+
"mcp/tools/eas_attest.py"
|
|
55
|
+
],
|
|
44
56
|
"description": "Submit EAS attestation on Sepolia testnet",
|
|
45
57
|
"env": {
|
|
46
58
|
"SEPOLIA_RPC_URL": "https://sepolia.drpc.org",
|
|
@@ -50,51 +62,74 @@
|
|
|
50
62
|
{
|
|
51
63
|
"name": "exploit-simulator",
|
|
52
64
|
"command": "python3",
|
|
53
|
-
"args": [
|
|
65
|
+
"args": [
|
|
66
|
+
"mcp/tools/exploit_simulator.py"
|
|
67
|
+
],
|
|
54
68
|
"description": "Simulate attack scenarios against smart contracts",
|
|
55
69
|
"env": {}
|
|
56
70
|
},
|
|
57
71
|
{
|
|
58
72
|
"name": "evidence-chain",
|
|
59
73
|
"command": "python3",
|
|
60
|
-
"args": [
|
|
74
|
+
"args": [
|
|
75
|
+
"mcp/tools/evidence_chain.py"
|
|
76
|
+
],
|
|
61
77
|
"description": "Merkle-based audit evidence chain for verifiable audit trails",
|
|
62
78
|
"env": {}
|
|
63
79
|
},
|
|
64
80
|
{
|
|
65
81
|
"name": "halmos-runner",
|
|
66
82
|
"command": "python3",
|
|
67
|
-
"args": [
|
|
83
|
+
"args": [
|
|
84
|
+
"mcp/tools/halmos_runner.py"
|
|
85
|
+
],
|
|
68
86
|
"description": "Symbolic execution and formal verification via Halmos",
|
|
69
87
|
"env": {}
|
|
70
88
|
},
|
|
71
89
|
{
|
|
72
90
|
"name": "protocol-scanner",
|
|
73
91
|
"command": "python3",
|
|
74
|
-
"args": [
|
|
92
|
+
"args": [
|
|
93
|
+
"mcp/tools/protocol_scanner.py"
|
|
94
|
+
],
|
|
75
95
|
"description": "Protocol-level dependency and interaction scanning",
|
|
76
96
|
"env": {}
|
|
77
97
|
},
|
|
78
98
|
{
|
|
79
99
|
"name": "repair-validator",
|
|
80
100
|
"command": "python3",
|
|
81
|
-
"args": [
|
|
101
|
+
"args": [
|
|
102
|
+
"mcp/tools/repair_validator.py"
|
|
103
|
+
],
|
|
82
104
|
"description": "Validate that vulnerability fixes are correct and complete",
|
|
83
105
|
"env": {}
|
|
84
106
|
},
|
|
85
107
|
{
|
|
86
108
|
"name": "incremental-auditor",
|
|
87
109
|
"command": "python3",
|
|
88
|
-
"args": [
|
|
110
|
+
"args": [
|
|
111
|
+
"mcp/tools/incremental_auditor.py"
|
|
112
|
+
],
|
|
89
113
|
"description": "Incremental audit for contract updates and diffs",
|
|
90
114
|
"env": {}
|
|
91
115
|
},
|
|
92
116
|
{
|
|
93
117
|
"name": "gev-analyzer",
|
|
94
118
|
"command": "python3",
|
|
95
|
-
"args": [
|
|
119
|
+
"args": [
|
|
120
|
+
"mcp/tools/gev_analyzer.py"
|
|
121
|
+
],
|
|
96
122
|
"description": "Governance, Economic, and Value analysis for DeFi protocols",
|
|
97
123
|
"env": {}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "nft-minter",
|
|
127
|
+
"command": "python3",
|
|
128
|
+
"args": [
|
|
129
|
+
"mcp/tools/nft_minter.py"
|
|
130
|
+
],
|
|
131
|
+
"description": "Mint ERC-1155 audit certificate NFTs on Sepolia/Base Sepolia",
|
|
132
|
+
"env": {}
|
|
98
133
|
}
|
|
99
134
|
]
|
|
100
|
-
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Athena NFT Certificate Minter — MCP Tool
|
|
4
|
+
Mints ERC-1155 audit certificate NFTs on Sepolia/Base Sepolia.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# Contract addresses
|
|
14
|
+
CONTRACTS = {
|
|
15
|
+
"sepolia": "0x3247d57d37bd1878479f03a077aba807649dbaf5",
|
|
16
|
+
"base-sepolia": "0xb8f167a84816b5b9373997337119a2186c6e3708",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Severity tiers: 1=S, 2=A, 3=B, 4=C
|
|
20
|
+
TIER_MAP = {"S": 1, "A": 2, "B": 3, "C": 4}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_tool_definitions() -> list:
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
"name": "mint_audit_nft",
|
|
27
|
+
"description": "Mint an ERC-1155 audit certificate NFT. Requires a valid EAS attestation UID from a prior eas_attest step. The NFT tier (S/A/B/C) is determined by the audit severity.",
|
|
28
|
+
"inputSchema": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"eas_uid": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "The EAS attestation UID (0x..., 66 chars) from the eas_attest step"
|
|
34
|
+
},
|
|
35
|
+
"tier": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"enum": ["S", "A", "B", "C"],
|
|
38
|
+
"description": "Audit certificate tier: S=critical findings, A=high, B=medium, C=low",
|
|
39
|
+
"default": "S"
|
|
40
|
+
},
|
|
41
|
+
"chain": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"enum": ["sepolia", "base-sepolia"],
|
|
44
|
+
"description": "Target chain (default: sepolia)",
|
|
45
|
+
"default": "sepolia"
|
|
46
|
+
},
|
|
47
|
+
"recipient": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Recipient address (0x...). Defaults to the deployer wallet."
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": ["eas_uid"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def mint_nft(eas_uid: str, tier: str = "S", chain: str = "sepolia", recipient: Optional[str] = None) -> dict:
|
|
59
|
+
"""Mint an audit certificate NFT."""
|
|
60
|
+
private_key = os.environ.get("SEPOLIA_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
|
61
|
+
rpc_url = os.environ.get("SEPOLIA_RPC_URL") or os.environ.get("RPC_URL")
|
|
62
|
+
|
|
63
|
+
if not private_key:
|
|
64
|
+
return {"error": "SEPOLIA_PRIVATE_KEY not set"}
|
|
65
|
+
if not rpc_url:
|
|
66
|
+
return {"error": "SEPOLIA_RPC_URL not set"}
|
|
67
|
+
|
|
68
|
+
contract = CONTRACTS.get(chain)
|
|
69
|
+
if not contract:
|
|
70
|
+
return {"error": f"Unsupported chain: {chain}"}
|
|
71
|
+
|
|
72
|
+
# Validate UID format
|
|
73
|
+
if not eas_uid.startswith("0x") or len(eas_uid) != 66:
|
|
74
|
+
return {"error": f"Invalid EAS UID format: {eas_uid} (expected 0x + 64 hex chars)"}
|
|
75
|
+
|
|
76
|
+
tier_num = TIER_MAP.get(tier.upper(), 1)
|
|
77
|
+
|
|
78
|
+
# Get recipient address
|
|
79
|
+
if not recipient:
|
|
80
|
+
try:
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
["cast", "wallet", "address", private_key],
|
|
83
|
+
capture_output=True, text=True, timeout=10
|
|
84
|
+
)
|
|
85
|
+
recipient = result.stdout.strip()
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return {"error": f"Failed to get wallet address: {e}"}
|
|
88
|
+
|
|
89
|
+
# Build cast command
|
|
90
|
+
# mintCertificate(address to, bytes32 uid, uint8 tier)
|
|
91
|
+
cmd = [
|
|
92
|
+
"cast", "send", contract,
|
|
93
|
+
"mintCertificate(address,bytes32,uint8)",
|
|
94
|
+
recipient, eas_uid, str(tier_num),
|
|
95
|
+
"--rpc-url", rpc_url,
|
|
96
|
+
"--private-key", private_key
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
101
|
+
|
|
102
|
+
if result.returncode != 0:
|
|
103
|
+
return {"error": f"cast send failed: {result.stderr}"}
|
|
104
|
+
|
|
105
|
+
# Extract tx hash from output
|
|
106
|
+
output = result.stdout + result.stderr
|
|
107
|
+
tx_hash = None
|
|
108
|
+
for line in output.split("\n"):
|
|
109
|
+
if "transactionHash" in line or "0x" in line:
|
|
110
|
+
import re
|
|
111
|
+
match = re.search(r'0x[0-9a-fA-F]{64}', line)
|
|
112
|
+
if match:
|
|
113
|
+
tx_hash = match.group(0)
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
# Get receipt
|
|
117
|
+
if tx_hash:
|
|
118
|
+
receipt_cmd = ["cast", "receipt", tx_hash, "--rpc-url", rpc_url]
|
|
119
|
+
receipt_result = subprocess.run(receipt_cmd, capture_output=True, text=True, timeout=30)
|
|
120
|
+
status = "success" if "status: 1" in receipt_result.stdout or "status: 0x1" in receipt_result.stdout else "failed"
|
|
121
|
+
|
|
122
|
+
explorer_base = "https://sepolia.etherscan.io" if chain == "sepolia" else "https://sepolia.basescan.org"
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"success": True,
|
|
126
|
+
"tx_hash": tx_hash,
|
|
127
|
+
"tx_url": f"{explorer_base}/tx/{tx_hash}",
|
|
128
|
+
"contract": contract,
|
|
129
|
+
"contract_url": f"{explorer_base}/address/{contract}",
|
|
130
|
+
"recipient": recipient,
|
|
131
|
+
"eas_uid": eas_uid,
|
|
132
|
+
"tier": tier.upper(),
|
|
133
|
+
"chain": chain,
|
|
134
|
+
"status": status,
|
|
135
|
+
"verify_command": f"cast call {contract} \"balanceOf(address,uint256)\" {recipient} 1 --rpc-url {rpc_url}"
|
|
136
|
+
}
|
|
137
|
+
else:
|
|
138
|
+
return {"error": f"Could not extract tx hash from output: {output}"}
|
|
139
|
+
|
|
140
|
+
except subprocess.TimeoutExpired:
|
|
141
|
+
return {"error": "Transaction timed out (120s)"}
|
|
142
|
+
except Exception as e:
|
|
143
|
+
return {"error": str(e)}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def handle_request(request: dict) -> dict:
|
|
147
|
+
"""Handle JSON-RPC request."""
|
|
148
|
+
method = request.get("method")
|
|
149
|
+
params = request.get("params", {})
|
|
150
|
+
req_id = request.get("id", 1)
|
|
151
|
+
|
|
152
|
+
if method == "initialize":
|
|
153
|
+
return {
|
|
154
|
+
"jsonrpc": "2.0",
|
|
155
|
+
"id": req_id,
|
|
156
|
+
"result": {
|
|
157
|
+
"protocolVersion": "2024-11-05",
|
|
158
|
+
"capabilities": {"tools": {"listChanged": False}},
|
|
159
|
+
"serverInfo": {"name": "athena-nft-minter", "version": "1.0.0"}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
elif method == "tools/list":
|
|
164
|
+
return {
|
|
165
|
+
"jsonrpc": "2.0",
|
|
166
|
+
"id": req_id,
|
|
167
|
+
"result": {"tools": build_tool_definitions()}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
elif method == "tools/call":
|
|
171
|
+
tool_name = params.get("name")
|
|
172
|
+
args = params.get("arguments", {})
|
|
173
|
+
|
|
174
|
+
if tool_name == "mint_audit_nft":
|
|
175
|
+
result = mint_nft(**args)
|
|
176
|
+
return {
|
|
177
|
+
"jsonrpc": "2.0",
|
|
178
|
+
"id": req_id,
|
|
179
|
+
"result": {
|
|
180
|
+
"content": [{"type": "text", "text": json.dumps(result, indent=2)}],
|
|
181
|
+
"isError": not result.get("success", False)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else:
|
|
185
|
+
return {
|
|
186
|
+
"jsonrpc": "2.0",
|
|
187
|
+
"id": req_id,
|
|
188
|
+
"error": {"code": -32601, "message": f"Unknown tool: {tool_name}"}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
elif method == "notifications/initialized":
|
|
192
|
+
return None # No response needed for notifications
|
|
193
|
+
|
|
194
|
+
else:
|
|
195
|
+
return {
|
|
196
|
+
"jsonrpc": "2.0",
|
|
197
|
+
"id": req_id,
|
|
198
|
+
"error": {"code": -32601, "message": f"Unknown method: {method}"}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def main():
|
|
203
|
+
"""Main entry point — reads JSON-RPC from stdin, writes to stdout."""
|
|
204
|
+
for line in sys.stdin:
|
|
205
|
+
line = line.strip()
|
|
206
|
+
if not line:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
request = json.loads(line)
|
|
211
|
+
response = handle_request(request)
|
|
212
|
+
if response is not None:
|
|
213
|
+
print(json.dumps(response), flush=True)
|
|
214
|
+
except json.JSONDecodeError:
|
|
215
|
+
error_response = {
|
|
216
|
+
"jsonrpc": "2.0",
|
|
217
|
+
"id": None,
|
|
218
|
+
"error": {"code": -32700, "message": "Parse error"}
|
|
219
|
+
}
|
|
220
|
+
print(json.dumps(error_response), flush=True)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|