lean-lsp-mcp

repository·main·Indexed 19 days ago

https://github.com/ooo0ooo/lean-lsp-mcp

An MCP (Model Context Protocol) server that enables LLM agents to interact with the Lean theorem prover via the Language Server Protocol (LSP). It provides tools for diagnostics, goal state inspection, and theorem searching. The server supports stdio, streamable-http, and sse transport methods and can be integrated with VSCode, Cursor, Claude Code, and Mistral Vibe.

Tokens
15.2K
Snippets
46
Records
72
Agent score
65%

What's inside lean-lsp-mcp

  1. Choose a Transport Method

    main

    The server supports three transport methods:

    1. stdio (Default): Supports automatic project inference and switching as you move between Lean projects.
    2. streamable-http: HTTP streaming. Requires LEAN_PROJECT_PATH at startup and does not support project switching.
    3. sse: Server-sent events (legacy). Requires LEAN_PROJECT_PATH at startup.

    Use the --transport flag to specify the method. For HTTP/SSE, you can also specify --host and --port.

    uvx lean-lsp-mcp --transport stdio # Default transport
    uvx lean-lsp-mcp --transport streamable-http # Available at http://127.0.0.1:8000/mcp
    uvx lean-lsp-mcp --transport sse --host localhost --port 12345 # Available at http://localhost:12345/sse
  2. Mental model of an MCP tool

    main

    An MCP tool in this project is composed of five key elements:

    1. Decorator metadata: The @mcp.tool decorator which defines the public name and ToolAnnotations.
    2. Typed signature: A function signature where ctx: Context is the first argument, followed by parameters defined using Annotated[Type, Field(...)].
    3. Pydantic return model: A model defined in models.py that specifies the structured output.
    4. One-line docstring: A concise description of the tool's purpose.
    5. Delegating body: A minimal function body that validates paths, retrieves the LeanLSPClient from the lifespan_context, and delegates the actual work to a helper module.
  3. Manage tool lifespan state and rate limiting

    main

    If a tool requires long-running state (e.g., a subprocess or cache) or needs rate limiting, you must modify server.py.

    Adding Lifespan State

    1. Add the field to the AppContext dataclass in server.py.
    2. Initialize the resource in app_lifespan after lean_project_path is computed.
    3. Ensure cleanup in the finally block of app_lifespan.

    Rate Limiting

    To protect remote services, use the @rate_limited decorator. You must first register the category in the rate_limit dictionary within app_lifespan.

    # Example rate limit usage
    @rate_limited("my_tool", max_requests=3, per_seconds=30)
    @mcp.tool(...)
    async def my_tool(ctx: Context, ...):
        ...
    # In server.py
    @dataclass
    class AppContext:
        ... 
        my_thing: MyThing | None = None
    
    # Inside app_lifespan
    my_thing = await initialise_my_thing(lean_project_path)
    context = AppContext(..., my_thing=my_thing)
    
    # In the finally block
    if context and context.my_thing:
        await context.my_thing.close()
  4. Install lean-lsp-mcp via uv

    main

    The recommended way to install and run the lean-lsp-mcp server is using uv, a Python package manager. This allows you to run the server without manual installation via uvx.

    First, install uv on your system:

    curl -LsSf https://astral.sh/uv/install.sh | sh
    curl -LsSf https://astral.sh/uv/install.sh | sh
  5. Run Local Loogle to Avoid Rate Limits

    main

    Running Loogle locally avoids the remote API's rate limit (3 req/30s). This is supported on Unix systems (Linux/macOS) only.

    Requirements:

    • git, lake (elan), and a built Mathlib project.
    • Substantial memory (initial Mathlib indexing can use ~13 GiB peak RSS).
    • --lean-project-path must point to a built project that depends on Mathlib.

    Usage:

    uvx lean-lsp-mcp --loogle-local
    # OR
    export LEAN_LOOGLE_LOCAL=true
  6. Connect to OpenAI via Secure MCP Tunnel

    main

    To use lean-lsp-mcp with OpenAI surfaces (like ChatGPT) without exposing it to the public internet, use the OpenAI Secure MCP Tunnel.

    1. Create a tunnel in OpenAI Platform settings.
    2. Run tunnel-client on a host that can reach your Lean project.

    For stdio transport:

    export CONTROL_PLANE_API_KEY="sk-..."
    
    tunnel-client init \
      --sample sample_mcp_stdio_local \
      --profile lean-lsp-local \
      --tunnel-id tunnel_0123456789abcdef0123456789abcdef \
      --mcp-command "uvx lean-lsp-mcp --transport stdio --lean-project-path /path/to/lean/project"
    
    tunnel-client run --profile lean-lsp-local

    For streamable-http transport: Bind the server to loopback and use the --mcp-server-url in your tunnel profile:

    export LEAN_PROJECT_PATH="/path/to/lean/project"
    uvx lean-lsp-mcp --transport streamable-http --host 127.0.1 --port 8000
  7. Configure lean-lsp-mcp for Claude Code

    main

    Run one of the following commands in the root directory of your Lean project (where lakefile.toml is located):

    Local-scoped MCP server:

    claude mcp add lean-lsp uvx lean-lsp-mcp

    Project-scoped MCP server (creates/updates .mcp.json in current directory):

    claude mcp add lean-lsp -s project uvx lean-lsp-mcp
    claude mcp add lean-lsp uvx lean-lsp-mcp
  8. Configure lean-lsp-mcp for Cursor

    main

    To add the server to Cursor:

    1. Open MCP Settings (File > Preferences > Cursor Settings > MCP).
    2. Click + Add a new global MCP Server and select Create File.
    3. Paste the following configuration into the mcp.json file:
    {
        "mcpServers": {
            "lean-lsp": {
                "command": "uvx",
                "args": ["lean-lsp-mcp"]
            }
        }
    }
    {
        "mcpServers": {
            "lean-lsp": {
                "command": "uvx",
                "args": ["lean-lsp-mcp"]
            }
        }
    }
  9. Configure lean-lsp-mcp for VSCode

    main

    You can configure the lean-lsp server in VSCode using the setup wizard or by manually editing your mcp.json configuration.

    Using the Setup Wizard:

    1. Press Ctrl+Shift+P.
    2. Select MCP: Add Server....
    3. Select Command (stdio).
    4. Enter uvx lean-lsp-mcp as the command.
    5. Name it lean-lsp.
    6. Choose Global or Workspace.

    Manual Configuration (User Settings): Open your user configuration via Ctrl+Shift+P > MCP: Open User Configuration and add:

    {
        "servers": {
            "lean-lsp": {
                "type": "stdio",
                "command": "uvx",
                "args": [
                    "lean-lsp-mcp"
                ]
            }
        }
    }

    WSL2 Users (Windows): If you are developing in WSL2, use this configuration instead:

    {
        "servers": {
            "lean-lsp": {
                "type": "stdio",
                "command": "wsl.exe",
                "args": [
                    "uvx",
                    "lean-lsp-mcp"
                ]
            }
        }
    }
    {
        "servers": {
            "lean-lsp": {
                "type": "stdio",
                "command": "uvx",
                "args": [
                    "lean-lsp-mcp"
                ]
            }
        }
    }
  10. How to add a new MCP tool

    main

    To add a new tool to the lean-lsp-mcp server, follow this structured workflow to ensure proper integration with the MCP lifecycle and Lean LSP client:

    1. Define the return model: Create a Pydantic model in src/lean_lsp_mcp/models.py. Use the wrapper pattern (a top-level model containing a list field) rather than returning a bare list. Use Optional[...] with a None default for optional fields.
    2. Implement logic in a helper module: Place the heavy lifting (LSP calls, data processing) in a separate helper module (e.g., src/lean_lsp_mcp/utils.py). This module should be plain Python and should not import MCP-specific components, making it easier to test.
    3. Register the tool in server.py: Use the @mcp.tool decorator to register the function.
      • The decorator argument is the public identifier string.
      • Use ToolAnnotations to provide metadata like title, readOnlyHint, idempotentHint, and openWorldHint.
      • The function signature must have ctx: Context as the first argument.
      • Use Annotated with Field for parameter descriptions and constraints.
      • The tool body should: validate the path using setup_client_for_file, retrieve the client from ctx.request_context.lifespan_context.client, and delegate to the helper module.
    4. Handle Lifespan State: If the tool requires new state that persists across requests, initialize it inside app_lifespan after lean_project_path is computed, and ensure it is cleaned up in the finally block.
    @mcp.tool(
        "tool_name",
        annotations=ToolAnnotations(
            title="Tool Title",
            readOnlyHint=True,
            idempotentHint=True,
            openWorldHint=False,
        ),
    )\ndef tool_function(
        ctx: Context,
        param: Annotated[str, Field(description="Description")]
    ) -> ReturnModel:
        """One-line docstring."""
        # 1. Validate path
        # 2. Get client from ctx.request_context.lifespan_context.client
        # 3. Call helper
        return helper_function(client, param)
  11. Secure Setup using Docker (Containerized)

    main

    For stricter isolation, use the provided Docker image. This is recommended as the server has access to your local file system and build capabilities.

    1. Build the image:
    docker build -t lean-lsp-mcp:containerized .
    1. Run with a mounted project root (Read-only source + writable Lake cache):
    docker run --rm -i \
      -v "$PWD":/workspace:ro \
      -v lean-lsp-mcp-lake-cache:/workspace/.lake \
      lean-lsp-mcp:containerized

    Default Container Settings:

    • LEAN_PROJECT_PATH=/workspace
    • LEAN_MCP_DISABLED_TOOLS=lean_run_code (can be overridden with -e).
    • Warning: Using --network none may break tools requiring network access (e.g., leansearch, loogle, leanfinder).
  12. Configure lean-lsp-mcp for Mistral Vibe

    main

    For Mac/Linux, edit ~/.vibe/config.toml and add the following configuration:

    [[mcp_servers]]
    name = "lean-lsp"
    transport = "stdio"
    command = "uvx"
    args = ["lean-lsp-mcp"]
    tool_timeout_sec = 600

    Note: If there are no existing MCP servers, you may need to remove mcp_servers = [] from the file.

    [[mcp_servers]]
    name = "lean-lsp"
    transport = "stdio"
    command = "uvx"
    args = ["lean-lsp-mcp"]
    tool_timeout_sec = 600