MCP server: One MCP server.

gutenberg-mcp-server

@cyanheadsMCP server · External

MCP server for Project Gutenberg: 78,000+ public-domain ebooks with full plain-text retrieval.

Books & readingv0.2.0Apache-2.0TypeScript
Freeno charge per call

One address for any MCP client. No sign-in needed on this server.

What it does

4 read

Grouped by tool name. At most: only reads or looks up information.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Tools

4 · grouped by tool name · checked 20 Oct 2026

Read · 4Look things up

  • gutenberg_search_books
  • gutenberg_get_book
  • gutenberg_get_text
  • gutenberg_browse_popular
Try it in the Inspector
README7 min read

@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

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework

Public Hosted Server: https://gutenberg.caseyjhand.com/mcp


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

ToolDescription
gutenberg_search_booksSearch the catalog by title, author, topic, language, or author lifespan, with pagination and popularity sorting
gutenberg_get_bookFetch complete metadata for a book by ID — authors, translators, editors, full formats map, subjects, copyright status
gutenberg_get_textRetrieve a book's plain-text content stripped of license boilerplate, with offset/limit chunking
gutenberg_browse_popularBrowse the most-downloaded books, optionally filtered by language or topic

Capability reference

gutenberg_search_books tool

  • Filters: query (title/author words), topic, languages (ISO 639-1), 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
  • 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 (readable by gutenberg_get_text) and has_plain_text. Fails with 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
  • Fails with not_found, audio_book (media_type "Sound"), no_text_format, offset_out_of_range, text_file_not_found, text_fetch_rejected, invalid_text_response, text_fetch_failed, or catalog_unavailable

  • 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 on every search and browse result flags editions gutenberg_get_text can read
  • offset, length, totalChars, remainingChars, and hasMore support reliable sequential reads
  • provenance and sourceFormat on every text response cover attribution and fidelity

Getting started

Public Hosted Instance

A public instance is available at https://gutenberg.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

json
{
  "mcpServers": {
    "gutenberg-mcp-server": {
      "type": "streamable-http",
      "url": "https://gutenberg.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

No API key required. Add the following to your MCP client configuration file:

json
{
  "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):

json
{
  "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:

json
{
  "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:

sh
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Prerequisites

  • Bun v1.4.0 or higher (or Node.js v24+).
  • No API key required — Project Gutenberg data is freely available.

Installation

  1. Clone the repository:
sh
git clone https://github.com/cyanheads/gutenberg-mcp-server.git
  1. Navigate into the directory:
sh
cd gutenberg-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment:
sh
cp .env.example .env
# edit .env if you need to override any defaults

Configuration

VariableDescriptionDefault
GUTENDEX_BASE_URLGutendex catalog API base URL.https://gutendex.com/books/
GUTENBERG_TEXT_BASE_URLContent mirror serving the /cache/epub file tree.https://gutenberg.pglaf.org
GUTENBERG_MIRROR_PATHSQLite catalog path; use a persistent volume in containers../.mirror/gutenberg-catalog.sqlite
GUTENBERG_MIRROR_REFRESH_CRONCron schedule for in-process catalog refresh.Unset
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_SESSION_MODEHTTP session mode: auto, stateful, or stateless. Overrides the stateless posture the server declares in createApp().stateless
MCP_HTTP_PORTPort for HTTP server.3010
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (RFC 5424).info
LOGS_DIRDirectory for log files (Node.js only).<project-root>/logs
STORAGE_PROVIDER_TYPEStorage backend.in-memory
OTEL_ENABLEDEnable OpenTelemetry instrumentation.false

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

  • Build and run:

    sh
    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    sh
    bun run devcheck   # Lint, format, typecheck, security
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

sh
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.


Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers tools and inits services.
src/config/server-config.tsServer-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/.

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:

sh
bun run devcheck
bun run test

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.

Similar modules

Browse Books & reading