packages=["src/bookfusion_mcp"] already bundles web/*.user.js. Recent hatchling rejects the duplicate add from force-include (ValueError: second file at same path) - non-deterministic build surfacing only on a host with newer hatchling. Web asset still shipped via packages (verified in image). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| deploy | ||
| src/bookfusion_mcp | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
BookFusion MCP
An MCP server that lets Claude (or any other MCP client) sync books from a Calibre library — including the one managed by Calibre-Web-Automated — to your BookFusion account.
It reuses the exact same HTTP API that the official BookFusion Calibre plugin talks to, so anything you already have configured on the BookFusion side (account, quota, integrations, API key) just works.
What it does
The server reads metadata directly from the Calibre metadata.db SQLite
database and the on-disk book / cover files. It never writes to the database.
It exposes the following MCP tools:
| Tool | Purpose |
|---|---|
library_status |
Show the configured library path, book count and BookFusion endpoint. |
list_books |
Paginated list of local books, optionally filtered by title/author. |
get_book |
Full details for one book (formats, identifiers, digests). |
get_bookfusion_limits |
Fetch upload limits (max filesize, etc.). |
check_book_in_bookfusion |
Look up a local book on BookFusion (by id, ISBN or digest). |
sync_book_to_bookfusion |
Upload or update a single book. |
sync_books_to_bookfusion |
Same, in parallel for a list of book ids. |
sync_all_books |
Sync every book matching a list_books filter. |
Configuration
Copy .env.example to .env and fill it in:
cp .env.example .env
| Env var | Required | Default | Notes |
|---|---|---|---|
BOOKFUSION_API_KEY |
yes | — | Get it from BookFusion → Settings → Integrations → Calibre. |
CALIBRE_LIBRARY_PATH |
yes | — | Host path to the directory containing metadata.db. For Calibre-Web-Automated this is the host folder you mapped to /calibre-library. |
BOOKFUSION_API_BASE |
no | https://www.bookfusion.com/calibre-api/v1 |
Override only if you self-host or test against a staging instance. |
BOOKFUSION_UPLOAD_THREADS |
no | 2 |
Parallel uploads when running sync_books_to_bookfusion / sync_all_books. |
BOOKFUSION_TIMEOUT |
no | 120 |
Request timeout in seconds. |
Never commit your API key.
.envis already in.gitignore.
Install
The server requires Python 3.10+.
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Run the test suite to make sure everything is wired up:
pytest -q
Running the server
bookfusion-mcp
This starts a stdio MCP server. Connect any MCP client to it.
Claude Desktop / Claude Code
Add the server to your client config (Claude Desktop's
claude_desktop_config.json or Claude Code's ~/.claude.json):
{
"mcpServers": {
"bookfusion": {
"command": "bookfusion-mcp",
"env": {
"BOOKFUSION_API_KEY": "your-bookfusion-api-key",
"CALIBRE_LIBRARY_PATH": "/path/to/your/calibre-library"
}
}
}
}
If you installed inside a virtualenv, point command at the venv's
bookfusion-mcp binary, e.g.
/home/you/FGU-bookfusion/.venv/bin/bookfusion-mcp.
Calibre-Web-Automated specifics
The server only needs read access to:
- the
metadata.dbfile produced by Calibre / CWA, and - the book / cover files inside the same library directory.
If you run CWA in Docker and want the MCP server on the same host, point
CALIBRE_LIBRARY_PATH at whatever you mounted into the container at
/calibre-library. Example:
docker run -d \
-v /opt/cwa/library:/calibre-library \
... \
crocodilestick/calibre-web-automated
→ on the host, set CALIBRE_LIBRARY_PATH=/opt/cwa/library.
How the sync works
For each book the server:
- Picks the first format the BookFusion plugin supports (EPUB, PDF, CBZ, CBR, CBT, MOBI).
- Computes the file digest the way the plugin does
(
sha256(filesize_bytes || 0x00 || file_contents)) and a metadata digest over the same fields the plugin hashes — so digests match what BookFusion already has. - Looks up the book on BookFusion using the most specific identifier
available, in this order: existing
bookfusionidentifier →isbn→ file digest. - If the upstream digest matches and
reupload=false, skips the book. - Otherwise either
PUT /uploads/{id}to update an existing book, or runs the three-stepinit → upload to presigned URL → finalizeflow for a new one.
Notes & limits
- Uploads are one-way (Calibre → BookFusion), exactly like the official plugin.
metadata.dbis opened read-only viafile:...?mode=roso Calibre / CWA can keep the file open at the same time.- The
bookshelvesfield is wired through the API but not yet populated from Calibre custom columns; PRs welcome.
Running as a Docker service next to Calibre-Web-Automated
A ready-to-use Docker setup lives in deploy/. It runs the
same Python package as an HTTP companion (bookfusion-http) that:
- exposes a small REST API (
/api/books,/api/books/{id}/sync, …) with CORS, - serves a Tampermonkey userscript at
/bookfusion-sync.user.jsthat adds a Sync to BookFusion button on every CWA book detail page.
See deploy/README.md for the full step-by-step, but
in short:
cd /Volumes/docker/calibre-web-automated
git clone --branch claude/add-bookfusion-mcp-ocUTD \
https://github.com/Fguarien/FGU-bookfusion.git bookfusion-mcp
cd bookfusion-mcp/deploy
cp .env.example .env # set BOOKFUSION_API_KEY + CALIBRE_LIBRARY_HOST_PATH
docker compose up -d --build
open http://localhost:8765/ # click "Install userscript" in Tampermonkey
Then open any book in CWA — the button is injected by the userscript, so CWA itself is never modified.
Download as a ZIP
If you don't want to git clone, GitHub serves a ready-to-use tarball /
ZIP of the branch:
- ZIP: https://github.com/Fguarien/FGU-bookfusion/archive/refs/heads/claude/add-bookfusion-mcp-ocUTD.zip
- tar.gz: https://github.com/Fguarien/FGU-bookfusion/archive/refs/heads/claude/add-bookfusion-mcp-ocUTD.tar.gz
License
GPL-3.0-or-later, matching the upstream BookFusion Calibre plugin.