document-index-mcp
Use it as a local document retrieval server: index files from a library folder, then let an AI agent search, inspect outlines, and read bounded passages with source locators.
Search ingested documents (
search_document) with hybrid BM25 + semantic, lexical, or semantic modes; returns ranked snippets, locators, scores, and optional filters by document, kind, page range, or section path.Browse document structure and library (
get_document_outline) to get heading trees with chunk spans, list all documents, and check ingest status/progress.Read passage context (
get_chunk_context) for one chunk plus up to 5 neighbours in reading order, addressed bychunk_idordocument_id+seq; hard-capped at ~24,000 characters.Ingest documents (
ingest_document) from the library root, supporting Markdown, plain text, PDF (including scanned PDFs via OCR), and Word.docx; EPUB, PowerPoint, HTML, and legacy.docare refused with reasons.Delete indexed documents (
delete_document) from the index without touching files on disk.Keep retrieval local: documents are indexed into SQLite on your machine, search and passage retrieval are local, and search never returns full document bodies.
Ingests faithful, timestamped Markdown transcripts of YouTube videos (captured with the companion YouTube Transcript Notes tool) so video content can be searched as bounded, source-located passages: search returns ranked snippets with clickable timestamps, and get_chunk_context reads the surrounding transcript text for verification.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@document-index-mcpSearch my document library for 'neural networks' and show passages with page numbers."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Document Index MCP
Indexes documents on your computer so AI agents can retrieve only the relevant, source-located passages.
Point it at a folder of PDFs, Word files and Markdown. It builds one SQLite index on your machine. An agent can then search that library and read short, bounded passages — each carrying the page or section it came from, so a quotation can be checked against the original.
Uploading a folder of course PDFs into an AI chat is slow, unreliable and expensive in context. This retrieves locally instead: embedding runs on your CPU, search returns snippets rather than documents, and a body read is hard-capped.
Status: beta. Four formats, all load-bearing: Markdown, plain text, PDF — including scanned PDFs, via automatic in-process OCR — and Word.
EPUB and PowerPoint readers existed and were removed in August 2026 rather than finished. Both could cite confidently and wrongly — an EPUB locator named a spine file while calling it a chapter, a chart-built deck indexed its titles and none of its data — and neither had read a file in real use. A format that can mislead is worse than one that is absent. docs/roadmap.md has the full reasoning.
For a slide deck, convert it first and ingest both outputs — a PDF of the slides, one page each, and a Markdown file of the speaker notes:
pwsh scripts/convert-for-ingest.ps1 "C:\Users\you\Library\Lectures" -RecurseBoth matter. A PDF export drops speaker notes entirely, and on a real 28-slide deck 22 slides carried notes holding figures that appear nowhere in the slide text — the slides were artwork.
⚠️ That script is Windows-only. It drives your installed Word and PowerPoint through COM, so it needs Microsoft Office. On macOS and Linux a deck has no route in at all: convert it to PDF by whatever means you already have and ingest that, knowing the notes are lost.
Install
Requires Node 22 or newer (developed on 24), on Windows x64, Linux x64 or macOS. That limit is inherited rather than chosen — the embedding model's tokenizer ships binaries for exactly those targets. On Linux arm64, Alpine or Windows-on-ARM the install succeeds and the first search then fails from inside a dependency, which is a miserable way to find out.
git clone https://github.com/ekelly95/document-index-mcp.git
cd document-index-mcp
corepack enable # provides the pnpm 11 that package.json pins
pnpm install
pnpm buildThen choose a library root: one folder holding the documents you want indexed. It is a security boundary as well as a convenience — the server refuses to read anything outside it, including through a symlink that lexically passes but physically escapes.
Pick it before you ingest anything. source_path is stored relative to the root. Moving the whole
library folder, index included, needs nothing. Pointing an existing index at a different folder —
the parent, a subfolder — is flagged at startup and in the library listing, and
pnpm rebase --library=<new root> --db=<index> re-expresses every path against it, or refuses and
changes nothing if a document would fall outside.
The index is a snapshot of each file as it was ingested, but it knows when that snapshot is stale.
Every search hit, every passage read and every library entry carries a source_status: present,
changed (edited since indexing, so a quote may be out of date) or missing. It is a stat
comparison, never a re-read. To catch up:
pnpm sync --library=/path/to/your/library "Papers" --recursiveThat re-ingests changed files (keeping their titles), retries failed ones whose file has since
changed, ingests new files under the named folders, and lists missing ones. It never deletes:
a missing file looks the same as an unmounted drive or a sync client mid-download, so dropping one
is delete_document's job. --dry-run shows the plan without changing anything.
Register with Claude Desktop
Merge this into claude_desktop_config.json — never overwrite it, the file also holds your
preferences. It lives at %APPDATA%\Claude\claude_desktop_config.json on Windows, and
~/Library/Application Support/Claude/claude_desktop_config.json on macOS.
{
"mcpServers": {
"document-index": {
"command": "/absolute/path/to/node",
"args": ["/absolute/path/to/document-index-mcp/dist/index.js"],
"env": {
"DOCUMENT_INDEX_LIBRARY_PATH": "/absolute/path/to/your/library"
}
}
}
}Use the absolute path to the Node binary: a GUI-launched application does not reliably inherit your shell PATH. Then quit Claude Desktop completely and reopen it — on Windows it persists in the system tray, so closing the window is not enough.
Register with Codex
[mcp_servers.document-index]
command = '/absolute/path/to/node'
args = ['/absolute/path/to/document-index-mcp/dist/index.js']
startup_timeout_sec = 30
[mcp_servers.document-index.env]
DOCUMENT_INDEX_LIBRARY_PATH = '/absolute/path/to/your/library'First run
The first ingest downloads the embedding model (bge-small-en-v1.5, ~65 MB) into
<library>/.document-index/models, once, and checks it against a pinned SHA-256. Searching is
entirely local. Privacy has the whole network story — it is two downloads and nothing
else.
If the library sits in a synced folder (OneDrive, Dropbox, iCloud), put the index and model cache
outside it with DOCUMENT_INDEX_DB_PATH and DOCUMENT_INDEX_MODEL_CACHE (or --db= / --models=).
A sync client uploading SQLite's -wal and -shm files mid-write is a conflict waiting to happen,
and nobody needs the model in their cloud storage.
For a whole library at once, use the bulk CLI rather than ingesting file by file in chat:
pnpm ingest --library=/path/to/your/library "Papers" --recursive⚠️ Name a subdirectory rather than . unless you are certain what is under the root.
--recursive walks the entire tree, skipping only dot-directories, so a root with an application-data
directory beneath it turns one command into a multi-hour sweep that fills the index with junk. This is
the main argument for a dedicated library folder rather than your home directory.
After upgrading, or whenever the index format changes, rebuild it. The server refuses an index built
by an older schema and says so; rebuilding keeps every document's title and document_id, and the
chunk_id of every passage whose text came out the same:
pnpm reindex --library=/path/to/your/libraryA document is read whole into memory, so files above 512 MB are refused rather than attempted. Raise
it with --max-file-mb= or DOCUMENT_INDEX_MAX_FILE_MB; the refusal names both the file's size and
the limit, so you know which to change.
Related MCP server: rag-mcp
Formats
Format | Locators | Structure comes from | Known limits |
|
| ATX headings | Block text is sliced from the source, never re-serialized |
|
| Setext underlines, numbered sections, ALL-CAPS lines, named divisions | A flat outline may be correct rather than a failure |
|
| Embedded bookmarks (named destinations included) refined by heading styles (size + font). A bookmark that points partway down a page starts its section there; one that names only the page starts it at the top | Tables are read as prose; running headers, page numbers and citation markers are dropped as furniture; scans and mojibake go to OCR, or are refused under |
|
| Heading styles, resolved through the document's style sheet, so localized and custom heading styles count | Headers, footers, comments and tracked-change machinery are never read. Deletions cannot leak: only |
| — | — | Removed, not deferred. Refused by extension, with the remedy: convert to PDF or Markdown |
| — | — | Removed. Run |
| — | — | Recognised by content sniffing and refused with a reason |
| — | — | Legacy binary Word: refused, pointing at |
Format is decided by content, not by file extension.
Scanned PDFs
A PDF whose sampled pages are essentially imagery — at most a quarter of them carrying text — or whose text layer decodes to noise is routed through in-process OCR (tesseract.js, WASM, nothing to install). Within an OCR'd document the decision is re-made per page, so a scanned book's digitally typeset title page keeps its real text and only the scanned pages pay for recognition.
A mostly digital PDF with a few scanned pages (plates, inserts) stays on the fast path, and those
pages are not recognised. They are not silent either: the document's ingest_warning names every
page that yielded no text, as it does for an OCR'd page whose every line scored too low to keep.
It is slow and visibly so: roughly 1–5 seconds per page per worker, making a 400-page scan tens of
minutes. get_document_outline reports chunk_count rising against locator_count throughout.
Flag | Environment variable | Default |
|
|
|
|
|
|
|
|
|
|
| the CDN |
The embedding model is English-only, so a multilingual library retrieves poorly. See docs/roadmap.md.
The five tools
Tool | What it does |
| Hybrid BM25 + semantic search. Ranked snippets with locators, each with a similarity score, plus a |
| Heading tree with chunk ranges. Also lists the library (or one folder of it, with |
| The only tool that returns body text, hard-capped at 24,000 characters. A single chunk over the cap (possible only in an index built before October 2026's fixes) is cut and marked |
| Index a file. Returns immediately; indexing continues in the background. |
| Drop a document from the index. Never touches the file on disk. |
The normal workflow is three cheap steps: search_document for ranked snippets, each already
naming its document and locator; get_document_outline to orient inside the one that looks right,
if needed; get_chunk_context to read the passage and its neighbours, addressed by chunk_id
from the hit or document_id + seq from the outline.
A chunk_id or document_id that stops resolving — the file was edited, the document deleted, the
index rebuilt — explains itself instead of reading as unknown. The index keeps a metadata-only
tombstone for every retired id: what it was, where it sat (page or section, seq) and which document
replaced it. A retired document_id passed to search_document or get_document_outline is followed
to the current version, and the reply says so; a retired chunk_id is never followed, because the
passage at the same seq in a new version may be a different one. No old text is kept, so the index
stays rebuildable from your files.
Search never returns full document bodies. That is structural rather than conventional: the output schema for a search hit has no text field at all, so a refactor cannot quietly regress it.
Everything the reading tools return beyond their own framing comes from your files: titles, section
names, snippets, passages. Replies say so, each passage get_chunk_context returns is fenced so its
end is unambiguous, and the tool descriptions tell the model to treat that text as content, not
instructions. Like every MCP tool result, a get_chunk_context reply carries its text twice, once
rendered in content and once in structuredContent. A client that forwards both to the model pays
for the passage twice.
Reference lists are recognised at ingest (a References heading, or citation-dense text) and left out
of search by default, since a list of paper titles matches every question on its topic while
answering none. filter.kind: "references" searches them.
With YouTube Transcript Notes
YouTube Transcript Notes captures a video as faithful, timestamped Markdown. Ingest that here and the whole chain stays checkable:
YouTube video
→ YouTube Transcript Notes → timestamped Markdown
→ Document Index MCP → bounded passages with source locations
→ your agent → notes or synthesis you can verifyThe transcript's wording and clickable timestamps survive ingestion unchanged, so a claim in the final synthesis traces back to the second of video it came from.
Privacy
Documents are read from your disk and indexed into one SQLite file under your library root. Search, ranking and passage retrieval are entirely local: no document text is sent anywhere except to the AI client you deliberately connected.
The server makes exactly two outbound requests, both one-time downloads of its own machinery, neither carrying any part of your documents:
the embedding model (~65 MB) from
huggingface.co/Qdrant/bge-small-en-v1.5-onnx-Q, on first ingest;OCR language data (~3 MB per language) from
cdn.jsdelivr.net, on the first scanned PDF only. A library with no scans never makes this one, and--ocr-lang-path=<dir>removes it entirely by pointing at your own copy from tessdata_fast.
Both are cached and neither repeats. The model is checked against a pinned SHA-256 every time it is loaded; the OCR language data is not checked. SECURITY.md has the details.
The library root is a jail: paths outside it are refused, symlinks that escape it are refused, and an
extension allowlist keeps files like .env from being addressable at all. Results expose a
library-relative path, never an absolute one — including in error messages.
One caveat worth stating plainly. The allowlist stops secrets being addressable; it does not stop a private notes tree inside the root being addressable. Choose a root wide enough to hold your documents and no wider.
Development
pnpm install
pnpm build
pnpm test # tsc, then the full suite
pnpm inspect # MCP InspectorThe suite covers the chunker's boundary law, the path jail, index agreement across chunks/FTS/vectors, PDF probe refusals and real OCR over generated scan imagery, concurrent-ingest safety and lease recovery, hybrid fusion, and the five tools end to end over a real MCP client.
Three things to know. Never pipe the test run — a | tail once masked a failure here. Four
tests skip by default: two need DOCUMENT_INDEX_TEST_REAL_MODEL=1 (with
DOCUMENT_INDEX_MODEL_CACHE set) — the real tokenizer's window and the confidence calibration — and
CI runs them on one job; two symlink cases skip where a symlink cannot be created, which on Windows
means without Developer Mode or an elevated shell, and CI runs them on Linux. And there are no binary test fixtures — PDFs are hand-assembled at
test time, cross-reference table and all, and scanned pages are drawn onto a canvas and embedded as a
JPEG XObject, so OCR is tested against genuine imagery without a blob in the repository.
CONTRIBUTING.md has the rest, including what will fail review.
Retrieval evaluation
pnpm eval --library=/path/to/corpus --questions=eval/questions.jsonReports recall@1/3/5 and mean reciprocal rank for lexical, semantic and hybrid search separately. The test suite proves the machinery is correct and deterministic, which is a different claim from the ranking being good; this measures the second one, and stops a tuning change quietly regressing it.
The stress corpus
Parser behaviour is measured against ten open-access documents, each chosen because it breaks a different assumption. They are too large for git, so corpus/manifest.json records every source, licence and SHA-256:
node scripts/corpus.mjs list # sources and re-download URLs
node scripts/corpus.mjs verify --dir=<corpus>/docs # confirm nothing has driftedA second set of six openly licensed slide decks tests the conversion route rather than a parser
(--dir=<corpus>/decks --set=decks). Those found two PDF-reader defects, both fixed, and confirmed
the thing worth knowing about decks: a 51-slide survey report whose chart categories and percentages
pass straight through conversion into searchable text.
Design notes and limitations
The engineering record lives in docs/, and is worth reading before changing anything:
docs/design.md — the three ideas the architecture rests on, and every deliberate deviation from the original specification with its reasoning.
docs/gotchas.md — the accumulated sharp edges, most of them bugs first. Each is a trap a reasonable change would walk straight back into.
docs/roadmap.md — what is built, what was cut and why, the October 2026 audit against a real library with its before/after numbers, and the loose ends stated honestly.
Licence
MIT. See LICENSE, and NOTICE.md for the dependency choices that keep it
permissive — chiefly why PDF parsing uses pdfjs-dist rather than the AGPL library the specification
named.
Changes are in CHANGELOG.md; the security model and how to report a vulnerability are in SECURITY.md.
Available Tools
5 toolsdelete_documentDelete DocumentA
Remove a document from the index: its chunks, its full-text entries and its vectors. The source file on disk is never touched. Use it to drop something ingested by mistake — an edited file does NOT need this, because re-ingesting one already replaces the version it supersedes. Deletion is permanent; the document's chunk_ids stop resolving, and re-ingesting the file rebuilds it.
| Name | Required | Description | Default |
|---|---|---|---|
| document_id | Yes | From get_document_outline's library listing |
Output Schema
| Name | Required | Description |
|---|---|---|
| title | Yes | |
| document_id | Yes | |
| source_path | Yes | |
| chunks_removed | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description fully discloses the destructive nature of the operation, states that the source file is untouched, and warns that deletion is permanent and chunk_ids will stop resolving. It also explains that re-ingesting rebuilds the document, giving a complete behavioral picture.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, well-structured, and every sentence adds meaningful information: the primary effect, the scope, the typical use case, the exception for edits, and the permanence/consequence. No fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simple one-parameter schema and its rich explanatory description, the description is fully complete for an agent to decide when and how to use it. The presence of an output schema also means return-value details are not required in the description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema covers 100% of the single parameter (document_id) with a description referencing get_document_outline's library listing. The description does not add further param-specific meaning, but the schema already provides the necessary context, so a baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('Remove a document from the index') and lists exactly what is removed (chunks, full-text entries, vectors). It also distinguishes this from sibling tools like ingest_document by explicitly noting that re-ingesting replaces versions, so delete is only for mistaken ingestion.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use guidance ('Use it to drop something ingested by mistake') and an exclusions ('an edited file does NOT need this, because re-ingesting one already replaces the version it supersedes'). This clearly directs the agent away from unnecessary deletion.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_chunk_contextGet Chunk ContextA
Full text of one chunk plus up to 5 neighbours on each side in reading order. Address it by chunk_id (from search results) OR by document_id + seq (from outline spans). This is the only tool that returns body text and it is hard-capped at ~24k characters — walk seq windows to read progressively.
| Name | Required | Description | Default |
|---|---|---|---|
| seq | No | ||
| after | No | ||
| before | No | ||
| chunk_id | No | From search_document results | |
| document_id | No | With seq, from outline spans |
Output Schema
| Name | Required | Description |
|---|---|---|
| chunks | Yes | |
| document_id | Yes | |
| source_path | Yes | Library-relative path of the source file |
| document_title | Yes | Title of the source document |
| has_more_after | Yes | |
| has_more_before | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses the hard cap of ~24k characters, the neighbor window of up to 5 on each side, and the progressive reading strategy. This is valuable behavioral context beyond the schema, though it does not cover error cases or permission requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two dense sentences with no redundant words. The most important function is front-loaded, followed by quick addressing instructions and a practical cap warning. Perfectly sized.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema (so return structure need not be detailed) and the tool's moderate complexity, the description covers all essentials: what it returns, how to address chunks, neighbor bounds, the character cap, and progressive reading advice. No critical gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 40%, but the description fills gaps by explaining the two addressing modes (chunk_id OR document_id+seq) and the neighbor limit, which maps to after/before parameters. It adds meaning beyond the bare schema, especially for uncovered params like seq and after/before.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns 'Full text of one chunk plus up to 5 neighbours', which is a specific verb+resource+scope. It explicitly distinguishes itself from siblings by noting 'This is the only tool that returns body text', leaving no ambiguity about its unique role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear context for when to use it (when body text is needed) and how to address chunks via chunk_id or document_id+seq, with sources for those IDs. It also advises 'walk seq windows to read progressively' for large reads, though it does not explicitly state when not to use it or name alternatives beyond implying it's the only body-text tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_document_outlineGet Document OutlineA
Hierarchical heading tree with locators and chunk seq spans. Costs almost no context — use it to orient before targeted get_chunk_context reads, and jump straight to a section with document_id + chunk_seq_start. Never returns body text. Call it with no document_id to list the library. It also reports ingest progress: a document still being indexed shows status 'processing' with a rising chunk_count.
| Name | Required | Description | Default |
|---|---|---|---|
| max_depth | No | ||
| document_id | No | Omit to list every ingested document with its status |
Output Schema
| Name | Required | Description |
|---|---|---|
| title | No | |
| format | No | |
| entries | No | |
| documents | No | |
| chunk_count | No | |
| document_id | No | |
| error_message | No | |
| ingest_status | No | |
| locator_count | No | |
| ingest_warning | No | |
| locator_scheme | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses low context cost, no body text return, and dynamic ingest progress reporting (status 'processing' with rising chunk_count). This is strong transparency beyond any structured metadata.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each adding unique value: the return type, the usage context, and the progress-reporting behavior. Front-loaded with the core purpose and no filler words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Description covers output nature (headings, locators, chunk seq spans), a key limitation (no body text), and dynamic status reporting. Combined with output schema and sibling context, it gives an agent sufficient understanding to invoke correctly despite the max_depth omission.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema documents document_id with 'Omit to list every ingested document with its status' but max_depth lacks description. Description reinforces document_id behavior but never explains max_depth or its effect on outline depth. With 50% schema coverage, this only partially compensates for the missing parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool as returning a hierarchical heading tree with locators and chunk seq spans. It distinctively separates this from sibling get_chunk_context by stating it never returns body text and is for orientation before targeted content reads.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit guidance is provided: use to orient before get_chunk_context reads, call with no document_id to list the library, and avoid expecting body text. This tells when and how to invoke the tool, and implies when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ingest_documentIngest DocumentA
Index a file from the library into the retrieval index. PDF, DOCX, Markdown and plain text are ingestible in this build; EPUB, PowerPoint, HTML and legacy binary Office (.doc) are recognised and refused with a reason naming the remedy. Scanned PDFs are detected and OCR'd automatically — expect those to index slowly, a few seconds per page. Format is decided by content rather than extension, which is why a mislabelled file is still routed correctly. Returns immediately with a document_id while indexing continues in the background — poll get_document_outline with that id to watch chunk_count rise and see when status becomes 'ready'. Re-ingesting an identical file is a no-op and returns the existing document_id.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path relative to the library root | |
| title | No | Overrides the title detected from the file |
Output Schema
| Name | Required | Description |
|---|---|---|
| title | Yes | |
| format | Yes | |
| reused | Yes | |
| status | Yes | |
| warning | Yes | Set when known content was skipped; the index is incomplete |
| document_id | Yes | |
| locator_count | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full responsibility and excels. It discloses asynchronous indexing, immediate return of document_id, background processing, OCR for scanned PDFs, content-based format detection, and idempotent re-ingestion—far exceeding basic safety/behavioral expectations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, well-structured paragraph where every sentence adds substantive value. It front-loads the core purpose, then logically covers formats, OCR, format detection, async behavior, and re-ingestion—no filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (async indexing, OCR, format nuances) and absence of annotations, the description is remarkably complete. It even explains how to verify completion via get_document_outline, making it self-sufficient for an agent. The output schema covers return structure, so no further detail is needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides 100% coverage with descriptions for both parameters ('Path relative to the library root' and 'Overrides the title detected from the file'). The tool description adds no additional parameter-level insight, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Index a file from the library into the retrieval index,' clearly stating the verb and resource. It distinguishes this from siblings like search_document and delete_document by focusing exclusively on ingestion.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for use, including supported formats, refusal behavior, OCR handling, and re-ingest semantics. It does not explicitly contrast with alternatives, but the sibling tools are obviously different operations, so the guidance is adequate without formal exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_documentSearch DocumentsA
Hybrid BM25 + semantic search across ingested documents. Returns ranked snippets with precise locators (page or section, section path, bbox). This is the usual starting point. It never returns full text — follow a hit with get_chunk_context using its chunk_id to read.
| Name | Required | Description | Default |
|---|---|---|---|
| k | No | ||
| mode | No | hybrid | |
| query | Yes | ||
| filter | No | ||
| document_id | No | Restrict to one document; omit to search the whole library |
Output Schema
| Name | Required | Description |
|---|---|---|
| hits | Yes | |
| processing_documents | Yes | Documents still indexing. They were NOT searched, so this result is incomplete. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Without annotations, the description carries the full burden. It discloses output shape (ranked snippets with locators), a key limitation (never full text), and a recommended next action. It doesn't mention pagination or error conditions, but for a search tool the provided behavioral details are substantial and accurate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: first defines the search mechanism, second specifies the output and locators, third gives a crucial usage boundary and points to the sibling. Perfectly front-loaded with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The overall context is adequate for a simple search invocation: it explains output, the usual starting-point role, and the follow-up workflow. However, with 5 parameters including a nested filter object and no annotations, the description leaves mode/filter/scoping semantics unexplained, so it's not fully complete despite the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 20%, so the description must compensate. It only vaguely references the hybrid mode and the fact that search spans 'ingested documents'; it does not explain k, mode choices, the filter object, or how document_id scoping works. The description adds almost no value for the parameter set beyond what the bare schema shows.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it performs 'Hybrid BM25 + semantic search across ingested documents' and returns 'ranked snippets with precise locators' — a specific verb+resource. It distinguishes itself from siblings by emphasizing search and pointing to get_chunk_context as the follow-up for full text, so an agent knows this is the retrieval entry point.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says 'This is the usual starting point' and provides an exclusion: 'It never returns full text — follow a hit with get_chunk_context using its chunk_id to read.' This tells the agent when to use this tool and when to switch to a named alternative, which is exactly what this dimension asks for.
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.
5 tool updates
v0.1.0- First observed
delete_document - First observed
get_chunk_context - First observed
get_document_outline - First observed
ingest_document - First observed
search_document
TDQS
Scored across 5 tools
Each tool has a distinct role: search_document finds snippets, get_chunk_context retrieves full text, get_document_outline provides structure, ingest_document adds content, and delete_document removes it. There is no overlap in purpose; the descriptions clearly differentiate when to use each.
All tool names follow a consistent verb_noun pattern (ingest_document, delete_document, search_document, get_chunk_context, get_document_outline). The prefix 'get_' is used consistently for retrieval operations, and the other verbs are clear and action-oriented.
Five tools is well-scoped for a document indexing and retrieval server. Each tool is necessary and there are no redundant or superfluous entries. This is within the ideal range for a focused MCP server.
The tool surface covers the full document lifecycle: ingest (create), read via search/context/outline, update via re-ingestion (explicitly stated), and delete. It also includes library listing and ingestion status polling, so there are no obvious dead ends or missing capabilities for the stated purpose.
Maintenance
Related MCP Connectors
Ingest, manage, and retrieve documents for RAG-powered AI applications
Persistent docs and memory for AI agents — read, write, organize & search a shared workspace.
Universal persistent memory and knowledge retrieval layer for AI agents and LLMs.
Database for your AI agent. Turn its output into data, docs, skills, and apps you can actually use.
Related MCP Servers
- AlicenseAqualityAmaintenancePrivacy-first local document search using semantic search. Runs entirely on your machine with no cloud services, supporting PDF, DOCX, TXT, and Markdown files.94,493 npm412MIT
- FlicenseAqualityDmaintenanceEnables indexing local documents (PDF, Markdown, text, code) into a knowledge base and querying them via semantic search using local embeddings, all running privately on your machine.4-
- AlicenseNot gradedqualityAmaintenanceLocal-first memory and retrieval for private project knowledge. Enables indexing files, searching, and asking questions about project documents using local embeddings and LLM.6AGPL 3.0
- AlicenseNot gradedqualityAmaintenanceLocal-first document management and semantic search for AI coding agents, with a built-in web dashboard and REST API.95 npm343MIT