Skip to main content
Glama
Palanx

grimoire-beholder-mcp

by Palanx

grimoire-beholder-mcp

Python 3.12+ Platform: Apple Silicon Local-first, offline

A self-contained, fully-offline RAG library for PDFs, EPUBs, markdown, and plain text, built for Apple Silicon. It's a library, not a single book: ingest as many books as you like, in any mix of supported formats, into one shared index. Each book is parsed into a Book → Chapter → Section → Chunk hierarchy, every chunk is enriched with LLM-generated context (Anthropic's Contextual Retrieval technique) scoped to its section, embedded locally via Ollama, and stored in a single SQLite file that doubles as a crash-safe, resumable checkpoint. Retrieval is hybrid by default: vector (cosine) search and SQLite FTS5 keyword/BM25 search both run over the same contextualized chunks, fused with Reciprocal Rank Fusion -- see ARCHITECTURE.md for how the pieces fit together and how to extend them. A built-in MCP server exposes the whole library to Claude as read-only tools.

Getting grimoire-beholder-mcp running is two separate jobs: setting it up (Python deps, Ollama, models, and at least one ingested book -- all manual, all local) and connecting it to Claude Desktop (one click, via a .mcpb bundle). Only the second part is "one click" -- there is no zero-prerequisite install. Do Setup first; the bundle does not do it for you.

