lean-lsp-mcp 0.11.0__py3-none-any.whl → 0.11.1__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.
- lean_lsp_mcp/client_utils.py +49 -25
- lean_lsp_mcp/server.py +42 -33
- lean_lsp_mcp/utils.py +68 -34
- {lean_lsp_mcp-0.11.0.dist-info → lean_lsp_mcp-0.11.1.dist-info}/METADATA +25 -2
- lean_lsp_mcp-0.11.1.dist-info/RECORD +14 -0
- lean_lsp_mcp-0.11.0.dist-info/RECORD +0 -14
- {lean_lsp_mcp-0.11.0.dist-info → lean_lsp_mcp-0.11.1.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.11.0.dist-info → lean_lsp_mcp-0.11.1.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.11.0.dist-info → lean_lsp_mcp-0.11.1.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.11.0.dist-info → lean_lsp_mcp-0.11.1.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/client_utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from threading import Lock
|
|
3
4
|
|
|
@@ -64,35 +65,58 @@ def valid_lean_project_path(path: Path | str) -> bool:
|
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
def setup_client_for_file(ctx: Context, file_path: str) -> str | None:
|
|
67
|
-
"""
|
|
68
|
+
"""Ensure the LSP client matches the file's Lean project and return its relative path."""
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
lifespan = ctx.request_context.lifespan_context
|
|
71
|
+
project_cache = getattr(lifespan, "project_cache", {})
|
|
72
|
+
if not hasattr(lifespan, "project_cache"):
|
|
73
|
+
lifespan.project_cache = project_cache
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
abs_file_path = os.path.abspath(file_path)
|
|
76
|
+
file_dir = os.path.dirname(abs_file_path)
|
|
77
|
+
|
|
78
|
+
def activate_project(project_path: Path, cache_dirs: list[str]) -> str | None:
|
|
79
|
+
project_path_obj = project_path
|
|
80
|
+
rel = get_relative_file_path(project_path_obj, file_path)
|
|
81
|
+
if rel is None:
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
project_path_obj = project_path_obj.resolve()
|
|
85
|
+
lifespan.lean_project_path = project_path_obj
|
|
86
|
+
|
|
87
|
+
cache_targets: list[str] = []
|
|
88
|
+
for directory in cache_dirs + [str(project_path_obj)]:
|
|
89
|
+
if directory and directory not in cache_targets:
|
|
90
|
+
cache_targets.append(directory)
|
|
91
|
+
|
|
92
|
+
for directory in cache_targets:
|
|
93
|
+
project_cache[directory] = project_path_obj
|
|
94
|
+
|
|
95
|
+
startup_client(ctx)
|
|
96
|
+
return rel
|
|
97
|
+
|
|
98
|
+
# Fast path: current Lean project already valid for this file
|
|
99
|
+
if lifespan.lean_project_path is not None:
|
|
100
|
+
rel_path = activate_project(lifespan.lean_project_path, [file_dir])
|
|
80
101
|
if rel_path is not None:
|
|
81
|
-
startup_client(ctx)
|
|
82
102
|
return rel_path
|
|
83
103
|
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
rel_path =
|
|
104
|
+
# Walk up from file directory to root, using cache hits or lean-toolchain
|
|
105
|
+
prev_dir = None
|
|
106
|
+
current_dir = file_dir
|
|
107
|
+
while current_dir and current_dir != prev_dir:
|
|
108
|
+
cached_root = project_cache.get(current_dir)
|
|
109
|
+
if cached_root:
|
|
110
|
+
rel_path = activate_project(Path(cached_root), [current_dir])
|
|
111
|
+
if rel_path is not None:
|
|
112
|
+
return rel_path
|
|
113
|
+
elif valid_lean_project_path(current_dir):
|
|
114
|
+
rel_path = activate_project(Path(current_dir), [current_dir])
|
|
91
115
|
if rel_path is not None:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
return rel_path
|
|
117
|
+
else:
|
|
118
|
+
project_cache[current_dir] = ""
|
|
119
|
+
prev_dir = current_dir
|
|
120
|
+
current_dir = os.path.dirname(current_dir)
|
|
97
121
|
|
|
98
|
-
return
|
|
122
|
+
return None
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -7,7 +7,7 @@ from contextlib import asynccontextmanager
|
|
|
7
7
|
from collections.abc import AsyncIterator
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
import urllib
|
|
10
|
-
import
|
|
10
|
+
import orjson
|
|
11
11
|
import functools
|
|
12
12
|
import subprocess
|
|
13
13
|
import uuid
|
|
@@ -555,28 +555,37 @@ def multi_attempt(
|
|
|
555
555
|
update_file(ctx, rel_path)
|
|
556
556
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
557
557
|
|
|
558
|
-
|
|
558
|
+
try:
|
|
559
|
+
client.open_file(rel_path)
|
|
559
560
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
561
|
+
results = []
|
|
562
|
+
# Avoid mutating caller-provided snippets; normalize locally per attempt
|
|
563
|
+
for snippet in snippets:
|
|
564
|
+
snippet_str = snippet.rstrip("\n")
|
|
565
|
+
payload = f"{snippet_str}\n"
|
|
566
|
+
# Create a DocumentContentChange for the snippet
|
|
567
|
+
change = DocumentContentChange(
|
|
568
|
+
payload,
|
|
569
|
+
[line - 1, 0],
|
|
570
|
+
[line, 0],
|
|
571
|
+
)
|
|
572
|
+
# Apply the change to the file, capture diagnostics and goal state
|
|
573
|
+
client.update_file(rel_path, [change])
|
|
574
|
+
diag = client.get_diagnostics(rel_path)
|
|
575
|
+
formatted_diag = "\n".join(
|
|
576
|
+
format_diagnostics(diag, select_line=line - 1)
|
|
577
|
+
)
|
|
578
|
+
# Use the snippet text length without any trailing newline for the column
|
|
579
|
+
goal = client.get_goal(rel_path, line - 1, len(snippet_str))
|
|
580
|
+
formatted_goal = format_goal(goal, "Missing goal")
|
|
581
|
+
results.append(f"{snippet_str}:\n {formatted_goal}\n\n{formatted_diag}")
|
|
582
|
+
|
|
583
|
+
return results
|
|
584
|
+
finally:
|
|
585
|
+
try:
|
|
586
|
+
client.close_files([rel_path])
|
|
587
|
+
except Exception as exc: # pragma: no cover - close failures only logged
|
|
588
|
+
logger.warning("Failed to close `%s` after multi_attempt: %s", rel_path, exc)
|
|
580
589
|
|
|
581
590
|
|
|
582
591
|
@mcp.tool("lean_run_code")
|
|
@@ -702,9 +711,9 @@ def leansearch(ctx: Context, query: str, num_results: int = 5) -> List[Dict] | s
|
|
|
702
711
|
"""
|
|
703
712
|
try:
|
|
704
713
|
headers = {"User-Agent": "lean-lsp-mcp/0.1", "Content-Type": "application/json"}
|
|
705
|
-
payload =
|
|
714
|
+
payload = orjson.dumps(
|
|
706
715
|
{"num_results": str(num_results), "query": [query]}
|
|
707
|
-
)
|
|
716
|
+
)
|
|
708
717
|
|
|
709
718
|
req = urllib.request.Request(
|
|
710
719
|
"https://leansearch.net/search",
|
|
@@ -714,7 +723,7 @@ def leansearch(ctx: Context, query: str, num_results: int = 5) -> List[Dict] | s
|
|
|
714
723
|
)
|
|
715
724
|
|
|
716
725
|
with urllib.request.urlopen(req, timeout=20) as response:
|
|
717
|
-
results =
|
|
726
|
+
results = orjson.loads(response.read())
|
|
718
727
|
|
|
719
728
|
if not results or not results[0]:
|
|
720
729
|
return "No results found."
|
|
@@ -760,14 +769,14 @@ def loogle(ctx: Context, query: str, num_results: int = 8) -> List[dict] | str:
|
|
|
760
769
|
)
|
|
761
770
|
|
|
762
771
|
with urllib.request.urlopen(req, timeout=20) as response:
|
|
763
|
-
results =
|
|
772
|
+
results = orjson.loads(response.read())
|
|
764
773
|
|
|
765
774
|
if "hits" not in results:
|
|
766
775
|
return "No results found."
|
|
767
776
|
|
|
768
777
|
results = results["hits"][:num_results]
|
|
769
778
|
for result in results:
|
|
770
|
-
result.pop("doc")
|
|
779
|
+
result.pop("doc", None)
|
|
771
780
|
return results
|
|
772
781
|
except Exception as e:
|
|
773
782
|
return f"loogle error:\n{str(e)}"
|
|
@@ -797,7 +806,7 @@ def leanfinder(
|
|
|
797
806
|
"""
|
|
798
807
|
try:
|
|
799
808
|
headers = {"User-Agent": "lean-lsp-mcp/0.1", "Content-Type": "application/json"}
|
|
800
|
-
payload =
|
|
809
|
+
payload = orjson.dumps({"data": [query, num_results, "Normal"]})
|
|
801
810
|
|
|
802
811
|
req = urllib.request.Request(
|
|
803
812
|
"https://delta-lab-ai-lean-finder.hf.space/gradio_api/call/retrieve",
|
|
@@ -807,7 +816,7 @@ def leanfinder(
|
|
|
807
816
|
)
|
|
808
817
|
|
|
809
818
|
with urllib.request.urlopen(req, timeout=10) as response:
|
|
810
|
-
event_data =
|
|
819
|
+
event_data = orjson.loads(response.read())
|
|
811
820
|
event_id = event_data.get("event_id")
|
|
812
821
|
|
|
813
822
|
if not event_id:
|
|
@@ -820,7 +829,7 @@ def leanfinder(
|
|
|
820
829
|
for line in response:
|
|
821
830
|
line = line.decode("utf-8").strip()
|
|
822
831
|
if line.startswith("data: "):
|
|
823
|
-
data =
|
|
832
|
+
data = orjson.loads(line[6:])
|
|
824
833
|
if isinstance(data, list) and len(data) > 0:
|
|
825
834
|
html = data[0] if isinstance(data[0], str) else str(data)
|
|
826
835
|
|
|
@@ -887,7 +896,7 @@ def state_search(
|
|
|
887
896
|
)
|
|
888
897
|
|
|
889
898
|
with urllib.request.urlopen(req, timeout=20) as response:
|
|
890
|
-
results =
|
|
899
|
+
results = orjson.loads(response.read())
|
|
891
900
|
|
|
892
901
|
for result in results:
|
|
893
902
|
result.pop("rev")
|
|
@@ -941,11 +950,11 @@ def hammer_premise(
|
|
|
941
950
|
"Content-Type": "application/json",
|
|
942
951
|
},
|
|
943
952
|
method="POST",
|
|
944
|
-
data=
|
|
953
|
+
data=orjson.dumps(data),
|
|
945
954
|
)
|
|
946
955
|
|
|
947
956
|
with urllib.request.urlopen(req, timeout=20) as response:
|
|
948
|
-
results =
|
|
957
|
+
results = orjson.loads(response.read())
|
|
949
958
|
|
|
950
959
|
results = [result["name"] for result in results]
|
|
951
960
|
results.insert(0, f"Results for line:\n{f_line}")
|
lean_lsp_mcp/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import secrets
|
|
2
3
|
import sys
|
|
3
4
|
import tempfile
|
|
4
5
|
from typing import List, Dict, Optional
|
|
@@ -41,6 +42,19 @@ class OutputCapture:
|
|
|
41
42
|
return self.captured_output
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
class OptionalTokenVerifier(TokenVerifier):
|
|
46
|
+
"""Minimal verifier that accepts a single pre-shared token."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, expected_token: str):
|
|
49
|
+
self._expected_token = expected_token
|
|
50
|
+
|
|
51
|
+
async def verify_token(self, token: str | None) -> AccessToken | None:
|
|
52
|
+
if token is None or not secrets.compare_digest(token, self._expected_token):
|
|
53
|
+
return None
|
|
54
|
+
# AccessToken requires both client_id and scopes parameters to be provided.
|
|
55
|
+
return AccessToken(token=token, client_id="lean-lsp-mcp-optional", scopes=[])
|
|
56
|
+
|
|
57
|
+
|
|
44
58
|
def format_diagnostics(diagnostics: List[Dict], select_line: int = -1) -> List[str]:
|
|
45
59
|
"""Format the diagnostics messages.
|
|
46
60
|
|
|
@@ -190,38 +204,58 @@ def format_line(
|
|
|
190
204
|
|
|
191
205
|
|
|
192
206
|
def filter_diagnostics_by_position(
|
|
193
|
-
diagnostics: List[Dict], line: int, column: Optional[int]
|
|
207
|
+
diagnostics: List[Dict], line: Optional[int], column: Optional[int]
|
|
194
208
|
) -> List[Dict]:
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
209
|
+
"""Return diagnostics that intersect the requested (0-indexed) position."""
|
|
210
|
+
|
|
211
|
+
if line is None:
|
|
212
|
+
return list(diagnostics)
|
|
213
|
+
|
|
214
|
+
matches: List[Dict] = []
|
|
215
|
+
for diagnostic in diagnostics:
|
|
216
|
+
diagnostic_range = diagnostic.get("range") or diagnostic.get("fullRange")
|
|
217
|
+
if not diagnostic_range:
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
start = diagnostic_range.get("start", {})
|
|
221
|
+
end = diagnostic_range.get("end", {})
|
|
222
|
+
start_line = start.get("line")
|
|
223
|
+
end_line = end.get("line")
|
|
224
|
+
|
|
225
|
+
if start_line is None or end_line is None:
|
|
226
|
+
continue
|
|
227
|
+
if line < start_line or line > end_line:
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
start_char = start.get("character")
|
|
231
|
+
end_char = end.get("character")
|
|
232
|
+
|
|
233
|
+
if column is None:
|
|
234
|
+
if (
|
|
235
|
+
line == end_line
|
|
236
|
+
and line != start_line
|
|
237
|
+
and end_char is not None
|
|
238
|
+
and end_char == 0
|
|
239
|
+
):
|
|
240
|
+
continue
|
|
241
|
+
matches.append(diagnostic)
|
|
242
|
+
continue
|
|
243
|
+
|
|
244
|
+
if start_char is None:
|
|
245
|
+
start_char = 0
|
|
246
|
+
if end_char is None:
|
|
247
|
+
end_char = column + 1
|
|
248
|
+
|
|
249
|
+
if start_line == end_line and start_char == end_char:
|
|
250
|
+
if column == start_char:
|
|
251
|
+
matches.append(diagnostic)
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
if line == start_line and column < start_char:
|
|
255
|
+
continue
|
|
256
|
+
if line == end_line and column >= end_char:
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
matches.append(diagnostic)
|
|
260
|
+
|
|
261
|
+
return matches
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-lsp-mcp
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.1
|
|
4
4
|
Summary: Lean Theorem Prover MCP
|
|
5
5
|
Author-email: Oliver Dressler <hey@oli.show>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,7 +77,11 @@ OR using the setup wizard:
|
|
|
77
77
|
|
|
78
78
|
Ctrl+Shift+P > "MCP: Add Server..." > "Command (stdio)" > "uvx lean-lsp-mcp" > "lean-lsp" (or any name you like) > Global or Workspace
|
|
79
79
|
|
|
80
|
-
OR manually
|
|
80
|
+
OR manually adding config by opening `mcp.json` with:
|
|
81
|
+
|
|
82
|
+
Ctrl+Shift+P > "MCP: Open User Configuration"
|
|
83
|
+
|
|
84
|
+
and adding the following
|
|
81
85
|
|
|
82
86
|
```jsonc
|
|
83
87
|
{
|
|
@@ -92,6 +96,25 @@ OR manually add config to `mcp.json`:
|
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
```
|
|
99
|
+
|
|
100
|
+
If you installed VSCode on Windows and are using WSL2 as your development environment, you may need to use this config instead:
|
|
101
|
+
|
|
102
|
+
```jsonc
|
|
103
|
+
{
|
|
104
|
+
"servers": {
|
|
105
|
+
"lean-lsp": {
|
|
106
|
+
"type": "stdio",
|
|
107
|
+
"command": "wsl.exe",
|
|
108
|
+
"args": [
|
|
109
|
+
"uvx",
|
|
110
|
+
"lean-lsp-mcp"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
If that doesn't work, you can try cloning this repository and replace `"lean-lsp-mcp"` with `"/path/to/cloned/lean-lsp-mcp"`.
|
|
117
|
+
|
|
95
118
|
</details>
|
|
96
119
|
|
|
97
120
|
<details>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
lean_lsp_mcp/__init__.py,sha256=lxqDq0G_sI2iu2Nniy-pTW7BE9Ux7ZXeDoGf0OAWIDc,763
|
|
2
|
+
lean_lsp_mcp/__main__.py,sha256=XnpTzfJc0T-j9tHtdkA8ovTr1c139ffTewcJGhxYDaM,49
|
|
3
|
+
lean_lsp_mcp/client_utils.py,sha256=Z-2AoFBA36UO8oeauTNUtquE6Yz_ZRTKz_9n1iapyJ4,4519
|
|
4
|
+
lean_lsp_mcp/file_utils.py,sha256=qddegF-T5-egZop8dPe_3Cma-3rRSKsAErVDQLecmbE,2916
|
|
5
|
+
lean_lsp_mcp/instructions.py,sha256=y_gHlbeJoKnPohmcSVrQQds6mbBO1en-lxnXAfEypZE,892
|
|
6
|
+
lean_lsp_mcp/search_utils.py,sha256=cNpWzR1TuNyvRxCeCIb0HoEHGsCIW1ypqN1R1ZpbkdI,4170
|
|
7
|
+
lean_lsp_mcp/server.py,sha256=jK2bwi7iiM_agXtwRNFAMIpAo5FLDJ2FKfdDLvbaWfY,35433
|
|
8
|
+
lean_lsp_mcp/utils.py,sha256=zLu2VIhaX4yocY07F3Z94LB2jRGrkH1ID9SjR3poE9A,8255
|
|
9
|
+
lean_lsp_mcp-0.11.1.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
+
lean_lsp_mcp-0.11.1.dist-info/METADATA,sha256=IWxzDA2qqh5yK7jxCr36R6z7HuwwLV2bzoFlGqUWEoQ,19444
|
|
11
|
+
lean_lsp_mcp-0.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
lean_lsp_mcp-0.11.1.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
+
lean_lsp_mcp-0.11.1.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
+
lean_lsp_mcp-0.11.1.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
lean_lsp_mcp/__init__.py,sha256=lxqDq0G_sI2iu2Nniy-pTW7BE9Ux7ZXeDoGf0OAWIDc,763
|
|
2
|
-
lean_lsp_mcp/__main__.py,sha256=XnpTzfJc0T-j9tHtdkA8ovTr1c139ffTewcJGhxYDaM,49
|
|
3
|
-
lean_lsp_mcp/client_utils.py,sha256=M7ep3-bDaGjN_tc3cvaWuyG00js0cwH9npMMdOgDDBw,3672
|
|
4
|
-
lean_lsp_mcp/file_utils.py,sha256=qddegF-T5-egZop8dPe_3Cma-3rRSKsAErVDQLecmbE,2916
|
|
5
|
-
lean_lsp_mcp/instructions.py,sha256=y_gHlbeJoKnPohmcSVrQQds6mbBO1en-lxnXAfEypZE,892
|
|
6
|
-
lean_lsp_mcp/search_utils.py,sha256=cNpWzR1TuNyvRxCeCIb0HoEHGsCIW1ypqN1R1ZpbkdI,4170
|
|
7
|
-
lean_lsp_mcp/server.py,sha256=-qrXb1ESuuoJz3-XAmox22ePPsoB1tbDMXSd84Qc8EM,35075
|
|
8
|
-
lean_lsp_mcp/utils.py,sha256=HQA0bJSl5LWuIUmo7wPi7BMfP7R6ITOJbn5dOA2UfOk,7219
|
|
9
|
-
lean_lsp_mcp-0.11.0.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
-
lean_lsp_mcp-0.11.0.dist-info/METADATA,sha256=I3JjZPWmoawz4x_BA4PJ_AVDmg07q_YtvtnDoIHOvgc,18872
|
|
11
|
-
lean_lsp_mcp-0.11.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
lean_lsp_mcp-0.11.0.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
-
lean_lsp_mcp-0.11.0.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
-
lean_lsp_mcp-0.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|