Skip to main content
Glama
README.md
# Lyceum

<img src="public/logo-readme.png" alt="Lyceum" width="200">

An [MCP](https://modelcontextprotocol.io/) server for querying and managing an ebook library via AI agents. Works with any MCP client — Claude, ChatGPT, Cursor, Claude Code, and others.

Self-contained with SQLite + file storage, no external dependencies. Books are uploaded through the web UI or via the MCP tools.

## Setup

```bash
npm install
```

## Environment Variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `AUTH_PASSWORD` | Yes | — | Password for the OAuth authorization page |
| `DATA_DIR` | No | `/data` | Directory for the database and book files |
| `CONVERTER_URL` | No | — | URL of an ebook converter API for format conversion |
| `CONVERTER_API_KEY` | No | — | Bearer token for the converter API |
| `BASE_URL` | No | `http://localhost:3000` | Public URL of this server (used for OAuth redirects and signed URLs) |
| `PORT` | No | `3000` | Port to listen on |

## Running

```bash
AUTH_PASSWORD=your-secret DATA_DIR=./data npm run dev
```

For production:

```bash
AUTH_PASSWORD=your-secret \
  DATA_DIR=/var/lib/lyceum \
  BASE_URL=https://lyceum.yourdomain.com \
  npm start
```

### Importing from Calibre

To migrate an existing Calibre library into Lyceum, run the import script once before starting the server:

```bash
CALIBRE_SERVER_URL=http://calibre:8080 \
  DATA_DIR=./data \
  npm run import
```

This downloads all books and covers from the Calibre content server and stores them in the local SQLite database and file store.

## Container

Pre-built images are available from GitHub Container Registry:

```bash
podman pull ghcr.io/matthewp/lyceum:latest
```

The container exposes port 3000. Mount a volume at `/data` to persist state across restarts:

```bash
podman run -d \
  -p 3009:3000 \
  -v lyceum-data:/data \
  -e AUTH_PASSWORD=your-secret \
  -e BASE_URL=https://lyceum.yourdomain.com \
  ghcr.io/matthewp/lyceum:latest
```

To build from source instead, the included `Containerfile` uses `node:24-slim`. Node 24 supports native TypeScript type stripping, so no build step is needed — the source runs directly with `--experimental-strip-types`.

```bash
podman build -t lyceum .
```

For sensitive values, use podman secrets:

```bash
printf 'your-secret' | podman secret create lyceum_auth_password -
```

Then reference them in a [quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) `.container` file:

```ini
Secret=lyceum_auth_password,type=env,target=AUTH_PASSWORD
```

## Format Conversion

The `convert_book` MCP tool converts a book from one format to another (e.g. EPUB to MOBI). This requires [ebook-converter-api](https://github.com/matthewp/ebook-converter-api):

- `CONVERTER_URL` — base URL of the service (e.g. `http://converter:8080`)
- `CONVERTER_API_KEY` — optional Bearer token

## MCP Tools

| Tool | Description |
|---|---|
| `list_books` | List books sorted by most recently added |
| `get_book` | Get full details for a book (authors, tags, series, formats, etc.) |
| `search_books` | Search books by title, author, tag, or series name |
| `list_authors` | List all authors with book counts |
| `list_tags` | List all tags with book counts |
| `list_series` | List all series with book counts |
| `list_books_by_series` | List all books in a series, ordered by series index |
| `list_books_by_author` | List all books by a specific author |
| `get_view_link` | Get a signed URL to view a book's details page with cover and metadata (expires in 10 minutes) |
| `get_download_link` | Get a signed download URL for a book file (expires in 5 minutes) |
| `get_upload_link` | Get a signed URL to upload a book via browser (expires in 10 minutes) |
| `get_add_format_link` | Get a signed URL to upload an additional format to an existing book (expires in 10 minutes) |
| `set_metadata` | Update metadata fields on a book (title, authors, tags, series, etc.) |
| `set_cover` | Set a book's cover image from a URL |
| `fetch_metadata` | Search Google Books for metadata by title, author, or ISBN |
| `remove_book` | Permanently remove one or more books from the library |
| `remove_format` | Remove specific file formats from a book (e.g. remove MOBI but keep EPUB) |
| `convert_book` | Convert a book to a different format (e.g. EPUB to MOBI) |
| `add_device` | Start adding an e-reader device (sends a verification code) |
| `verify_device` | Complete device setup with the verification code |
| `list_devices` | List all configured e-reader devices |
| `remove_device` | Remove a configured device |
| `send_to_device` | Send a book to an e-reader device |
| `get_opds_settings` | Get current OPDS catalog settings |
| `set_opds_settings` | Configure OPDS catalog (enable/disable, set username and password) |
| `get_kosync_settings` | Get current KOSync (reading progress sync) settings |
| `set_kosync_settings` | Configure KOSync (enable/disable, set username and password) |

## Connecting an MCP Client

Lyceum exposes its MCP endpoint at `/mcp` (e.g. `https://lyceum.yourdomain.com/mcp`). Any MCP client that supports HTTP transport with OAuth 2.1 can connect. Here are a few examples:

### claude.ai

Go to **Settings > Connectors > Add custom connector** and enter your `/mcp` URL. You'll be prompted to authenticate via the OAuth flow.

### Claude Code

```bash
claude mcp add --transport http lyceum https://lyceum.yourdomain.com/mcp
```

### ChatGPT

Go to **Settings > Integrations**, add a new MCP server, and enter your `/mcp` URL. ChatGPT will handle the OAuth authentication automatically. See [OpenAI's MCP documentation](https://platform.openai.com/docs/guides/tools-remote-mcp) for current setup details.

### VS Code (Copilot)

Add a remote MCP server to your `.vscode/mcp.json`:

```json
{
  "servers": {
    "lyceum": {
      "type": "http",
      "url": "https://lyceum.yourdomain.com/mcp"
    }
  }
}
```

VS Code handles the OAuth flow when you first connect. See [VS Code's MCP documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for current setup details.

### Other clients

Lyceum works with any MCP client that supports HTTP transport with OAuth 2.1, including Cursor, Windsurf, and others. Point the client at your `/mcp` URL — Lyceum uses dynamic client registration, so clients that support the MCP auth spec will authenticate automatically.

## Send to Device

Lyceum can send books directly to e-reader devices. Currently supported:

### Boox

Send books to [Boox](https://www.boox.com/) e-readers via the Send2Boox cloud service. To set up, ask your agent to add your device — it will walk you through the flow:

1. **Add device**: Provide your Boox account email and region (`us`, `eu`, or `cn`). A verification code is sent to your email.
2. **Verify**: Enter the code to complete setup. The device is saved and ready to use.
3. **Send**: Ask your agent to send any book in your library to the device by name.

### Xteink

Send books to [Xteink](https://www.xteink.com/) e-readers (X3, X4) via the XT Cloud service. The device polls for new files approximately every 30 seconds.

1. **Add device**: Provide your Xteink account email and password. Lyceum logs in and lists your bound devices.
2. **Verify**: Select which device to use (by number if you have multiple).
3. **Send**: Ask your agent to send any book to the device. It appears in the "Pushed Files" folder.

Note: The Xteink cloud API communicates over unencrypted HTTP.

### CrossPoint (Xteink)

Send books directly to Xteink devices running [CrossPoint Reader](https://github.com/crosspoint-reader/crosspoint-reader), a community-built replacement firmware. This transfers files over your local network via WebSocket — no cloud account required.

Before adding a CrossPoint device, put your e-reader into **File Transfer → Connect to Calibre** mode. Then:

1. **Add device**: No credentials needed. Lyceum broadcasts a UDP discovery packet on the local network and lists found devices. If auto-discovery doesn't work (e.g. due to network segmentation), you can provide `{ip, port}` manually.
2. **Confirm**: Select which device to use by number.
3. **Send**: Ask your agent to send any book. The file is transferred directly to the device over WebSocket.

Device credentials are persisted to `lyceum.db` so they survive restarts.

## OPDS Catalog

OPDS (Open Publication Distribution System) is an open standard that lets e-reader apps browse, search, and download books from a server. If you use a reading app on your phone, tablet, or e-reader, OPDS is how you connect it to your Lyceum library — no manual file transfers needed.

Most popular reading apps support OPDS, including [KOReader](https://koreader.rocks/), [Moon+ Reader](https://www.moondownload.com/), [FBReader](https://fbreader.org/), [Aldiko](https://www.aldiko.com/), [Foliate](https://johnfactotum.github.io/foliate/) (Linux), and PocketBook devices (built-in). Kobo and Kindle e-readers do not have native OPDS support, but you can use it if you install KOReader on them.

### Enabling OPDS

OPDS is disabled by default. To enable it, you need to set a username and password that your reader apps will use to authenticate. There are two ways to do this:

**Via MCP:**

If you have Lyceum connected to an MCP client, you can ask your agent to enable OPDS and set the username and password for you. For example: *"Enable OPDS with username reader and password secret123"*. The agent will call the `set_opds_settings` tool on your behalf. You can also check the current configuration by asking *"What are my OPDS settings?"*.

**Via the web UI:**

1. Log into Lyceum and go to **Settings** in the sidebar.
2. Check **Enable OPDS feeds**.
3. Set a **username** and **password**. These are separate from your Lyceum login password — they are only used by reader apps.
4. Click **Save**.

Once enabled, the OPDS catalog URL is shown on the Settings page:

```
https://lyceum.yourdomain.com/opds/
```

### Connecting a Reader App

Every app is slightly different, but the general steps are the same:

1. Open your reader app's **OPDS catalog** or **network library** settings.
2. Add a new catalog with:
   - **URL**: `https://lyceum.yourdomain.com/opds/`
   - **Username**: the OPDS username you configured
   - **Password**: the OPDS password you configured
3. Browse the catalog. You'll see sections for recent additions, authors, series, and tags.
4. Tap a book to download it to your device.

#### Boox

Boox e-readers have built-in OPDS support through the **PushRead** app, which comes preinstalled — no need to install KOReader or any other app.

1. Open **PushRead** and switch to the **OPDS** tab.
2. Go to **Directory > Add subscription > Custom**.
3. Tap **Add Group** and enter a group name (e.g. "Lyceum").
4. Tap the **Add icon** in the upper right corner and enter your OPDS URL.
5. Enter your OPDS username and password when prompted.

Downloaded books will open in NeoReader.

#### Xteink (CrossPoint Reader)

The stock Xteink firmware does not support OPDS. To use OPDS on an Xteink X4, install [CrossPoint Reader](https://github.com/crosspoint-reader/crosspoint-reader), a community-built replacement firmware.

1. In CrossPoint Reader, open the **OPDS Browser**.
2. Enter your OPDS URL (e.g. `https://lyceum.yourdomain.com/opds/`).
3. Enter your OPDS username and password.

Note: CrossPoint Reader only supports **HTTP Basic** authentication, which is what Lyceum uses.

#### KOReader

Works on Kobo, Kindle, PocketBook, Android, and other devices with KOReader sideloaded.

1. Open the file browser and tap the globe icon (or go to **Search > OPDS catalog**).
2. Tap **+** to add a new catalog.
3. Enter the catalog name (e.g. "Lyceum") and the OPDS URL.
4. KOReader will prompt for username and password when you first open the catalog.

#### Moon+ Reader (Android)

1. Go to **Net Library > OPDS Catalogs**.
2. Tap the **+** button.
3. Enter the name, URL, username, and password.

#### Foliate (Linux)

1. Open the **Library** view.
2. Go to **Catalogs** and click **+**.
3. Enter the OPDS URL. Foliate will prompt for credentials.

### What OPDS provides

- **Browse** your library by author, series, tag, or recent additions
- **Search** by title, author, or keyword
- **Download** books in any available format (EPUB, PDF, MOBI, etc.)
- **Cover images** for each book

OPDS is read-only — you cannot add, delete, or edit books through it. All library management is done through the Lyceum web UI or MCP tools.

## KOSync (Reading Progress Sync)

[KOReader](https://koreader.rocks/) is an open-source ebook reader that runs on Kobo, Kindle, PocketBook, and Android devices. KOSync is its built-in reading progress sync feature — it lets you stop reading on one device and pick up where you left off on another.

Lyceum acts as a KOSync server. When you open a book in KOReader, it checks Lyceum for a newer reading position. When you close a book or turn a page, KOReader sends your current position to Lyceum. If you read the same book on multiple KOReader devices, they stay in sync automatically.

KOSync tracks **reading position only** — it does not sync bookmarks, highlights, or annotations.

### Enabling KOSync

KOSync is disabled by default. To enable it, set a username and password that your KOReader devices will use.

**Via MCP:**

If you have Lyceum connected to an MCP client, you can ask your agent to enable KOSync and set the username and password for you. For example: *"Enable KOSync with username reader and password secret123"*. The agent will call the `set_kosync_settings` tool on your behalf. You can also check the current configuration by asking *"What are my KOSync settings?"*.

**Via the web UI:**

1. Log into Lyceum and go to **Settings** in the sidebar.
2. Scroll to the **KOSync** section.
3. Check **Enable KOSync**.
4. Set a **username** and **password**.
5. Click **Save**.

Once enabled, the sync server URL is shown on the Settings page:

```
https://lyceum.yourdomain.com/kosync
```

### Connecting KOReader

1. In KOReader, go to **Top menu > Tools > Progress sync**.
2. Tap **Custom sync server** and enter your Lyceum URL (e.g. `https://lyceum.yourdomain.com/kosync`).
3. Tap **Login** and enter the username and password you configured in Lyceum.
4. Tap **Register** — KOReader will confirm the registration succeeded. (Lyceum validates that your credentials match rather than creating a new account, so only the configured user can register.)
5. Enable **Auto sync now and when KOReader is closed** for seamless syncing.

### Connecting CrossPoint Reader (Xteink)

[CrossPoint Reader](https://github.com/crosspoint-reader/crosspoint-reader) supports KOSync for syncing reading progress between Xteink e-readers and other KOReader devices.

1. Open CrossPoint Reader's settings.
2. Enter your Lyceum KOSync URL (e.g. `https://lyceum.yourdomain.com/kosync`), username, and password.
3. Use **Sync Progress** in the chapters menu to sync manually, or enable automatic syncing.

Note: CrossPoint's KOSync currently syncs to chapter-level accuracy rather than exact position. A [pull request](https://github.com/crosspoint-reader/crosspoint-reader/pull/1217) is in progress to improve this to precise position sync.

### How it works

KOSync identifies books by the MD5 hash of the file. This means:

- The same file on different devices syncs correctly.
- Different formats of the same book (e.g. EPUB vs PDF) are treated as separate books and have independent reading positions.
- Books must be the exact same file — if you modify or re-download a book, it gets a new hash and previous progress won't carry over.

### Security

KOReader sends an MD5-hashed password in request headers on every sync call. Only the username and password configured in Lyceum are accepted — there is no open registration. Over HTTPS this is secure for self-hosted use.

## Authentication

Lyceum uses OAuth 2.1 with dynamic client registration. When a client connects, it registers automatically, then the user authenticates with the `AUTH_PASSWORD`. Sessions are persisted to disk so they survive server restarts.

Download and upload links use HMAC-SHA256 signed URLs so they can be opened in a browser without additional authentication.