sol-mcp 0.2.0__py3-none-any.whl
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.
- sol_mcp-0.2.0.dist-info/METADATA +218 -0
- sol_mcp-0.2.0.dist-info/RECORD +20 -0
- sol_mcp-0.2.0.dist-info/WHEEL +4 -0
- sol_mcp-0.2.0.dist-info/entry_points.txt +3 -0
- solana_mcp/__init__.py +3 -0
- solana_mcp/cli.py +527 -0
- solana_mcp/config.py +324 -0
- solana_mcp/expert/__init__.py +5 -0
- solana_mcp/expert/guidance.py +452 -0
- solana_mcp/indexer/__init__.py +8 -0
- solana_mcp/indexer/chunker.py +457 -0
- solana_mcp/indexer/compiler.py +1101 -0
- solana_mcp/indexer/downloader.py +304 -0
- solana_mcp/indexer/embedder.py +755 -0
- solana_mcp/indexer/manifest.py +411 -0
- solana_mcp/logging.py +85 -0
- solana_mcp/models.py +62 -0
- solana_mcp/server.py +746 -0
- solana_mcp/tools/__init__.py +1 -0
- solana_mcp/versions.py +391 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Download Solana source repositories for indexing.
|
|
2
|
+
|
|
3
|
+
Clones:
|
|
4
|
+
- anza-xyz/agave (reference validator runtime)
|
|
5
|
+
- solana-foundation/solana-improvement-documents (SIMDs)
|
|
6
|
+
- anza-xyz/alpenglow (new consensus protocol, not yet live)
|
|
7
|
+
- firedancer-io/firedancer (Jump's C implementation)
|
|
8
|
+
- jito-foundation/jito-solana (MEV-enabled Agave fork, ~70% of stake)
|
|
9
|
+
- jito-foundation/jito-programs (on-chain tip distribution programs)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Callable
|
|
15
|
+
|
|
16
|
+
# Default data directory
|
|
17
|
+
DEFAULT_DATA_DIR = Path.home() / ".solana-mcp"
|
|
18
|
+
|
|
19
|
+
# Repositories to clone
|
|
20
|
+
REPOS = {
|
|
21
|
+
# Reference implementation (Anza)
|
|
22
|
+
"agave": {
|
|
23
|
+
"url": "https://github.com/anza-xyz/agave.git",
|
|
24
|
+
"branch": "master",
|
|
25
|
+
"client": True,
|
|
26
|
+
"stake_pct": 8.0, # ~8% stake on vanilla Agave
|
|
27
|
+
"sparse_paths": [
|
|
28
|
+
"programs/stake",
|
|
29
|
+
"programs/vote",
|
|
30
|
+
"programs/bpf_loader",
|
|
31
|
+
"programs/system",
|
|
32
|
+
"runtime",
|
|
33
|
+
"svm",
|
|
34
|
+
"poh",
|
|
35
|
+
"turbine",
|
|
36
|
+
"gossip",
|
|
37
|
+
"ledger",
|
|
38
|
+
"validator",
|
|
39
|
+
"sdk",
|
|
40
|
+
"core", # TowerBFT consensus, replay_stage, fork choice
|
|
41
|
+
"docs",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
# Jito-Agave: MEV-enabled fork, dominant client (~70% stake)
|
|
45
|
+
"jito-solana": {
|
|
46
|
+
"url": "https://github.com/jito-foundation/jito-solana.git",
|
|
47
|
+
"branch": "master",
|
|
48
|
+
"client": True,
|
|
49
|
+
"stake_pct": 70.0, # ~70% of mainnet stake
|
|
50
|
+
"sparse_paths": [
|
|
51
|
+
"core", # Block engine integration
|
|
52
|
+
"runtime",
|
|
53
|
+
"programs/stake",
|
|
54
|
+
"programs/vote",
|
|
55
|
+
"poh",
|
|
56
|
+
"turbine",
|
|
57
|
+
"gossip",
|
|
58
|
+
"validator",
|
|
59
|
+
"bundle", # Jito-specific: bundle processing
|
|
60
|
+
"tip-distributor", # Jito-specific: tip distribution
|
|
61
|
+
"block-engine", # Jito-specific: MEV infrastructure
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
# Firedancer: Jump's C implementation (independent failure domain)
|
|
65
|
+
"firedancer": {
|
|
66
|
+
"url": "https://github.com/firedancer-io/firedancer.git",
|
|
67
|
+
"branch": "main",
|
|
68
|
+
"client": True,
|
|
69
|
+
"stake_pct": 22.0, # ~21% Frankendancer + <1% full Firedancer
|
|
70
|
+
"sparse_paths": [
|
|
71
|
+
"src/app", # Validator application
|
|
72
|
+
"src/ballet", # Cryptography, hashing
|
|
73
|
+
"src/disco", # Distributed consensus
|
|
74
|
+
"src/flamenco", # Runtime, accounts
|
|
75
|
+
"src/tango", # Messaging infrastructure
|
|
76
|
+
"src/waltz", # Networking (QUIC, UDP)
|
|
77
|
+
"src/choreo", # Fork choice, TowerBFT
|
|
78
|
+
"contrib", # Build scripts, tests
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
# SIMDs (small repo, full clone)
|
|
82
|
+
"solana-improvement-documents": {
|
|
83
|
+
"url": "https://github.com/solana-foundation/solana-improvement-documents.git",
|
|
84
|
+
"branch": "main",
|
|
85
|
+
"client": False,
|
|
86
|
+
"sparse_paths": None, # Clone full repo (it's small)
|
|
87
|
+
},
|
|
88
|
+
# Alpenglow: Future consensus (not yet live)
|
|
89
|
+
"alpenglow": {
|
|
90
|
+
"url": "https://github.com/anza-xyz/alpenglow.git",
|
|
91
|
+
"branch": "master",
|
|
92
|
+
"client": False,
|
|
93
|
+
"sparse_paths": None, # Clone full repo
|
|
94
|
+
},
|
|
95
|
+
# ===================
|
|
96
|
+
# MEV Infrastructure
|
|
97
|
+
# ===================
|
|
98
|
+
# Jito on-chain programs (tip distribution, etc.)
|
|
99
|
+
"jito-programs": {
|
|
100
|
+
"url": "https://github.com/jito-foundation/jito-programs.git",
|
|
101
|
+
"branch": "master",
|
|
102
|
+
"client": False,
|
|
103
|
+
"sparse_paths": [
|
|
104
|
+
"mev-programs", # Core MEV programs
|
|
105
|
+
"tip-distribution", # Tip distribution program
|
|
106
|
+
"tip-payment", # Tip payment handling
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def run_git(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProcess:
|
|
113
|
+
"""Run a git command."""
|
|
114
|
+
result = subprocess.run(
|
|
115
|
+
["git"] + args,
|
|
116
|
+
cwd=cwd,
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True,
|
|
119
|
+
)
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def clone_repo(
|
|
124
|
+
name: str,
|
|
125
|
+
url: str,
|
|
126
|
+
dest: Path,
|
|
127
|
+
branch: str = "main",
|
|
128
|
+
sparse_paths: list[str] | None = None,
|
|
129
|
+
progress_callback: Callable[[str], None] | None = None,
|
|
130
|
+
) -> bool:
|
|
131
|
+
"""
|
|
132
|
+
Clone a repository, optionally with sparse checkout.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
name: Repository name for logging
|
|
136
|
+
url: Git URL to clone
|
|
137
|
+
dest: Destination path
|
|
138
|
+
branch: Branch to checkout
|
|
139
|
+
sparse_paths: If provided, only checkout these paths (sparse checkout)
|
|
140
|
+
progress_callback: Optional callback for progress updates
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
True if successful, False otherwise
|
|
144
|
+
"""
|
|
145
|
+
def log(msg: str):
|
|
146
|
+
if progress_callback:
|
|
147
|
+
progress_callback(msg)
|
|
148
|
+
else:
|
|
149
|
+
print(msg)
|
|
150
|
+
|
|
151
|
+
if dest.exists():
|
|
152
|
+
log(f" {name}: Already exists, pulling latest...")
|
|
153
|
+
result = run_git(["pull", "--ff-only"], cwd=dest)
|
|
154
|
+
if result.returncode != 0:
|
|
155
|
+
log(f" {name}: Pull failed, trying fetch + reset...")
|
|
156
|
+
run_git(["fetch", "origin"], cwd=dest)
|
|
157
|
+
run_git(["reset", "--hard", f"origin/{branch}"], cwd=dest)
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
log(f" {name}: Cloning from {url}...")
|
|
161
|
+
|
|
162
|
+
if sparse_paths:
|
|
163
|
+
# Sparse checkout for large repos
|
|
164
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
165
|
+
|
|
166
|
+
# Initialize repo
|
|
167
|
+
run_git(["init"], cwd=dest)
|
|
168
|
+
run_git(["remote", "add", "origin", url], cwd=dest)
|
|
169
|
+
|
|
170
|
+
# Configure sparse checkout
|
|
171
|
+
run_git(["config", "core.sparseCheckout", "true"], cwd=dest)
|
|
172
|
+
|
|
173
|
+
# Write sparse-checkout file
|
|
174
|
+
sparse_file = dest / ".git" / "info" / "sparse-checkout"
|
|
175
|
+
sparse_file.parent.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
sparse_file.write_text("\n".join(sparse_paths) + "\n")
|
|
177
|
+
|
|
178
|
+
# Fetch and checkout
|
|
179
|
+
log(f" {name}: Fetching (sparse checkout: {len(sparse_paths)} paths)...")
|
|
180
|
+
result = run_git(["fetch", "--depth=1", "origin", branch], cwd=dest)
|
|
181
|
+
if result.returncode != 0:
|
|
182
|
+
log(f" {name}: Fetch failed: {result.stderr}")
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
result = run_git(["checkout", branch], cwd=dest)
|
|
186
|
+
if result.returncode != 0:
|
|
187
|
+
log(f" {name}: Checkout failed: {result.stderr}")
|
|
188
|
+
return False
|
|
189
|
+
else:
|
|
190
|
+
# Full clone for small repos
|
|
191
|
+
result = run_git(["clone", "--depth=1", "--branch", branch, url, str(dest)])
|
|
192
|
+
if result.returncode != 0:
|
|
193
|
+
log(f" {name}: Clone failed: {result.stderr}")
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
log(f" {name}: Done")
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def download_repos(
|
|
201
|
+
data_dir: Path | None = None,
|
|
202
|
+
repos: list[str] | None = None,
|
|
203
|
+
progress_callback: Callable[[str], None] | None = None,
|
|
204
|
+
) -> dict[str, bool]:
|
|
205
|
+
"""
|
|
206
|
+
Download all configured repositories.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
data_dir: Base directory for downloads (default: ~/.solana-mcp)
|
|
210
|
+
repos: List of repo names to download (default: all)
|
|
211
|
+
progress_callback: Optional callback for progress updates
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Dict mapping repo name to success status
|
|
215
|
+
"""
|
|
216
|
+
if data_dir is None:
|
|
217
|
+
data_dir = DEFAULT_DATA_DIR
|
|
218
|
+
|
|
219
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
|
220
|
+
|
|
221
|
+
if repos is None:
|
|
222
|
+
repos = list(REPOS.keys())
|
|
223
|
+
|
|
224
|
+
results = {}
|
|
225
|
+
|
|
226
|
+
for name in repos:
|
|
227
|
+
if name not in REPOS:
|
|
228
|
+
if progress_callback:
|
|
229
|
+
progress_callback(f" {name}: Unknown repository, skipping")
|
|
230
|
+
results[name] = False
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
config = REPOS[name]
|
|
234
|
+
dest = data_dir / name
|
|
235
|
+
|
|
236
|
+
success = clone_repo(
|
|
237
|
+
name=name,
|
|
238
|
+
url=config["url"],
|
|
239
|
+
dest=dest,
|
|
240
|
+
branch=config["branch"],
|
|
241
|
+
sparse_paths=config.get("sparse_paths"),
|
|
242
|
+
progress_callback=progress_callback,
|
|
243
|
+
)
|
|
244
|
+
results[name] = success
|
|
245
|
+
|
|
246
|
+
return results
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def get_repo_path(name: str, data_dir: Path | None = None) -> Path | None:
|
|
250
|
+
"""Get the path to a downloaded repository."""
|
|
251
|
+
if data_dir is None:
|
|
252
|
+
data_dir = DEFAULT_DATA_DIR
|
|
253
|
+
|
|
254
|
+
path = data_dir / name
|
|
255
|
+
if path.exists():
|
|
256
|
+
return path
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_repo_version(name: str, data_dir: Path | None = None) -> str | None:
|
|
261
|
+
"""Get the current commit hash of a repository."""
|
|
262
|
+
path = get_repo_path(name, data_dir)
|
|
263
|
+
if path is None:
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
result = run_git(["rev-parse", "HEAD"], cwd=path)
|
|
267
|
+
if result.returncode == 0:
|
|
268
|
+
return result.stdout.strip()[:12]
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def list_downloaded_repos(data_dir: Path | None = None) -> dict[str, dict]:
|
|
273
|
+
"""List all downloaded repositories with their status."""
|
|
274
|
+
if data_dir is None:
|
|
275
|
+
data_dir = DEFAULT_DATA_DIR
|
|
276
|
+
|
|
277
|
+
status = {}
|
|
278
|
+
for name in REPOS:
|
|
279
|
+
path = data_dir / name
|
|
280
|
+
if path.exists():
|
|
281
|
+
version = get_repo_version(name, data_dir)
|
|
282
|
+
status[name] = {
|
|
283
|
+
"path": str(path),
|
|
284
|
+
"version": version,
|
|
285
|
+
"exists": True,
|
|
286
|
+
}
|
|
287
|
+
else:
|
|
288
|
+
status[name] = {
|
|
289
|
+
"path": str(path),
|
|
290
|
+
"version": None,
|
|
291
|
+
"exists": False,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return status
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
# Test download
|
|
299
|
+
print("Downloading Solana repositories...")
|
|
300
|
+
results = download_repos(progress_callback=print)
|
|
301
|
+
print("\nResults:")
|
|
302
|
+
for name, success in results.items():
|
|
303
|
+
status = "✓" if success else "✗"
|
|
304
|
+
print(f" {status} {name}")
|