Metadata-Version: 2.4
Name: calibremcp
Version: 3.0.0
Summary: FastMCP server for conversational management of a local Calibre e-book library, with LanceDB RAG.
Project-URL: Homepage, https://github.com/moranon/calibremcp
Project-URL: Repository, https://github.com/moranon/calibremcp.git
Project-URL: Issues, https://github.com/moranon/calibremcp/issues
Author-email: moranon <moranon@thynik.com>
License: MIT
Keywords: calibre,ebooks,fastmcp,mcp
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.12
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: beautifulsoup4>=4.12.0
Requires-Dist: concurrent-log-handler>=0.9.25
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: ebooklib>=0.18.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: fastembed>=0.4.0
Requires-Dist: fastmcp>=3.4.2
Requires-Dist: httpx>=0.27.0
Requires-Dist: keyring>=24.0.0
Requires-Dist: lancedb>=0.17.0
Requires-Dist: lxml>=5.1.0
Requires-Dist: prefab-ui>=0.18.0
Requires-Dist: psutil>=5.9.0
Requires-Dist: py-key-value-aio[filetree]<0.5.0,>=0.4.4
Requires-Dist: pydantic>=2.9.0
Requires-Dist: pymupdf>=1.24.0
Requires-Dist: python-dotenv>=1.0.1
Requires-Dist: rich>=13.8.0
Requires-Dist: sqlalchemy>=2.0.0
Requires-Dist: structlog>=24.4.0
Requires-Dist: tenacity>=8.2.0
Provides-Extra: browser
Requires-Dist: playwright>=1.40; extra == 'browser'
Description-Content-Type: text/markdown

# calibremcp

FastMCP 3.4+ server for conversational Calibre e-book library management.
Reads Calibre's `metadata.db` directly (local SQLite, no Content Server required),
indexes metadata for semantic search via LanceDB, and exposes portmanteau MCP tools
for natural-language library management.

## Install

