Skip to main content
Glama
BNJ02

marl-book-mcp

by BNJ02
README.md
# marl-book-mcp

An [MCP](https://modelcontextprotocol.io) server that exposes the full text of
*Multi-Agent Reinforcement Learning: Foundations and Modern Approaches* by
Stefano V. Albrecht, Filippos Christianos, and Lukas Schäfer (The MIT Press,
2024), so an LLM can search it, pull a specific page, or read a whole chapter
instead of guessing from memory.

Built with [FastMCP](https://github.com/jlowin/fastmcp). Runs locally over stdio,
or as a remote HTTP server with OAuth for clients like claude.ai. Modeled on the
sibling `rlbook/` server (Sutton & Barto).

> **Licensing:** the code is MIT. The book text under `pages_corrected/` and the
> raw OCR under `ocr_raw/` is **not** — it belongs to the authors and MIT Press.
> Read [`NOTICE.md`](NOTICE.md) before you reuse anything from those directories.

## Tools

| Tool | What it does |
|---|---|
| `search(query, max_results=5)` | Case-insensitive term search across all 381 reconstructed pages. Returns the best-scoring pages with a surrounding excerpt and the chapter each one falls in. |
| `get_page(page_number)` | One page verbatim, 1–395. Page numbers are **PDF** page numbers (printed number = PDF number − 29). |
| `get_chapter(chapter_number)` | A whole chapter, 0–16. Index `0` is the front matter; `1` is Chapter 1. Call `list_chapters` for the mapping. |
| `list_chapters()` | All 17 sections with their page ranges. |

Math is returned as LaTeX (inline `\( … \)`, display `\[ … \]`), reconstructed
from the Nougat OCR pass.

## The corpus and how it was built

The book PDF is freely available (color, ~8 MB) at <https://www.marl-book.com>
(the "Download" link). It is a **native-text** PDF, but a plain PyMuPDF
extraction flattens all the mathematics. To keep the equations, the corpus was
built with a **two-source OCR + reconciliation** pipeline (same idea as
`rlbook/`, but the source here is digital rather than scanned):

1. **PyMuPDF** (`extract.py`) — faithful running text, used as the spelling /
   wording reference. Output in `ocr_raw/pymupdf_pages_*.txt`.
2. **Nougat** (`run_nougat.py`, run on the `dataia25` GPU cluster, 2× RTX 3090) —
   math-aware OCR producing LaTeX. Output in `ocr_raw/nougat_pages_*.mmd`.
3. **Reconciliation** — every page was cross-checked between the two sources and
   hand-corrected into `pages_corrected/pages_XXX-YYY.md` (10–14 pages/file,
   `--- PAGE N ---` separators, N = PDF page). Section numbers Nougat dropped
   were restored, all algorithm boxes and linear programs were reconstructed,
   figures are described in-line as `[Figure N.M: …]`, and Nougat runaway/repeat
   pages (191, 283, 37, …) were rebuilt from PyMuPDF.

`pages_corrected/` is what the server actually reads via `_load_all_pages()`.
References (p.370–391) and the index (p.392–395) are included from the PyMuPDF
text. The source PDF is gitignored (`*.pdf`) — download it yourself if you want
to re-extract.

### Nougat on the cluster (gotchas)

Nougat 0.1.17 is abandoned and hostile to modern Python. The working recipe
(isolated venv, py3.12) pins around it:

- `transformers==4.34.1` (5.x → `NameError: name 'nn' is not defined`)
- `albumentations==1.3.1` (2.x changed the `ImageCompression` signature)
- `pypdfium2==4.20.0` (≥4.25 removed `PdfDocument.render`)
- install `nougat-ocr --no-deps` after the pinned wheels (its `datasets[vision]`
  otherwise backtracks to an ancient `pyarrow` sdist that won't build on 3.12)
- `sentencepiece` is required but not pulled in with `--no-deps`.

Drive it page-by-page with `batch_size=1` (`run_nougat.py`) so page↔prediction
alignment is guaranteed and `--- PAGE N ---` markers are exact.

## Chapter map (PDF page ranges)

Front matter 1–29 · Ch1 30–45 · Part I 46–47 · Ch2 48–71 · Ch3 72–89 ·
Ch4 90–117 · Ch5 118–143 · Ch6 144–187 · Part II 188–189 · Ch7 190–211 ·
Ch8 212–247 · Ch9 248–333 · Ch10 334–347 · Ch11 348–365 · Appendix A 366–369 ·
References 370–391 · Index 392–395.

## Run

### Local (stdio)

```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python server.py
```

Register with Claude Code:

```bash
claude mcp add marlbook -- /home/bnj/mcp_servers/.venv/bin/python /home/bnj/mcp_servers/marlbook/server.py
```

### Local editor integrations (Continue, Zed, …)

Loopback, no auth:

```bash
python server.py --http --no-auth --port=8008
```

`--no-auth` binds to `127.0.0.1` and refuses any other host. Do not put this mode
behind a reverse proxy — use OAuth for anything off-machine. See
[`continue-mcp.yaml`](continue-mcp.yaml).

### Remote (HTTP + OAuth)

Copy `.env.example` to `.env` and fill it in (`MCP_CLIENT_SECRET` from
`openssl rand -hex 32`, a `MCP_ACCESS_PIN`, and the public `MCP_BASE_URL`). Then:

```bash
set -a && source .env && set +a
python server.py --http --port=8008
```

Deployed here as the systemd user service `mcp-marlbook.service` on port **8008**,
behind Caddy on the rpi5 (`mcp-marlbook.benjamin-lepourtois.fr`, `flush_interval -1`
for SSE), with a Hostinger A record and the connector registered on claude.ai.

## Known limitations

- **Page numbers are PDF-relative** (printed number = PDF − 29).
- **Figures are described, not reproduced** — schematics, learning curves, and
  matrix-game diagrams are summarized in brackets from their captions.
- **Search is literal** (term counts, no stemming/semantics).
- **OCR is imperfect** on the densest equations and tables; a handful of Nougat
  runaway pages were rebuilt from PyMuPDF and are marked as such.

## Credits

The book is by Stefano V. Albrecht, Filippos Christianos, and Lukas Schäfer,
published by The MIT Press. This repository only wraps its text in an MCP
interface. See [`NOTICE.md`](NOTICE.md).