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,456 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server: Protocol Scanner
|
|
4
|
+
Auto-discovers all Solidity files in a directory, analyzes call graphs,
|
|
5
|
+
and identifies cross-contract attack surfaces.
|
|
6
|
+
"""
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
15
|
+
logger = logging.getLogger("protocol_scanner")
|
|
16
|
+
|
|
17
|
+
TOOL_NAME = "protocol_scanner"
|
|
18
|
+
TOOL_VERSION = "1.0.0"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_tool_definitions() -> list:
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
"name": "scan_protocol",
|
|
25
|
+
"description": "Scan a directory of Solidity files to discover contracts and analyze their relationships",
|
|
26
|
+
"inputSchema": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"directory": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Path to directory containing .sol files"
|
|
32
|
+
},
|
|
33
|
+
"exclude_patterns": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": {"type": "string"},
|
|
36
|
+
"description": "Patterns to exclude (e.g., ['test/', 'mock/'])",
|
|
37
|
+
"default": ["test/", "mock/", "node_modules/"]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["directory"]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"name": "analyze_call_graph",
|
|
45
|
+
"description": "Analyze contract call relationships and identify attack surfaces",
|
|
46
|
+
"inputSchema": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"contracts": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {"type": "object"},
|
|
52
|
+
"description": "Contract data from scan_protocol"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"required": ["contracts"]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "find_cross_contract_vulnerabilities",
|
|
60
|
+
"description": "Identify potential cross-contract vulnerabilities",
|
|
61
|
+
"inputSchema": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"contracts": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {"type": "object"},
|
|
67
|
+
"description": "Contract data from scan_protocol"
|
|
68
|
+
},
|
|
69
|
+
"call_graph": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"description": "Call graph from analyze_call_graph"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"required": ["contracts"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def scan_protocol(directory: str, exclude_patterns: list = None) -> dict:
|
|
81
|
+
"""Scan directory for Solidity files and analyze contracts."""
|
|
82
|
+
if exclude_patterns is None:
|
|
83
|
+
exclude_patterns = ["test/", "mock/", "node_modules/", ".git/"]
|
|
84
|
+
|
|
85
|
+
directory = os.path.abspath(directory)
|
|
86
|
+
|
|
87
|
+
if not os.path.isdir(directory):
|
|
88
|
+
return {"success": False, "error": f"Directory not found: {directory}"}
|
|
89
|
+
|
|
90
|
+
# Find all .sol files
|
|
91
|
+
sol_files = []
|
|
92
|
+
for root, dirs, files in os.walk(directory):
|
|
93
|
+
# Skip excluded directories
|
|
94
|
+
skip = False
|
|
95
|
+
for pattern in exclude_patterns:
|
|
96
|
+
if pattern in root:
|
|
97
|
+
skip = True
|
|
98
|
+
break
|
|
99
|
+
if skip:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
for file in files:
|
|
103
|
+
if file.endswith(".sol"):
|
|
104
|
+
sol_files.append(os.path.join(root, file))
|
|
105
|
+
|
|
106
|
+
# Analyze each file
|
|
107
|
+
contracts = []
|
|
108
|
+
for sol_file in sol_files:
|
|
109
|
+
try:
|
|
110
|
+
with open(sol_file, 'r') as f:
|
|
111
|
+
content = f.read()
|
|
112
|
+
|
|
113
|
+
# Extract contract info
|
|
114
|
+
file_contracts = _extract_contracts(content, sol_file)
|
|
115
|
+
contracts.extend(file_contracts)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.warning(f"Failed to read {sol_file}: {e}")
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"success": True,
|
|
121
|
+
"directory": directory,
|
|
122
|
+
"total_files": len(sol_files),
|
|
123
|
+
"total_contracts": len(contracts),
|
|
124
|
+
"contracts": contracts,
|
|
125
|
+
"files": [os.path.relpath(f, directory) for f in sol_files]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _extract_contracts(content: str, filepath: str) -> list:
|
|
130
|
+
"""Extract contract information from Solidity source."""
|
|
131
|
+
contracts = []
|
|
132
|
+
|
|
133
|
+
# Find contract declarations
|
|
134
|
+
contract_pattern = r'contract\s+(\w+)(?:\s+is\s+([^{]+))?\s*\{'
|
|
135
|
+
for match in re.finditer(contract_pattern, content):
|
|
136
|
+
name = match.group(1)
|
|
137
|
+
inherits = match.group(2).strip() if match.group(2) else ""
|
|
138
|
+
inherit_list = [i.strip() for i in inherits.split(",")] if inherits else []
|
|
139
|
+
|
|
140
|
+
# Find contract body (simplified - doesn't handle nested braces perfectly)
|
|
141
|
+
start_pos = match.end()
|
|
142
|
+
brace_count = 1
|
|
143
|
+
pos = start_pos
|
|
144
|
+
while pos < len(content) and brace_count > 0:
|
|
145
|
+
if content[pos] == '{':
|
|
146
|
+
brace_count += 1
|
|
147
|
+
elif content[pos] == '}':
|
|
148
|
+
brace_count -= 1
|
|
149
|
+
pos += 1
|
|
150
|
+
body = content[start_pos:pos-1]
|
|
151
|
+
|
|
152
|
+
# Extract functions
|
|
153
|
+
functions = _extract_functions(body)
|
|
154
|
+
|
|
155
|
+
# Extract imports
|
|
156
|
+
imports = re.findall(r'import\s+.*?from\s+["\'](.+?)["\']', content)
|
|
157
|
+
import_pattern = r'import\s+["\'](.+?)["\']'
|
|
158
|
+
imports.extend(re.findall(import_pattern, content))
|
|
159
|
+
|
|
160
|
+
# Extract state variables
|
|
161
|
+
state_vars = _extract_state_variables(body)
|
|
162
|
+
|
|
163
|
+
# Check for external calls
|
|
164
|
+
external_calls = re.findall(r'\.call\{|\.call\.value\(|\.delegatecall\(|\.staticcall\(', body)
|
|
165
|
+
|
|
166
|
+
# Check for modifiers
|
|
167
|
+
modifiers = re.findall(r'modifier\s+(\w+)', body)
|
|
168
|
+
|
|
169
|
+
contracts.append({
|
|
170
|
+
"name": name,
|
|
171
|
+
"file": filepath,
|
|
172
|
+
"inherits": inherit_list,
|
|
173
|
+
"functions": functions,
|
|
174
|
+
"state_variables": state_vars,
|
|
175
|
+
"external_calls": len(external_calls),
|
|
176
|
+
"modifiers": modifiers,
|
|
177
|
+
"imports": imports
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
return contracts
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _extract_functions(body: str) -> list:
|
|
184
|
+
"""Extract function information from contract body."""
|
|
185
|
+
functions = []
|
|
186
|
+
|
|
187
|
+
# Function pattern
|
|
188
|
+
func_pattern = r'function\s+(\w+)\s*\(([^)]*)\)(?:\s+(?:public|private|internal|external|view|pure|payable|nonpayable|virtual|override))*\s*(?:returns\s*\(([^)]*)\))?\s*\{?'
|
|
189
|
+
|
|
190
|
+
for match in re.finditer(func_pattern, body):
|
|
191
|
+
name = match.group(1)
|
|
192
|
+
params = match.group(2)
|
|
193
|
+
returns = match.group(3) or ""
|
|
194
|
+
|
|
195
|
+
# Determine visibility
|
|
196
|
+
func_text = match.group(0)
|
|
197
|
+
visibility = "internal"
|
|
198
|
+
if "public" in func_text:
|
|
199
|
+
visibility = "public"
|
|
200
|
+
elif "external" in func_text:
|
|
201
|
+
visibility = "external"
|
|
202
|
+
elif "private" in func_text:
|
|
203
|
+
visibility = "private"
|
|
204
|
+
|
|
205
|
+
# Check for payable
|
|
206
|
+
payable = "payable" in func_text
|
|
207
|
+
|
|
208
|
+
# Check for modifiers (simplified)
|
|
209
|
+
modifiers = []
|
|
210
|
+
|
|
211
|
+
functions.append({
|
|
212
|
+
"name": name,
|
|
213
|
+
"visibility": visibility,
|
|
214
|
+
"payable": payable,
|
|
215
|
+
"params": params.strip(),
|
|
216
|
+
"returns": returns.strip(),
|
|
217
|
+
"modifiers": modifiers
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return functions
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _extract_state_variables(body: str) -> list:
|
|
224
|
+
"""Extract state variable declarations."""
|
|
225
|
+
variables = []
|
|
226
|
+
|
|
227
|
+
# Simple pattern for state variables
|
|
228
|
+
var_pattern = r'(?:uint256|uint|address|bool|string|bytes32|mapping)\s+(?:public|private|internal)?\s*(\w+)'
|
|
229
|
+
|
|
230
|
+
for match in re.finditer(var_pattern, body):
|
|
231
|
+
variables.append(match.group(1))
|
|
232
|
+
|
|
233
|
+
return variables
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def analyze_call_graph(contracts: list) -> dict:
|
|
237
|
+
"""Analyze call relationships between contracts."""
|
|
238
|
+
# Build contract name to info mapping
|
|
239
|
+
contract_map = {c["name"]: c for c in contracts}
|
|
240
|
+
|
|
241
|
+
# Build call graph
|
|
242
|
+
call_graph = {}
|
|
243
|
+
cross_calls = []
|
|
244
|
+
|
|
245
|
+
for contract in contracts:
|
|
246
|
+
contract_calls = []
|
|
247
|
+
|
|
248
|
+
# Check for calls to other contracts
|
|
249
|
+
for func in contract.get("functions", []):
|
|
250
|
+
# Look for external contract calls in function params/returns
|
|
251
|
+
params = func.get("params", "")
|
|
252
|
+
for other_name in contract_map:
|
|
253
|
+
if other_name != contract["name"]:
|
|
254
|
+
if other_name.lower() in params.lower():
|
|
255
|
+
contract_calls.append({
|
|
256
|
+
"caller": contract["name"],
|
|
257
|
+
"callee": other_name,
|
|
258
|
+
"function": func["name"],
|
|
259
|
+
"type": "parameter"
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
# Check imports for dependencies
|
|
263
|
+
for imp in contract.get("imports", []):
|
|
264
|
+
for other in contracts:
|
|
265
|
+
if other["name"] in imp or other["file"] in imp:
|
|
266
|
+
contract_calls.append({
|
|
267
|
+
"caller": contract["name"],
|
|
268
|
+
"callee": other["name"],
|
|
269
|
+
"function": "import",
|
|
270
|
+
"type": "dependency"
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
if contract_calls:
|
|
274
|
+
call_graph[contract["name"]] = contract_calls
|
|
275
|
+
cross_calls.extend(contract_calls)
|
|
276
|
+
|
|
277
|
+
# Identify attack surface
|
|
278
|
+
attack_surface = []
|
|
279
|
+
for contract in contracts:
|
|
280
|
+
if contract.get("external_calls", 0) > 0:
|
|
281
|
+
attack_surface.append({
|
|
282
|
+
"contract": contract["name"],
|
|
283
|
+
"external_calls": contract["external_calls"],
|
|
284
|
+
"risk": "high" if contract["external_calls"] > 2 else "medium"
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"success": True,
|
|
289
|
+
"call_graph": call_graph,
|
|
290
|
+
"cross_calls": cross_calls,
|
|
291
|
+
"attack_surface": attack_surface,
|
|
292
|
+
"total_cross_calls": len(cross_calls)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
async def find_cross_contract_vulnerabilities(contracts: list, call_graph: dict = None) -> dict:
|
|
297
|
+
"""Identify potential cross-contract vulnerabilities."""
|
|
298
|
+
vulnerabilities = []
|
|
299
|
+
|
|
300
|
+
# Check for oracle dependency
|
|
301
|
+
for contract in contracts:
|
|
302
|
+
for func in contract.get("functions", []):
|
|
303
|
+
if "price" in func["name"].lower() or "oracle" in func["name"].lower():
|
|
304
|
+
vulnerabilities.append({
|
|
305
|
+
"type": "oracle_dependency",
|
|
306
|
+
"contract": contract["name"],
|
|
307
|
+
"function": func["name"],
|
|
308
|
+
"risk": "high",
|
|
309
|
+
"description": f"{contract['name']}.{func['name']} depends on external oracle"
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
# Check for reentrancy vectors
|
|
313
|
+
for contract in contracts:
|
|
314
|
+
if contract.get("external_calls", 0) > 0:
|
|
315
|
+
# Check if state is updated after external call
|
|
316
|
+
has_state_update = False
|
|
317
|
+
for var in contract.get("state_variables", []):
|
|
318
|
+
if "balance" in var.lower() or "amount" in var.lower():
|
|
319
|
+
has_state_update = True
|
|
320
|
+
break
|
|
321
|
+
|
|
322
|
+
if has_state_update:
|
|
323
|
+
vulnerabilities.append({
|
|
324
|
+
"type": "potential_reentrancy",
|
|
325
|
+
"contract": contract["name"],
|
|
326
|
+
"risk": "high",
|
|
327
|
+
"description": f"{contract['name']} has external calls and state variables"
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
# Check for access control issues
|
|
331
|
+
for contract in contracts:
|
|
332
|
+
public_funcs = [f for f in contract.get("functions", []) if f["visibility"] == "public"]
|
|
333
|
+
admin_funcs = [f for f in public_funcs if "admin" in f["name"].lower() or "owner" in f["name"].lower()]
|
|
334
|
+
|
|
335
|
+
if admin_funcs:
|
|
336
|
+
# Check for onlyOwner modifier
|
|
337
|
+
has_modifier = any("onlyOwner" in str(f.get("modifiers", [])) or "onlyAdmin" in str(f.get("modifiers", [])) for f in admin_funcs)
|
|
338
|
+
if not has_modifier:
|
|
339
|
+
vulnerabilities.append({
|
|
340
|
+
"type": "missing_access_control",
|
|
341
|
+
"contract": contract["name"],
|
|
342
|
+
"functions": [f["name"] for f in admin_funcs],
|
|
343
|
+
"risk": "critical",
|
|
344
|
+
"description": f"Admin functions without access control in {contract['name']}"
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
# Check for cross-contract call chains
|
|
348
|
+
if call_graph:
|
|
349
|
+
for caller, calls in call_graph.items():
|
|
350
|
+
for call in calls:
|
|
351
|
+
if call.get("type") == "parameter":
|
|
352
|
+
vulnerabilities.append({
|
|
353
|
+
"type": "cross_contract_call",
|
|
354
|
+
"caller": caller,
|
|
355
|
+
"callee": call.get("callee"),
|
|
356
|
+
"function": call.get("function"),
|
|
357
|
+
"risk": "medium",
|
|
358
|
+
"description": f"Cross-contract call from {caller} to {call.get('callee')}"
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
"success": True,
|
|
363
|
+
"vulnerabilities": vulnerabilities,
|
|
364
|
+
"total_vulnerabilities": len(vulnerabilities),
|
|
365
|
+
"by_risk": {
|
|
366
|
+
"critical": len([v for v in vulnerabilities if v["risk"] == "critical"]),
|
|
367
|
+
"high": len([v for v in vulnerabilities if v["risk"] == "high"]),
|
|
368
|
+
"medium": len([v for v in vulnerabilities if v["risk"] == "medium"]),
|
|
369
|
+
"low": len([v for v in vulnerabilities if v["risk"] == "low"])
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
async def execute_tool(tool_name: str, arguments: dict) -> dict:
|
|
375
|
+
if tool_name == "scan_protocol":
|
|
376
|
+
return await scan_protocol(
|
|
377
|
+
arguments.get("directory", ""),
|
|
378
|
+
arguments.get("exclude_patterns")
|
|
379
|
+
)
|
|
380
|
+
elif tool_name == "analyze_call_graph":
|
|
381
|
+
return await analyze_call_graph(arguments.get("contracts", []))
|
|
382
|
+
elif tool_name == "find_cross_contract_vulnerabilities":
|
|
383
|
+
return await find_cross_contract_vulnerabilities(
|
|
384
|
+
arguments.get("contracts", []),
|
|
385
|
+
arguments.get("call_graph")
|
|
386
|
+
)
|
|
387
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
async def handle_request(request: dict) -> dict:
|
|
391
|
+
method = request.get("method")
|
|
392
|
+
params = request.get("params", {})
|
|
393
|
+
|
|
394
|
+
if method == "initialize":
|
|
395
|
+
return {
|
|
396
|
+
"protocolVersion": "2024-11-05",
|
|
397
|
+
"capabilities": {"tools": {}},
|
|
398
|
+
"serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
|
|
399
|
+
}
|
|
400
|
+
elif method == "tools/list":
|
|
401
|
+
return {"tools": build_tool_definitions()}
|
|
402
|
+
elif method == "tools/call":
|
|
403
|
+
tool_name = params.get("name")
|
|
404
|
+
arguments = params.get("arguments", {})
|
|
405
|
+
result = await execute_tool(tool_name, arguments)
|
|
406
|
+
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
|
407
|
+
elif method == "ping":
|
|
408
|
+
return {}
|
|
409
|
+
return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def main():
|
|
413
|
+
reader = asyncio.StreamReader()
|
|
414
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
415
|
+
loop = asyncio.get_event_loop()
|
|
416
|
+
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
417
|
+
|
|
418
|
+
logger.info(f"{TOOL_NAME} MCP server started")
|
|
419
|
+
|
|
420
|
+
while True:
|
|
421
|
+
line = await reader.readline()
|
|
422
|
+
if not line:
|
|
423
|
+
break
|
|
424
|
+
|
|
425
|
+
line_str = line.decode("utf-8").strip()
|
|
426
|
+
if not line_str:
|
|
427
|
+
continue
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
request = json.loads(line_str)
|
|
431
|
+
response = await handle_request(request)
|
|
432
|
+
response["jsonrpc"] = "2.0"
|
|
433
|
+
response["id"] = request.get("id")
|
|
434
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
435
|
+
sys.stdout.flush()
|
|
436
|
+
except json.JSONDecodeError:
|
|
437
|
+
error_resp = {
|
|
438
|
+
"jsonrpc": "2.0",
|
|
439
|
+
"id": None,
|
|
440
|
+
"error": {"code": -32700, "message": "Parse error"}
|
|
441
|
+
}
|
|
442
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
443
|
+
sys.stdout.flush()
|
|
444
|
+
except Exception as e:
|
|
445
|
+
logger.exception("Error handling request")
|
|
446
|
+
error_resp = {
|
|
447
|
+
"jsonrpc": "2.0",
|
|
448
|
+
"id": request.get("id") if "request" in dir() else None,
|
|
449
|
+
"error": {"code": -32603, "message": f"Internal error: {str(e)}"}
|
|
450
|
+
}
|
|
451
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
452
|
+
sys.stdout.flush()
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
if __name__ == "__main__":
|
|
456
|
+
asyncio.run(main())
|