Features

  • 100% local and offline -- LLM context generation and embeddings both run through Ollama; no cloud API is ever called.

  • Multi-format ingestion -- PDF, EPUB, Markdown, and plain text share one pipeline; see Supported source types.

  • Book → Chapter → Section → Chunk hierarchy with LLM-generated, section-scoped context per chunk (Anthropic's Contextual Retrieval).

  • Hybrid retrieval -- vector (cosine) and SQLite FTS5 (BM25) search fused with Reciprocal Rank Fusion; see Hybrid search.

  • Crash-safe and resumable -- ingestion checkpoints into SQLite at the chunk level; see How resume works.

  • One-click Claude Desktop integration via a .mcpb bundle exposing read-only MCP tools -- ingest/delete/reindex stay CLI-only by design.

Related MCP server: Co-Reading MCP

Table of contents

Supported source types

extension

parser

chapters from

page_start is

.pdf

PDF

table-of-contents level-1 entries (falls back to heading detection)

a real 1-indexed PDF page number

.epub

EPUB

one chapter per spine document, titled from the EPUB's nav/TOC

a synthetic, strictly increasing location ordinal (not a real page)

.md, .markdown

Markdown

top-level (# ) headings

a 1-based paragraph ordinal within the file

.txt

Plain text

the whole file is one chapter

a 1-based paragraph ordinal within the file

ingest picks the parser by file extension automatically -- there's no flag to set. Everything downstream of parsing (sectioning, chunking, contextualization, embedding, indexing, retrieval) is identical regardless of source type. See ARCHITECTURE.md for how to add another one.

Why sections?

Long, dense chapters (e.g. a 40-page chapter on philosophy or psychology) are too broad for one summary to usefully situate every chunk inside them. grimoire-beholder-mcp inserts a Section level between chapter and chunk, derived per chapter with this priority:

  1. If the source has sub-headings under the chapter (a PDF's TOC sub-entries; nothing for EPUB/markdown/text), each one becomes a section.

  2. Otherwise, if the chapter is longer than section_split_tokens (~3000 tokens by default), it's auto-split into ~3000-token sections, breaking on paragraph boundaries where possible.

  3. Otherwise, the whole chapter is a single section.

This hierarchy always exists, and a chunk never crosses a section boundary. Chunk context is generated from its section's summary, not the whole chapter, so contextualization stays tight even in dense, unstructured books.

query and search_book rank chunks with two independent retrieval strategies over the same contextualized, embedded chunks:

  • Vector: cosine similarity between the query embedding and every chunk's embedding.

  • FTS5: SQLite's full-text index (BM25-ranked) over each chunk's raw text and generated context.

Both arms run with the same filters (book_id, author, source_type), each returning its own top-candidate_pool_size candidates, and are fused with Reciprocal Rank Fusion (score = Σ 1/(rrf_k + rank) per chunk, summed across whichever ranking(s) it appears in). RRF combines rankings by relative position, not raw score, which is what makes it possible to fuse cosine similarity (bounded, [-1, 1]) with BM25 (unbounded) at all. A keyword match that vector search alone would have missed or under-ranked can out-rank a vector-only hit, and vice versa.

Set retrieval_mode = "vector" in config.toml (or pass --mode vector to query for a one-off) to disable the FTS5 arm and fall back to pure cosine ranking.

The FTS5 index is populated incrementally as chunks are embedded -- no separate indexing step. If you ever need to rebuild it from scratch (e.g. after restoring an old database backup), run grimoire-beholder reindex-fts.

Setup (manual, run once, in order)

Everything here is local. Run these in order, in the directory you want to use as your library (where config.toml and book.db will live):

  1. Install Python dependencies:

    uv sync

    Requires uv (brew install uv); it pins Python 3.12 and installs everything else for you.

  2. Pull the LLM model (used for section summaries and chunk context):

    ollama pull cogito:8b
  3. Pull the embedding model:

    ollama pull nomic-embed-text

    These are the defaults in config.toml -- if you've changed llm_model / embedding_model there, pull whatever you set instead.

  4. Confirm Ollama is running at http://localhost:11434 (ollama serve, or just have the Ollama app open). grimoire-beholder-mcp checks for required models on every run and refuses to proceed (with the exact ollama pull ... command) if Ollama is unreachable or a model is missing -- it never pulls one for you.

  5. Ingest at least one book (PDF, EPUB, markdown, or plain text):

    uv run grimoire-beholder ingest path/to/book.pdf [--name slug]

    This is slow (every section gets an LLM summary, every chunk gets LLM context and an embedding) but fully resumable -- interrupting it with Ctrl-C is fine, re-running the same command picks up where it left off instead of starting over. See How resume works below.

Once you've done this once, the library is ready to query from the CLI (uv run grimoire-beholder query "...") and ready to connect to Claude.

Usage

uv run grimoire-beholder ingest "<path-to-book.[pdf|epub|md|txt]>" [--name "Display Name"] [--force]
uv run grimoire-beholder list
uv run grimoire-beholder delete <slug> [--yes]
uv run grimoire-beholder query "<your question>" [--book <slug>] [--author <name>] [--type <pdf|epub|markdown|text>] [--mode hybrid|vector] [--expand]
uv run grimoire-beholder status
uv run grimoire-beholder reindex-fts [--book <slug>]
uv run grimoire-beholder serve-mcp
  • ingest picks a parser by file extension (see Supported source types above), extracts chapters and sections, chunks each section, generates a per-section situating summary and per-chunk context with the LLM model, and embeds and FTS5-indexes every chunk. The book's display name (and the slug it's stored under) defaults to title metadata from the file itself where available, falling back to the filename; override it with --name. Author and source type are recorded automatically. Re-running ingest on the same file is idempotent and resumable. If a different file would collide with an existing slug, ingest refuses unless you pass --force to replace it.

  • list shows every book in the library with its author, source type, page count, chapter count, section count, and chunk status breakdown.

  • delete <slug> permanently removes a book and everything under it (chapters, sections, chunks, embeddings, FTS5 rows) in one transaction. It prompts for confirmation unless you pass --yes. This command is CLI-only and is never exposed to Claude or the MCP server.

  • query embeds your question and ranks chunks with hybrid (vector + FTS5, RRF-fused) search by default across the whole library, printing the top matches with book/chapter/page citations. Scope or filter with --book <slug>, --author <name>, and/or --type <pdf|epub|markdown|text> (composable); override the retrieval mode for one query with --mode vector (debug/comparison only -- config.toml's retrieval_mode is the persistent setting). Pass --expand to print each hit's full parent section text instead of just the chunk. It never calls a cloud LLM.

  • status prints the configured models, the database path, and every book's chapter/section/chunk counts, including how many chunks are pending / contextualized / embedded.

  • reindex-fts drops and repopulates the FTS5 keyword index from every currently-embedded chunk, library-wide or for one --book <slug>. The index is normally kept up to date incrementally as chunks are embedded; this is only needed to recover a hand-edited database or an old backup. CLI-only.

  • serve-mcp starts the read-only MCP server over stdio -- this is what the .mcpb bundle (and the manual config below) both launch.

ingest, delete, and reindex-fts are all CLI-only by design: none of them are wired into the MCP server, so an agent talking to Claude can search and read your library but can never add to, remove from, or reindex it.

Connect to Claude (one-click via .mcpb)

Prerequisite: finish Setup above first. The .mcpb bundle only wires an already-working grimoire-beholder serve-mcp into Claude Desktop's settings -- it does not install Python, uv, Ollama, the models, or ingest any books. If you install it before completing Setup, Claude Desktop will show the extension as installed but the server will fail to start the moment it's invoked.

  1. Build (or download) grimoire-beholder-mcp.mcpb -- see Building the bundle below if you need to build it yourself.

  2. In Claude Desktop, go to Settings → Extensions → Install Extension and pick grimoire-beholder-mcp.mcpb.

  3. When prompted for configuration, fill in:

    • grimoire-beholder-mcp project directory -- the absolute path to this repo clone (where you ran uv sync).

    • Library directory -- the absolute path to the directory containing your config.toml and book.db (where you ran grimoire-beholder ingest). This can be the same path as the project directory, or anywhere else.

That's the "one click" part: Claude Desktop generates the server config for you from those two paths and starts grimoire-beholder serve-mcp itself.

Manual alternative (no .mcpb)

You can wire the same server in by hand by adding it to claude_desktop_config.json directly:

{
  "mcpServers": {
    "grimoire-beholder-mcp": {
      "command": "uv",
      "args": [
        "run",
        "--project",
        "/absolute/path/to/grimoire-beholder-mcp",
        "--directory",
        "/absolute/path/to/your/library",
        "grimoire-beholder",
        "serve-mcp"
      ]
    }
  }
}

--project points at this repo (so uv can find the grimoire-beholder entry point and its synced environment); --directory is the directory containing the config.toml and book.db for the library you want Claude to search -- it can be anywhere, and is typically not this repo. Both the bundled and the manual setup ultimately run the exact same command; the bundle just collects the two paths through a settings UI instead of you hand-editing JSON.

Alternatively, set the GRIMOIRE_BEHOLDER_CONFIG environment variable to the absolute path of your config.toml (e.g. via an "env" block in the server entry) and drop --directory entirely -- the config is then found regardless of the server's working directory, and a relative db_path inside it resolves against the config file's own directory. If the variable points at a file that doesn't exist, the server fails loudly at startup instead of silently serving an empty library.

The five tools

tool

purpose

list_books()

List every book (id, slug, name, author, source type, page count).

get_book_outline(book_id)

The chapter/section map for one book -- indices, titles, page_start, and an approx_tokens size hint per section. No section text.

search_book(question, book_id=None, top_k=None, author=None, source_type=None)

Hybrid (vector + FTS5) search the library, optionally scoped to one book and/or filtered by exact author or source type, for cited excerpts.

get_section(book_id, chapter_index, section_index)

Fetch a section's full text and summary -- the parent of a search hit, or a section located via get_book_outline.

book_status()

Chapter/section/chunk status counts for every book.

There is no ingest, delete, or reindex tool, and no cloud LLM is ever called from the server -- the only model it invokes is the local embedding model, to embed search questions. The server always uses config.toml's retrieval_mode (hybrid by default); the CLI-only --mode override has no MCP equivalent.

get_book_outline exists because get_section needs a chapter_index / section_index pair that nothing else surfaces -- without it, Claude has no way to resolve "the section about X" to a real index unless it happens to come from a search_book hit. Auto-split sections (no native heading) get a synthesized title -- "Section 3 -- <snippet of its first words>..." -- so they're still identifiable in the outline even with no real title.

Asking Claude

Once connected, there are two natural flows:

  • Browse: ask Claude to call list_books, then get_book_outline for one of them to see its chapters and sections, then get_section with a specific chapter_index / section_index to read one in full.

  • Search: just ask your question -- Claude will call search_book and cite chunks back to you. If a hit looks like it's missing surrounding context, ask Claude to pull the full section with get_section using the hit's book_id / chapter_index / section_index.

Building the bundle

The bundle source lives in mcpb/ (manifest.json plus a documentation stub -- it ships no code or dependencies; see the long_description in the manifest for why). To build grimoire-beholder-mcp.mcpb from it:

npm install -g @anthropic-ai/mcpb   # one-time; the official MCPB CLI
mcpb validate mcpb/manifest.json
mcpb pack mcpb grimoire-beholder-mcp.mcpb

Re-run mcpb pack after any change to mcpb/manifest.json.

Where the index lives

Everything -- books, chapters, sections, chunks, generated context, and embeddings -- is stored in a single SQLite database, book.db by default (configurable via db_path in config.toml), in the directory you run grimoire-beholder-mcp from. There is no separate checkpoint file: the database is the checkpoint, and it's shared across every book in the library.

All books in one database must share the same embedding model: the model used on the very first ingest is stamped into the database, and any later ingest -- of any book -- with a different embedding_model fails loudly rather than silently mixing incompatible vector spaces. To switch embedding models, point db_path at a fresh file to start a new index.

Hybrid search requires SQLite's FTS5 extension, which grimoire-beholder-mcp checks for on every connect() and fails loudly (not silently degrading to vector-only) if it's missing. The official python.org installers and Homebrew's sqlite3 both ship with it; this has not been an issue in practice.

How resume works

Every chunk has a status column that moves through pending -> contextualized -> embedded. Each ingest stage only looks at chunks (or sections) in the state it cares about:

  • Section summaries are written one section at a time and are skipped once set, so a crash loses at most one in-flight summary.

  • Contextualization commits one chunk at a time, so a crash loses at most one in-flight chunk.

  • Embedding processes one batch at a time (sequential, no concurrency) and commits after each whole batch, so a crash loses at most one in-flight batch.

Re-running ingest on the same PDF re-extracts and re-loads (cheap and idempotent -- rows are keyed by book/chapter/section/chunk index, so existing rows are never duplicated or overwritten), then picks up summarization, contextualization, and embedding exactly where they left off. Nothing restarts from zero.

Configuration

All settings live in config.toml in the working directory, with built-in defaults so the pipeline runs with zero edits:

key

default

meaning

llm_model

cogito:8b

model used for section summaries and chunk context

embedding_model

nomic-embed-text

model used for embeddings (locked in per-database, see above)

chunk_size

600

target chunk size, in approx. tokens (chars/4)

chunk_overlap

80

overlap between chunks, in approx. tokens

section_split_tokens

3000

chapters longer than this (with no TOC sub-headings) are auto-split into sections of about this size

embed_batch_size

16

chunks per batched embedding request

top_k

5

results returned by query / search_book

db_path

book.db

path to the shared SQLite library index

retrieval_mode

hybrid

hybrid (vector + FTS5, RRF-fused) or vector (cosine only)

candidate_pool_size

50

candidates each retrieval arm contributes before fusion

rrf_k

60

the k constant in Reciprocal Rank Fusion (score = Σ 1/(k+rank))

Running the tests

The test suite mocks Ollama entirely (a fake client returns deterministic canned text and vectors) and uses temporary SQLite databases, so it runs with no Ollama daemon and no models pulled:

uv run pytest

Available Tools

5 tools
book_statusA

Report ingest status (chapter/section counts, chunk counts per stage) for every book.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

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

Without annotations, the description carries the burden of transparency. It accurately states the tool returns status data for all books, with no side effects. However, it could be more explicit about potential performance implications (e.g., large result set) or data freshness, but overall it's sufficiently clear.

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?

The description is a single sentence (12 words) that conveys the core purpose efficiently. Every word earns its place; there is no redundancy or filler.

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?

Given the tool has no parameters and an output schema exists, the description is complete. It provides enough context to understand what the tool does without needing elaboration on return values.

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

Parameters4/5

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

The tool has zero parameters and schema coverage is 100%, so the description adds no parameter information. Baseline 4 is appropriate since no parameters exist and the description does not need to compensate.

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

Purpose5/5

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

The description clearly states the tool reports ingest status (chapter/section counts, chunk counts per stage) for every book. The verb 'report' and resource 'ingest status for every book' are specific. It distinguishes from siblings which handle outlines, sections, listing, and searching.

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

Usage Guidelines2/5

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

No guidance is provided on when to use this tool versus alternatives like get_book_outline or list_books. The description lacks explicit context for when to invoke or not invoke, and does not mention any prerequisites or exclusions.

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

get_book_outlineA

Return one book's chapter/section outline -- the map for get_section.

Lists every chapter (chapter_index, title, page_start) and, nested under each, its sections (section_index, title, page_start, approx_tokens). Auto-split sections have no native title; one is synthesized from a text snippet so it's still identifiable. Returns no section text -- that's get_section's job; this is just the map.

Typical flow: list_books -> get_book_outline(book_id) -> get_section( book_id, chapter_index, section_index) for targeted reading. Use search_book instead when you don't already know which section to read.

ParametersJSON Schema
NameRequiredDescriptionDefault
book_idYes

TDQS

A4.4/5.0
Behavior4/5

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

Discloses behavior for auto-split sections (synthesized title) and confirms no section text is returned. With no annotations, the description adequately covers behavioral traits, though it omits explicit read-only indication.

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

Conciseness4/5

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

Well-structured with purpose first, then details, then usage flow. However, the paragraph about auto-split sections is slightly verbose for the core purpose.

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?

For a tool with one parameter and no output schema, the description covers all necessary context: what it returns, what it doesn't, and how it fits into the typical workflow alongside sibling tools.

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?

The single parameter book_id is not described in schema (0% coverage). The description mentions it in the flow but does not explain what it is or how to obtain it, adding minimal meaning beyond the name.

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

Purpose5/5

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

Clearly states it returns the chapter/section outline (the map for get_section). Distinguishes from get_section by specifying it returns metadata like chapter_index, title, page_start, and section details, not the text.

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

Usage Guidelines5/5

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

Explicitly provides typical flow: list_books -> get_book_outline -> get_section. Also advises using search_book when the target section is unknown, offering clear when-to-use and when-not-to-use.

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

get_sectionB

Fetch one section's full text and summary.

The parent of a search_book hit, or a section located via get_book_outline's chapter_index/section_index map.

ParametersJSON Schema
NameRequiredDescriptionDefault
book_idYes
chapter_indexYes
section_indexYes

TDQS

B3.3/5.0
Behavior3/5

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

No annotations are provided, so the description carries full burden. It discloses that the tool returns 'full text and summary', implying a read-only operation. However, it doesn't specify behavior for invalid indices or any error conditions. For a simple fetch, this is adequate but not comprehensive.

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?

The description is two sentences long, front-loaded with the main action. Every sentence adds value: the first defines the function, the second explains how to locate the section. There is no unnecessary text.

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

Completeness3/5

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

The tool is simple (fetch with three integer parameters), but there is no output schema. The description mentions 'full text and summary' but does not elaborate on the response structure or what 'summary' entails. It is complete enough for basic use but leaves some ambiguity.

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

Parameters2/5

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

The input schema has 0% description coverage, meaning no parameter descriptions. The parameter names (book_id, chapter_index, section_index) are self-explanatory, but the description adds minimal meaning beyond the schema. It only indirectly references chapter_index and section_index. With zero coverage, the description should compensate more.

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?

The description clearly states it fetches one section's full text and summary. It mentions how sections are located via search_book or get_book_outline, but does not explicitly differentiate from sibling tools like get_book_outline, which returns the outline structure. The purpose is clear but not fully differentiated.

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

Usage Guidelines3/5

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

The description indicates when to use this tool: after obtaining indices from search_book or get_book_outline. It does not explicitly state when not to use it or provide alternatives. The usage context is clear but lacks exclusions.

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

list_booksA

List every book in the library, with its id, slug, name, author, type, and page count.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior2/5

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

No annotations are provided, so the description carries full burden. It only lists output fields but does not disclose behavioral details such as pagination, ordering, or performance characteristics. Agent cannot assess if results are paginated or if there are limits.

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?

Single sentence that is front-loaded with the action ('list every book') and immediately describes the output fields. No wasted words.

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

Completeness4/5

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

The tool has an output schema, so return values are documented. For a simple list tool, the description is mostly complete, but missing potential details like pagination or data limits that an agent might need.

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

Parameters4/5

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

There are no parameters in the input schema, so schema description coverage is 100%. The description adds no parameter info, but given zero parameters, a baseline of 4 is appropriate.

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

Purpose5/5

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

The description clearly states the tool lists every book and specifies exactly which fields are returned (id, slug, name, author, type, page count). It is easily distinguishable from sibling tools like book_status, get_book_outline, and search_book.

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

Usage Guidelines3/5

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

The description implies usage for retrieving a complete list of all books, but does not explicitly state when to use this tool over alternatives like search_book. No when-not or exclusion criteria are mentioned.

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

search_bookA

Search the library and return cited, scored excerpts.

Combines semantic and keyword search by default. Optionally scope to one book (book_id), or filter by exact author or source_type (one of "pdf", "epub", "markdown", "text").

ParametersJSON Schema
NameRequiredDescriptionDefault
questionYes
book_idNo
top_kNo
authorNo
source_typeNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

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

With no annotations, the description carries full burden. It discloses hybrid search, scoring, and return format. Lacks details on rate limits, error handling, or pagination, but is adequate for expected behavior.

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?

Two effici

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

Completeness4/5

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

Covers purpose, return type, and filters. Minor gap on top_k parameter, but overall complete given the presence of an output schema and sibling differentiation.

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

Parameters4/5

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

Schema has 0% description coverage; the description compensates by explaining book_id, author, source_type (including allowed values). Missing explanation for top_k, but four of five parameters are adequately described.

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

Purpose5/5

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

The description clearly states the tool searches the library and returns cited, scored excerpts. It distinguishes from sibling tools like get_book_outline and get_section by specifying the output type and optional filters.

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?

The description explains when to use (searching with excerpts and scoring, optional scoping/filtering) and implies when not to (e.g., use get_section for exact section). It lacks explicit exclusions or alternative naming but provides sufficient context.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedbook_status
    • First observedget_book_outline
    • First observedget_section
    • First observedlist_books
    • First observedsearch_book

TDQS

A4/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: list_books for listing, book_status for ingest status, get_book_outline for structure, get_section for content, and search_book for queries. No overlap or ambiguity.

Naming Consistency4/5

Most tools follow a verb_noun pattern (list_books, get_book_outline, get_section, search_book), but book_status uses a noun_noun format, creating a slight inconsistency. Overall still readable.

Tool Count5/5

Five tools is an ideal number for a library reading server, covering all essential read and search operations without unnecessary complexity.

Completeness5/5

The tool surface covers listing, status, navigation, content retrieval, and search. No obvious gaps for the stated purpose of browsing and reading books.

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers