lean-lsp-mcp 0.16.1__py3-none-any.whl → 0.16.2__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/models.py +77 -0
- lean_lsp_mcp/search_utils.py +121 -27
- lean_lsp_mcp/server.py +37 -34
- {lean_lsp_mcp-0.16.1.dist-info → lean_lsp_mcp-0.16.2.dist-info}/METADATA +3 -4
- {lean_lsp_mcp-0.16.1.dist-info → lean_lsp_mcp-0.16.2.dist-info}/RECORD +9 -9
- {lean_lsp_mcp-0.16.1.dist-info → lean_lsp_mcp-0.16.2.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.16.1.dist-info → lean_lsp_mcp-0.16.2.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.16.1.dist-info → lean_lsp_mcp-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.16.1.dist-info → lean_lsp_mcp-0.16.2.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/models.py
CHANGED
|
@@ -118,3 +118,80 @@ class RunResult(BaseModel):
|
|
|
118
118
|
class DeclarationInfo(BaseModel):
|
|
119
119
|
file_path: str = Field(description="Path to declaration file")
|
|
120
120
|
content: str = Field(description="File content")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# Wrapper models for list-returning tools
|
|
124
|
+
# FastMCP flattens bare lists into separate TextContent blocks, causing serialization issues.
|
|
125
|
+
# Wrapping in a model ensures proper JSON serialization.
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class DiagnosticsResult(BaseModel):
|
|
129
|
+
"""Wrapper for diagnostic messages list."""
|
|
130
|
+
|
|
131
|
+
items: List[DiagnosticMessage] = Field(
|
|
132
|
+
default_factory=list, description="List of diagnostic messages"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class CompletionsResult(BaseModel):
|
|
137
|
+
"""Wrapper for completions list."""
|
|
138
|
+
|
|
139
|
+
items: List[CompletionItem] = Field(
|
|
140
|
+
default_factory=list, description="List of completion items"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class MultiAttemptResult(BaseModel):
|
|
145
|
+
"""Wrapper for multi-attempt results list."""
|
|
146
|
+
|
|
147
|
+
items: List[AttemptResult] = Field(
|
|
148
|
+
default_factory=list, description="List of attempt results"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class LocalSearchResults(BaseModel):
|
|
153
|
+
"""Wrapper for local search results list."""
|
|
154
|
+
|
|
155
|
+
items: List[LocalSearchResult] = Field(
|
|
156
|
+
default_factory=list, description="List of local search results"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class LeanSearchResults(BaseModel):
|
|
161
|
+
"""Wrapper for LeanSearch results list."""
|
|
162
|
+
|
|
163
|
+
items: List[LeanSearchResult] = Field(
|
|
164
|
+
default_factory=list, description="List of LeanSearch results"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class LoogleResults(BaseModel):
|
|
169
|
+
"""Wrapper for Loogle results list."""
|
|
170
|
+
|
|
171
|
+
items: List[LoogleResult] = Field(
|
|
172
|
+
default_factory=list, description="List of Loogle results"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class LeanFinderResults(BaseModel):
|
|
177
|
+
"""Wrapper for Lean Finder results list."""
|
|
178
|
+
|
|
179
|
+
items: List[LeanFinderResult] = Field(
|
|
180
|
+
default_factory=list, description="List of Lean Finder results"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class StateSearchResults(BaseModel):
|
|
185
|
+
"""Wrapper for state search results list."""
|
|
186
|
+
|
|
187
|
+
items: List[StateSearchResult] = Field(
|
|
188
|
+
default_factory=list, description="List of state search results"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class PremiseResults(BaseModel):
|
|
193
|
+
"""Wrapper for premise results list."""
|
|
194
|
+
|
|
195
|
+
items: List[PremiseResult] = Field(
|
|
196
|
+
default_factory=list, description="List of premise results"
|
|
197
|
+
)
|
lean_lsp_mcp/search_utils.py
CHANGED
|
@@ -8,6 +8,7 @@ import platform
|
|
|
8
8
|
import re
|
|
9
9
|
import shutil
|
|
10
10
|
import subprocess
|
|
11
|
+
import threading
|
|
11
12
|
from orjson import loads as _json_loads
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
@@ -27,6 +28,21 @@ _PLATFORM_INSTRUCTIONS: dict[str, Iterable[str]] = {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def _create_ripgrep_process(command: list[str], *, cwd: str) -> subprocess.Popen[str]:
|
|
32
|
+
"""Spawn ripgrep and return a process with line-streaming stdout.
|
|
33
|
+
|
|
34
|
+
Separated for test monkeypatching and to allow early termination once we
|
|
35
|
+
have enough matches.
|
|
36
|
+
"""
|
|
37
|
+
return subprocess.Popen(
|
|
38
|
+
command,
|
|
39
|
+
stdout=subprocess.PIPE,
|
|
40
|
+
stderr=subprocess.PIPE,
|
|
41
|
+
text=True,
|
|
42
|
+
cwd=cwd,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
30
46
|
def check_ripgrep_status() -> tuple[bool, str]:
|
|
31
47
|
"""Check whether ``rg`` is available on PATH and return status + message."""
|
|
32
48
|
|
|
@@ -84,38 +100,116 @@ def lean_local_search(
|
|
|
84
100
|
if lean_src := _get_lean_src_search_path():
|
|
85
101
|
command.append(lean_src)
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
matches = []
|
|
90
|
-
for line in result.stdout.splitlines():
|
|
91
|
-
if not line or (event := _json_loads(line)).get("type") != "match":
|
|
92
|
-
continue
|
|
103
|
+
process = _create_ripgrep_process(command, cwd=str(root))
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
abs_path = (
|
|
102
|
-
file_path if file_path.is_absolute() else (root / file_path).resolve()
|
|
103
|
-
)
|
|
105
|
+
matches: list[dict[str, str]] = []
|
|
106
|
+
stderr_text = ""
|
|
107
|
+
terminated_early = False
|
|
108
|
+
stderr_chunks: list[str] = []
|
|
109
|
+
stderr_chars = 0
|
|
110
|
+
stderr_truncated = False
|
|
111
|
+
max_stderr_chars = 100_000
|
|
104
112
|
|
|
113
|
+
def _drain_stderr(pipe) -> None:
|
|
114
|
+
nonlocal stderr_chars, stderr_truncated
|
|
105
115
|
try:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
for err_line in pipe:
|
|
117
|
+
if stderr_chars < max_stderr_chars:
|
|
118
|
+
stderr_chunks.append(err_line)
|
|
119
|
+
stderr_chars += len(err_line)
|
|
120
|
+
else:
|
|
121
|
+
stderr_truncated = True
|
|
122
|
+
except Exception:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
stderr_thread: threading.Thread | None = None
|
|
126
|
+
if process.stderr is not None:
|
|
127
|
+
stderr_thread = threading.Thread(
|
|
128
|
+
target=_drain_stderr,
|
|
129
|
+
args=(process.stderr,),
|
|
130
|
+
name="lean-local-search-rg-stderr",
|
|
131
|
+
daemon=True,
|
|
132
|
+
)
|
|
133
|
+
stderr_thread.start()
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
|
|
135
|
+
try:
|
|
136
|
+
stdout = process.stdout
|
|
137
|
+
if stdout is None:
|
|
138
|
+
raise RuntimeError("ripgrep did not provide stdout pipe")
|
|
139
|
+
|
|
140
|
+
for line in stdout:
|
|
141
|
+
if not line or (event := _json_loads(line)).get("type") != "match":
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
data = event["data"]
|
|
145
|
+
parts = data["lines"]["text"].lstrip().split(maxsplit=2)
|
|
146
|
+
if len(parts) < 2:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
decl_kind, decl_name = parts[0], parts[1].rstrip(":")
|
|
150
|
+
file_path = Path(data["path"]["text"])
|
|
151
|
+
abs_path = (
|
|
152
|
+
file_path if file_path.is_absolute() else (root / file_path).resolve()
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
display_path = str(abs_path.relative_to(root))
|
|
157
|
+
except ValueError:
|
|
158
|
+
display_path = str(file_path)
|
|
159
|
+
|
|
160
|
+
matches.append({"name": decl_name, "kind": decl_kind, "file": display_path})
|
|
161
|
+
|
|
162
|
+
if len(matches) >= limit:
|
|
163
|
+
terminated_early = True
|
|
164
|
+
try:
|
|
165
|
+
process.terminate()
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
break
|
|
114
169
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
170
|
+
try:
|
|
171
|
+
if terminated_early:
|
|
172
|
+
process.wait(timeout=5)
|
|
173
|
+
else:
|
|
174
|
+
process.wait()
|
|
175
|
+
except subprocess.TimeoutExpired:
|
|
176
|
+
process.kill()
|
|
177
|
+
process.wait()
|
|
178
|
+
finally:
|
|
179
|
+
if process.returncode is None:
|
|
180
|
+
try:
|
|
181
|
+
process.terminate()
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
try:
|
|
185
|
+
process.wait(timeout=5)
|
|
186
|
+
except Exception:
|
|
187
|
+
try:
|
|
188
|
+
process.kill()
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
try:
|
|
192
|
+
process.wait(timeout=5)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
if stderr_thread is not None:
|
|
196
|
+
stderr_thread.join(timeout=1)
|
|
197
|
+
if process.stdout is not None:
|
|
198
|
+
process.stdout.close()
|
|
199
|
+
if process.stderr is not None:
|
|
200
|
+
process.stderr.close()
|
|
201
|
+
|
|
202
|
+
if stderr_chunks:
|
|
203
|
+
stderr_text = "".join(stderr_chunks)
|
|
204
|
+
if stderr_truncated:
|
|
205
|
+
stderr_text += "\n[stderr truncated]"
|
|
206
|
+
|
|
207
|
+
returncode = process.returncode if process.returncode is not None else 0
|
|
208
|
+
|
|
209
|
+
if returncode not in (0, 1) and not matches:
|
|
210
|
+
error_msg = f"ripgrep exited with code {returncode}"
|
|
211
|
+
if stderr_text:
|
|
212
|
+
error_msg += f"\n{stderr_text}"
|
|
119
213
|
raise RuntimeError(error_msg)
|
|
120
214
|
|
|
121
215
|
return matches
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -12,7 +12,7 @@ import functools
|
|
|
12
12
|
import uuid
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
-
from pydantic import
|
|
15
|
+
from pydantic import Field
|
|
16
16
|
from mcp.server.fastmcp import Context, FastMCP
|
|
17
17
|
from mcp.server.fastmcp.utilities.logging import get_logger, configure_logging
|
|
18
18
|
from mcp.server.auth.settings import AuthSettings
|
|
@@ -46,6 +46,16 @@ from lean_lsp_mcp.models import (
|
|
|
46
46
|
BuildResult,
|
|
47
47
|
RunResult,
|
|
48
48
|
DeclarationInfo,
|
|
49
|
+
# Wrapper models for list-returning tools
|
|
50
|
+
DiagnosticsResult,
|
|
51
|
+
CompletionsResult,
|
|
52
|
+
MultiAttemptResult,
|
|
53
|
+
LocalSearchResults,
|
|
54
|
+
LeanSearchResults,
|
|
55
|
+
LoogleResults,
|
|
56
|
+
LeanFinderResults,
|
|
57
|
+
StateSearchResults,
|
|
58
|
+
PremiseResults,
|
|
49
59
|
)
|
|
50
60
|
from lean_lsp_mcp.utils import (
|
|
51
61
|
COMPLETION_KIND,
|
|
@@ -67,13 +77,6 @@ class LeanToolError(Exception):
|
|
|
67
77
|
pass
|
|
68
78
|
|
|
69
79
|
|
|
70
|
-
def _to_json_array(items: List[BaseModel]) -> str:
|
|
71
|
-
"""Serialize list of models as JSON array (avoids FastMCP list flattening)."""
|
|
72
|
-
return orjson.dumps(
|
|
73
|
-
[item.model_dump() for item in items], option=orjson.OPT_INDENT_2
|
|
74
|
-
).decode()
|
|
75
|
-
|
|
76
|
-
|
|
77
80
|
_LOG_LEVEL = os.environ.get("LEAN_LOG_LEVEL", "INFO")
|
|
78
81
|
configure_logging("CRITICAL" if _LOG_LEVEL == "NONE" else _LOG_LEVEL)
|
|
79
82
|
logger = get_logger(__name__)
|
|
@@ -414,7 +417,7 @@ def diagnostic_messages(
|
|
|
414
417
|
declaration_name: Annotated[
|
|
415
418
|
Optional[str], Field(description="Filter to declaration (slow)")
|
|
416
419
|
] = None,
|
|
417
|
-
) ->
|
|
420
|
+
) -> DiagnosticsResult:
|
|
418
421
|
"""Get compiler diagnostics (errors, warnings, infos) for a Lean file."""
|
|
419
422
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
420
423
|
if not rel_path:
|
|
@@ -443,7 +446,7 @@ def diagnostic_messages(
|
|
|
443
446
|
inactivity_timeout=15.0,
|
|
444
447
|
)
|
|
445
448
|
|
|
446
|
-
return
|
|
449
|
+
return DiagnosticsResult(items=_to_diagnostic_messages(diagnostics))
|
|
447
450
|
|
|
448
451
|
|
|
449
452
|
@mcp.tool(
|
|
@@ -610,7 +613,7 @@ def completions(
|
|
|
610
613
|
line: Annotated[int, Field(description="Line number (1-indexed)", ge=1)],
|
|
611
614
|
column: Annotated[int, Field(description="Column number (1-indexed)", ge=1)],
|
|
612
615
|
max_completions: Annotated[int, Field(description="Max completions", ge=1)] = 32,
|
|
613
|
-
) ->
|
|
616
|
+
) -> CompletionsResult:
|
|
614
617
|
"""Get IDE autocompletions. Use on INCOMPLETE code (after `.` or partial name)."""
|
|
615
618
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
616
619
|
if not rel_path:
|
|
@@ -639,7 +642,7 @@ def completions(
|
|
|
639
642
|
)
|
|
640
643
|
|
|
641
644
|
if not items:
|
|
642
|
-
return
|
|
645
|
+
return CompletionsResult(items=[])
|
|
643
646
|
|
|
644
647
|
# Find the sort term: The last word/identifier before the cursor
|
|
645
648
|
lines = content.splitlines()
|
|
@@ -666,7 +669,7 @@ def completions(
|
|
|
666
669
|
items.sort(key=lambda x: x.label.lower())
|
|
667
670
|
|
|
668
671
|
# Truncate if too many results
|
|
669
|
-
return
|
|
672
|
+
return CompletionsResult(items=items[:max_completions])
|
|
670
673
|
|
|
671
674
|
|
|
672
675
|
@mcp.tool(
|
|
@@ -741,7 +744,7 @@ def multi_attempt(
|
|
|
741
744
|
snippets: Annotated[
|
|
742
745
|
List[str], Field(description="Tactics to try (3+ recommended)")
|
|
743
746
|
],
|
|
744
|
-
) ->
|
|
747
|
+
) -> MultiAttemptResult:
|
|
745
748
|
"""Try multiple tactics without modifying file. Returns goal state for each."""
|
|
746
749
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
747
750
|
if not rel_path:
|
|
@@ -753,8 +756,6 @@ def multi_attempt(
|
|
|
753
756
|
client.open_file(rel_path)
|
|
754
757
|
|
|
755
758
|
try:
|
|
756
|
-
client.open_file(rel_path)
|
|
757
|
-
|
|
758
759
|
results: List[AttemptResult] = []
|
|
759
760
|
# Avoid mutating caller-provided snippets; normalize locally per attempt
|
|
760
761
|
for snippet in snippets:
|
|
@@ -781,7 +782,7 @@ def multi_attempt(
|
|
|
781
782
|
)
|
|
782
783
|
)
|
|
783
784
|
|
|
784
|
-
return
|
|
785
|
+
return MultiAttemptResult(items=results)
|
|
785
786
|
finally:
|
|
786
787
|
try:
|
|
787
788
|
client.close_files([rel_path])
|
|
@@ -878,7 +879,7 @@ def local_search(
|
|
|
878
879
|
project_root: Annotated[
|
|
879
880
|
Optional[str], Field(description="Project root (inferred if omitted)")
|
|
880
881
|
] = None,
|
|
881
|
-
) ->
|
|
882
|
+
) -> LocalSearchResults:
|
|
882
883
|
"""Fast local search to verify declarations exist. Use BEFORE trying a lemma name."""
|
|
883
884
|
if not _RG_AVAILABLE:
|
|
884
885
|
raise LocalSearchError(_RG_MESSAGE)
|
|
@@ -910,7 +911,7 @@ def local_search(
|
|
|
910
911
|
LocalSearchResult(name=r["name"], kind=r["kind"], file=r["file"])
|
|
911
912
|
for r in raw_results
|
|
912
913
|
]
|
|
913
|
-
return
|
|
914
|
+
return LocalSearchResults(items=results)
|
|
914
915
|
except RuntimeError as exc:
|
|
915
916
|
raise LocalSearchError(f"Search failed: {exc}")
|
|
916
917
|
|
|
@@ -929,7 +930,7 @@ def leansearch(
|
|
|
929
930
|
ctx: Context,
|
|
930
931
|
query: Annotated[str, Field(description="Natural language or Lean term query")],
|
|
931
932
|
num_results: Annotated[int, Field(description="Max results", ge=1)] = 5,
|
|
932
|
-
) ->
|
|
933
|
+
) -> LeanSearchResults:
|
|
933
934
|
"""Search Mathlib via leansearch.net using natural language.
|
|
934
935
|
|
|
935
936
|
Examples: "sum of two even numbers is even", "Cauchy-Schwarz inequality",
|
|
@@ -949,7 +950,7 @@ def leansearch(
|
|
|
949
950
|
results = orjson.loads(response.read())
|
|
950
951
|
|
|
951
952
|
if not results or not results[0]:
|
|
952
|
-
return
|
|
953
|
+
return LeanSearchResults(items=[])
|
|
953
954
|
|
|
954
955
|
raw_results = [r["result"] for r in results[0][:num_results]]
|
|
955
956
|
items = [
|
|
@@ -961,7 +962,7 @@ def leansearch(
|
|
|
961
962
|
)
|
|
962
963
|
for r in raw_results
|
|
963
964
|
]
|
|
964
|
-
return
|
|
965
|
+
return LeanSearchResults(items=items)
|
|
965
966
|
|
|
966
967
|
|
|
967
968
|
@mcp.tool(
|
|
@@ -979,7 +980,7 @@ async def loogle(
|
|
|
979
980
|
str, Field(description="Type pattern, constant, or name substring")
|
|
980
981
|
],
|
|
981
982
|
num_results: Annotated[int, Field(description="Max results", ge=1)] = 8,
|
|
982
|
-
) ->
|
|
983
|
+
) -> LoogleResults:
|
|
983
984
|
"""Search Mathlib by type signature via loogle.lean-lang.org.
|
|
984
985
|
|
|
985
986
|
Examples: `Real.sin`, `"comm"`, `(?a → ?b) → List ?a → List ?b`,
|
|
@@ -992,7 +993,7 @@ async def loogle(
|
|
|
992
993
|
try:
|
|
993
994
|
results = await app_ctx.loogle_manager.query(query, num_results)
|
|
994
995
|
if not results:
|
|
995
|
-
return
|
|
996
|
+
return LoogleResults(items=[])
|
|
996
997
|
items = [
|
|
997
998
|
LoogleResult(
|
|
998
999
|
name=r.get("name", ""),
|
|
@@ -1001,7 +1002,7 @@ async def loogle(
|
|
|
1001
1002
|
)
|
|
1002
1003
|
for r in results
|
|
1003
1004
|
]
|
|
1004
|
-
return
|
|
1005
|
+
return LoogleResults(items=items)
|
|
1005
1006
|
except Exception as e:
|
|
1006
1007
|
logger.warning(f"Local loogle failed: {e}, falling back to remote")
|
|
1007
1008
|
|
|
@@ -1010,13 +1011,15 @@ async def loogle(
|
|
|
1010
1011
|
now = int(time.time())
|
|
1011
1012
|
rate_limit[:] = [t for t in rate_limit if now - t < 30]
|
|
1012
1013
|
if len(rate_limit) >= 3:
|
|
1013
|
-
|
|
1014
|
+
raise LeanToolError(
|
|
1015
|
+
"Rate limit exceeded: 3 requests per 30s. Use --loogle-local to avoid limits."
|
|
1016
|
+
)
|
|
1014
1017
|
rate_limit.append(now)
|
|
1015
1018
|
|
|
1016
1019
|
result = loogle_remote(query, num_results)
|
|
1017
1020
|
if isinstance(result, str):
|
|
1018
|
-
|
|
1019
|
-
return
|
|
1021
|
+
raise LeanToolError(result) # Error message from remote
|
|
1022
|
+
return LoogleResults(items=result)
|
|
1020
1023
|
|
|
1021
1024
|
|
|
1022
1025
|
@mcp.tool(
|
|
@@ -1033,7 +1036,7 @@ def leanfinder(
|
|
|
1033
1036
|
ctx: Context,
|
|
1034
1037
|
query: Annotated[str, Field(description="Mathematical concept or proof state")],
|
|
1035
1038
|
num_results: Annotated[int, Field(description="Max results", ge=1)] = 5,
|
|
1036
|
-
) ->
|
|
1039
|
+
) -> LeanFinderResults:
|
|
1037
1040
|
"""Semantic search by mathematical meaning via Lean Finder.
|
|
1038
1041
|
|
|
1039
1042
|
Examples: "commutativity of addition on natural numbers",
|
|
@@ -1065,7 +1068,7 @@ def leanfinder(
|
|
|
1065
1068
|
)
|
|
1066
1069
|
)
|
|
1067
1070
|
|
|
1068
|
-
return
|
|
1071
|
+
return LeanFinderResults(items=results)
|
|
1069
1072
|
|
|
1070
1073
|
|
|
1071
1074
|
@mcp.tool(
|
|
@@ -1084,7 +1087,7 @@ def state_search(
|
|
|
1084
1087
|
line: Annotated[int, Field(description="Line number (1-indexed)", ge=1)],
|
|
1085
1088
|
column: Annotated[int, Field(description="Column number (1-indexed)", ge=1)],
|
|
1086
1089
|
num_results: Annotated[int, Field(description="Max results", ge=1)] = 5,
|
|
1087
|
-
) ->
|
|
1090
|
+
) -> StateSearchResults:
|
|
1088
1091
|
"""Find lemmas to close the goal at a position. Searches premise-search.com."""
|
|
1089
1092
|
rel_path = setup_client_for_file(ctx, file_path)
|
|
1090
1093
|
if not rel_path:
|
|
@@ -1114,7 +1117,7 @@ def state_search(
|
|
|
1114
1117
|
results = orjson.loads(response.read())
|
|
1115
1118
|
|
|
1116
1119
|
items = [StateSearchResult(name=r["name"]) for r in results]
|
|
1117
|
-
return
|
|
1120
|
+
return StateSearchResults(items=items)
|
|
1118
1121
|
|
|
1119
1122
|
|
|
1120
1123
|
@mcp.tool(
|
|
@@ -1133,7 +1136,7 @@ def hammer_premise(
|
|
|
1133
1136
|
line: Annotated[int, Field(description="Line number (1-indexed)", ge=1)],
|
|
1134
1137
|
column: Annotated[int, Field(description="Column number (1-indexed)", ge=1)],
|
|
1135
1138
|
num_results: Annotated[int, Field(description="Max results", ge=1)] = 32,
|
|
1136
|
-
) ->
|
|
1139
|
+
) -> PremiseResults:
|
|
1137
1140
|
"""Get premise suggestions for automation tactics at a goal position.
|
|
1138
1141
|
|
|
1139
1142
|
Returns lemma names to try with `simp only [...]`, `aesop`, or as hints.
|
|
@@ -1174,7 +1177,7 @@ def hammer_premise(
|
|
|
1174
1177
|
results = orjson.loads(response.read())
|
|
1175
1178
|
|
|
1176
1179
|
items = [PremiseResult(name=r["name"]) for r in results]
|
|
1177
|
-
return
|
|
1180
|
+
return PremiseResults(items=items)
|
|
1178
1181
|
|
|
1179
1182
|
|
|
1180
1183
|
if __name__ == "__main__":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-lsp-mcp
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.2
|
|
4
4
|
Summary: Lean Theorem Prover MCP
|
|
5
5
|
Author-email: Oliver Dressler <hey@oli.show>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,9 +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.6.
|
|
12
|
-
Requires-Dist: mcp[cli]==1.
|
|
13
|
-
Requires-Dist: mcp[cli]>=1.22.0
|
|
11
|
+
Requires-Dist: leanclient==0.6.2
|
|
12
|
+
Requires-Dist: mcp[cli]==1.24.0
|
|
14
13
|
Requires-Dist: orjson>=3.11.1
|
|
15
14
|
Provides-Extra: lint
|
|
16
15
|
Requires-Dist: ruff>=0.2.0; extra == "lint"
|
|
@@ -4,14 +4,14 @@ lean_lsp_mcp/client_utils.py,sha256=HgPuB35rMitn2Xm8SCAErsFLq15trB6VMz3FDFgmPd8,
|
|
|
4
4
|
lean_lsp_mcp/file_utils.py,sha256=kCTYQSfmV-R2cm_NCi_L8W5Dcsm0_rTOPpTtpyAin78,1365
|
|
5
5
|
lean_lsp_mcp/instructions.py,sha256=S1y834V8v-SFSYJlxxy6Dj-Z0szMyEBT5SkEyM6Npr8,1756
|
|
6
6
|
lean_lsp_mcp/loogle.py,sha256=ChybtPM8jOxP8s28358yNqcLiYvGlQqkAEFFLzR87Zw,11971
|
|
7
|
-
lean_lsp_mcp/models.py,sha256=
|
|
7
|
+
lean_lsp_mcp/models.py,sha256=gDfyAX09YzKtjpKzuo6JtA2mNDc9pRWJ7iT44nHwi94,6326
|
|
8
8
|
lean_lsp_mcp/outline_utils.py,sha256=-eoZNbx2eaKaYmuyFJnwUMWP8I9YXNWusue_2OYpDBM,10981
|
|
9
|
-
lean_lsp_mcp/search_utils.py,sha256=
|
|
10
|
-
lean_lsp_mcp/server.py,sha256=
|
|
9
|
+
lean_lsp_mcp/search_utils.py,sha256=MLqKGe4bhEvyfFLIBCmiDxkbcH4O5J3vl9mWnRSb_v0,6801
|
|
10
|
+
lean_lsp_mcp/server.py,sha256=AvjzoS8lwomUtIP2wrBln4z28-cXzCf1hNgXd9O1w4E,39749
|
|
11
11
|
lean_lsp_mcp/utils.py,sha256=355kzyB3dkwU7_4Mfcg--JXEorFaE2gtqs6-HbH5rRE,11722
|
|
12
|
-
lean_lsp_mcp-0.16.
|
|
13
|
-
lean_lsp_mcp-0.16.
|
|
14
|
-
lean_lsp_mcp-0.16.
|
|
15
|
-
lean_lsp_mcp-0.16.
|
|
16
|
-
lean_lsp_mcp-0.16.
|
|
17
|
-
lean_lsp_mcp-0.16.
|
|
12
|
+
lean_lsp_mcp-0.16.2.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
13
|
+
lean_lsp_mcp-0.16.2.dist-info/METADATA,sha256=Wrhb1l5m-Up77bQyegUdesd0k1ryWhe0C6dIHIWJ5mM,20787
|
|
14
|
+
lean_lsp_mcp-0.16.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
15
|
+
lean_lsp_mcp-0.16.2.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
16
|
+
lean_lsp_mcp-0.16.2.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
17
|
+
lean_lsp_mcp-0.16.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|