Requires Python 3.12+ and [uv](https://docs.astral.sh/uv/).

```bash
git clone https://github.com/moranon/calibremcp
cd calibremcp
uv sync
```

## Configure

Create a `.env` file (see [`.env.example`](.env.example)):

```bash
# Single library
CALIBRE_LIBRARY_PATH=/path/to/your/Calibre Library

# OR: root dir containing multiple sub-libraries
CALIBRE_LIBRARY_ROOT=/path/to/libraries
```

## Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "calibre-mcp": {
      "command": "uv",
      "args": ["--directory", "/path/to/calibremcp", "run", "calibre-mcp"],
      "env": {
        "CALIBRE_LIBRARY_PATH": "/path/to/your/Calibre Library"
      }
    }
  }
}
```

## HTTP transport

```bash
MCP_TRANSPORT=http MCP_HOST=127.0.0.1 MCP_PORT=10720 uv run calibre-mcp
```

> stdio (the default) uses no port. `MCP_PORT` only applies to the `http`/`sse`
> transports and defaults to `10720`.

## Tools

Most tools follow the portmanteau pattern — a single tool name with an `operation`
parameter. The table below lists the **actual** operations each handler accepts.

| Tool | Operations |
|------|-----------|
| `manage_libraries` | `list`, `switch`, `stats`, `search`, `discover`, `test_connection` |
| `query_books` | `search`, `list`, `recent`, `by_author`, `by_series` |
| `manage_books` | `add`, `get`, `details`, `update`, `delete` |
| `manage_metadata` | `update`, `organize_tags`, `fix_issues`, `show` |
| `manage_comments` | `create`, `read`, `update`, `delete`, `append`, `replace` |
| `manage_authors` | `list`, `get`, `get_books`, `stats`, `by_letter` |
| `manage_series` | `list`, `get`, `get_books`, `stats`, `by_letter` |
| `manage_tags` | `list`, `get`, `create`, `update`, `delete`, `merge`, `statistics`, `find_duplicates`, `get_unused`, `delete_unused` |
| `manage_publishers` | `list`, `get`, `get_books`, `stats`, `by_letter` |
| `manage_files` | `convert`, `download`, `bulk` |
| `manage_viewer` | `open`, `open_file`, `open_random`, `close`, `get_metadata`, `get_page`, `get_state`, `update_state` |
| `manage_analysis` | `tag_statistics`, `duplicate_books`, `series_analysis`, `library_health`, `reading_stats`, `unread_priority` |
| `manage_library_operations` | `list_books`, `analyze_series`, `merge_series`, `fix_series_metadata` |
| `export_books` | `json`, `csv`, `html`, `pandoc`, `stats` |
| `manage_import` | `from_path`, `from_url`, `annas_search`, `annas_download`, `gutenberg_search`, `gutenberg_import`, `arxiv_search`, `arxiv_import` |
| `manage_system` | `status`, `health_check`, `help`, `tool_help`, `list_tools`, `hello_world` |
| `manage_bulk_operations` | `update_metadata`, `export`, `delete`, `convert` |
| `manage_times` | `added_in_range`, `published_in_range`, `recent_additions`, `stats_by_month_added`, `stats_by_month_published`, `date_stats` |
| `manage_rag` | `fts_index_build`, `fts_retrieve`, `metadata_index_build`, `metadata_search`, `status`, `job_status`, `list_jobs` |
| `manage_recommendations` | `similar_books`, `for_you`, `discover` |

These tools are **not** portmanteaus (no `operation` parameter):

| Tool | Purpose |
|------|---------|
| `search_fulltext` | Full-text search inside book content via Calibre's FTS database (`query`, `limit`, snippets) |
| `calibre_metadata_export_json` | Export full library metadata as JSON |
| `help_tool` | Usage guidance |

`show_api_docs` (a machine-readable tool catalog) is always registered. When
`CALIBRE_PREFAB_APPS` is enabled (the default), two MCP-App UI cards are also registered:
`show_book_prefab_card` and `show_libraries_prefab_card`.

## RAG / semantic search

calibremcp includes two LanceDB backends, both stored inside the active library directory:

- **FTS content index** (`lancedb/books_rag`) — semantic search over book passages using Calibre's full-text search data (`fts_index_build` / `fts_retrieve`). Requires FTS to be enabled in Calibre.
- **Metadata index** (`lancedb_metadata/`) — semantic search over titles, authors, tags, series, comments (`metadata_index_build` / `metadata_search`).

Index builds run as background jobs and return a `job_id` immediately:

```
manage_rag(operation="fts_index_build")
→ {"job_id": 1, "status": "pending"}

manage_rag(operation="job_status", job_id=1)
→ {"status": "done", "result": {"chunks_indexed": 42381}}

