lean-lsp-mcp 0.11.2__py3-none-any.whl → 0.11.3__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 -36
- lean_lsp_mcp/server.py +12 -7
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.11.3.dist-info}/METADATA +1 -1
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.11.3.dist-info}/RECORD +8 -8
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.11.3.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.11.3.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.11.3.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.11.2.dist-info → lean_lsp_mcp-0.11.3.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/client_utils.py
CHANGED
|
@@ -64,59 +64,75 @@ def valid_lean_project_path(path: Path | str) -> bool:
|
|
|
64
64
|
return (path_obj / "lean-toolchain").is_file()
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def
|
|
68
|
-
"""
|
|
67
|
+
def infer_project_path(ctx: Context, file_path: str) -> Path | None:
|
|
68
|
+
"""Infer and cache the Lean project path for a file WITHOUT starting the client.
|
|
69
|
+
|
|
70
|
+
Walks up the directory tree to find a lean-toolchain file, caches the result.
|
|
71
|
+
Sets ctx.request_context.lifespan_context.lean_project_path if found.
|
|
72
|
+
|
|
73
|
+
Side effects when path changes:
|
|
74
|
+
- Next LSP tool will restart the client for the new project
|
|
75
|
+
- File content hashes will be cleared
|
|
69
76
|
|
|
77
|
+
Args:
|
|
78
|
+
ctx (Context): Context object
|
|
79
|
+
file_path (str): Absolute or relative path to a Lean file
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Path | None: The resolved project path if found, None otherwise
|
|
83
|
+
"""
|
|
70
84
|
lifespan = ctx.request_context.lifespan_context
|
|
71
|
-
project_cache = getattr(lifespan, "project_cache", {})
|
|
72
85
|
if not hasattr(lifespan, "project_cache"):
|
|
73
|
-
lifespan.project_cache =
|
|
86
|
+
lifespan.project_cache = {}
|
|
74
87
|
|
|
75
88
|
abs_file_path = os.path.abspath(file_path)
|
|
76
89
|
file_dir = os.path.dirname(abs_file_path)
|
|
77
90
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if rel is None:
|
|
91
|
+
def set_project_path(project_path: Path, cache_dirs: list[str]) -> Path | None:
|
|
92
|
+
"""Validate file is in project, set path, update cache."""
|
|
93
|
+
if get_relative_file_path(project_path, file_path) is None:
|
|
82
94
|
return None
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
lifespan.lean_project_path =
|
|
96
|
+
project_path = project_path.resolve()
|
|
97
|
+
lifespan.lean_project_path = project_path
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
for directory in cache_dirs + [str(
|
|
89
|
-
if directory
|
|
90
|
-
|
|
99
|
+
# Update all relevant directories in cache
|
|
100
|
+
for directory in set(cache_dirs + [str(project_path)]):
|
|
101
|
+
if directory:
|
|
102
|
+
lifespan.project_cache[directory] = project_path
|
|
91
103
|
|
|
92
|
-
|
|
93
|
-
project_cache[directory] = project_path_obj
|
|
104
|
+
return project_path
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
# Fast path: current project already valid for this file
|
|
107
|
+
if lifespan.lean_project_path and set_project_path(
|
|
108
|
+
lifespan.lean_project_path, [file_dir]
|
|
109
|
+
):
|
|
110
|
+
return lifespan.lean_project_path
|
|
97
111
|
|
|
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
|
|
112
|
+
# Walk up directory tree using cache and lean-toolchain detection
|
|
106
113
|
current_dir = file_dir
|
|
107
|
-
while current_dir and current_dir !=
|
|
108
|
-
cached_root = project_cache.get(current_dir)
|
|
114
|
+
while current_dir and current_dir != os.path.dirname(current_dir):
|
|
115
|
+
cached_root = lifespan.project_cache.get(current_dir)
|
|
116
|
+
|
|
109
117
|
if cached_root:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return rel_path
|
|
118
|
+
if result := set_project_path(Path(cached_root), [current_dir]):
|
|
119
|
+
return result
|
|
113
120
|
elif valid_lean_project_path(current_dir):
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return rel_path
|
|
121
|
+
if result := set_project_path(Path(current_dir), [current_dir]):
|
|
122
|
+
return result
|
|
117
123
|
else:
|
|
118
|
-
project_cache[current_dir] = ""
|
|
119
|
-
|
|
124
|
+
lifespan.project_cache[current_dir] = "" # Mark as checked
|
|
125
|
+
|
|
120
126
|
current_dir = os.path.dirname(current_dir)
|
|
121
127
|
|
|
122
128
|
return None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def setup_client_for_file(ctx: Context, file_path: str) -> str | None:
|
|
132
|
+
"""Ensure the LSP client matches the file's Lean project and return its relative path."""
|
|
133
|
+
project_path = infer_project_path(ctx, file_path)
|
|
134
|
+
if project_path is None:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
startup_client(ctx)
|
|
138
|
+
return get_relative_file_path(project_path, file_path)
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -18,7 +18,11 @@ 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
|
|
21
|
+
from lean_lsp_mcp.client_utils import (
|
|
22
|
+
setup_client_for_file,
|
|
23
|
+
startup_client,
|
|
24
|
+
infer_project_path,
|
|
25
|
+
)
|
|
22
26
|
from lean_lsp_mcp.file_utils import get_file_contents, update_file
|
|
23
27
|
from lean_lsp_mcp.instructions import INSTRUCTIONS
|
|
24
28
|
from lean_lsp_mcp.search_utils import check_ripgrep_status, lean_local_search
|
|
@@ -154,10 +158,7 @@ async def lsp_build(
|
|
|
154
158
|
ctx.request_context.lifespan_context.lean_project_path = lean_project_path_obj
|
|
155
159
|
|
|
156
160
|
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
|
-
)
|
|
161
|
+
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
162
|
|
|
162
163
|
build_output = ""
|
|
163
164
|
try:
|
|
@@ -247,6 +248,10 @@ def file_contents(ctx: Context, file_path: str, annotate_lines: bool = True) ->
|
|
|
247
248
|
Returns:
|
|
248
249
|
str: File content or error msg
|
|
249
250
|
"""
|
|
251
|
+
# Infer project path but do not start a client
|
|
252
|
+
if file_path.endswith(".lean"):
|
|
253
|
+
infer_project_path(ctx, file_path) # Silently fails for non-project files
|
|
254
|
+
|
|
250
255
|
try:
|
|
251
256
|
data = get_file_contents(file_path)
|
|
252
257
|
except FileNotFoundError:
|
|
@@ -604,7 +609,7 @@ def run_code(ctx: Context, code: str) -> List[str] | str:
|
|
|
604
609
|
lifespan_context = ctx.request_context.lifespan_context
|
|
605
610
|
lean_project_path = lifespan_context.lean_project_path
|
|
606
611
|
if lean_project_path is None:
|
|
607
|
-
return "No valid Lean project path found. Run another tool (e.g. `
|
|
612
|
+
return "No valid Lean project path found. Run another tool (e.g. `lean_file_contents`) first to set it up."
|
|
608
613
|
|
|
609
614
|
# Use a unique snippet filename to avoid collisions under concurrency
|
|
610
615
|
rel_path = f"_mcp_snippet_{uuid.uuid4().hex}.lean"
|
|
@@ -684,7 +689,7 @@ def local_search(
|
|
|
684
689
|
|
|
685
690
|
stored_root = ctx.request_context.lifespan_context.lean_project_path
|
|
686
691
|
if stored_root is None:
|
|
687
|
-
return "Lean project path not set. Call a file-based tool (like
|
|
692
|
+
return "Lean project path not set. Call a file-based tool (like lean_file_contents) first to set the project path."
|
|
688
693
|
|
|
689
694
|
return lean_local_search(query=query.strip(), limit=limit, project_root=stored_root)
|
|
690
695
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
lean_lsp_mcp/__init__.py,sha256=lxqDq0G_sI2iu2Nniy-pTW7BE9Ux7ZXeDoGf0OAWIDc,763
|
|
2
2
|
lean_lsp_mcp/__main__.py,sha256=XnpTzfJc0T-j9tHtdkA8ovTr1c139ffTewcJGhxYDaM,49
|
|
3
|
-
lean_lsp_mcp/client_utils.py,sha256=
|
|
3
|
+
lean_lsp_mcp/client_utils.py,sha256=FNfiEWQagRj9tmo2xUDxbSZYVz5_RfaNH6OApgNQ9ZM,5065
|
|
4
4
|
lean_lsp_mcp/file_utils.py,sha256=qddegF-T5-egZop8dPe_3Cma-3rRSKsAErVDQLecmbE,2916
|
|
5
5
|
lean_lsp_mcp/instructions.py,sha256=y_gHlbeJoKnPohmcSVrQQds6mbBO1en-lxnXAfEypZE,892
|
|
6
6
|
lean_lsp_mcp/search_utils.py,sha256=X2LPynDNLi767UDxbxHpMccOkbnfKJKv_HxvRNxIXM4,3984
|
|
7
|
-
lean_lsp_mcp/server.py,sha256=
|
|
7
|
+
lean_lsp_mcp/server.py,sha256=zwiHeMlgRpERNIF-E_6OKzI8a-YmAx09EVX3QO2gJAk,34792
|
|
8
8
|
lean_lsp_mcp/utils.py,sha256=zLu2VIhaX4yocY07F3Z94LB2jRGrkH1ID9SjR3poE9A,8255
|
|
9
|
-
lean_lsp_mcp-0.11.
|
|
10
|
-
lean_lsp_mcp-0.11.
|
|
11
|
-
lean_lsp_mcp-0.11.
|
|
12
|
-
lean_lsp_mcp-0.11.
|
|
13
|
-
lean_lsp_mcp-0.11.
|
|
14
|
-
lean_lsp_mcp-0.11.
|
|
9
|
+
lean_lsp_mcp-0.11.3.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
+
lean_lsp_mcp-0.11.3.dist-info/METADATA,sha256=1mf98hyaVVN1lZLWMZEdtSV6-nDZ7KAFyVLPRc7gbx8,19626
|
|
11
|
+
lean_lsp_mcp-0.11.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
lean_lsp_mcp-0.11.3.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
+
lean_lsp_mcp-0.11.3.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
+
lean_lsp_mcp-0.11.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|