Skip to main content
Glama

gutenberg-mcp-server

Get Gutenberg Book Text

gutenberg_get_text
Read-onlyIdempotent

Retrieve the plain-text content of a Project Gutenberg book, stripped of the standard license header and footer. Front matter such as producer credits, the title page, contents, and prefaces stays in the text. For long works — novels routinely run 500KB–2MB — use offset and limit to read in chunks rather than fetching the whole book at once, or pass find to jump to a known passage without paging. The response reports totalChars and remainingChars for reliable pagination. Prefers UTF-8 plain text; falls back to an HTML edition converted to text; refuses audio books (media_type "Sound") with a clear error.

Input Schema

NameRequiredDescriptionDefault
idYesProject Gutenberg book ID. Use gutenberg_search_books or gutenberg_get_book to find IDs. Example: 1342 for Pride and Prejudice, 2600 for War and Peace, 84 for Frankenstein.
findNoA passage to locate instead of paging to it: the response starts at the first occurrence at or after offset and adds match, and an absent passage fails with passage_not_found rather than returning other text. Matching ignores letter case and whitespace around find. Any run of spaces, tabs, or line breaks matches any other such run, so a hard-wrapped line still matches. Straight and curly quotes match each other. Accents count: "cafe" does not match "café". Everything else is literal, with no wildcards or patterns, so an italic word stored as _word_ needs its underscores. A blank find is an ordinary read.
limitNoRequested maximum characters. Default 20,000; requests above 50,000 are capped locally at 50,000. Paragraph-boundary trimming may return less than the applied limit — use length for continuation. Trimming never cuts before a find match ends.
offsetNoCharacter offset into the stripped literary text at which to start reading. 0 returns the beginning of the work. To read subsequent chunks, use offset = prior_offset + prior_length (the length field from the previous response — NOT offset + limit, because the actual returned length may be slightly less than limit due to paragraph-boundary trimming). With find, the search starts here and the returned chunk starts at the match.

Output Schema

NameRequiredDescriptionDefault
idNoGutenberg book ID.
textNoThe requested chunk of literary text, stripped of Gutenberg license boilerplate. Encoding: UTF-8. Line endings: normalized to LF.
errorNoPresent when the call failed. Absent on success.
matchNoWhere find matched. Absent on a read without find.
titleNoBook title, from the catalog record.
lengthNoNumber of characters in this chunk.
offsetNoCharacter offset where this chunk begins: the match start when find matched.
hasMoreNoTrue if there is more text after this chunk. When true, call again with offset = offset + length and no find.
provenanceNoOne-line source note with Project Gutenberg ID, title, and license URL.
totalCharsNoTotal characters in the stripped literary text. Use with offset and length to determine progress and plan subsequent calls.
appliedLimitNoCharacter limit applied to this chunk, at most 50,000.
sourceFormatNoThe format that was fetched. "text/html" indicates HTML-to-text conversion was applied because no plain-text format was available.
remainingCharsNoCharacters remaining after this chunk (totalChars - offset - length). 0 means this chunk includes the end of the book.
requestedLimitNoPositive integral character limit requested by the caller, before capping.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changed
    • changedInput schema / properties / limit / description
      Previous value: -"Requested maximum characters. Default 20,000 (~4–5 pages of prose); requests above 50,000 are capped locally at 50,000. Paragraph-boundary trimming may return less than the applied limit — use length for continuation. Trimming never cuts before a find match ends."New value: +"Requested maximum characters. Default 20,000; requests above 50,000 are capped locally at 50,000. Paragraph-boundary trimming may return less than the applied limit — use length for continuation. Trimming never cuts before a find match ends."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `text_file_not_found`: The catalog entry exists but its mirror text file returned HTTP 404. `text_fetch_rejected`: The mirror permanently rejected this text request. `invalid_text_response`: The mirror returned an empty document, HTML on a plain-text path, or recognizable server failure document. `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `passage_not_found`: find has no occurrence at or after offset. `text_fetch_failed`: The Gutenberg mirror had a transient failure or did not respond within the request budget. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `text_file_not_found`: The catalog entry exists but its mirror text file returned HTTP 404. `text_fetch_rejected`: The mirror permanently rejected this text request. `invalid_text_response`: The mirror returned an empty document, HTML on a plain-text path, or recognizable server failure document. `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an ASCII-only or PDF/TeX-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `passage_not_found`: find has no occurrence at or after offset. `text_fetch_failed`: The Gutenberg mirror had a transient failure or the download did not finish within the remaining upstream allowance. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."
  2. Changed8 schema fields changed
    • addedInput schema / properties / find
      Added value: +{
      +  "description": "A passage to locate instead of paging to it: the response starts at the first occurrence at or after offset and adds match, and an absent passage fails with passage_not_found rather than returning other text. Matching ignores letter case and whitespace around find. Any run of spaces, tabs, or line breaks matches any other such run, so a hard-wrapped line still matches. Straight and curly quotes match each other. Accents count: \"cafe\" does not match \"café\". Everything else is literal, with no wildcards or patterns, so an italic word stored as _word_ needs its underscores. A blank find is an ordinary read.",
      +  "type": "string"
      +}
    • changedInput schema / properties / limit / description
      Previous value: -"Requested maximum characters. Default 20,000 (~4–5 pages of prose); requests above 50,000 are capped locally at 50,000. Paragraph-boundary trimming may return less than the applied limit — use length for continuation."New value: +"Requested maximum characters. Default 20,000 (~4–5 pages of prose); requests above 50,000 are capped locally at 50,000. Paragraph-boundary trimming may return less than the applied limit — use length for continuation. Trimming never cuts before a find match ends."
    • changedInput schema / properties / offset / description
      Previous value: -"Character offset into the stripped literary text at which to start reading. 0 returns the beginning of the work. To read subsequent chunks, use offset = prior_offset + prior_length (the length field from the previous response — NOT offset + limit, because the actual returned length may be slightly less than limit due to paragraph-boundary trimming)."New value: +"Character offset into the stripped literary text at which to start reading. 0 returns the beginning of the work. To read subsequent chunks, use offset = prior_offset + prior_length (the length field from the previous response — NOT offset + limit, because the actual returned length may be slightly less than limit due to paragraph-boundary trimming). With find, the search starts here and the returned chunk starts at the match."
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `text_file_not_found`: The catalog entry exists but its mirror text file returned HTTP 404. `text_fetch_rejected`: The mirror permanently rejected this text request. `invalid_text_response`: The mirror returned an empty document, HTML on a plain-text path, or recognizable server failure document. `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `text_fetch_failed`: The Gutenberg mirror had a transient failure or did not respond within the request budget. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `text_file_not_found`: The catalog entry exists but its mirror text file returned HTTP 404. `text_fetch_rejected`: The mirror permanently rejected this text request. `invalid_text_response`: The mirror returned an empty document, HTML on a plain-text path, or recognizable server failure document. `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `passage_not_found`: find has no occurrence at or after offset. `text_fetch_failed`: The Gutenberg mirror had a transient failure or did not respond within the request budget. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "text_file_not_found",
      -  "text_fetch_rejected",
      -  "invalid_text_response",
      -  "not_found",
      -  "audio_book",
      -  "no_text_format",
      -  "offset_out_of_range",
      -  "text_fetch_failed",
      -  "catalog_unavailable"
      -]New value: +[
      +  "text_file_not_found",
      +  "text_fetch_rejected",
      +  "invalid_text_response",
      +  "not_found",
      +  "audio_book",
      +  "no_text_format",
      +  "offset_out_of_range",
      +  "passage_not_found",
      +  "text_fetch_failed",
      +  "catalog_unavailable"
      +]
    • changedOutput schema / properties / hasMore / description
      Previous value: -"True if there is more text after this chunk. When true, call again with offset = offset + length."New value: +"True if there is more text after this chunk. When true, call again with offset = offset + length and no find."
    • addedOutput schema / properties / match
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Where find matched. Absent on a read without find.",
      +  "properties": {
      +    "laterMatches": {
      +      "description": "Non-overlapping occurrences after this one. While above 0, repeat the same find with offset = match.offset + match.length for the next.",
      +      "type": "number"
      +    },
      +    "length": {
      +      "description": "Characters the match spans in the stored text. Can differ from the length of find, since a whitespace run matches a run of any length.",
      +      "type": "number"
      +    },
      +    "offset": {
      +      "description": "Start of the match, in the same coordinates as offset and totalChars.",
      +      "type": "number"
      +    }
      +  },
      +  "required": [
      +    "offset",
      +    "length",
      +    "laterMatches"
      +  ],
      +  "type": "object"
      +}
    • changedOutput schema / properties / offset / description
      Previous value: -"Character offset where this chunk begins."New value: +"Character offset where this chunk begins: the match start when find matched."
  3. Changed7 schema fields changed
    • changedInput schema / properties / limit / description
      Previous value: -"Maximum number of characters to return in this chunk. Default 20,000 (~4–5 pages of prose). Increase toward 50,000 for large context windows. The actual returned length may be slightly less than limit when a natural paragraph boundary is found within 500 characters of the limit — check the length field in the response for the actual character count returned."New value: +"Requested maximum characters. Default 20,000 (~4–5 pages of prose); requests above 50,000 are capped locally at 50,000. Paragraph-boundary trimming may return less than the applied limit — use length for continuation."
    • changedInput schema / properties / limit / maximum
      Previous value: -50000New value: +9007199254740991
    • changedOutput schema / anyOf
      Previous value: -[
      -  {
      -    "not": {
      -      "required": [
      -        "error"
      -      ]
      -    },
      -    "required": [
      -      "id",
      -      "title",
      -      "text",
      -      "offset",
      -      "length",
      -      "totalChars",
      -      "remainingChars",
      -      "hasMore",
      -      "provenance",
      -      "sourceFormat"
      -    ]
      -  },
      -  {
      -    "required": [
      -      "error"
      -    ]
      -  }
      -]New value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "id",
      +      "title",
      +      "text",
      +      "offset",
      +      "length",
      +      "requestedLimit",
      +      "appliedLimit",
      +      "totalChars",
      +      "remainingChars",
      +      "hasMore",
      +      "provenance",
      +      "sourceFormat"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / appliedLimit
      Added value: +{
      +  "description": "Character limit applied to this chunk, at most 50,000.",
      +  "type": "number"
      +}
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `text_fetch_failed`: The Gutenberg file server returned an error or did not respond within the timeout. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `text_file_not_found`: The catalog entry exists but its mirror text file returned HTTP 404. `text_fetch_rejected`: The mirror permanently rejected this text request. `invalid_text_response`: The mirror returned an empty document, HTML on a plain-text path, or recognizable server failure document. `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `text_fetch_failed`: The Gutenberg mirror had a transient failure or did not respond within the request budget. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "not_found",
      -  "audio_book",
      -  "no_text_format",
      -  "offset_out_of_range",
      -  "text_fetch_failed",
      -  "catalog_unavailable"
      -]New value: +[
      +  "text_file_not_found",
      +  "text_fetch_rejected",
      +  "invalid_text_response",
      +  "not_found",
      +  "audio_book",
      +  "no_text_format",
      +  "offset_out_of_range",
      +  "text_fetch_failed",
      +  "catalog_unavailable"
      +]
    • addedOutput schema / properties / requestedLimit
      Added value: +{
      +  "description": "Positive integral character limit requested by the caller, before capping.",
      +  "type": "number"
      +}
  4. Changed2 schema fields changed
    • changedOutput schema / properties / error / properties / data / properties / reason / description
      Previous value: -"Machine-readable failure mode. Declared by this tool: `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `text_fetch_failed`: The Gutenberg file server returned an error or did not respond within the timeout. Other values are possible when a failure originates below the handler."New value: +"Machine-readable failure mode. Declared by this tool: `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `text_fetch_failed`: The Gutenberg file server returned an error or did not respond within the timeout. `catalog_unavailable`: The catalog lookup that precedes reading a book did not answer within the time this server allows. Other values are possible when a failure originates below the handler."
    • changedOutput schema / properties / error / properties / data / properties / reason / examples
      Previous value: -[
      -  "not_found",
      -  "audio_book",
      -  "no_text_format",
      -  "offset_out_of_range",
      -  "text_fetch_failed"
      -]New value: +[
      +  "not_found",
      +  "audio_book",
      +  "no_text_format",
      +  "offset_out_of_range",
      +  "text_fetch_failed",
      +  "catalog_unavailable"
      +]
  5. Changed6 schema fields changed
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedInput schema / additionalProperties
      Added value: +false
    • changedOutput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • addedOutput schema / anyOf
      Added value: +[
      +  {
      +    "not": {
      +      "required": [
      +        "error"
      +      ]
      +    },
      +    "required": [
      +      "id",
      +      "title",
      +      "text",
      +      "offset",
      +      "length",
      +      "totalChars",
      +      "remainingChars",
      +      "hasMore",
      +      "provenance",
      +      "sourceFormat"
      +    ]
      +  },
      +  {
      +    "required": [
      +      "error"
      +    ]
      +  }
      +]
    • addedOutput schema / properties / error
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Present when the call failed. Absent on success.",
      +  "properties": {
      +    "code": {
      +      "description": "JSON-RPC error code for this failure.",
      +      "maximum": 9007199254740991,
      +      "minimum": -9007199254740991,
      +      "type": "integer"
      +    },
      +    "data": {
      +      "additionalProperties": {},
      +      "properties": {
      +        "reason": {
      +          "description": "Machine-readable failure mode. Declared by this tool: `not_found`: No book exists with the given ID. `audio_book`: The book is an audio recording (media_type \"Sound\"), not a text book — no literary plain text is available. `no_text_format`: The book has no UTF-8 plain-text or HTML edition available to read (for example an older ASCII-only entry). `offset_out_of_range`: The offset is greater than or equal to totalChars (past the end of the book). `text_fetch_failed`: The Gutenberg file server returned an error or did not respond within the timeout. Other values are possible when a failure originates below the handler.",
      +          "examples": [
      +            "not_found",
      +            "audio_book",
      +            "no_text_format",
      +            "offset_out_of_range",
      +            "text_fetch_failed"
      +          ],
      +          "type": "string"
      +        },
      +        "recovery": {
      +          "additionalProperties": {},
      +          "description": "Actionable next step for the caller.",
      +          "properties": {
      +            "hint": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "hint"
      +          ],
      +          "type": "object"
      +        },
      +        "retryable": {
      +          "description": "Whether retrying may succeed.",
      +          "type": "boolean"
      +        }
      +      },
      +      "type": "object"
      +    },
      +    "message": {
      +      "description": "Human-readable description of what went wrong.",
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "code",
      +    "message"
      +  ],
      +  "type": "object"
      +}
    • removedOutput schema / required
      Removed value: -[
      -  "id",
      -  "title",
      -  "text",
      -  "offset",
      -  "length",
      -  "totalChars",
      -  "remainingChars",
      -  "hasMore",
      -  "provenance",
      -  "sourceFormat"
      -]
  6. First observed

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent, so the bar is lower, yet the description adds substantial context beyond them: license boilerplate removal, retention of front matter, realistic size ranges (500KB–2MB), UTF-8 preference with HTML fallback, and an explicit refusal path for audio books (media_type "Sound"). It also discloses that the response carries totalChars/remainingChars for pagination.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first clause, then chunking guidance, then the behavioral caveats. Roughly five dense sentences with no filler — each one carries information an agent would otherwise have to discover by trial.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value detail is not required, yet the description still flags totalChars/remainingChars for pagination. Combined with the size ranges and fallback/refusal behavior, an agent has everything needed to call this correctly on the first try.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the parameter docs are already very detailed, so the baseline of 3 applies. The description restates the chunking strategy and find-jump pattern but adds no matching syntax or edge-case rules beyond what the schema already spells out.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Retrieve the plain-text content of a Project Gutenberg book') plus precise scope: license header/footer stripped, front matter retained. The distinction from siblings such as gutenberg_get_book is only implicit — the description never names the metadata tool it complements — so it falls just short of full sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit operational guidance: chunk with offset/limit for long works, or use find to jump to a known passage instead of paging. What's missing is routing relative to alternatives (e.g. 'use gutenberg_get_book first if you only need metadata'), so usage is clear but not exclusive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.