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.
- package/README.md +477 -0
- package/install.js +327 -0
- package/mcp/servers.json +100 -0
- package/mcp/tools/README.md +64 -0
- package/mcp/tools/__init__.py +1 -0
- package/mcp/tools/aderyn_runner.py +226 -0
- package/mcp/tools/eas_attest.py +404 -0
- package/mcp/tools/evidence_chain.py +363 -0
- package/mcp/tools/exploit_simulator.py +545 -0
- package/mcp/tools/fuzz_runner.py +440 -0
- package/mcp/tools/gev_analyzer.py +362 -0
- package/mcp/tools/halmos_runner.py +408 -0
- package/mcp/tools/incremental_auditor.py +441 -0
- package/mcp/tools/knowledge_base.py +378 -0
- package/mcp/tools/poc_generator.py +479 -0
- package/mcp/tools/protocol_scanner.py +456 -0
- package/mcp/tools/repair_validator.py +421 -0
- package/mcp/tools/slither_runner.py +221 -0
- package/package.json +52 -0
- package/requirements.txt +20 -0
- package/skills/glm-audit-skill/SKILL.md +73 -0
- package/skills/glm-audit-skill/references/audit-agents/access-control-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/asymmetry-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/boundary-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/economic-security-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/execution-trace-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/first-principles-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/flow-gap-agent.md +38 -0
- package/skills/glm-audit-skill/references/audit-agents/invariant-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/math-precision-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/numerical-gap-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/periphery-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/shared-rules.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/trust-gap-agent.md +39 -0
- package/skills/glm-audit-skill/references/judging.md +45 -0
- package/skills/glm-audit-skill/references/report-formatting.md +22 -0
- package/skills/glm-audit-skill/references/senior-auditor-sop.md +34 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server: PoC Generator
|
|
4
|
+
Generates Foundry-based Proof of Concept exploit test contracts for Solidity vulnerabilities.
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
14
|
+
logger = logging.getLogger("poc_generator")
|
|
15
|
+
|
|
16
|
+
TOOL_NAME = "poc_generator"
|
|
17
|
+
TOOL_VERSION = "1.0.0"
|
|
18
|
+
|
|
19
|
+
# Template-based PoC generation patterns
|
|
20
|
+
VULNERABILITY_TEMPLATES = {
|
|
21
|
+
"reentrancy": {
|
|
22
|
+
"imports": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport \"forge-std/Test.sol\";",
|
|
23
|
+
"description": "Reentrancy attack exploiting external call before state update",
|
|
24
|
+
"template": """
|
|
25
|
+
/// @notice PoC: Reentrancy Attack
|
|
26
|
+
/// @dev This test demonstrates a reentrancy vulnerability where an attacker
|
|
27
|
+
/// can re-enter the vulnerable function during an external call.
|
|
28
|
+
contract ReentrancyAttacker {{
|
|
29
|
+
address public target;
|
|
30
|
+
uint256 public attackCount;
|
|
31
|
+
|
|
32
|
+
constructor(address _target) {{
|
|
33
|
+
target = _target;
|
|
34
|
+
}}
|
|
35
|
+
|
|
36
|
+
function attack() external payable {{
|
|
37
|
+
// Trigger the vulnerable function
|
|
38
|
+
ITarget(target).deposit{{value: msg.value}}();
|
|
39
|
+
ITarget(target).withdraw();
|
|
40
|
+
}}
|
|
41
|
+
|
|
42
|
+
receive() external payable {{
|
|
43
|
+
if (address(target).balance >= msg.value) {{
|
|
44
|
+
attackCount++;
|
|
45
|
+
if (attackCount < 10) {{
|
|
46
|
+
ITarget(target).withdraw();
|
|
47
|
+
}}
|
|
48
|
+
}}
|
|
49
|
+
}}
|
|
50
|
+
}}
|
|
51
|
+
|
|
52
|
+
interface ITarget {{
|
|
53
|
+
function deposit() external payable;
|
|
54
|
+
function withdraw() external;
|
|
55
|
+
}}
|
|
56
|
+
|
|
57
|
+
contract ReentrancyPoCTest is Test {{
|
|
58
|
+
Target victim;
|
|
59
|
+
ReentrancyAttacker attacker;
|
|
60
|
+
|
|
61
|
+
function setUp() public {{
|
|
62
|
+
victim = new Target();
|
|
63
|
+
attacker = new ReentrancyAttacker(address(victim));
|
|
64
|
+
vm.deal(address(attacker), 10 ether);
|
|
65
|
+
}}
|
|
66
|
+
|
|
67
|
+
function test_reentrancy_exploit() public {{
|
|
68
|
+
uint256 initialBalance = address(victim).balance;
|
|
69
|
+
|
|
70
|
+
vm.startPrank(address(attacker));
|
|
71
|
+
attacker.attack{{value: 1 ether}}();
|
|
72
|
+
vm.stopPrank();
|
|
73
|
+
|
|
74
|
+
// After reentrancy, the attacker should have drained more than deposited
|
|
75
|
+
uint256 stolen = address(attacker).balance - 1 ether;
|
|
76
|
+
assertGt(stolen, 0, "Reentrancy attack should steal funds");
|
|
77
|
+
|
|
78
|
+
emit log_named_uint("Funds stolen (wei)", stolen);
|
|
79
|
+
emit log_named_uint("Victim remaining balance", address(victim).balance);
|
|
80
|
+
}}
|
|
81
|
+
}}
|
|
82
|
+
"""
|
|
83
|
+
},
|
|
84
|
+
"integer_overflow": {
|
|
85
|
+
"imports": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport \"forge-std/Test.sol\";",
|
|
86
|
+
"description": "Integer overflow/underflow exploitation",
|
|
87
|
+
"template": """
|
|
88
|
+
/// @notice PoC: Integer Overflow/Underflow
|
|
89
|
+
/// @dev Pre-0.8.0 contracts may be vulnerable. Solidity 0.8+ has built-in checks.
|
|
90
|
+
contract IntegerOverflowPoCTest is Test {{
|
|
91
|
+
function setUp() public {{}}
|
|
92
|
+
|
|
93
|
+
function test_overflow_detection() public {{
|
|
94
|
+
// Demonstrate overflow behavior
|
|
95
|
+
uint256 maxVal = type(uint256).max;
|
|
96
|
+
|
|
97
|
+
// In Solidity 0.8+, this will revert
|
|
98
|
+
vm.expectRevert();
|
|
99
|
+
uint256 overflow = maxVal + 1;
|
|
100
|
+
|
|
101
|
+
// For pre-0.8.0 contracts, use unchecked
|
|
102
|
+
unchecked {{
|
|
103
|
+
uint256 overflowResult = maxVal + 1;
|
|
104
|
+
assertEq(overflowResult, 0, "Overflow should wrap to 0");
|
|
105
|
+
}}
|
|
106
|
+
}}
|
|
107
|
+
|
|
108
|
+
function test_underflow_detection() public {{
|
|
109
|
+
// Demonstrate underflow behavior
|
|
110
|
+
vm.expectRevert();
|
|
111
|
+
uint256 underflow = 0 - 1;
|
|
112
|
+
|
|
113
|
+
unchecked {{
|
|
114
|
+
uint256 underflowResult = 0 - 1;
|
|
115
|
+
assertEq(underflowResult, type(uint256).max, "Underflow should wrap to max");
|
|
116
|
+
}}
|
|
117
|
+
}}
|
|
118
|
+
}}
|
|
119
|
+
"""
|
|
120
|
+
},
|
|
121
|
+
"access_control": {
|
|
122
|
+
"imports": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport \"forge-std/Test.sol\";",
|
|
123
|
+
"description": "Access control bypass exploitation",
|
|
124
|
+
"template": """
|
|
125
|
+
/// @notice PoC: Access Control Vulnerability
|
|
126
|
+
/// @dev Demonstrates missing or broken access control on critical functions.
|
|
127
|
+
interface IVulnerableContract {{
|
|
128
|
+
function criticalFunction() external;
|
|
129
|
+
function withdrawFunds(address to) external;
|
|
130
|
+
function setOwner(address newOwner) external;
|
|
131
|
+
}}
|
|
132
|
+
|
|
133
|
+
contract AccessControlPoCTest is Test {{
|
|
134
|
+
IVulnerableContract target;
|
|
135
|
+
address attacker = address(0xBEEF);
|
|
136
|
+
|
|
137
|
+
function setUp() public {{
|
|
138
|
+
// Deploy target contract
|
|
139
|
+
// target = IVulnerableContract(address(deployedContract));
|
|
140
|
+
}}
|
|
141
|
+
|
|
142
|
+
function test_unauthorized_access() public {{
|
|
143
|
+
vm.startPrank(attacker);
|
|
144
|
+
|
|
145
|
+
// Attempt to call privileged function without authorization
|
|
146
|
+
// This should revert if access control is properly implemented
|
|
147
|
+
vm.expectRevert();
|
|
148
|
+
target.criticalFunction();
|
|
149
|
+
|
|
150
|
+
vm.stopPrank();
|
|
151
|
+
}}
|
|
152
|
+
|
|
153
|
+
function test_unauthorized_withdraw() public {{
|
|
154
|
+
vm.startPrank(attacker);
|
|
155
|
+
|
|
156
|
+
// Attempt to withdraw funds
|
|
157
|
+
vm.expectRevert();
|
|
158
|
+
target.withdrawFunds(attacker);
|
|
159
|
+
|
|
160
|
+
vm.stopPrank();
|
|
161
|
+
}}
|
|
162
|
+
|
|
163
|
+
function test_owner_takeover() public {{
|
|
164
|
+
vm.startPrank(attacker);
|
|
165
|
+
|
|
166
|
+
// Attempt to change owner
|
|
167
|
+
vm.expectRevert();
|
|
168
|
+
target.setOwner(attacker);
|
|
169
|
+
|
|
170
|
+
vm.stopPrank();
|
|
171
|
+
}}
|
|
172
|
+
}}
|
|
173
|
+
"""
|
|
174
|
+
},
|
|
175
|
+
"flash_loan": {
|
|
176
|
+
"imports": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport \"forge-std/Test.sol\";",
|
|
177
|
+
"description": "Flash loan attack vector",
|
|
178
|
+
"template": """
|
|
179
|
+
/// @notice PoC: Flash Loan Attack
|
|
180
|
+
/// @dev Demonstrates a flash loan attack pattern that can be used to
|
|
181
|
+
/// manipulate prices or drain liquidity pools.
|
|
182
|
+
|
|
183
|
+
interface IFlashLoanProvider {{
|
|
184
|
+
function flashLoan(uint256 amount, address receiver, bytes calldata data) external;
|
|
185
|
+
function getReserve() external view returns (uint256);
|
|
186
|
+
}}
|
|
187
|
+
|
|
188
|
+
interface IVulnerableProtocol {{
|
|
189
|
+
function deposit() external payable;
|
|
190
|
+
function withdraw(uint256 amount) external;
|
|
191
|
+
function getPrice() external view returns (uint256);
|
|
192
|
+
function swap(address tokenIn, uint256 amountIn) external returns (uint256);
|
|
193
|
+
}}
|
|
194
|
+
|
|
195
|
+
contract FlashLoanAttacker {{
|
|
196
|
+
IFlashLoanProvider lender;
|
|
197
|
+
IVulnerableProtocol target;
|
|
198
|
+
uint256 profit;
|
|
199
|
+
|
|
200
|
+
constructor(address _lender, address _target) {{
|
|
201
|
+
lender = IFlashLoanProvider(_lender);
|
|
202
|
+
target = IVulnerableProtocol(_target);
|
|
203
|
+
}}
|
|
204
|
+
|
|
205
|
+
function executeAttack(uint256 flashAmount) external {{
|
|
206
|
+
lender.flashLoan(flashAmount, address(this), "");
|
|
207
|
+
}}
|
|
208
|
+
|
|
209
|
+
function executeOperation(uint256 amount, bytes calldata) external {{
|
|
210
|
+
// Step 1: Use flash loan to manipulate price
|
|
211
|
+
// Step 2: Exploit the manipulated price
|
|
212
|
+
// Step 3: Repay loan and keep profit
|
|
213
|
+
|
|
214
|
+
// Example: price manipulation
|
|
215
|
+
target.swap(address(this), amount);
|
|
216
|
+
|
|
217
|
+
// Repay flash loan (amount + fee)
|
|
218
|
+
uint256 fee = amount * 9 / 10000; // 0.09% fee
|
|
219
|
+
IERC20(address(lender)).transfer(address(lender), amount + fee);
|
|
220
|
+
|
|
221
|
+
profit = amount - fee;
|
|
222
|
+
}}
|
|
223
|
+
}}
|
|
224
|
+
|
|
225
|
+
interface IERC20 {{
|
|
226
|
+
function transfer(address to, uint256 amount) external returns (bool);
|
|
227
|
+
}}
|
|
228
|
+
|
|
229
|
+
contract FlashLoanPoCTest is Test {{
|
|
230
|
+
function test_flash_loan_attack() public {{
|
|
231
|
+
// Setup: deploy or fork the target protocol
|
|
232
|
+
// FlashLoanAttacker attacker = new FlashLoanAttacker(lender, target);
|
|
233
|
+
// attacker.executeAttack(1000000e18);
|
|
234
|
+
// assertGt(attacker.profit(), 0);
|
|
235
|
+
}}
|
|
236
|
+
}}
|
|
237
|
+
"""
|
|
238
|
+
},
|
|
239
|
+
"default": {
|
|
240
|
+
"imports": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport \"forge-std/Test.sol\";",
|
|
241
|
+
"description": "Generic vulnerability PoC template",
|
|
242
|
+
"template": """
|
|
243
|
+
/// @notice PoC: {vuln_type}
|
|
244
|
+
/// @dev {vuln_description}
|
|
245
|
+
///
|
|
246
|
+
/// Vulnerability Details:
|
|
247
|
+
/// {detailed_description}
|
|
248
|
+
contract PoCTest is Test {{
|
|
249
|
+
|
|
250
|
+
function setUp() public {{
|
|
251
|
+
// Deploy or setup the target contract
|
|
252
|
+
}}
|
|
253
|
+
|
|
254
|
+
function test_exploit() public {{
|
|
255
|
+
// Step 1: Setup attack prerequisites
|
|
256
|
+
// Step 2: Execute the exploit
|
|
257
|
+
// Step 3: Verify the exploit succeeded
|
|
258
|
+
|
|
259
|
+
// TODO: Implement specific exploit logic based on vulnerability analysis
|
|
260
|
+
|
|
261
|
+
emit log("PoC test template - customize based on specific vulnerability");
|
|
262
|
+
}}
|
|
263
|
+
}}
|
|
264
|
+
"""
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def generate_template_poc(contract_code: str, vulnerability: dict) -> dict:
|
|
270
|
+
"""Generate a template-based PoC for the given vulnerability."""
|
|
271
|
+
vuln_type = vulnerability.get("type", "").lower().replace(" ", "_").replace("-", "_")
|
|
272
|
+
vuln_description = vulnerability.get("description", "No description provided")
|
|
273
|
+
|
|
274
|
+
# Match to known templates
|
|
275
|
+
matched_template = None
|
|
276
|
+
for key in VULNERABILITY_TEMPLATES:
|
|
277
|
+
if key in vuln_type or vuln_type in key:
|
|
278
|
+
matched_template = VULNERABILITY_TEMPLATES[key]
|
|
279
|
+
break
|
|
280
|
+
|
|
281
|
+
if not matched_template:
|
|
282
|
+
matched_template = VULNERABILITY_TEMPLATES["default"]
|
|
283
|
+
|
|
284
|
+
# Build the PoC contract
|
|
285
|
+
poc_code = matched_template["imports"] + "\n\n"
|
|
286
|
+
|
|
287
|
+
# Add contract code reference as comment
|
|
288
|
+
poc_code += "/*\n"
|
|
289
|
+
poc_code += " * Original Contract (reference):\n"
|
|
290
|
+
# Limit contract code in comments to avoid huge PoC files
|
|
291
|
+
code_lines = contract_code.split("\n")[:50]
|
|
292
|
+
for line in code_lines:
|
|
293
|
+
poc_code += f" * {line}\n"
|
|
294
|
+
if len(contract_code.split("\n")) > 50:
|
|
295
|
+
poc_code += f" * ... ({len(contract_code.split(chr(10)))} lines total)\n"
|
|
296
|
+
poc_code += " */\n\n"
|
|
297
|
+
|
|
298
|
+
# Add the PoC template with formatting
|
|
299
|
+
if matched_template == VULNERABILITY_TEMPLATES["default"]:
|
|
300
|
+
poc_code += matched_template["template"].format(
|
|
301
|
+
vuln_type=vulnerability.get("type", "Unknown"),
|
|
302
|
+
vuln_description=vuln_description,
|
|
303
|
+
detailed_description=vuln_description
|
|
304
|
+
)
|
|
305
|
+
else:
|
|
306
|
+
poc_code += matched_template["template"]
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
"success": True,
|
|
310
|
+
"vulnerability_type": vulnerability.get("type", "Unknown"),
|
|
311
|
+
"poc_code": poc_code,
|
|
312
|
+
"template_used": vuln_type if any(k in vuln_type for k in VULNERABILITY_TEMPLATES) else "default",
|
|
313
|
+
"note": "This is a template-based PoC. Customize the exploit logic based on the specific contract under audit."
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
async def generate_with_llm(contract_code: str, vulnerability: dict) -> dict:
|
|
318
|
+
"""Attempt LLM-based PoC generation (optional, falls back to template)."""
|
|
319
|
+
# Check for OpenAI-compatible API endpoint
|
|
320
|
+
api_base = os.environ.get("OPENAI_API_BASE", os.environ.get("LLM_API_BASE", ""))
|
|
321
|
+
api_key = os.environ.get("OPENAI_API_KEY", os.environ.get("LLM_API_KEY", ""))
|
|
322
|
+
model = os.environ.get("LLM_MODEL", "gpt-4")
|
|
323
|
+
|
|
324
|
+
if not api_base or not api_key:
|
|
325
|
+
logger.info("No LLM API configured, falling back to template-based generation")
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
import aiohttp
|
|
330
|
+
|
|
331
|
+
prompt = f"""You are a smart contract security expert. Generate a Foundry test contract that demonstrates the following vulnerability.
|
|
332
|
+
|
|
333
|
+
VULNERABILITY:
|
|
334
|
+
Type: {vulnerability.get('type', 'Unknown')}
|
|
335
|
+
Description: {vulnerability.get('description', '')}
|
|
336
|
+
|
|
337
|
+
TARGET CONTRACT CODE:
|
|
338
|
+
```solidity
|
|
339
|
+
{contract_code[:4000]}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Requirements:
|
|
343
|
+
1. Use pragma solidity ^0.8.20
|
|
344
|
+
2. Import forge-std/Test.sol
|
|
345
|
+
3. Include a setUp() function
|
|
346
|
+
4. Include a test_exploit() function that demonstrates the vulnerability
|
|
347
|
+
5. Use vm.startPrank, vm.deal, etc. for test setup
|
|
348
|
+
6. Add detailed comments explaining each step of the exploit
|
|
349
|
+
7. The test should fail if the vulnerability is fixed
|
|
350
|
+
|
|
351
|
+
Return ONLY the Solidity code, no explanation."""
|
|
352
|
+
|
|
353
|
+
async with aiohttp.ClientSession() as session:
|
|
354
|
+
async with session.post(
|
|
355
|
+
f"{api_base}/v1/chat/completions",
|
|
356
|
+
headers={
|
|
357
|
+
"Authorization": f"Bearer {api_key}",
|
|
358
|
+
"Content-Type": "application/json"
|
|
359
|
+
},
|
|
360
|
+
json={
|
|
361
|
+
"model": model,
|
|
362
|
+
"messages": [
|
|
363
|
+
{"role": "system", "content": "You are a Solidity security expert."},
|
|
364
|
+
{"role": "user", "content": prompt}
|
|
365
|
+
],
|
|
366
|
+
"temperature": 0.3,
|
|
367
|
+
"max_tokens": 4096
|
|
368
|
+
}
|
|
369
|
+
) as resp:
|
|
370
|
+
if resp.status == 200:
|
|
371
|
+
data = await resp.json()
|
|
372
|
+
poc_code = data["choices"][0]["message"]["content"]
|
|
373
|
+
|
|
374
|
+
# Extract code from markdown blocks if present
|
|
375
|
+
code_match = re.search(r'```(?:solidity)?\n(.*?)```', poc_code, re.DOTALL)
|
|
376
|
+
if code_match:
|
|
377
|
+
poc_code = code_match.group(1)
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
"success": True,
|
|
381
|
+
"vulnerability_type": vulnerability.get("type", "Unknown"),
|
|
382
|
+
"poc_code": poc_code,
|
|
383
|
+
"template_used": "llm-generated",
|
|
384
|
+
"model": model,
|
|
385
|
+
"note": "This PoC was generated by an LLM. Review and test before use."
|
|
386
|
+
}
|
|
387
|
+
except Exception as e:
|
|
388
|
+
logger.warning(f"LLM generation failed: {e}, falling back to template")
|
|
389
|
+
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
async def execute_tool(tool_name: str, arguments: dict) -> dict:
|
|
394
|
+
if tool_name == "generate_poc":
|
|
395
|
+
contract_code = arguments.get("contract_code", "")
|
|
396
|
+
vulnerability = arguments.get("vulnerability", {})
|
|
397
|
+
|
|
398
|
+
if not contract_code:
|
|
399
|
+
return {"success": False, "error": "contract_code is required"}
|
|
400
|
+
if not vulnerability:
|
|
401
|
+
return {"success": False, "error": "vulnerability object is required"}
|
|
402
|
+
|
|
403
|
+
# Try LLM generation first, fall back to template
|
|
404
|
+
llm_result = await generate_with_llm(contract_code, vulnerability)
|
|
405
|
+
if llm_result:
|
|
406
|
+
return llm_result
|
|
407
|
+
|
|
408
|
+
return generate_template_poc(contract_code, vulnerability)
|
|
409
|
+
|
|
410
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
async def handle_request(request: dict) -> dict:
|
|
414
|
+
method = request.get("method")
|
|
415
|
+
params = request.get("params", {})
|
|
416
|
+
|
|
417
|
+
if method == "initialize":
|
|
418
|
+
return {
|
|
419
|
+
"protocolVersion": "2024-11-05",
|
|
420
|
+
"capabilities": {"tools": {}},
|
|
421
|
+
"serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
|
|
422
|
+
}
|
|
423
|
+
elif method == "tools/list":
|
|
424
|
+
return {"tools": build_tool_definitions()}
|
|
425
|
+
elif method == "tools/call":
|
|
426
|
+
tool_name = params.get("name")
|
|
427
|
+
arguments = params.get("arguments", {})
|
|
428
|
+
result = await execute_tool(tool_name, arguments)
|
|
429
|
+
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
|
430
|
+
elif method == "ping":
|
|
431
|
+
return {}
|
|
432
|
+
return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
async def main():
|
|
436
|
+
reader = asyncio.StreamReader()
|
|
437
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
438
|
+
loop = asyncio.get_event_loop()
|
|
439
|
+
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
440
|
+
|
|
441
|
+
logger.info(f"{TOOL_NAME} MCP server started")
|
|
442
|
+
|
|
443
|
+
while True:
|
|
444
|
+
line = await reader.readline()
|
|
445
|
+
if not line:
|
|
446
|
+
break
|
|
447
|
+
|
|
448
|
+
line_str = line.decode("utf-8").strip()
|
|
449
|
+
if not line_str:
|
|
450
|
+
continue
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
request = json.loads(line_str)
|
|
454
|
+
response = await handle_request(request)
|
|
455
|
+
response["jsonrpc"] = "2.0"
|
|
456
|
+
response["id"] = request.get("id")
|
|
457
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
458
|
+
sys.stdout.flush()
|
|
459
|
+
except json.JSONDecodeError:
|
|
460
|
+
error_resp = {
|
|
461
|
+
"jsonrpc": "2.0",
|
|
462
|
+
"id": None,
|
|
463
|
+
"error": {"code": -32700, "message": "Parse error"}
|
|
464
|
+
}
|
|
465
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
466
|
+
sys.stdout.flush()
|
|
467
|
+
except Exception as e:
|
|
468
|
+
logger.exception("Error handling request")
|
|
469
|
+
error_resp = {
|
|
470
|
+
"jsonrpc": "2.0",
|
|
471
|
+
"id": request.get("id") if "request" in dir() else None,
|
|
472
|
+
"error": {"code": -32603, "message": f"Internal error: {str(e)}"}
|
|
473
|
+
}
|
|
474
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
475
|
+
sys.stdout.flush()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
if __name__ == "__main__":
|
|
479
|
+
asyncio.run(main())
|