lean-lsp-mcp 0.11.2__py3-none-any.whl → 0.12.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.
- lean_lsp_mcp/client_utils.py +52 -37
- lean_lsp_mcp/file_utils.py +5 -56
- lean_lsp_mcp/server.py +55 -25
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.12.0.dist-info}/METADATA +3 -3
- lean_lsp_mcp-0.12.0.dist-info/RECORD +14 -0
- lean_lsp_mcp-0.11.2.dist-info/RECORD +0 -14
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.12.0.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.12.0.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.12.0.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/client_utils.py
CHANGED
|
@@ -34,7 +34,6 @@ def startup_client(ctx: Context):
|
|
|
34
34
|
return # Client already set up correctly - reuse it!
|
|
35
35
|
# Different project path - close old client
|
|
36
36
|
client.close()
|
|
37
|
-
ctx.request_context.lifespan_context.file_content_hashes.clear()
|
|
38
37
|
|
|
39
38
|
# Need to create a new client
|
|
40
39
|
with OutputCapture() as output:
|
|
@@ -64,59 +63,75 @@ def valid_lean_project_path(path: Path | str) -> bool:
|
|
|
64
63
|
return (path_obj / "lean-toolchain").is_file()
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
def
|
|
68
|
-
"""
|
|
66
|
+
def infer_project_path(ctx: Context, file_path: str) -> Path | None:
|
|
67
|
+
"""Infer and cache the Lean project path for a file WITHOUT starting the client.
|
|
68
|
+
|
|
69
|
+
Walks up the directory tree to find a lean-toolchain file, caches the result.
|
|
70
|
+
Sets ctx.request_context.lifespan_context.lean_project_path if found.
|
|
71
|
+
|
|
72
|
+
Side effects when path changes:
|
|
73
|
+
- Next LSP tool will restart the client for the new project
|
|
74
|
+
- File content hashes will be cleared
|
|
69
75
|
|
|
76
|
+
Args:
|
|
77
|
+
ctx (Context): Context object
|
|
78
|
+
file_path (str): Absolute or relative path to a Lean file
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Path | None: The resolved project path if found, None otherwise
|
|
82
|
+
"""
|
|
70
83
|
lifespan = ctx.request_context.lifespan_context
|
|
71
|
-
project_cache = getattr(lifespan, "project_cache", {})
|
|
72
84
|
if not hasattr(lifespan, "project_cache"):
|
|
73
|
-
lifespan.project_cache =
|
|
85
|
+
lifespan.project_cache = {}
|
|
74
86
|
|
|
75
87
|
abs_file_path = os.path.abspath(file_path)
|
|
76
88
|
file_dir = os.path.dirname(abs_file_path)
|
|
77
89
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if rel is None:
|
|
90
|
+
def set_project_path(project_path: Path, cache_dirs: list[str]) -> Path | None:
|
|
91
|
+
"""Validate file is in project, set path, update cache."""
|
|
92
|
+
if get_relative_file_path(project_path, file_path) is None:
|
|
82
93
|
return None
|
|
83
94
|
|
|
84
|
-
|
|
85
|
-
lifespan.lean_project_path =
|
|
95
|
+
project_path = project_path.resolve()
|
|
96
|
+
lifespan.lean_project_path = project_path
|
|
86
97
|
|
|
87
|
-
|
|
88
|
-
for directory in cache_dirs + [str(
|
|
89
|
-
if directory
|
|
90
|
-
|
|
98
|
+
# Update all relevant directories in cache
|
|
99
|
+
for directory in set(cache_dirs + [str(project_path)]):
|
|
100
|
+
if directory:
|
|
101
|
+
lifespan.project_cache[directory] = project_path
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
project_cache[directory] = project_path_obj
|
|
103
|
+
return project_path
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
# Fast path: current project already valid for this file
|
|
106
|
+
if lifespan.lean_project_path and set_project_path(
|
|
107
|
+
lifespan.lean_project_path, [file_dir]
|
|
108
|
+
):
|
|
109
|
+
return lifespan.lean_project_path
|
|
97
110
|
|
|
98
|
-
#
|
|
99
|
-
if lifespan.lean_project_path is not None:
|
|
100
|
-
rel_path = activate_project(lifespan.lean_project_path, [file_dir])
|
|
101
|
-
if rel_path is not None:
|
|
102
|
-
return rel_path
|
|
103
|
-
|
|
104
|
-
# Walk up from file directory to root, using cache hits or lean-toolchain
|
|
105
|
-
prev_dir = None
|
|
111
|
+
# Walk up directory tree using cache and lean-toolchain detection
|
|
106
112
|
current_dir = file_dir
|
|
107
|
-
while current_dir and current_dir !=
|
|
108
|
-
cached_root = project_cache.get(current_dir)
|
|
113
|
+
while current_dir and current_dir != os.path.dirname(current_dir):
|
|
114
|
+
cached_root = lifespan.project_cache.get(current_dir)
|
|
115
|
+
|
|
109
116
|
if cached_root:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return rel_path
|
|
117
|
+
if result := set_project_path(Path(cached_root), [current_dir]):
|
|
118
|
+
return result
|
|
113
119
|
elif valid_lean_project_path(current_dir):
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return rel_path
|
|
120
|
+
if result := set_project_path(Path(current_dir), [current_dir]):
|
|
121
|
+
return result
|
|
117
122
|
else:
|
|
118
|
-
project_cache[current_dir] = ""
|
|
119
|
-
|
|
123
|
+
lifespan.project_cache[current_dir] = "" # Mark as checked
|
|
124
|
+
|
|
120
125
|
current_dir = os.path.dirname(current_dir)
|
|
121
126
|
|
|
122
127
|
return None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def setup_client_for_file(ctx: Context, file_path: str) -> str | None:
|
|
131
|
+
"""Ensure the LSP client matches the file's Lean project and return its relative path."""
|
|
132
|
+
project_path = infer_project_path(ctx, file_path)
|
|
133
|
+
if project_path is None:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
startup_client(ctx)
|
|
137
|
+
return get_relative_file_path(project_path, file_path)
|
lean_lsp_mcp/file_utils.py
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from mcp.server.fastmcp import Context
|
|
5
|
-
from mcp.server.fastmcp.utilities.logging import get_logger
|
|
6
|
-
from leanclient import LeanLSPClient
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
logger = get_logger(__name__)
|
|
10
|
-
|
|
11
4
|
|
|
12
5
|
def get_relative_file_path(lean_project_path: Path, file_path: str) -> Optional[str]:
|
|
13
6
|
"""Convert path relative to project path.
|
|
@@ -21,25 +14,24 @@ def get_relative_file_path(lean_project_path: Path, file_path: str) -> Optional[
|
|
|
21
14
|
"""
|
|
22
15
|
file_path_obj = Path(file_path)
|
|
23
16
|
|
|
24
|
-
#
|
|
17
|
+
# Absolute path under project
|
|
25
18
|
if file_path_obj.is_absolute() and file_path_obj.exists():
|
|
26
19
|
try:
|
|
27
20
|
return str(file_path_obj.relative_to(lean_project_path))
|
|
28
21
|
except ValueError:
|
|
29
|
-
# File is not in this project
|
|
30
22
|
return None
|
|
31
23
|
|
|
32
|
-
#
|
|
24
|
+
# Relative to project path
|
|
33
25
|
path = lean_project_path / file_path
|
|
34
26
|
if path.exists():
|
|
35
27
|
return str(path.relative_to(lean_project_path))
|
|
36
28
|
|
|
37
|
-
#
|
|
29
|
+
# Relative to CWD, but only if inside project root
|
|
38
30
|
cwd = Path.cwd()
|
|
39
31
|
path = cwd / file_path
|
|
40
32
|
if path.exists():
|
|
41
33
|
try:
|
|
42
|
-
return str(path.relative_to(lean_project_path))
|
|
34
|
+
return str(path.resolve().relative_to(lean_project_path))
|
|
43
35
|
except ValueError:
|
|
44
36
|
return None
|
|
45
37
|
|
|
@@ -55,46 +47,3 @@ def get_file_contents(abs_path: str) -> str:
|
|
|
55
47
|
continue
|
|
56
48
|
with open(abs_path, "r", encoding=None) as f:
|
|
57
49
|
return f.read()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def update_file(ctx: Context, rel_path: str) -> str:
|
|
61
|
-
"""Update the file contents in the context.
|
|
62
|
-
Args:
|
|
63
|
-
ctx (Context): Context object.
|
|
64
|
-
rel_path (str): Relative file path.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
str: Updated file contents.
|
|
68
|
-
"""
|
|
69
|
-
# Get file contents and hash
|
|
70
|
-
abs_path = ctx.request_context.lifespan_context.lean_project_path / rel_path
|
|
71
|
-
file_content = get_file_contents(str(abs_path))
|
|
72
|
-
hashed_file = hash(file_content)
|
|
73
|
-
|
|
74
|
-
# Check if file_contents have changed
|
|
75
|
-
file_content_hashes: Dict[str, str] = (
|
|
76
|
-
ctx.request_context.lifespan_context.file_content_hashes
|
|
77
|
-
)
|
|
78
|
-
if rel_path not in file_content_hashes:
|
|
79
|
-
file_content_hashes[rel_path] = hashed_file
|
|
80
|
-
return file_content
|
|
81
|
-
|
|
82
|
-
elif hashed_file == file_content_hashes[rel_path]:
|
|
83
|
-
return file_content
|
|
84
|
-
|
|
85
|
-
# Update file_contents
|
|
86
|
-
file_content_hashes[rel_path] = hashed_file
|
|
87
|
-
|
|
88
|
-
# Reload file in LSP
|
|
89
|
-
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
90
|
-
try:
|
|
91
|
-
client.close_files([rel_path])
|
|
92
|
-
except FileNotFoundError as e:
|
|
93
|
-
logger.warning(
|
|
94
|
-
f"Attempted to close file {rel_path} that wasn't open in LSP client: {e}"
|
|
95
|
-
)
|
|
96
|
-
except Exception as e:
|
|
97
|
-
logger.error(
|
|
98
|
-
f"Unexpected error closing file {rel_path}: {type(e).__name__}: {e}"
|
|
99
|
-
)
|
|
100
|
-
return file_content
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -18,8 +18,12 @@ from mcp.server.fastmcp.utilities.logging import get_logger, configure_logging
|
|
|
18
18
|
from mcp.server.auth.settings import AuthSettings
|
|
19
19
|
from leanclient import LeanLSPClient, DocumentContentChange
|
|
20
20
|
|
|
21
|
-
from lean_lsp_mcp.client_utils import
|
|
22
|
-
|
|
21
|
+
from lean_lsp_mcp.client_utils import (
|
|
22
|
+
setup_client_for_file,
|
|
23
|
+
startup_client,
|
|
24
|
+
infer_project_path,
|
|
25
|
+
)
|
|
26
|
+
from lean_lsp_mcp.file_utils import get_file_contents
|
|
23
27
|
from lean_lsp_mcp.instructions import INSTRUCTIONS
|
|
24
28
|
from lean_lsp_mcp.search_utils import check_ripgrep_status, lean_local_search
|
|
25
29
|
from lean_lsp_mcp.utils import (
|
|
@@ -47,7 +51,6 @@ _RG_AVAILABLE, _RG_MESSAGE = check_ripgrep_status()
|
|
|
47
51
|
class AppContext:
|
|
48
52
|
lean_project_path: Path | None
|
|
49
53
|
client: LeanLSPClient | None
|
|
50
|
-
file_content_hashes: Dict[str, str]
|
|
51
54
|
rate_limit: Dict[str, List[int]]
|
|
52
55
|
lean_search_available: bool
|
|
53
56
|
|
|
@@ -64,7 +67,6 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
|
64
67
|
context = AppContext(
|
|
65
68
|
lean_project_path=lean_project_path,
|
|
66
69
|
client=None,
|
|
67
|
-
file_content_hashes={},
|
|
68
70
|
rate_limit={
|
|
69
71
|
"leansearch": [],
|
|
70
72
|
"loogle": [],
|
|
@@ -154,10 +156,7 @@ async def lsp_build(
|
|
|
154
156
|
ctx.request_context.lifespan_context.lean_project_path = lean_project_path_obj
|
|
155
157
|
|
|
156
158
|
if lean_project_path_obj is None:
|
|
157
|
-
return (
|
|
158
|
-
"Lean project path not known yet. Provide `lean_project_path` explicitly or call a "
|
|
159
|
-
"tool that infers it (e.g. `lean_goal`) before running `lean_build`."
|
|
160
|
-
)
|
|
159
|
+
return "Lean project path not known yet. Provide `lean_project_path` explicitly or call a tool that infers it (e.g. `lean_file_contents`) before running `lean_build`."
|
|
161
160
|
|
|
162
161
|
build_output = ""
|
|
163
162
|
try:
|
|
@@ -165,7 +164,6 @@ async def lsp_build(
|
|
|
165
164
|
if client:
|
|
166
165
|
ctx.request_context.lifespan_context.client = None
|
|
167
166
|
client.close()
|
|
168
|
-
ctx.request_context.lifespan_context.file_content_hashes.clear()
|
|
169
167
|
|
|
170
168
|
if clean:
|
|
171
169
|
subprocess.run(["lake", "clean"], cwd=lean_project_path_obj, check=False)
|
|
@@ -247,6 +245,10 @@ def file_contents(ctx: Context, file_path: str, annotate_lines: bool = True) ->
|
|
|
247
245
|
Returns:
|
|
248
246
|
str: File content or error msg
|
|
249
247
|
"""
|
|
248
|
+
# Infer project path but do not start a client
|
|
249
|
+
if file_path.endswith(".lean"):
|
|
250
|
+
infer_project_path(ctx, file_path) # Silently fails for non-project files
|
|
251
|
+
|
|
250
252
|
try:
|
|
251
253
|
data = get_file_contents(file_path)
|
|
252
254
|
except FileNotFoundError:
|
|
@@ -266,13 +268,20 @@ def file_contents(ctx: Context, file_path: str, annotate_lines: bool = True) ->
|
|
|
266
268
|
|
|
267
269
|
|
|
268
270
|
@mcp.tool("lean_diagnostic_messages")
|
|
269
|
-
def diagnostic_messages(
|
|
271
|
+
def diagnostic_messages(
|
|
272
|
+
ctx: Context,
|
|
273
|
+
file_path: str,
|
|
274
|
+
start_line: Optional[int] = None,
|
|
275
|
+
end_line: Optional[int] = None,
|
|
276
|
+
) -> List[str] | str:
|
|
270
277
|
"""Get all diagnostic msgs (errors, warnings, infos) for a Lean file.
|
|
271
278
|
|
|
272
279
|
"no goals to be solved" means code may need removal.
|
|
273
280
|
|
|
274
281
|
Args:
|
|
275
282
|
file_path (str): Abs path to Lean file
|
|
283
|
+
start_line (int, optional): Start line (1-indexed) for filtering diagnostics. If provided, only diagnostics from this line onwards are returned.
|
|
284
|
+
end_line (int, optional): End line (1-indexed) for filtering diagnostics. If provided with start_line, only diagnostics in this range are returned.
|
|
276
285
|
|
|
277
286
|
Returns:
|
|
278
287
|
List[str] | str: Diagnostic msgs or error msg
|
|
@@ -281,10 +290,22 @@ def diagnostic_messages(ctx: Context, file_path: str) -> List[str] | str:
|
|
|
281
290
|
if not rel_path:
|
|
282
291
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
283
292
|
|
|
284
|
-
update_file(ctx, rel_path)
|
|
285
|
-
|
|
286
293
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
287
|
-
|
|
294
|
+
|
|
295
|
+
# leanclient requires both start_line and end_line to filter
|
|
296
|
+
# Convert 1-indexed to 0-indexed for leanclient
|
|
297
|
+
if start_line is not None or end_line is not None:
|
|
298
|
+
start_line_0 = (start_line - 1) if start_line is not None else 0
|
|
299
|
+
end_line_0 = (end_line - 1) if end_line is not None else 999999
|
|
300
|
+
diagnostics = client.get_diagnostics(
|
|
301
|
+
rel_path,
|
|
302
|
+
start_line=start_line_0,
|
|
303
|
+
end_line=end_line_0,
|
|
304
|
+
inactivity_timeout=10.0,
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
diagnostics = client.get_diagnostics(rel_path, inactivity_timeout=10.0)
|
|
308
|
+
|
|
288
309
|
return format_diagnostics(diagnostics)
|
|
289
310
|
|
|
290
311
|
|
|
@@ -309,8 +330,9 @@ def goal(ctx: Context, file_path: str, line: int, column: Optional[int] = None)
|
|
|
309
330
|
if not rel_path:
|
|
310
331
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
311
332
|
|
|
312
|
-
content = update_file(ctx, rel_path)
|
|
313
333
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
334
|
+
client.open_file(rel_path)
|
|
335
|
+
content = client.get_file_content(rel_path)
|
|
314
336
|
|
|
315
337
|
if column is None:
|
|
316
338
|
lines = content.splitlines()
|
|
@@ -355,14 +377,15 @@ def term_goal(
|
|
|
355
377
|
if not rel_path:
|
|
356
378
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
357
379
|
|
|
358
|
-
|
|
380
|
+
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
381
|
+
client.open_file(rel_path)
|
|
382
|
+
content = client.get_file_content(rel_path)
|
|
359
383
|
if column is None:
|
|
360
384
|
lines = content.splitlines()
|
|
361
385
|
if line < 1 or line > len(lines):
|
|
362
386
|
return "Line number out of range. Try elsewhere?"
|
|
363
387
|
column = len(content.splitlines()[line - 1])
|
|
364
388
|
|
|
365
|
-
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
366
389
|
term_goal = client.get_term_goal(rel_path, line - 1, column - 1)
|
|
367
390
|
f_line = format_line(content, line, column)
|
|
368
391
|
if term_goal is None:
|
|
@@ -389,8 +412,9 @@ def hover(ctx: Context, file_path: str, line: int, column: int) -> str:
|
|
|
389
412
|
if not rel_path:
|
|
390
413
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
391
414
|
|
|
392
|
-
file_content = update_file(ctx, rel_path)
|
|
393
415
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
416
|
+
client.open_file(rel_path)
|
|
417
|
+
file_content = client.get_file_content(rel_path)
|
|
394
418
|
hover_info = client.get_hover(rel_path, line - 1, column - 1)
|
|
395
419
|
if hover_info is None:
|
|
396
420
|
f_line = format_line(file_content, line, column)
|
|
@@ -435,9 +459,10 @@ def completions(
|
|
|
435
459
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
436
460
|
if not rel_path:
|
|
437
461
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
438
|
-
content = update_file(ctx, rel_path)
|
|
439
462
|
|
|
440
463
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
464
|
+
client.open_file(rel_path)
|
|
465
|
+
content = client.get_file_content(rel_path)
|
|
441
466
|
completions = client.get_completions(rel_path, line - 1, column - 1)
|
|
442
467
|
formatted = [c["label"] for c in completions if "label" in c]
|
|
443
468
|
f_line = format_line(content, line, column)
|
|
@@ -497,14 +522,16 @@ def declaration_file(ctx: Context, file_path: str, symbol: str) -> str:
|
|
|
497
522
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
498
523
|
if not rel_path:
|
|
499
524
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
500
|
-
|
|
525
|
+
|
|
526
|
+
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
527
|
+
client.open_file(rel_path)
|
|
528
|
+
orig_file_content = client.get_file_content(rel_path)
|
|
501
529
|
|
|
502
530
|
# Find the first occurence of the symbol (line and column) in the file,
|
|
503
531
|
position = find_start_position(orig_file_content, symbol)
|
|
504
532
|
if not position:
|
|
505
533
|
return f"Symbol `{symbol}` (case sensitive) not found in file `{rel_path}`. Add it first, then try again."
|
|
506
534
|
|
|
507
|
-
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
508
535
|
declaration = client.get_declarations(
|
|
509
536
|
rel_path, position["line"], position["column"]
|
|
510
537
|
)
|
|
@@ -552,8 +579,9 @@ def multi_attempt(
|
|
|
552
579
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
553
580
|
if not rel_path:
|
|
554
581
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
555
|
-
|
|
582
|
+
|
|
556
583
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
584
|
+
client.open_file(rel_path)
|
|
557
585
|
|
|
558
586
|
try:
|
|
559
587
|
client.open_file(rel_path)
|
|
@@ -604,7 +632,7 @@ def run_code(ctx: Context, code: str) -> List[str] | str:
|
|
|
604
632
|
lifespan_context = ctx.request_context.lifespan_context
|
|
605
633
|
lean_project_path = lifespan_context.lean_project_path
|
|
606
634
|
if lean_project_path is None:
|
|
607
|
-
return "No valid Lean project path found. Run another tool (e.g. `
|
|
635
|
+
return "No valid Lean project path found. Run another tool (e.g. `lean_file_contents`) first to set it up."
|
|
608
636
|
|
|
609
637
|
# Use a unique snippet filename to avoid collisions under concurrency
|
|
610
638
|
rel_path = f"_mcp_snippet_{uuid.uuid4().hex}.lean"
|
|
@@ -684,7 +712,7 @@ def local_search(
|
|
|
684
712
|
|
|
685
713
|
stored_root = ctx.request_context.lifespan_context.lean_project_path
|
|
686
714
|
if stored_root is None:
|
|
687
|
-
return "Lean project path not set. Call a file-based tool (like
|
|
715
|
+
return "Lean project path not set. Call a file-based tool (like lean_file_contents) first to set the project path."
|
|
688
716
|
|
|
689
717
|
return lean_local_search(query=query.strip(), limit=limit, project_root=stored_root)
|
|
690
718
|
|
|
@@ -853,8 +881,9 @@ def state_search(
|
|
|
853
881
|
if not rel_path:
|
|
854
882
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
855
883
|
|
|
856
|
-
file_contents = update_file(ctx, rel_path)
|
|
857
884
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
885
|
+
client.open_file(rel_path)
|
|
886
|
+
file_contents = client.get_file_content(rel_path)
|
|
858
887
|
goal = client.get_goal(rel_path, line - 1, column - 1)
|
|
859
888
|
|
|
860
889
|
f_line = format_line(file_contents, line, column)
|
|
@@ -903,8 +932,9 @@ def hammer_premise(
|
|
|
903
932
|
if not rel_path:
|
|
904
933
|
return "Invalid Lean file path: Unable to start LSP server or load file"
|
|
905
934
|
|
|
906
|
-
file_contents = update_file(ctx, rel_path)
|
|
907
935
|
client: LeanLSPClient = ctx.request_context.lifespan_context.client
|
|
936
|
+
client.open_file(rel_path)
|
|
937
|
+
file_contents = client.get_file_content(rel_path)
|
|
908
938
|
goal = client.get_goal(rel_path, line - 1, column - 1)
|
|
909
939
|
|
|
910
940
|
f_line = format_line(file_contents, line, column)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-lsp-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: Lean Theorem Prover MCP
|
|
5
5
|
Author-email: Oliver Dressler <hey@oli.show>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,8 +8,8 @@ Project-URL: Repository, https://github.com/oOo0oOo/lean-lsp-mcp
|
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: leanclient==0.
|
|
12
|
-
Requires-Dist: mcp[cli]==1.
|
|
11
|
+
Requires-Dist: leanclient==0.5.0
|
|
12
|
+
Requires-Dist: mcp[cli]==1.21.0
|
|
13
13
|
Requires-Dist: orjson>=3.11.1
|
|
14
14
|
Provides-Extra: lint
|
|
15
15
|
Requires-Dist: ruff>=0.2.0; extra == "lint"
|
|
@@ -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=hF941DEeRE3ICMgMhv9J4vv6bO4hZPJOAcFU03yIDXs,4988
|
|
4
|
+
lean_lsp_mcp/file_utils.py,sha256=kCTYQSfmV-R2cm_NCi_L8W5Dcsm0_rTOPpTtpyAin78,1365
|
|
5
|
+
lean_lsp_mcp/instructions.py,sha256=y_gHlbeJoKnPohmcSVrQQds6mbBO1en-lxnXAfEypZE,892
|
|
6
|
+
lean_lsp_mcp/search_utils.py,sha256=X2LPynDNLi767UDxbxHpMccOkbnfKJKv_HxvRNxIXM4,3984
|
|
7
|
+
lean_lsp_mcp/server.py,sha256=oxuytxr58QLS-lCVRxYgtlLGEW0uHPhw2gKC5oRChqk,35799
|
|
8
|
+
lean_lsp_mcp/utils.py,sha256=zLu2VIhaX4yocY07F3Z94LB2jRGrkH1ID9SjR3poE9A,8255
|
|
9
|
+
lean_lsp_mcp-0.12.0.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
+
lean_lsp_mcp-0.12.0.dist-info/METADATA,sha256=wMGQwNNQxVIt_hxZZgOZDIJ1E5wQmq2h4x9hRJrBgjw,19626
|
|
11
|
+
lean_lsp_mcp-0.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
lean_lsp_mcp-0.12.0.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
+
lean_lsp_mcp-0.12.0.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
+
lean_lsp_mcp-0.12.0.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=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=X2LPynDNLi767UDxbxHpMccOkbnfKJKv_HxvRNxIXM4,3984
|
|
7
|
-
lean_lsp_mcp/server.py,sha256=knA8HsJZeUNnMqNdv2rnAnS__Eblr0aPnlwKH1FrLbA,34661
|
|
8
|
-
lean_lsp_mcp/utils.py,sha256=zLu2VIhaX4yocY07F3Z94LB2jRGrkH1ID9SjR3poE9A,8255
|
|
9
|
-
lean_lsp_mcp-0.11.2.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
-
lean_lsp_mcp-0.11.2.dist-info/METADATA,sha256=2sGVG-wCFYATL2iKhQ7Twt_6jcvPumAk7P74y_2uCto,19626
|
|
11
|
-
lean_lsp_mcp-0.11.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
lean_lsp_mcp-0.11.2.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
-
lean_lsp_mcp-0.11.2.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
-
lean_lsp_mcp-0.11.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|