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.
@@ -1,4 +1,4 @@
1
- import os
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
- client = LeanLSPClient(
41
- lean_project_path, initial_build=False, print_warnings=False
42
- )
43
- logger.error(f"Could not do initial build, error: {e}")
44
- logger.info("Build output: " + output.get_output())
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
- if not os.path.exists(path):
58
- return False
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 new correct project path by checking all directories in file_path.
81
- file_dir = os.path.dirname(file_path)
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
- while file_dir:
84
- if valid_lean_project_path(file_dir):
85
- lean_project_path = file_dir
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
@@ -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: str, file_path: str) -> Optional[str]:
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 (str): Path to the Lean project root.
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 os.path.exists(file_path):
24
- return os.path.relpath(file_path, lean_project_path)
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 = os.path.join(lean_project_path, file_path)
28
- if os.path.exists(path):
29
- return os.path.relpath(path, lean_project_path)
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 = os.getcwd().strip() # Strip necessary?
33
- path = os.path.join(cwd, file_path)
34
- if os.path.exists(path):
35
- return os.path.relpath(path, lean_project_path)
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 = os.path.join(
62
- ctx.request_context.lifespan_context.lean_project_path, rel_path
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: str | None
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
- lean_project_path = os.environ.get("LEAN_PROJECT_PATH", "").strip()
57
- if not lean_project_path:
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 = os.path.abspath(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
- lean_project_path = ctx.request_context.lifespan_context.lean_project_path
139
+ lean_project_path_obj = ctx.request_context.lifespan_context.lean_project_path
140
140
  else:
141
- lean_project_path = os.path.abspath(lean_project_path)
142
- ctx.request_context.lifespan_context.lean_project_path = 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=lean_project_path, check=False)
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, f"Not a valid goal position. Try elsewhere?")
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
- diag = client.update_file(rel_path, [change])
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 = os.path.join(lean_project_path, rel_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(mode="w+", delete=False)
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.9.1
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.2.1
12
- Requires-Dist: mcp[cli]==1.17.0
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 build` 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.
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. a) VSCode Setup
66
+ ### 3. Configure your IDE/Setup
83
67
 
84
- VSCode and VSCode Insiders are supporting MCPs in [agent mode](https://code.visualstudio.com/blogs/2025/04/07/agentMode). For VSCode you might have to enable `Chat > Agent: Enable` in the settings.
85
-
86
- 1. One-click config setup:
68
+ <details>
69
+ <summary><b>VSCode</b></summary>
70
+ One-click config setup:
87
71
 
88
72
  [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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
- 2. Click "Start" above server config, open a Lean file, change to agent mode in the chat and run e.g. "auto proof" to get started.
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
- ### 3. c) Claude Code
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
- ### Other Setups
135
+ #### Claude Skill: Lean4 Theorem Proving
151
136
 
152
- Other setups, such as [Claude Desktop](https://modelcontextprotocol.io/quickstart/user), [OpenAI Agent SDK](https://openai.github.io/openai-agents-python/mcp/), [Windsurf](https://docs.windsurf.com/windsurf/cascade/mcp) or [Goose](https://block.github.io/goose/docs/getting-started/using-extensions) should work with similar configs.
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`, please install [ripgrep](https://github.com/BurntSushi/ripgrep?tab=readme-ov-file#installation) (`rg`) and make sure it is available in your PATH.
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
- l20c42-l20c46, severity: 1<br>
159
+ ```
160
+ l20c42-l20c46, severity: 1
237
161
  simp made no progress
238
162
 
239
- l21c11-l21c45, severity: 1<br>
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
- Before:<br>
255
- S : Type u_1<br>
256
- inst✝¹ : Fintype S<br>
257
- inst✝ : Nonempty S<br>
258
- P : Finset (Set S)<br>
259
- hPP : T ∈ P, ∀ U ∈ P, T ∩ U ≠ ∅<br>
260
- hPS : ¬∃ T ∉ P, ∀ U ∈ P, T ∩ U ≠ ∅<br>
261
- compl : Set S Set S := fun T univ \ T<br>
262
- hcompl : T ∈ P, compl T P<br>
263
- all_subsets : Finset (Set S) := Finset.univ<br>
264
- h_comp_in_P : ∀ T P, compl T P<br>
265
- h_partition : (T : Set S), T ∈ P ∨ compl T ∈ P<br>
266
- P.card = 2 ^ (Fintype.card S - 1)<br>
267
- After:<br>
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
- This is intended for stubbing-out incomplete parts of a proof while still having a syntactically correct proof skeleton.<br>
285
- Lean will give a warning whenever a proof uses `sorry`, so you aren't likely to miss it,<br>
286
- but you can double check if a theorem depends on `sorry` by looking for `sorryAx` in the output<br>
287
- of the `#print axioms my_thm` command, the axiom used by the implementation of `sorry`.<br>
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
- l1c1-l1c6, severity: 3<br>
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
- rw [Nat.pow_sub (Fintype.card_pos_of_nonempty S)]:<br>
315
- S : Type u_1<br>
316
- inst✝¹ : Fintype S<br>
317
- inst✝ : Nonempty S<br>
318
- P : Finset (Set S)<br>
319
- hPP : T ∈ P, ∀ U ∈ P, T ∩ U ≠ ∅<br>
320
- hPS : ¬∃ T ∉ P, ∀ U ∈ P, T ∩ U ≠ ∅<br>
321
- P.card = 2 ^ (Fintype.card S - 1)<br>
322
- <br>
323
- l14c7-l14c51, severity: 1<br>
324
- unknown constant 'Nat.pow_sub'<br>
325
- <br>
326
- by_contra h_neq:<br>
327
- S : Type u_1<br>
328
- inst✝¹ : Fintype S<br>
329
- inst✝ : Nonempty S<br>
330
- P : Finset (Set S)<br>
331
- hPP : T ∈ P, ∀ U ∈ P, T ∩ U ≠ ∅<br>
332
- hPS : ¬∃ T ∉ P, ∀ U ∈ P, T ∩ U ≠ ∅<br>
333
- h_neq : ¬P.card = 2 ^ (Fintype.card S - 1)<br>
334
- False<br>
335
- <br>
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**. This will change based on provider feedback.
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
- ## Disabling Tools
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
- ## Example Uses
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
- Here are a few example prompts and interactions to try. All examples use VSCode (Agent Mode) and Gemini 2.5 Pro (Preview).
438
+ ### Transport Methods
469
439
 
470
- ### Use tools to assist with a proof
440
+ The Lean LSP MCP server supports the following transport methods:
471
441
 
472
- After installing the MCP, tools are **automatically available** to the agent.
473
- E.g. Open a Lean file with a sorry and run the following prompt: "Solve this sorry"
474
- The agent should use various tools such as `lean_goal` to understand and create a proof.
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
- ### Analyze a theorem
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
- Open `Algebra/Lie/Abelian.lean`. Example prompt:
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
- "Analyze commutative_ring_iff_abelian_lie_ring thoroughly using various tools such as goal, term goal, hover info. Explain the key proof steps in english.".
454
+ ### Bearer Token Authentication
482
455
 
483
- ![Analyzing a theorem in chat](media/analyze_theorem.png)
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
- ### Design proof approaches
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
- Open an incomplete proof such as [putnam 1964 b2](https://github.com/trishullab/PutnamBench/blob/main/lean4/src/putnam_1964_b2.lean). Example prompt:
460
+ Example Linux/MacOS setup:
488
461
 
489
- "First analyze the problem statement by checking the goal, hover info and looking up key declarations. Next use up to three queries to leansearch to design three different approaches to solve this problem. Very concisely present each approach and its key challenge."
462
+ ```bash
463
+ export LEAN_LSP_MCP_TOKEN="your_secret_token"
464
+ uvx lean-lsp-mcp --transport streamable-http
465
+ ```
490
466
 
491
- ![Designing proof approaches](media/proof_approaches.png)
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
- ## MCP Inspector
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
- ## Run Tests
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,,