
Connects Claude to Project Gutenberg's 75,000+ public domain books through a clean four-tool interface: search by title, author, topic, or language; fetch full metadata including format maps and contributor details; retrieve plain text with offset/limit chunking for context management; and browse by popularity. Built on the Gutendex JSON API with direct plain-text retrieval from Gutenberg's file servers. No API key required. The chunking support is the standout feature here since classic novels routinely hit 500KB to 2MB and you need to read them in manageable pieces. Each response includes provenance data and precise pagination fields so you can walk through long works reliably. Strips license boilerplate automatically and falls back through UTF-8, ASCII, then HTML conversion as needed.
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.
Public Hosted Server: https://gutenberg.caseyjhand.com/mcp
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.
| Tool | Description |
|---|---|
gutenberg_search_books | Search the catalog by title, author, topic, language, or author lifespan, with pagination and popularity sorting |
gutenberg_get_book | Fetch complete metadata for a book by ID — authors, translators, editors, full formats map, subjects, copyright status |
gutenberg_get_text | Retrieve a book's plain-text content stripped of license boilerplate, with offset/limit chunking |
gutenberg_browse_popular | Browse the most-downloaded books, optionally filtered by language or topic |
gutenberg_search_books toolquery matches titles and author names; topic matches subjects/bookshelves separately from querylanguages (ISO 639-1 codes), author_year_start/author_year_end lifespan range, ids for a batch lookup by known ID listsort: popular (default, by downloads in the last 30 days at the source snapshot) or ascending/descending by Gutenberg IDtotalCount and hasMore drive further pagesis_readable to select UTF-8 plain-text or HTML editions supported by gutenberg_get_text; has_plain_text specifically reports UTF-8 plain text on Text recordsno_results, page_out_of_range, catalog_unavailablegutenberg_get_book toolformats map (MIME type → download URL) — plain text, HTML, EPUB, cover imageis_readable follows the reader's non-audio guard and supported UTF-8 plain-text or HTML formats; has_plain_text specifically reports UTF-8 plain text on Text recordssource_notes preserves RDF bibliographic notes and explicit edition IDs verbatim. [] means the RDF was inspected and had none; null means unavailable from Gutendex or an older mirror row. Existing mirror rows remain readable, with notes filled on the next normal harvestdownload_count is downloads in the last 30 days at the source snapshot, not a lifetime or live countsummary is the first entry of Gutendex's summaries array — both are null/empty when Gutendex has nonenot_found, catalog_unavailablegutenberg_get_text tooloffset/limit chunking (positive integral requested limit, default 20,000; capped locally at 50,000) for works that routinely run 500KB–2MB. requestedLimit and appliedLimit report both limitssourceFormat reports which)totalChars, length, remainingChars, hasMore — use length, not limit, to compute the next offset, since paragraph-boundary trimming can return slightly less than requestedprovenance carries the Gutenberg ID, title, and license URL for attributionnot_found, audio_book (refuses media_type "Sound"), no_text_format, offset_out_of_range, text_file_not_found (mirror HTTP 404), text_fetch_rejected (permanent HTTP rejection), invalid_text_response (empty or recognizable non-book document), text_fetch_failed (transient failure), catalog_unavailablegutenberg_browse_popular toollimit, default 20) ordered by downloads in the last 30 days at the source snapshot, most popular first. Larger positive integral requests are capped locally at 32; requestedLimit and appliedLimit report both limitslanguages and topic filters, applied togethertotalInCatalog gives full context (e.g. "top 20 of 60,000")gutenberg_search_books with the same filters and popular sort to page through the restno_results, catalog_unavailableBuilt 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:
gutenberg_get_book, gutenberg_get_text) read from a local SQLite+FTS5 catalog mirror when it holds the record, falling back to the live Gutendex API otherwiseAgent-friendly output:
is_readable on every search/browse result lets agents select supported UTF-8 plain-text or HTML editions before text retrievalgutenberg_get_text — offset, length, totalChars, remainingChars, hasMore for reliable sequential readsprovenance and discriminated sourceFormat fields on every text response, for attribution and fidelity awarenessgutenberg_browse_popular enrichment reports truncation state and the downloads-in-the-last-30-days ceiling at the source snapshot for omitted resultsA public instance is available at https://gutenberg.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"gutenberg-mcp-server": {
"type": "streamable-http",
"url": "https://gutenberg.caseyjhand.com/mcp"
}
}
}
No API key required. Add the following to your MCP client configuration file:
{
"mcpServers": {
"gutenberg-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/gutenberg-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"gutenberg-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/gutenberg-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"gutenberg-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/gutenberg-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/gutenberg-mcp-server.git
cd gutenberg-mcp-server
bun install
cp .env.example .env
# edit .env if you need to override any defaults
| Variable | Description | Default |
|---|---|---|
GUTENDEX_BASE_URL | Base URL for the Gutendex catalog API. Override for self-hosted instances. | https://gutendex.com/books/ |
GUTENBERG_TEXT_BASE_URL | Base URL for a Project Gutenberg content mirror serving the /cache/epub file tree. Override to use a different mirror. | https://gutenberg.pglaf.org |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | HTTP session mode: auto, stateful, or stateless. Overrides the stateless posture the server declares in createApp(). | stateless |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
docker build -t gutenberg-mcp-server .
docker run --rm -p 3010:3010 gutenberg-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/gutenberg-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and inits services. |
src/config/server-config.ts | Server-specific environment variable parsing (Gutendex and file-server URL overrides). |
src/mcp-server/tools/definitions/ | Tool definitions (*.tool.ts). |
src/services/gutendex/ | Gutendex catalog API client — search and book metadata. |
src/services/catalog-mirror/ | Local SQLite+FTS5 catalog mirror — RDF ingestion and read helpers. |
src/services/gutenberg-text/ | Full plain-text retrieval, boilerplate stripping, tenant-scoped caching, and chunking. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagesrc/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Data from Project Gutenberg is in the public domain. Catalog metadata sourced from Gutendex (MIT license).
Apache-2.0 — see LICENSE for details.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.