@cyanheads/gutenberg-mcp-server
Search, browse, and read 78,000+ public-domain books from Project Gutenberg with full plain-text retrieval and offset/limit chunking via MCP. STDIO or Streamable HTTP.
4 Tools
Overview
Project Gutenberg's library of 78,000+ public-domain books. Search by title, author, topic, or language, fetch full book metadata, and read supported UTF-8 plain-text and HTML editions in manageable chunks.
Tools
The published gutenberg-guide skill covers deciding whether a book can be read, ruling out search blind spots before calling a book missing, finding openings and chapters past front matter, paging long texts, and ranking editions. Plugin installs include it; MCP clients can discover it through the Skills extension and its skill resource.
Capability reference
gutenberg_search_books tool
- Filters:
query (title/author words), topic, translator, languages (catalog codes exactly as results report them, two or three letters: en, not eng), author_year_start/author_year_end, ids, mime_type, and copyright ("true", "false", "null"; false means public domain in the USA). Live fallback rejects queries over four words and the translator filter
- With both author-year bounds, one author must meet both. The live fallback checks the bounds separately and says so in a notice
sort is popular (default) or ascending/descending by Gutenberg ID. Up to 32 books per page; use totalCount and hasMore to page
- Each result carries
is_readable (eligible Text formats; mirror file availability is not guaranteed) and has_plain_text. The local mirror ignores case and accents in query, translator and topic matching; live matching is upstream-defined. Result rows omit translators; gutenberg_get_book reports them. Fails with translator_requires_mirror, query_requires_mirror, page_out_of_range, or catalog_unavailable
gutenberg_get_book tool
- Returns the full
formats map (MIME type → download URL) and authors, translators, and editors with birth/death years
- Also carries
is_readable, has_plain_text, summary, source_notes, and download_count (downloads in the last 30 days, not lifetime)
- Fails with
not_found or catalog_unavailable
gutenberg_get_text tool
- Reads a book in
offset/limit chunks (default limit 20,000, capped at 50,000), with the Gutenberg license header and footer stripped. requestedLimit and appliedLimit report both
- Prefers UTF-8 plain text and falls back to HTML converted to text;
sourceFormat says which. Returns totalChars, length, remainingChars, hasMore, and provenance (ID, title, license URL). Use length, not limit, to compute the next offset
find jumps to a passage instead of paging to it: the chunk starts at the first match at or after offset, and match reports its offset, length, and laterMatches. Matching ignores letter case and whitespace runs, and treats straight and curly quotes alike; accents and punctuation must match
- Converted HTML editions blank every line that holds no visible character and keep each table row on one line, with tab-separated cells
- Fails with
not_found, audio_book (media_type "Sound"), no_text_format, offset_out_of_range, passage_not_found, text_file_not_found, text_fetch_rejected, invalid_text_response, text_fetch_failed, or catalog_unavailable
gutenberg_browse_popular tool
- Returns up to 32 titles (
limit, default 20), most downloaded in the last 30 days first. requestedLimit and appliedLimit report the cap
- Optional
languages, topic, mime_type, and copyright filters work as in search. totalInCatalog gives the full count
- Output reports truncation and carries a
continuation pointing at gutenberg_search_books. Empty results succeed with a notice; fails with catalog_unavailable
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Gutenberg-specific:
- Searches and lookups read a local SQLite+FTS5 catalog mirror, falling back to Gutendex when the mirror is cold or missing a record
- Full plain-text retrieval from a Project Gutenberg content mirror, with UTF-8/HTML fallback
- Book text is cached per tenant, so later chunk reads skip the fetch
- No API key required
Agent-friendly output:
is_readable flags Text records with UTF-8 plain text, or HTML without audio formats; a missing mirror file still returns text_file_not_found
offset, length, totalChars, remainingChars, and hasMore support reliable sequential reads
provenance and sourceFormat on every text response cover attribution and fidelity
Getting started
Connect to the hosted instance, no install:
Or run it locally:
For other clients, add this to the MCP config file:
No API key required. For Docker or a source build, see Running the server.
Configuration
Build the local catalog with bun run mirror:init and check it with bun run mirror:verify. The server never harvests on startup, and uses Gutendex until the first harvest completes. Keep the catalog up to date with bun run mirror:refresh on a host scheduler, or set GUTENBERG_MIRROR_REFRESH_CRON.
Running the server
Local development
Requires Bun v1.4.0 or higher (or Node.js v24+).
-
Clone and install:
-
Build and run:
-
Run checks and tests:
Docker
The image defaults to HTTP transport, stateless session mode, and logs to /var/log/gutenberg-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/gutenberg-mcp-server:latest"]. To build locally, docker build -t gutenberg-mcp-server .; OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
Development guide
See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catch in tool logic
- Use
ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
- Register new tools via the entry arrays in
src/index.ts
- Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
Data attribution
Data from Project Gutenberg is in the public domain. Catalog metadata sourced from Gutendex (MIT license).
License
Apache-2.0 — see LICENSE for details.