manage_rag(operation="fts_retrieve", query="murder mystery with a locked room")
```

## Recommendations

`manage_recommendations` turns the metadata index into a recommendation engine — no
embedding model is loaded (it reuses the vectors stored at index-build time):

```
manage_recommendations(operation="similar_books", book_id=42)   # more like this
manage_recommendations(operation="for_you")                      # picks from your ratings + read history
manage_recommendations(operation="discover", min_age_days=365)   # unread hidden gems in your backlog
```

The taste profile is built from books rated ≥ `min_rating` stars (default 4) and books
marked read (`CALIBRE_READ_COLUMN`); both signals degrade gracefully when absent.
Requires the metadata index (`manage_rag(operation="metadata_index_build")`).

## Environment variables

| Variable | Description |
|----------|-------------|
| `CALIBRE_LIBRARY_PATH` | Absolute path to a single Calibre library directory |
| `CALIBRE_LIBRARY_ROOT` | Root directory containing multiple library sub-directories |
| `CALIBRE_LIBRARIES` | Comma-separated explicit list of library paths |
| `MCP_TRANSPORT` | `stdio` (default), `http`, or `sse` (deprecated) |
| `MCP_HOST` | HTTP/SSE host (default `127.0.0.1`) |
| `MCP_PORT` | HTTP/SSE port (default `10720`); ignored in stdio mode |
| `MCP_PATH` | HTTP endpoint path (default `/mcp`) |
| `MCP_CORS_ORIGINS` | HTTP CORS allow-list (comma-separated). Default: localhost only. `*` opens it (credentials disabled) |
| `RAG_OP_TIMEOUT` | Seconds before `manage_rag` retrieve/search/status time out (default `30`) |
| `RAG_MODEL_LOAD_TIMEOUT` | Seconds allowed for the ONNX embedding model to load (default `300`) |
| `CALIBRE_MAX_DOWNLOAD_MB` | Max size (MB) for any network import download (`from_url`, Anna's, arXiv, Gutenberg); default `500` |
| `CALIBRE_RAG_CHUNK_EXCLUDE_FORMATS` | Chunk-RAG only: comma-separated formats to skip (unset → excludes PDF) |
| `CALIBRE_RAG_MAX_BOOK_TEXT_CHARS` | Chunk-RAG: skip `books_text` rows longer than this |
| `CALIBRE_METADATA_COMMENT_MAX_CHARS` | Metadata RAG: cap on chars of the Comments field per book in embedding text |
| `CALIBRE_METADATA_STRIP_HTML` | Metadata RAG: `1` (default) strips HTML in comments; `0`/`false` keeps raw |
| `CALIBRE_RAG_CACHE_DIR` | Override the fastembed model cache directory |
| `CALIBRE_DEFAULT_LIMIT` | Default search result limit (default `50`) |
| `CALIBRE_MAX_LIMIT` | Maximum search result limit (default `200`) |
| `CALIBRE_ANNAS_MIRRORS` | Comma-separated Anna's Archive mirror URLs |
| `CALIBRE_ANNAS_BROWSER_TIMEOUT` | Seconds for the optional headless-browser download fallback (default `180`); see "Anna's Archive downloads" below |
| `CALIBRE_GUTENBERG_MIRROR` | Project Gutenberg base URL |
| `CALIBRE_PREFAB_APPS` | Set `0`/`false` to skip the prefab MCP-App UI tools (default on) |
| `CALIBRE_BETA_TOOLS` | Legacy/no-op: the former beta tools are now first-class and always loaded |

## Anna's Archive downloads

`manage_import operation="annas_download"` tries, in order: the member
fast-download API (set `CALIBRE_ANNAS_SECRET_KEY`), then free external mirror
links over plain HTTP. When those are all blocked by Anna's DDoS-Guard JS
challenge, it can fall back to a **headless Chromium browser** that solves the
challenge and drives the free slow-download flow.

That fallback is an **optional extra** (it is lazily imported — everything else
works without it). From the project directory:

```bash
uv sync --extra browser            # installs Playwright into the project venv
uv run playwright install chromium # one-time Chromium download (~150 MB)
```

> If a CalibreMCP server is already running from this venv, stop it first —
> `uv sync` rewrites the launcher scripts and Windows will block that while the
> server holds them open.

Tune the overall browser session timeout with `CALIBRE_ANNAS_BROWSER_TIMEOUT`
(seconds, default `180`).

## Safety / write policy

**Close the Calibre GUI before writes.** Calibre keeps `metadata.db` open and holds an
in-memory cache; if the GUI is running it can overwrite changes written underneath it.

How writes are routed:

- **Core metadata** (`title`, `sort`, `pubdate`, `series`/`series_index`, `authors`,
  `author_sort`, `tags`, `rating`, `comments`) is written **directly to `metadata.db`
  via SQLite** (SQLAlchemy). The connection registers Calibre's own `title_sort()`
  function (`db/database.py`) so Calibre's triggers fire and derived sort fields stay
  consistent. Writes are read-back verified — a tool reports `success: True` only after
  confirming the change persisted.
- **Covers** and fields not yet in the ORM (`isbn`, `publisher`, `languages`) are not
  written directly; they route through the `calibredb` subprocess (with a timeout) or
  are reported as unsupported for manual `calibredb` use.
- There is **no second database**: background job history for RAG index builds is
  held in memory (per server process) and cleared on restart.

The HTTP transport holds the active library in process-global state and is intended for
**single-client / local** use; it is not isolated for concurrent multi-client access (a
library `switch` changes the active library for the whole process). Use stdio for
Claude Desktop.

## License

MIT
