corpus-mcp
Integrates with Calibre-Web to read public shelves and category/tag metadata from the connected Calibre library, enabling shelf- and category-filtered book lookups and full-text search.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@corpus-mcpSearch my books for passages on cognitive dissonance and cite the page numbers."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
corpus-mcp
An MCP server that lets an AI agent search, read, and cite the books in a Calibre library.
It indexes PDF, EPUB, and MOBI files into a SQLite full-text index. An agent can then find a passage, read it in context, and get a citation with a real locator: the printed page number for a PDF, or the chapter for a reflowable book.
It was built to supply book citations for Concept Clusters puzzle authoring. It complements a Zotero MCP server: Zotero handles papers and their attachments, and corpus-mcp handles the long-form books that live in Calibre.
Tools
Tool | What it does |
| Full-text search (SQLite FTS5, BM25 ranking) over every indexed book. Returns ranked snippets with book, page, printed page label, and section. Supports phrases, prefixes, and filters by book, library, or author. |
| Looks up books by title, author, tag, publisher, ISBN, citekey, or shelf. |
| Lists Calibre tags (Calibre-Web's Categories) as a tree; |
| Lists Calibre-Web's public shelves (topic collections) with book counts; any shelf name can filter |
| Returns metadata and the table of contents with the page where each section starts. |
| Returns contiguous text for a page range or a named section, within a character budget, and says where to continue. |
| Returns a PNG of a PDF page, for figures, tables, and equations. |
| A book's citation as a CSL JSON item, with a CSL locator for a page range, the reading position, and filing data: a BibTeX entry, a valid ISBN, or printed ISBN candidates (see Citations). |
| Records the citekey a book was given elsewhere, such as in the reference manager it was filed in. |
| The vocabulary's terms as Calibre tags, the controlled vocabulary for categories. |
| Books still without a tag proposal, with the context needed to choose tags. |
| Proposes 1–3 vocabulary tags for a book, for a person to review. |
| Saves a passage as a quote after checking it against the book; the book's exact wording is stored. |
| Find, read, and edit saved quotes (note, tags). |
| A quote's verified text and locator with its citation object. |
| Reports index freshness, counts, books that failed, have no text layer, or have no outline, and which books are ready for a citekey. |
The citekey, tag-proposal, and quote tools write to corpus-mcp's own data; corpus-mcp never writes to Calibre itself.
Related MCP server: Calibre MCP Server
Categories
Calibre tags (Calibre-Web's Categories) follow a controlled vocabulary that
the taxonomy's owner supplies as a JSON file (format below). Here that owner is
Concept Clusters; any project can
supply its own. Term ids are the tags, nested with . such as
social-sciences.psychology.social-psychology, so a renamed term leaves
Calibre's tags valid. Tools and the review page show the titles
("Social Sciences › Psychology › Social Psychology") and accept either form. An agent takes
books from tagging_worklist and calls propose_tags. A person approves,
edits, or rejects each proposal at /admin/proposals, or sets a book's categories directly on its page. Then
npm run tags:apply -- --apply runs tools/calibre-tags.sh
on the Calibre-Web host. That script backs up metadata.db and adds the
approved tags with calibredb, keeping each book's existing tags.
The vocabulary file (corpus-vocabulary/1) is a flat list of terms:
{
"format": "corpus-vocabulary/1",
"version": "any string",
"generatedAt": "2026-10-25T12:00:00Z",
"terms": [
{ "id": "social-sciences", "label": "Social Sciences", "assignable": false },
{ "id": "social-sciences.psychology", "label": "Psychology", "parent": "social-sciences" }
],
"retired": [{ "id": "old.term", "replacedBy": ["social-sciences.psychology"] }]
}An id is its parent's id,
., and one segment of lowercase letters, digits, and single hyphens. A label is the term's own title, without.or›.assignable: falsemarks a term that only groups others.A retired term with one replacement is re-tagged by the tags job; one that was split or dropped waits for a person.
The server reads
/var/lib/corpus-mcp/vocabulary.json(CORPUS_VOCABULARY) and reloads it when it changes; an invalid file is reported and the last good one stays in use.npm run deploy -- --vocabulary=<file>validates and installs one.vocabulary_report(and the categories page) lists retired terms still in Calibre, tags shaped like terms that the vocabulary lacks, and books tagged only at a term with narrower ones. corpus-mcp never edits the vocabulary.
Admin
An admin suite on the corpus server, on the model of Concept Clusters' own admin. Pages show the current state of each kind of object. History is kept on a separate Changes page. Anything an MCP agent can change, a person can change here too, not just approve.
Page | Shows | People can edit |
| Metrics and health, linked into the pages below; jobs | Refresh, back up, and preview-then-apply Calibre jobs |
| Every book, with filters for library, format, status, shelf, category, citekey, ISBN, and problems | — |
| Metadata, categories, citekey, shelves, ISBNs, file, outline, quotes, and recent changes | The book's categories (complete set) and citekey (pin, replace, unpin) |
| The book's text page by page, with saved quotes highlighted | Select a passage to save it as a quote |
| Every quote, filtered by words, tag, book category, author, and status; one quote in its surrounding text, with its citation | A quote's note and tags; delete |
| The vocabulary tree with book counts, and other tags | — |
| Pinned keys, and books ready or blocked | On each book page |
| Agent proposals | Approve, edit, reject, reopen |
| Shelves; jobs, with each job's per-book changes and log | Apply a preview |
| The change log, filterable, with changes per day | — |
An approved category set is a book's complete set of vocabulary tags.
Applying it adds and removes vocabulary tags to match, and never touches
other tags. Calibre changes always run as preview, then apply. The apply
runs exactly the previewed plan, backs up metadata.db, and refreshes the
index. They run on the Calibre-Web host as named commands of the sync key's
forced command, never as a shell.
Every write needs a verified Cloudflare Access identity
(CORPUS_ACCESS_TEAM_DOMAIN, CORPUS_ACCESS_AUD). Direct LAN access is
read-only.
Quotes
Quotes are passages saved from the books, by agents (save_quote) or by
people (select text in a book's reader). Either way, the passage is checked
against the book's text. Quote marks, dashes, spacing, case, and a page break
inside the passage may differ; what's stored is the book's exact wording. A
passage that isn't in the book isn't saved, and the closest passages are
returned instead.
Each quote records:
an anchor: its pages, offsets, and the text around it
free-form tags for themes and forms (
overwork,parable) and links (cc:puzzle:<id>), kept to one spelling eacha note
Quotes have no categories of their own: the book gives the subject, and searching by category matches the source book's categories.
When a book's file changes, its quotes are found again by their text, or
marked orphaned; never deleted. Quotes live in data.db and are backed up
nightly.
Shelves
Calibre-Web shelves are topic collections, such as Misinformation, kept in
Calibre-Web's own app.db rather than in the Calibre library. The sync key's
forced command, deploy/corpus-source.sh, allows
exactly two things: read-only rsync of the library, and a shelves command
that prints the public shelves and their book ids as JSON. app.db, which
also holds password hashes, never leaves the Calibre-Web host. Each refresh
replaces the index's shelves and book_shelves tables from that export.
If the export fails, the previous membership is kept.
Citekeys
Every book gets a proposed citekey (author + year + first significant title
word, as in nye2011future), built from its Calibre metadata by
@jmajerus/citekey, a Better BibTeX–style rule packaged
for reuse (other projects install it from a GitHub release). The preset is
CORPUS_CITEKEY_PRESET: author-year-word (the default) or bbt-default.
To file a cited book in a reference manager, cite_passage returns a BibTeX
@book entry built from Calibre's own metadata (bibtex) and the book's
ISBN when it passes its check digit (isbn). The project that files the book
assigns the final key; pin_citekey then stores it against the book's Calibre
UUID. Any BibTeX-safe key is accepted and kept as given; one that doesn't
follow this server's rule gets a warning. A pinned key never changes on its own and is accepted anywhere a book id
is. A citekey identifier set in Calibre takes precedence over a pinned key.
Pinned keys live in data.db, which unlike the index is never rebuilt and is
backed up nightly. corpus_status lists books whose metadata blocks a key:
an unknown author, no year, or one author record naming several people.
When Calibre has no valid ISBN, cite_passage also returns isbn_candidates.
These are the checksum-valid ISBNs printed in the book's first and last pages,
each with its page, the printed line, and its binding (hardcover, paperback,
or ebook) when one is printed beside it. Copyright pages often list several
editions, and back pages often advertise other books, so these are offered for
someone to confirm, never filed automatically.
Citations
cite_passage (and cite_quote, for a saved quote) returns:
{
"csl": { "id": "nye2011future", "citation-key": "nye2011future", "type": "book", "title": "The Future of Power",
"author": [{ "family": "Nye", "given": "Joseph S., Jr." }], "publisher": "PublicAffairs",
"issued": { "date-parts": [[2011]] }, "ISBN": "9781586488925" },
"locator": { "label": "page", "locator": "4-5" },
"position": { "page": 19, "end_page": 20, "page_kind": "page", "page_label": "4", "end_page_label": "5", "section": "Part One › Chapter 1" },
"citekey": "nye2011future", "citekey_source": "pinned",
"bibtex": "@book{nye2011future, …}", "isbn": "9781586488925", "isbn_candidates": [],
"warnings": []
}cslis a CSL JSON item. Itsidis the citekey, orcalibre:<uuid>when there is none. A Calibre author with a comma in the sort form ("Nye, Joseph S., Jr.") becomesfamilyandgiven; one without becomesliteral.issuedis the year; empty fields are left out.locatoris a CSL locator: printed page labels for PDFs (physical pages, with a warning, when a PDF has none), and null for EPUB and MOBI, whose locations are not pages.positionis where the passage is in the index.bibtexis null when the citekey is blocked;isbnis null when Calibre's ISBN is missing or fails its check digit.Until consumers switch, the legacy
citation(title,author,publisher,year,pages) andzotero_filingfields are also returned.
Locators
PDF. Pages are physical pages. Each page also keeps its printed label from the PDF's page-label tree, so physical page 19 can be cited as page
4. Citations use the printed label.EPUB and MOBI. These reflow, so page numbers depend on layout. corpus-mcp lays every reflowable book out at one fixed size, which gives stable location numbers for reading. They are never presented as page numbers. Citations name the chapter, taken from the book's outline.
How it works
Calibre library ── rsync (read-only) ──▶ books/<library>/…
│
corpus-mcp index ▼ metadata.db → MuPDF → SQLite FTS5
corpus.db
│ read-only
corpus-mcp serve ▼ Streamable HTTP /mcp, or stdio
│
MCP clientsCatalogue. Each library's
metadata.dbis the source of truth for books, authors, publishers, ISBNs, and format files. The indexer reads an in-memory copy, so it never locks Calibre's database. It doesn't use themetadata.opfbackups, which go stale.Extraction. The official
mupdfWebAssembly package reads all three formats. It provides each page's text, printed page labels, and the outline. Running heads and folios are stripped from PDFs.Incremental indexing. A rescan skips files whose size and modification time are unchanged, and rehashes the rest. Only changed books are re-extracted. Identical files in more than one library are collapsed in search results.
Exclusions. Only allowlisted libraries are indexed. Books carrying an excluded Calibre tag (by default
private) are never indexed.
Quick start
Requires Node.js 22 or later. better-sqlite3 compiles natively, so you also
need a C++ toolchain and Python.
npm install
npm run build
# Index a directory that holds one folder per Calibre library
CORPUS_BOOKS_ROOT=~/Calibre CORPUS_LIBRARIES=main CORPUS_DB=./corpus.db \
node dist/cli.js index
# Serve over Streamable HTTP on :8000/mcp (or add --stdio)
CORPUS_BOOKS_ROOT=~/Calibre CORPUS_DB=./corpus.db node dist/cli.js serveRegister it with an MCP client, for example Claude Code:
claude mcp add --transport http corpus http://localhost:8000/mcpRun the tests with npm test. The test fixtures (a PDF with page labels and
an outline, an EPUB, and a Calibre metadata.db) are generated in code.
Configuration
Variable | Default | Meaning |
|
| Directory containing one folder per Calibre library |
|
| Comma-separated library folders to index |
|
| Calibre tags that keep a book out of the index |
|
| Index file (derived; can be rebuilt) |
|
| Primary data such as pinned citekeys (never rebuilt) |
|
| Where |
|
| HTTP listen address |
The server also answers GET /health.
Deployment
deploy/ holds systemd units, the rsync pull script, and the setup
scripts. tools/deploy.mjs provisions a Debian LXC,
installs releases with a health check, and rolls back on failure. Both are
written for the author's own Proxmox network (*.localdomain hosts), so adapt
the hostnames before using them elsewhere. npm run deploy and
npm run tags:apply read their settings from a gitignored .env (see
.env.example). PLAN.md records the
design decisions, the facts about the corpus, and the known issues.
This server cannot be deployed
Maintenance
Related MCP Connectors
Machine Library: cited search over papers, books, patents and social posts, with agent comments.
Federated search of books and papers, BibTeX/RIS citations, open-access retrieval and reading.
Academic literature search, retrieval, and private library management on top of OpenAlex.
The media memory layer for AI agents and their humans. Your AI client gets 29 tools to search your collection, add items, update ratings, preview music, and find patterns across everything you've read, watched, and listened to.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceProvides AI agents with research capabilities for local Calibre e-book libraries, including fulltext search across titles, ISBNs, and comments, plus structured excerpt retrieval from books.2GPL 3.0
- AlicenseBqualityCmaintenanceConnects AI agents to Calibre ebook libraries for searching, reading, and managing digital collections. It supports metadata updates, format conversion, and full-text content searches while providing granular permission controls for library access.723MIT
- AlicenseNot gradedqualityDmaintenanceEnables searching, reading, and managing a Calibre ebook library through natural language, with features like metadata search, full-text search, content extraction, and library management.149 npmApache 2.0
- AlicenseNot gradedqualityAmaintenanceBridges your Calibre e-book library with AI assistants via the Model Context Protocol, enabling natural-language library management, semantic search, RAG, and agentic workflows.49MIT