Skip to main content
Glama
indulge
by indulge
README.md
# Leela MCP Server (`lc0-mcp`)

An [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server that
exposes the [Leela Chess Zero](https://lczero.org) neural-network chess engine
(**lc0**) to any MCP client. Once connected, a client (Claude, or any other MCP
host) can analyze positions, get best moves, evaluate lines, and manipulate
boards through a small set of tools.

> **Naming:** `lc0` is the binary name for **L**eela **C**hess **Zer**o, hence
> the `lc0-*` command and tool names in this repo.

This package is **just the MCP server**. The engine binary (`lc0`) and a network
weights file are **external dependencies** you provide — see
[Prerequisites](#prerequisites).

---

## Quick start

For the impatient — each step links to its detailed section below.

1. **Install Python ≥ 3.10** and [pipx](https://pipx.pypa.io)
   (`python3 -m pip install --user pipx && python3 -m pipx ensurepath`).
2. **Get the `lc0` engine binary** — build it or download a release.
   See [1. The `lc0` engine](#1-the-lc0-engine). Note its path.
3. **Get a network** (`*.pb.gz` weights file) and note its path.
   See [2. A network (weights) file](#2-a-network-weights-file).
4. **Install this server:**
   ```bash
   git clone https://github.com/indulge/leela-mcp-server.git
   cd leela-mcp-server
   pipx install .
   ```
5. **Connect it to your MCP client** with the two paths from steps 2–3.
   For Claude Code:
   ```bash
   claude mcp add -s user lc0-chess lc0-mcp \
     -e LC0_PATH=/path/to/lc0 \
     -e LC0_WEIGHTS=/path/to/network.pb.gz
   ```
   See [Use with an MCP client](#use-with-an-mcp-client) for Claude Desktop, Cursor, etc.
6. **Verify:** `claude mcp list` shows `lc0-chess … ✔ Connected`, or run the
   [smoke test](#run--test).

---

## Prerequisites

| Dependency | Required | How to get it |
|------------|----------|----------------|
| **Python** | ≥ 3.10 | system package / [python.org](https://www.python.org) |
| **`lc0` engine binary** | yes | build from source or download a release — see below |
| **A network weights file** (`*.pb.gz`) | yes | download — see below (a source build does **not** include one) |
| Python deps: `mcp[cli]`, `python-chess` | yes | installed automatically (see [Install](#install)) |

### 1. The `lc0` engine

lc0 is a separate project ([lczero.org](https://lczero.org) ·
[GitHub: LeelaChessZero/lc0](https://github.com/LeelaChessZero/lc0)).

- **Official prebuilt binaries are Windows-only.** On Linux/macOS, build from
  source with `meson` + `ninja` (see lc0's
  [build instructions](https://github.com/LeelaChessZero/lc0#building-and-running-lc0)),
  or install via your package manager if it ships lc0.
- The build produces a binary, typically at `build/release/lc0`. Note that path —
  you'll point `LC0_PATH` at it.

### 2. A network (weights) file

A source build of lc0 does **not** bundle a network, and lc0 will not start
without one.

- Download a `.pb.gz` network from the
  [lc0 networks page](https://lczero.org/play/networks/bestnets/) or
  [storage.lczero.org](https://storage.lczero.org/).
- Smaller nets (e.g. a distilled 256×10, ~37 MB) run acceptably on CPU; larger
  nets (768×15, 170–380 MB) are stronger but want a GPU backend.
- Note the file path — you'll point `LC0_WEIGHTS` at it.

---

## Install

The server installs a `lc0-mcp` console command. Install it **for the current
user** with [pipx](https://pipx.pypa.io) (recommended — isolates the
dependencies and avoids PEP 668 "externally-managed environment" errors):

```bash
git clone https://github.com/indulge/leela-mcp-server.git
cd leela-mcp-server
pipx install .
```

This puts `lc0-mcp` on your `PATH` and pulls in its Python dependencies
(`mcp[cli]`, `python-chess`). Verify with `which lc0-mcp` — it should print a path
like `~/.local/bin/lc0-mcp`. (There's no `--help`; it's a stdio server that waits
for an MCP client to connect — see [Run / test](#run--test).)

Alternatives:

```bash
pipx install -e .                 # editable: track the working tree (for development)
pip install --user .              # without pipx (may need a venv on PEP-668 systems)
```

To upgrade: `pipx reinstall lc0-mcp`. To remove: `pipx uninstall lc0-mcp`.

---

## Configure (environment variables)

The server is configured entirely through environment variables. **`LC0_PATH`
and `LC0_WEIGHTS` are effectively required** — set them to your engine binary
and network from [Prerequisites](#prerequisites). (As a convenience, if you place
the binary at `./lc0/build/release/lc0` and any `*.pb.gz` net in `./networks/`
relative to the server source, the defaults below find them — but for an
installed command you should set the paths explicitly.)

| Variable | Default | Meaning |
|----------|---------|---------|
| `LC0_PATH` | `./lc0/build/release/lc0` | path to the `lc0` engine binary |
| `LC0_WEIGHTS` | first `*.pb.gz` in `./networks/` | path to the network `.pb.gz` |
| `LC0_BACKEND` | auto-detect | force a backend (`eigen`, `blas`, `cuda`, …) |
| `LC0_THREADS` | `min(8, cpu_count)` | search threads |
| `LC0_DEFAULT_NODES` | `2000` | default search budget per call |
| `LC0_MAX_NODES` | `5000000` | hard cap per request |
| `LC0_MAX_MOVETIME_MS` | `60000` | hard cap per request |

---

## Use with an MCP client

`lc0-mcp` is a **stdio MCP server**: a client launches it as a subprocess and
talks JSON-RPC over stdin/stdout. Every stdio MCP client configures the same
three things — a **command**, optional **args**, and **env** vars:

```
command: lc0-mcp
args:    (none)
env:     LC0_PATH=/path/to/lc0
         LC0_WEIGHTS=/path/to/network.pb.gz
```

A ready-to-edit JSON example is in [`examples/mcp-config.json`](examples/mcp-config.json).
Two concrete clients:

### Claude Code (CLI)

```bash
claude mcp add -s user lc0-chess lc0-mcp \
  -e LC0_PATH=/path/to/lc0 \
  -e LC0_WEIGHTS=/path/to/network.pb.gz \
  -e LC0_DEFAULT_NODES=2000
```

Then `claude mcp list` should show `lc0-chess: … ✔ Connected`.

### Claude Desktop, Cursor, and other JSON-config clients

Most MCP hosts (Claude Desktop, Cursor, Windsurf, Zed, …) take the same
`command` / `args` / `env` shape in a JSON config file — only the file's location
differs (Claude Desktop: `claude_desktop_config.json`; Cursor: `.cursor/mcp.json`).
Add an entry under `mcpServers`:

```json
{
  "mcpServers": {
    "lc0-chess": {
      "command": "lc0-mcp",
      "env": {
        "LC0_PATH": "/path/to/lc0",
        "LC0_WEIGHTS": "/path/to/network.pb.gz"
      }
    }
  }
}
```

Then restart (or reload) the client and it will launch `lc0-mcp` on demand.

> **If `lc0-mcp` isn't found:** the client's launch environment may not include
> your user bin directory on `PATH`. Use the absolute path instead — find it with
> `which lc0-mcp` (e.g. `~/.local/bin/lc0-mcp`) and put that in `"command"`.

---

## Tools

| Tool | What it does |
|------|--------------|
| `analyze_position(fen, nodes?, movetime_ms?, depth?, multipv=1)` | Evaluate a position; returns score (centipawns / mate), lc0 Win/Draw/Loss + expectation, and best line(s) in UCI + SAN. |
| `best_move(fen, nodes?, movetime_ms?, depth?)` | Single best move + resulting FEN + eval. |
| `play_move(fen, move)` | Apply a move (UCI or SAN); returns new FEN + board + game status. |
| `position_from_moves(moves, start_fen?)` | Build a position from a sequence of moves. |
| `legal_moves(fen)` | All legal moves (UCI + SAN). |
| `show_board(fen)` | Text + unicode board diagram and status. |
| `engine_info()` | Engine id, configured paths, and limits. |

`fen` accepts a FEN string or the literal `"startpos"`. The search budget
defaults to `LC0_DEFAULT_NODES`; pass `nodes`, `movetime_ms`, or `depth` to
override per call.

---

## Run / test

```bash
# Run the installed server standalone (stdio; it waits for an MCP client — Ctrl-C to stop)
LC0_PATH=/path/to/lc0 LC0_WEIGHTS=/path/to/network.pb.gz lc0-mcp

# Smoke test: drives the server as a real MCP client end-to-end.
# Requires LC0_PATH / LC0_WEIGHTS in the environment (or the default paths populated).
pip install -r requirements.txt          # if running from source without installing
LC0_PATH=/path/to/lc0 LC0_WEIGHTS=/path/to/network.pb.gz python test_mcp.py
```

---

## Project layout

```
leela-mcp-server/
├── lc0_mcp_server.py     # the MCP server (all tools)
├── pyproject.toml        # packaging — defines the `lc0-mcp` command + deps (source of truth)
├── requirements.txt      # convenience deps for running from source (mirrors pyproject)
├── test_mcp.py           # end-to-end MCP smoke test
├── examples/
│   └── mcp-config.json   # generic client config (placeholder paths)
├── LICENSE               # MIT
└── README.md
```

---

## References

- **MCP** — protocol spec & SDKs: <https://modelcontextprotocol.io>
- **Leela Chess Zero** — project home: <https://lczero.org>
- **lc0** — engine source & build guide: <https://github.com/LeelaChessZero/lc0>
- **Networks** — weights downloads: <https://lczero.org/play/networks/bestnets/> · <https://storage.lczero.org/>
- **python-chess** — board/move/FEN library used here: <https://python-chess.readthedocs.io>

## License

MIT — see [LICENSE](LICENSE).