lean-lsp-mcp 0.9.1__py3-none-any.whl → 0.10.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 +21 -22
- lean_lsp_mcp/file_utils.py +23 -15
- lean_lsp_mcp/server.py +14 -17
- lean_lsp_mcp/utils.py +3 -1
- {lean_lsp_mcp-0.9.1.dist-info → lean_lsp_mcp-0.10.1.dist-info}/METADATA +139 -161
- lean_lsp_mcp-0.10.1.dist-info/RECORD +14 -0
- lean_lsp_mcp-0.9.1.dist-info/RECORD +0 -14
- {lean_lsp_mcp-0.9.1.dist-info → lean_lsp_mcp-0.10.1.dist-info}/WHEEL +0 -0
- {lean_lsp_mcp-0.9.1.dist-info → lean_lsp_mcp-0.10.1.dist-info}/entry_points.txt +0 -0
- {lean_lsp_mcp-0.9.1.dist-info → lean_lsp_mcp-0.10.1.dist-info}/licenses/LICENSE +0 -0
- {lean_lsp_mcp-0.9.1.dist-info → lean_lsp_mcp-0.10.1.dist-info}/top_level.txt +0 -0
lean_lsp_mcp/client_utils.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from mcp.server.fastmcp import Context
|
|
4
4
|
from mcp.server.fastmcp.utilities.logging import get_logger
|
|
@@ -25,38 +25,39 @@ def startup_client(ctx: Context):
|
|
|
25
25
|
client: LeanLSPClient | None = ctx.request_context.lifespan_context.client
|
|
26
26
|
|
|
27
27
|
if client is not None:
|
|
28
|
+
# Both are Path objects now, direct comparison works
|
|
28
29
|
if client.project_path == lean_project_path:
|
|
29
|
-
return
|
|
30
|
+
return # Client already set up correctly - reuse it!
|
|
31
|
+
# Different project path - close old client
|
|
30
32
|
client.close()
|
|
31
33
|
ctx.request_context.lifespan_context.file_content_hashes.clear()
|
|
32
34
|
|
|
35
|
+
# Need to create a new client
|
|
33
36
|
with OutputCapture() as output:
|
|
34
37
|
try:
|
|
35
|
-
client = LeanLSPClient(
|
|
36
|
-
lean_project_path, initial_build=True, print_warnings=False
|
|
37
|
-
)
|
|
38
|
+
client = LeanLSPClient(lean_project_path)
|
|
38
39
|
logger.info(f"Connected to Lean language server at {lean_project_path}")
|
|
39
40
|
except Exception as e:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
logger.warning(f"Initial connection failed, trying with build: {e}")
|
|
42
|
+
client = LeanLSPClient(lean_project_path, initial_build=True)
|
|
43
|
+
logger.info(f"Connected with initial build to {lean_project_path}")
|
|
44
|
+
build_output = output.get_output()
|
|
45
|
+
if build_output:
|
|
46
|
+
logger.debug(f"Build output: {build_output}")
|
|
45
47
|
ctx.request_context.lifespan_context.client = client
|
|
46
48
|
|
|
47
49
|
|
|
48
|
-
def valid_lean_project_path(path: str) -> bool:
|
|
50
|
+
def valid_lean_project_path(path: Path | str) -> bool:
|
|
49
51
|
"""Check if the given path is a valid Lean project path (contains a lean-toolchain file).
|
|
50
52
|
|
|
51
53
|
Args:
|
|
52
|
-
path (str): Absolute path to check.
|
|
54
|
+
path (Path | str): Absolute path to check.
|
|
53
55
|
|
|
54
56
|
Returns:
|
|
55
57
|
bool: True if valid Lean project path, False otherwise.
|
|
56
58
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return os.path.isfile(os.path.join(path, "lean-toolchain"))
|
|
59
|
+
path_obj = Path(path) if isinstance(path, str) else path
|
|
60
|
+
return (path_obj / "lean-toolchain").is_file()
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def setup_client_for_file(ctx: Context, file_path: str) -> str | None:
|
|
@@ -77,12 +78,12 @@ def setup_client_for_file(ctx: Context, file_path: str) -> str | None:
|
|
|
77
78
|
startup_client(ctx)
|
|
78
79
|
return rel_path
|
|
79
80
|
|
|
80
|
-
# Try to find the
|
|
81
|
-
|
|
81
|
+
# Try to find the correct project path by checking all directories in file_path.
|
|
82
|
+
file_path_obj = Path(file_path)
|
|
82
83
|
rel_path = None
|
|
83
|
-
|
|
84
|
-
if valid_lean_project_path(
|
|
85
|
-
lean_project_path =
|
|
84
|
+
for parent in file_path_obj.parents:
|
|
85
|
+
if valid_lean_project_path(parent):
|
|
86
|
+
lean_project_path = parent
|
|
86
87
|
rel_path = get_relative_file_path(lean_project_path, file_path)
|
|
87
88
|
if rel_path is not None:
|
|
88
89
|
ctx.request_context.lifespan_context.lean_project_path = (
|
|
@@ -90,7 +91,5 @@ def setup_client_for_file(ctx: Context, file_path: str) -> str | None:
|
|
|
90
91
|
)
|
|
91
92
|
startup_client(ctx)
|
|
92
93
|
break
|
|
93
|
-
# Move up one directory
|
|
94
|
-
file_dir = os.path.dirname(file_dir)
|
|
95
94
|
|
|
96
95
|
return rel_path
|
lean_lsp_mcp/file_utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from typing import Optional, Dict
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
from mcp.server.fastmcp import Context
|
|
5
6
|
from mcp.server.fastmcp.utilities.logging import get_logger
|
|
@@ -9,30 +10,39 @@ from leanclient import LeanLSPClient
|
|
|
9
10
|
logger = get_logger(__name__)
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
def get_relative_file_path(lean_project_path:
|
|
13
|
+
def get_relative_file_path(lean_project_path: Path, file_path: str) -> Optional[str]:
|
|
13
14
|
"""Convert path relative to project path.
|
|
14
15
|
|
|
15
16
|
Args:
|
|
16
|
-
lean_project_path (
|
|
17
|
+
lean_project_path (Path): Path to the Lean project root.
|
|
17
18
|
file_path (str): File path.
|
|
18
19
|
|
|
19
20
|
Returns:
|
|
20
21
|
str: Relative file path.
|
|
21
22
|
"""
|
|
23
|
+
file_path_obj = Path(file_path)
|
|
24
|
+
|
|
22
25
|
# Check if absolute path
|
|
23
|
-
if
|
|
24
|
-
|
|
26
|
+
if file_path_obj.is_absolute() and file_path_obj.exists():
|
|
27
|
+
try:
|
|
28
|
+
return str(file_path_obj.relative_to(lean_project_path))
|
|
29
|
+
except ValueError:
|
|
30
|
+
# File is not in this project
|
|
31
|
+
return None
|
|
25
32
|
|
|
26
33
|
# Check if relative to project path
|
|
27
|
-
path =
|
|
28
|
-
if
|
|
29
|
-
return
|
|
34
|
+
path = lean_project_path / file_path
|
|
35
|
+
if path.exists():
|
|
36
|
+
return str(path.relative_to(lean_project_path))
|
|
30
37
|
|
|
31
38
|
# Check if relative to CWD
|
|
32
|
-
cwd =
|
|
33
|
-
path =
|
|
34
|
-
if
|
|
35
|
-
|
|
39
|
+
cwd = Path.cwd()
|
|
40
|
+
path = cwd / file_path
|
|
41
|
+
if path.exists():
|
|
42
|
+
try:
|
|
43
|
+
return str(path.relative_to(lean_project_path))
|
|
44
|
+
except ValueError:
|
|
45
|
+
return None
|
|
36
46
|
|
|
37
47
|
return None
|
|
38
48
|
|
|
@@ -58,10 +68,8 @@ def update_file(ctx: Context, rel_path: str) -> str:
|
|
|
58
68
|
str: Updated file contents.
|
|
59
69
|
"""
|
|
60
70
|
# Get file contents and hash
|
|
61
|
-
abs_path =
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
file_content = get_file_contents(abs_path)
|
|
71
|
+
abs_path = ctx.request_context.lifespan_context.lean_project_path / rel_path
|
|
72
|
+
file_content = get_file_contents(str(abs_path))
|
|
65
73
|
hashed_file = hash(file_content)
|
|
66
74
|
|
|
67
75
|
# Check if file_contents have changed
|
lean_lsp_mcp/server.py
CHANGED
|
@@ -43,7 +43,7 @@ _RG_AVAILABLE, _RG_MESSAGE = check_ripgrep_status()
|
|
|
43
43
|
# Server and context
|
|
44
44
|
@dataclass
|
|
45
45
|
class AppContext:
|
|
46
|
-
lean_project_path:
|
|
46
|
+
lean_project_path: Path | None
|
|
47
47
|
client: LeanLSPClient | None
|
|
48
48
|
file_content_hashes: Dict[str, str]
|
|
49
49
|
rate_limit: Dict[str, List[int]]
|
|
@@ -53,11 +53,11 @@ class AppContext:
|
|
|
53
53
|
@asynccontextmanager
|
|
54
54
|
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
|
55
55
|
try:
|
|
56
|
-
|
|
57
|
-
if not
|
|
56
|
+
lean_project_path_str = os.environ.get("LEAN_PROJECT_PATH", "").strip()
|
|
57
|
+
if not lean_project_path_str:
|
|
58
58
|
lean_project_path = None
|
|
59
59
|
else:
|
|
60
|
-
lean_project_path =
|
|
60
|
+
lean_project_path = Path(lean_project_path_str).resolve()
|
|
61
61
|
|
|
62
62
|
context = AppContext(
|
|
63
63
|
lean_project_path=lean_project_path,
|
|
@@ -136,10 +136,10 @@ def lsp_build(ctx: Context, lean_project_path: str = None, clean: bool = False)
|
|
|
136
136
|
str: Build output or error msg
|
|
137
137
|
"""
|
|
138
138
|
if not lean_project_path:
|
|
139
|
-
|
|
139
|
+
lean_project_path_obj = ctx.request_context.lifespan_context.lean_project_path
|
|
140
140
|
else:
|
|
141
|
-
|
|
142
|
-
ctx.request_context.lifespan_context.lean_project_path =
|
|
141
|
+
lean_project_path_obj = Path(lean_project_path).resolve()
|
|
142
|
+
ctx.request_context.lifespan_context.lean_project_path = lean_project_path_obj
|
|
143
143
|
|
|
144
144
|
build_output = ""
|
|
145
145
|
try:
|
|
@@ -149,15 +149,11 @@ def lsp_build(ctx: Context, lean_project_path: str = None, clean: bool = False)
|
|
|
149
149
|
ctx.request_context.lifespan_context.file_content_hashes.clear()
|
|
150
150
|
|
|
151
151
|
if clean:
|
|
152
|
-
subprocess.run(["lake", "clean"], cwd=
|
|
152
|
+
subprocess.run(["lake", "clean"], cwd=lean_project_path_obj, check=False)
|
|
153
153
|
logger.info("Ran `lake clean`")
|
|
154
154
|
|
|
155
155
|
with OutputCapture() as output:
|
|
156
|
-
client = LeanLSPClient(
|
|
157
|
-
lean_project_path,
|
|
158
|
-
initial_build=True,
|
|
159
|
-
print_warnings=False,
|
|
160
|
-
)
|
|
156
|
+
client = LeanLSPClient(lean_project_path_obj, initial_build=True)
|
|
161
157
|
|
|
162
158
|
logger.info("Built project and re-started LSP client")
|
|
163
159
|
|
|
@@ -265,7 +261,7 @@ def goal(ctx: Context, file_path: str, line: int, column: Optional[int] = None)
|
|
|
265
261
|
|
|
266
262
|
else:
|
|
267
263
|
goal = client.get_goal(rel_path, line - 1, column - 1)
|
|
268
|
-
f_goal = format_goal(goal,
|
|
264
|
+
f_goal = format_goal(goal, "Not a valid goal position. Try elsewhere?")
|
|
269
265
|
f_line = format_line(content, line, column)
|
|
270
266
|
return f"Goals at:\n{f_line}\n{f_goal}"
|
|
271
267
|
|
|
@@ -500,7 +496,8 @@ def multi_attempt(
|
|
|
500
496
|
[line, 0],
|
|
501
497
|
)
|
|
502
498
|
# Apply the change to the file, capture diagnostics and goal state
|
|
503
|
-
|
|
499
|
+
client.update_file(rel_path, [change])
|
|
500
|
+
diag = client.get_diagnostics(rel_path)
|
|
504
501
|
formatted_diag = "\n".join(format_diagnostics(diag, select_line=line - 1))
|
|
505
502
|
goal = client.get_goal(rel_path, line - 1, len(snippet))
|
|
506
503
|
formatted_goal = format_goal(goal, "Missing goal")
|
|
@@ -529,10 +526,10 @@ def run_code(ctx: Context, code: str) -> List[str] | str:
|
|
|
529
526
|
return "No valid Lean project path found. Run another tool (e.g. `lean_diagnostic_messages`) first to set it up or set the LEAN_PROJECT_PATH environment variable."
|
|
530
527
|
|
|
531
528
|
rel_path = "temp_snippet.lean"
|
|
532
|
-
abs_path =
|
|
529
|
+
abs_path = lean_project_path / rel_path
|
|
533
530
|
|
|
534
531
|
try:
|
|
535
|
-
with open(abs_path, "w") as f:
|
|
532
|
+
with open(abs_path, "w", encoding="utf-8") as f:
|
|
536
533
|
f.write(code)
|
|
537
534
|
except Exception as e:
|
|
538
535
|
return f"Error writing code snippet to file `{abs_path}`:\n{str(e)}"
|
lean_lsp_mcp/utils.py
CHANGED
|
@@ -16,7 +16,9 @@ class OutputCapture:
|
|
|
16
16
|
self.captured_output = ""
|
|
17
17
|
|
|
18
18
|
def __enter__(self):
|
|
19
|
-
self.temp_file = tempfile.NamedTemporaryFile(
|
|
19
|
+
self.temp_file = tempfile.NamedTemporaryFile(
|
|
20
|
+
mode="w+", delete=False, encoding="utf-8"
|
|
21
|
+
)
|
|
20
22
|
self.original_stdout_fd = os.dup(sys.stdout.fileno())
|
|
21
23
|
self.original_stderr_fd = os.dup(sys.stderr.fileno())
|
|
22
24
|
os.dup2(self.temp_file.fileno(), sys.stdout.fileno())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lean-lsp-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
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.3.0
|
|
12
|
+
Requires-Dist: mcp[cli]==1.18.0
|
|
13
13
|
Requires-Dist: orjson>=3.11.1
|
|
14
14
|
Provides-Extra: lint
|
|
15
15
|
Requires-Dist: ruff>=0.2.0; extra == "lint"
|
|
@@ -40,8 +40,6 @@ Dynamic: license-file
|
|
|
40
40
|
|
|
41
41
|
MCP server that allows agentic interaction with the [Lean theorem prover](https://lean-lang.org/) via the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) using [leanclient](https://github.com/oOo0oOo/leanclient). This server provides a range of tools for LLM agents to understand, analyze and interact with Lean projects.
|
|
42
42
|
|
|
43
|
-
**Currently beta testing**: Please help us by submitting bug reports and feature requests!
|
|
44
|
-
|
|
45
43
|
## Key Features
|
|
46
44
|
|
|
47
45
|
* **Rich Lean Interaction**: Access diagnostics, goal states, term information, hover documentation and more.
|
|
@@ -59,31 +57,17 @@ MCP server that allows agentic interaction with the [Lean theorem prover](https:
|
|
|
59
57
|
|
|
60
58
|
### 1. Install uv
|
|
61
59
|
|
|
62
|
-
[Install uv](https://docs.astral.sh/uv/getting-started/installation/) for your system.
|
|
63
|
-
|
|
64
|
-
E.g. on Linux/MacOS:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
68
|
-
```
|
|
60
|
+
[Install uv](https://docs.astral.sh/uv/getting-started/installation/) for your system. On Linux/MacOS: `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
|
69
61
|
|
|
70
62
|
### 2. Run `lake build`
|
|
71
63
|
|
|
72
|
-
`lean-lsp-mcp` will run `lake
|
|
73
|
-
|
|
74
|
-
E.g. on Linux/MacOS:
|
|
75
|
-
```bash
|
|
76
|
-
cd /path/to/lean/project
|
|
77
|
-
lake build
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Note: Your build does not necessarily need to be successful, some errors or warnings (e.g. `declaration uses 'sorry'`) are OK.
|
|
64
|
+
`lean-lsp-mcp` will run `lake serve` in the project root to use the language server (for most tools). Some clients (e.g. Cursor) might timeout during this process. Therefore, it is recommended to run `lake build` manually before starting the MCP. This ensures a faster build time and avoids timeouts.
|
|
81
65
|
|
|
82
|
-
### 3.
|
|
66
|
+
### 3. Configure your IDE/Setup
|
|
83
67
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
<details>
|
|
69
|
+
<summary><b>VSCode</b></summary>
|
|
70
|
+
One-click config setup:
|
|
87
71
|
|
|
88
72
|
[](https://insiders.vscode.dev/redirect/mcp/install?name=lean-lsp&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22lean-lsp-mcp%22%5D%7D)
|
|
89
73
|
|
|
@@ -108,11 +92,10 @@ OR manually add config to `mcp.json`:
|
|
|
108
92
|
}
|
|
109
93
|
}
|
|
110
94
|
```
|
|
95
|
+
</details>
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
### 3. b) Cursor Setup
|
|
115
|
-
|
|
97
|
+
<details>
|
|
98
|
+
<summary><b>Cursor</b></summary>
|
|
116
99
|
1. Open MCP Settings (File > Preferences > Cursor Settings > MCP)
|
|
117
100
|
|
|
118
101
|
2. "+ Add a new global MCP Server" > ("Create File")
|
|
@@ -129,9 +112,10 @@ OR manually add config to `mcp.json`:
|
|
|
129
112
|
}
|
|
130
113
|
}
|
|
131
114
|
```
|
|
115
|
+
</details>
|
|
132
116
|
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
<details>
|
|
118
|
+
<summary><b>Claude Code</b></summary>
|
|
135
119
|
Run one of these commands in the root directory of your Lean project (where `lakefile.toml` is located):
|
|
136
120
|
|
|
137
121
|
```bash
|
|
@@ -146,79 +130,18 @@ claude mcp add lean-lsp uvx lean-lsp-mcp -e LEAN_PROJECT_PATH=$PWD
|
|
|
146
130
|
```
|
|
147
131
|
|
|
148
132
|
You can find more details about MCP server configuration for Claude Code [here](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/tutorials#configure-mcp-servers).
|
|
133
|
+
</details>
|
|
149
134
|
|
|
150
|
-
|
|
135
|
+
#### Claude Skill: Lean4 Theorem Proving
|
|
151
136
|
|
|
152
|
-
|
|
137
|
+
If you are using [Claude Desktop](https://modelcontextprotocol.io/quickstart/user) or [Claude Code](https://claude.ai/code), you can also install the [Lean4 Theorem Proving Skill](https://github.com/cameronfreer/lean4-skills/tree/main/lean4-theorem-proving). This skill provides additional prompts and templates for interacting with Lean4 projects and includes a section on interacting with the `lean-lsp-mcp` server.
|
|
153
138
|
|
|
154
139
|
### 4. Install ripgrep (optional but recommended)
|
|
155
140
|
|
|
156
|
-
For the local search tool `lean_local_search`,
|
|
157
|
-
|
|
158
|
-
### Transport Methods
|
|
159
|
-
|
|
160
|
-
The Lean LSP MCP server supports the following transport methods:
|
|
161
|
-
|
|
162
|
-
- `stdio`: Standard input/output (default)
|
|
163
|
-
- `streamable-http`: HTTP streaming
|
|
164
|
-
- `sse`: Server-sent events (MCP legacy, use `streamable-http` if possible)
|
|
165
|
-
|
|
166
|
-
You can specify the transport method using the `--transport` argument when running the server. For `sse` and `streamable-http` you can also optionally specify the host and port:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
uvx lean-lsp-mcp --transport stdio # Default transport
|
|
170
|
-
uvx lean-lsp-mcp --transport streamable-http # Available at http://127.0.0.1:8000/mcp
|
|
171
|
-
uvx lean-lsp-mcp --transport sse --host localhost --port 12345 # Available at http://localhost:12345/sse
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Bearer Token Authentication
|
|
175
|
-
|
|
176
|
-
Transport via `streamable-http` and `sse` supports bearer token authentication. This allows publicly accessible MCP servers to restrict access to authorized clients.
|
|
177
|
-
|
|
178
|
-
Set the `LEAN_LSP_MCP_TOKEN` environment variable (or see section 3 for setting env variables in MCP config) to a secret token before starting the server.
|
|
179
|
-
|
|
180
|
-
Example Linux/MacOS setup:
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
export LEAN_LSP_MCP_TOKEN="your_secret_token"
|
|
184
|
-
uvx lean-lsp-mcp --transport streamable-http
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
Clients should then include the token in the `Authorization` header.
|
|
188
|
-
|
|
189
|
-
### Environment Variables
|
|
190
|
-
|
|
191
|
-
Some (optional) features and integrations of `lean-lsp-mcp` are configured using environment variables. These must be set in your shell or process environment before running the server.
|
|
192
|
-
|
|
193
|
-
- `LEAN_PROJECT_PATH`: (optional) Path to your Lean project root. Set this if the server cannot automatically detect your project.
|
|
194
|
-
- `LEAN_LOG_LEVEL`: (optional) Log level for the server. Options are "INFO", "WARNING", "ERROR", "NONE". Defaults to "INFO".
|
|
195
|
-
- `LEAN_LSP_MCP_TOKEN`: (optional) Secret token for bearer authentication when using `streamable-http` or `sse` transport.
|
|
196
|
-
- `LEAN_STATE_SEARCH_URL`: (optional) URL for a self-hosted [premise-search.com](https://premise-search.com) instance.
|
|
197
|
-
- `LEAN_HAMMER_URL`: (optional) URL for a self-hosted [Lean Hammer Premise Search](https://github.com/hanwenzhu/lean-premise-server) instance.
|
|
198
|
-
|
|
199
|
-
You can also often set these environment variables in your MCP client configuration:
|
|
200
|
-
|
|
201
|
-
```jsonc
|
|
202
|
-
{
|
|
203
|
-
"servers": {
|
|
204
|
-
"lean-lsp": {
|
|
205
|
-
"type": "stdio",
|
|
206
|
-
"command": "uvx",
|
|
207
|
-
"args": [
|
|
208
|
-
"lean-lsp-mcp"
|
|
209
|
-
],
|
|
210
|
-
"env": {
|
|
211
|
-
"LEAN_PROJECT_PATH": "/path/to/your/lean/project",
|
|
212
|
-
"LEAN_LOG_LEVEL": "NONE"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
```
|
|
141
|
+
For the local search tool `lean_local_search`, install [ripgrep](https://github.com/BurntSushi/ripgrep?tab=readme-ov-file#installation) (`rg`) and make sure it is available in your PATH.
|
|
218
142
|
|
|
219
|
-
## Tools
|
|
143
|
+
## MCP Tools
|
|
220
144
|
|
|
221
|
-
Tools are currently the only way to interact with the MCP server.
|
|
222
145
|
|
|
223
146
|
### File interactions (LSP)
|
|
224
147
|
|
|
@@ -233,16 +156,18 @@ Get all diagnostic messages for a Lean file. This includes infos, warnings and e
|
|
|
233
156
|
<details>
|
|
234
157
|
<summary>Example output</summary>
|
|
235
158
|
|
|
236
|
-
|
|
159
|
+
```
|
|
160
|
+
l20c42-l20c46, severity: 1
|
|
237
161
|
simp made no progress
|
|
238
162
|
|
|
239
|
-
l21c11-l21c45, severity: 1
|
|
163
|
+
l21c11-l21c45, severity: 1
|
|
240
164
|
function expected at
|
|
241
165
|
h_empty
|
|
242
166
|
term has type
|
|
243
167
|
T ∩ compl T = ∅
|
|
244
168
|
|
|
245
169
|
...
|
|
170
|
+
```
|
|
246
171
|
</details>
|
|
247
172
|
|
|
248
173
|
#### lean_goal
|
|
@@ -251,21 +176,24 @@ Get the proof goal at a specific location (line or line & column) in a Lean file
|
|
|
251
176
|
|
|
252
177
|
<details>
|
|
253
178
|
<summary>Example output (line)</summary>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Before:
|
|
182
|
+
S : Type u_1
|
|
183
|
+
inst✝¹ : Fintype S
|
|
184
|
+
inst✝ : Nonempty S
|
|
185
|
+
P : Finset (Set S)
|
|
186
|
+
hPP : ∀ T ∈ P, ∀ U ∈ P, T ∩ U ≠ ∅
|
|
187
|
+
hPS : ¬∃ T ∉ P, ∀ U ∈ P, T ∩ U ≠ ∅
|
|
188
|
+
compl : Set S → Set S := fun T ↦ univ \ T
|
|
189
|
+
hcompl : ∀ T ∈ P, compl T ∉ P
|
|
190
|
+
all_subsets : Finset (Set S) := Finset.univ
|
|
191
|
+
h_comp_in_P : ∀ T ∉ P, compl T ∈ P
|
|
192
|
+
h_partition : ∀ (T : Set S), T ∈ P ∨ compl T ∈ P
|
|
193
|
+
⊢ P.card = 2 ^ (Fintype.card S - 1)
|
|
194
|
+
After:
|
|
268
195
|
no goals
|
|
196
|
+
```
|
|
269
197
|
</details>
|
|
270
198
|
|
|
271
199
|
#### lean_term_goal
|
|
@@ -278,13 +206,16 @@ Retrieve hover information (documentation) for symbols, terms, and expressions i
|
|
|
278
206
|
|
|
279
207
|
<details>
|
|
280
208
|
<summary>Example output (hover info on a `sorry`)</summary>
|
|
281
|
-
The `sorry` tactic is a temporary placeholder for an incomplete tactic proof,<br>
|
|
282
|
-
closing the main goal using `exact sorry`.<br><br>
|
|
283
209
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
210
|
+
```
|
|
211
|
+
The `sorry` tactic is a temporary placeholder for an incomplete tactic proof,
|
|
212
|
+
closing the main goal using `exact sorry`.
|
|
213
|
+
|
|
214
|
+
This is intended for stubbing-out incomplete parts of a proof while still having a syntactically correct proof skeleton.
|
|
215
|
+
Lean will give a warning whenever a proof uses `sorry`, so you aren't likely to miss it,
|
|
216
|
+
but you can double check if a theorem depends on `sorry` by looking for `sorryAx` in the output
|
|
217
|
+
of the `#print axioms my_thm` command, the axiom used by the implementation of `sorry`.
|
|
218
|
+
```
|
|
288
219
|
</details>
|
|
289
220
|
|
|
290
221
|
#### lean_declaration_file
|
|
@@ -300,8 +231,11 @@ Code auto-completion: Find available identifiers or import suggestions at a spec
|
|
|
300
231
|
Run/compile an independent Lean code snippet/file and return the result or error message.
|
|
301
232
|
<details>
|
|
302
233
|
<summary>Example output (code snippet: `#eval 5 * 7 + 3`)</summary>
|
|
303
|
-
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
l1c1-l1c6, severity: 3
|
|
304
237
|
38
|
|
238
|
+
```
|
|
305
239
|
</details>
|
|
306
240
|
|
|
307
241
|
#### lean_multi_attempt
|
|
@@ -311,29 +245,32 @@ This tool is useful to screen different proof attempts before using the most pro
|
|
|
311
245
|
|
|
312
246
|
<details>
|
|
313
247
|
<summary>Example output (attempting `rw [Nat.pow_sub (Fintype.card_pos_of_nonempty S)]` and `by_contra h_neq`)</summary>
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
rw [Nat.pow_sub (Fintype.card_pos_of_nonempty S)]:
|
|
251
|
+
S : Type u_1
|
|
252
|
+
inst✝¹ : Fintype S
|
|
253
|
+
inst✝ : Nonempty S
|
|
254
|
+
P : Finset (Set S)
|
|
255
|
+
hPP : ∀ T ∈ P, ∀ U ∈ P, T ∩ U ≠ ∅
|
|
256
|
+
hPS : ¬∃ T ∉ P, ∀ U ∈ P, T ∩ U ≠ ∅
|
|
257
|
+
⊢ P.card = 2 ^ (Fintype.card S - 1)
|
|
258
|
+
|
|
259
|
+
l14c7-l14c51, severity: 1
|
|
260
|
+
unknown constant 'Nat.pow_sub'
|
|
261
|
+
|
|
262
|
+
by_contra h_neq:
|
|
263
|
+
S : Type u_1
|
|
264
|
+
inst✝¹ : Fintype S
|
|
265
|
+
inst✝ : Nonempty S
|
|
266
|
+
P : Finset (Set S)
|
|
267
|
+
hPP : ∀ T ∈ P, ∀ U ∈ P, T ∩ U ≠ ∅
|
|
268
|
+
hPS : ¬∃ T ∉ P, ∀ U ∈ P, T ∩ U ≠ ∅
|
|
269
|
+
h_neq : ¬P.card = 2 ^ (Fintype.card S - 1)
|
|
270
|
+
⊢ False
|
|
271
|
+
|
|
336
272
|
...
|
|
273
|
+
```
|
|
337
274
|
</details>
|
|
338
275
|
|
|
339
276
|
### Local Search Tools
|
|
@@ -347,7 +284,7 @@ This tool requires [ripgrep](https://github.com/BurntSushi/ripgrep?tab=readme-ov
|
|
|
347
284
|
|
|
348
285
|
### External Search Tools
|
|
349
286
|
|
|
350
|
-
Currently all external tools are **rate limited to 3 requests per 30 seconds**.
|
|
287
|
+
Currently all external tools are separately **rate limited to 3 requests per 30 seconds**.
|
|
351
288
|
|
|
352
289
|
#### lean_leansearch
|
|
353
290
|
|
|
@@ -455,7 +392,7 @@ Note: We use a simplified version, [LeanHammer](https://github.com/JOSHCLUNE/Lea
|
|
|
455
392
|
|
|
456
393
|
Rebuild the Lean project and restart the Lean LSP server.
|
|
457
394
|
|
|
458
|
-
|
|
395
|
+
### Disabling Tools
|
|
459
396
|
|
|
460
397
|
Many clients allow the user to disable specific tools manually (e.g. lean_build).
|
|
461
398
|
|
|
@@ -463,32 +400,71 @@ Many clients allow the user to disable specific tools manually (e.g. lean_build)
|
|
|
463
400
|
|
|
464
401
|
**Cursor**: In "Cursor Settings" > "MCP" click on the name of a tool to disable it (strikethrough).
|
|
465
402
|
|
|
466
|
-
##
|
|
403
|
+
## MCP Configuration
|
|
404
|
+
|
|
405
|
+
This MCP server works out-of-the-box without any configuration. However, a few optional settings are available.
|
|
406
|
+
|
|
407
|
+
### Environment Variables
|
|
408
|
+
|
|
409
|
+
- `LEAN_LOG_LEVEL`: Log level for the server. Options are "INFO", "WARNING", "ERROR", "NONE". Defaults to "INFO".
|
|
410
|
+
- `LEAN_PROJECT_PATH`: Path to your Lean project root. Set this if the server cannot automatically detect your project.
|
|
411
|
+
- `LEAN_LSP_MCP_TOKEN`: Secret token for bearer authentication when using `streamable-http` or `sse` transport.
|
|
412
|
+
- `LEAN_STATE_SEARCH_URL`: URL for a self-hosted [premise-search.com](https://premise-search.com) instance.
|
|
413
|
+
- `LEAN_HAMMER_URL`: URL for a self-hosted [Lean Hammer Premise Search](https://github.com/hanwenzhu/lean-premise-server) instance.
|
|
414
|
+
|
|
415
|
+
You can also often set these environment variables in your MCP client configuration:
|
|
416
|
+
<details>
|
|
417
|
+
<summary><b>VSCode mcp.json Example</b></summary>
|
|
418
|
+
|
|
419
|
+
```jsonc
|
|
420
|
+
{
|
|
421
|
+
"servers": {
|
|
422
|
+
"lean-lsp": {
|
|
423
|
+
"type": "stdio",
|
|
424
|
+
"command": "uvx",
|
|
425
|
+
"args": [
|
|
426
|
+
"lean-lsp-mcp"
|
|
427
|
+
],
|
|
428
|
+
"env": {
|
|
429
|
+
"LEAN_PROJECT_PATH": "/path/to/your/lean/project",
|
|
430
|
+
"LEAN_LOG_LEVEL": "NONE"
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
</details>
|
|
467
437
|
|
|
468
|
-
|
|
438
|
+
### Transport Methods
|
|
469
439
|
|
|
470
|
-
|
|
440
|
+
The Lean LSP MCP server supports the following transport methods:
|
|
471
441
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
You can also ask the agent to use tools explicitly, e.g. "Help me write this proof using tools." or "Use tools to analyze the goal and hover information, then write a proof."
|
|
442
|
+
- `stdio`: Standard input/output (default)
|
|
443
|
+
- `streamable-http`: HTTP streaming
|
|
444
|
+
- `sse`: Server-sent events (MCP legacy, use `streamable-http` if possible)
|
|
476
445
|
|
|
477
|
-
|
|
446
|
+
You can specify the transport method using the `--transport` argument when running the server. For `sse` and `streamable-http` you can also optionally specify the host and port:
|
|
478
447
|
|
|
479
|
-
|
|
448
|
+
```bash
|
|
449
|
+
uvx lean-lsp-mcp --transport stdio # Default transport
|
|
450
|
+
uvx lean-lsp-mcp --transport streamable-http # Available at http://127.0.0.1:8000/mcp
|
|
451
|
+
uvx lean-lsp-mcp --transport sse --host localhost --port 12345 # Available at http://localhost:12345/sse
|
|
452
|
+
```
|
|
480
453
|
|
|
481
|
-
|
|
454
|
+
### Bearer Token Authentication
|
|
482
455
|
|
|
483
|
-
|
|
456
|
+
Transport via `streamable-http` and `sse` supports bearer token authentication. This allows publicly accessible MCP servers to restrict access to authorized clients.
|
|
484
457
|
|
|
485
|
-
|
|
458
|
+
Set the `LEAN_LSP_MCP_TOKEN` environment variable (or see section 3 for setting env variables in MCP config) to a secret token before starting the server.
|
|
486
459
|
|
|
487
|
-
|
|
460
|
+
Example Linux/MacOS setup:
|
|
488
461
|
|
|
489
|
-
|
|
462
|
+
```bash
|
|
463
|
+
export LEAN_LSP_MCP_TOKEN="your_secret_token"
|
|
464
|
+
uvx lean-lsp-mcp --transport streamable-http
|
|
465
|
+
```
|
|
490
466
|
|
|
491
|
-
|
|
467
|
+
Clients should then include the token in the `Authorization` header.
|
|
492
468
|
|
|
493
469
|
## Notes on MCP Security
|
|
494
470
|
|
|
@@ -503,13 +479,15 @@ Please be aware of these risks. Feel free to audit the code and report security
|
|
|
503
479
|
|
|
504
480
|
For more information, you can use [Awesome MCP Security](https://github.com/Puliczek/awesome-mcp-security) as a starting point.
|
|
505
481
|
|
|
506
|
-
##
|
|
482
|
+
## Development
|
|
483
|
+
|
|
484
|
+
### MCP Inspector
|
|
507
485
|
|
|
508
486
|
```bash
|
|
509
487
|
npx @modelcontextprotocol/inspector uvx --with-editable path/to/lean-lsp-mcp python -m lean_lsp_mcp.server
|
|
510
488
|
```
|
|
511
489
|
|
|
512
|
-
|
|
490
|
+
### Run Tests
|
|
513
491
|
|
|
514
492
|
```bash
|
|
515
493
|
uv sync --all-extras
|
|
@@ -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=mN0wQlGLMVR8bV0OqVJ8YHq6vUoty4MzebvA9A3u4dM,3502
|
|
4
|
+
lean_lsp_mcp/file_utils.py,sha256=6dVceZOCb28KsKtoub4QPD272TrtKzFZedg4H-N6KgA,2926
|
|
5
|
+
lean_lsp_mcp/instructions.py,sha256=qdGW1Qh14Gb_ezrMLU_SrS_F1X_r065KIsQu0HamwnU,825
|
|
6
|
+
lean_lsp_mcp/search_utils.py,sha256=cNpWzR1TuNyvRxCeCIb0HoEHGsCIW1ypqN1R1ZpbkdI,4170
|
|
7
|
+
lean_lsp_mcp/server.py,sha256=su-e52pgsczlFJWrGiUOWsGjlZ3wsv3txhH1u3pirOI,27995
|
|
8
|
+
lean_lsp_mcp/utils.py,sha256=8wqjD5gfZhIkM1vLxvLoDkh-RQx_kDrxdRRUb1VQAbI,6040
|
|
9
|
+
lean_lsp_mcp-0.10.1.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
+
lean_lsp_mcp-0.10.1.dist-info/METADATA,sha256=GagjAFqDoOntIJcazBaj_mJE4-d4em5jOaY4FLe4HC0,17455
|
|
11
|
+
lean_lsp_mcp-0.10.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
lean_lsp_mcp-0.10.1.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
+
lean_lsp_mcp-0.10.1.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
+
lean_lsp_mcp-0.10.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=Nwo6aA7cilFMYt7wM_PhhEpokuazU4g6GZEeNz_HqDQ,3329
|
|
4
|
-
lean_lsp_mcp/file_utils.py,sha256=mGp8ASeSpKofzETFbCrq8XwGRwv9Z-JrvsWLxdGJiFU,2742
|
|
5
|
-
lean_lsp_mcp/instructions.py,sha256=qdGW1Qh14Gb_ezrMLU_SrS_F1X_r065KIsQu0HamwnU,825
|
|
6
|
-
lean_lsp_mcp/search_utils.py,sha256=cNpWzR1TuNyvRxCeCIb0HoEHGsCIW1ypqN1R1ZpbkdI,4170
|
|
7
|
-
lean_lsp_mcp/server.py,sha256=Zjg2zk-F-b5LCgaobafczlRCIRFmaY5OkuDNIegSCEM,28004
|
|
8
|
-
lean_lsp_mcp/utils.py,sha256=KoA2L0SswQQSdv_F1hS8xPwVdL8Tzuke9xSXB_RpsTo,6000
|
|
9
|
-
lean_lsp_mcp-0.9.1.dist-info/licenses/LICENSE,sha256=CQlxnf0tQyoVrBE93JYvAUYxv6Z5Yg6sX0pwogOkFvo,1071
|
|
10
|
-
lean_lsp_mcp-0.9.1.dist-info/METADATA,sha256=j2dt6XJYLnLKqE3w2O4Vs3x9OVQq_q2Y3sCQm5odKNQ,19521
|
|
11
|
-
lean_lsp_mcp-0.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
lean_lsp_mcp-0.9.1.dist-info/entry_points.txt,sha256=nQbvwctWkWD7I-2f4VrdVQBZYGUw8CnUnFC6QjXxOSE,51
|
|
13
|
-
lean_lsp_mcp-0.9.1.dist-info/top_level.txt,sha256=LGEK0lgMSNPIQ6mG8EO-adaZEGPi_0daDs004epOTF0,13
|
|
14
|
-
lean_lsp_mcp-0.9.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|