A Knowledge Base for Claude in One Folder

Every team has the same pile: runbooks, decision records, postmortems, the page that explains what error E1042 means. Claude can use all of it, if it can search it. The usual answer is a vector database server, an embedding API and some glue.

This post builds the small version instead: a knowledge base in one folder, running on a laptop with no server and no API key, plugged into Claude Code as an MCP server. It does hybrid search, lets Claude save notes, ingests Markdown, PDFs and images, and keeps every version so a bad write can be undone. The code is on GitHub as lancedb-kb-mcp, with setup steps in the README.

Why LanceDB

The vector database post argued for starting small: exact search, data next to the vectors, no second system to keep in sync. LanceDB fits. It is an embedded database, like SQLite: a library that reads and writes files in a folder, not a server you run. Vectors sit in the same row as the text, title, tags and source file; keyword search runs on a built-in full-text index; and every write creates a new version you can read later.

On disk, a table is a folder of columnar data files, plus a small manifest per version listing which files make up that version. A write never edits an old file: adding one note to the demo table added a data file, a rebuilt index and two manifests, and left every existing data file alone.

Claude talks to it through the Model Context Protocol (MCP). Claude Code starts the server as a local process and talks to it over stdio.

How a question gets answered

Animated: Claude calls the search tool, the MCP server embeds the question with a local model, runs vector search and keyword search on the same table, merges the two rankings with reciprocal rank fusion, and returns chunks with their file and line range for Claude to cite.

When Claude needs context, it calls the search tool. The server embeds the question with a small local model and runs two searches on the same table: vector search for chunks that mean the same thing, and keyword search (BM25) for chunks with the same words. Reciprocal rank fusion merges the lists by each result's rank in both, so a cosine similarity is never compared with a BM25 score.

Filters for project, kind or tag apply before the search, so a narrow filter still returns as many results as you asked for, which avoids the "too few results" trap from the earlier post. Every result carries its file and line range, so Claude can say where an answer came from.

How well does it work?

A demo that only works on its own sample files proves little, so I ran three public benchmarks with human relevance labels, one for each thing it claims to do. The scripts are in the repo's bench/ folder.

What Benchmark Result
Text BEIR SciFact: 5,183 science abstracts, 300 claims nDCG@10: hybrid 0.722, vector 0.713, keyword 0.676
PDF pages ViDoRe V3: a 945-page textbook PDF, 132 questions the right page first for 74%, in the top five for 95%
Images Flickr30k: 1,000 photos, 5,000 captions the right photo first for 59%, in the top ten for 90%

nDCG@10 scores, from 0 to 1, how close the right documents are to the top of the first ten. The keyword-only 0.676 is close to the 0.665 the BEIR paper reports for BM25, a good sign the setup is sound.

Hybrid search came out best on text, but only just: 0.722 against 0.713 for vectors alone. It earns its place on exact strings such as error codes and names, and costs little elsewhere. It can also misfire. On the repo's tiny sample docs, "how often do we ship" ranked a feature-flag decision above the release notes, because it contains the words "features ship". The tool takes a mode argument for when you know which kind of search you need.

The PDF result covers the whole path: the real textbook PDF, read with plain text extraction, chunked and searched like everything else. Searching ViDoRe's cleaner per-page Markdown instead only moved "right page first" from 74% to 77%. One caveat: the labels belong to an older edition of the book, so the script fetches that edition from the Internet Archive, and questions about the other textbook in the set, whose edition is gone, are left out.

Is a bigger model worth it? I ran the same benchmarks with seven other local models, from 23 million to 600 million parameters, and with the 8-billion-parameter model my own knowledge base runs on a GPU.

Scatter of SciFact nDCG@10 against model size: small models score 0.645 to 0.740 with vector search and 0.707 to 0.752 with hybrid search, above keyword-only search at 0.676; Qwen3-Embedding-8B scores 0.796 with vectors and 0.775 with hybrid.

Among models a laptop runs comfortably, adding keyword search matters more than picking the model: hybrid lifts every one of them, and their spread shrinks from 0.65 to 0.74 down to 0.71 to 0.75. The 8B model is in another class at 0.796 with vectors alone, and it is the one model that adding keyword search made worse. Its vectors already find what the keywords would.

Bar chart of the share of questions where the right PDF page came first with hybrid search: Qwen3-Embedding-8B 82%, small models 73% to 77%, keyword only 69%.

On the PDF, every model beat keyword-only search (69%). The small ones land between 73% and 77%, and with 132 questions one question is worth almost a point, so those few points are close to noise. The 8B model reaches 85% with vector search alone.

Chunks that remember where they came from

Ingesting a folder splits each file at its headings, and long sections at blank lines, so a paragraph is never cut in half. Each chunk keeps its heading as a title and its line range. An early test caught a bug here: the first chunker only knew Markdown headings, so on mypy's reStructuredText docs every section ran into the next.

Running ingest again is cheap. A content hash per file skips unchanged files, and a changed file has all its chunks replaced, so no stale copy is left behind. Claude can write too: add_note and update_note put decisions and findings in the same table, so a search finds them next to the documents.

PDFs and images

A team's knowledge is not all Markdown. Postmortems get exported as PDFs, and the most useful thing in an incident channel is often a dashboard screenshot.

A PDF is read page by page and chunked like any other document, and every chunk keeps its page number. That needs a text layer: a scanned PDF needs OCR first.

Images need a different model. CLIP was trained on pictures and their captions, so it puts an image and a sentence describing it close together, and a question can be compared with images directly. Images go in a second table, because CLIP vectors cannot be compared with the text vectors, and a search merges both tables' results by rank. On the sample files, "what happened to the queue backlog during the outage?" returns page 1 of the postmortem PDF, the dashboard screenshot, the runbook section for the backlog alert and page 2 of the postmortem.

CLIP has a catch: it matches what a picture looks like, not what it means. "Architecture diagram of the order system" found the diagram; "which services does an order pass through" picked the dashboard. CLIP also scores unrelated pairs fairly high, so the demo leaves out images below 0.28. On Flickr30k that cut-off hid 10% of the correct photos and still let a photo through for 9% of questions about no photo at all. It is a trade-off, not a clean line.

Every write is a version

Animated: a folder is ingested and chunked by heading, unchanged files are skipped, and the rows land as version 8; Claude saves a note (version 10), a note is deleted (version 12), a search at version 10 still finds it, and restoring version 10 writes version 14.

An agent that can write to your knowledge base can also write something wrong. list_versions shows the history, search takes a version to search the table as it was, and restore_version makes an old version current by writing it as a new one, so nothing is lost. The numbers in the diagram are from a real run; each write makes two versions, one for the rows and one for the rebuilt index.

Old versions take disk space. LanceDB's optimize compacts files and by default removes versions older than seven days, so if you run it, that is your undo window.

Models that fit on a laptop

Text uses BAAI's bge-small-en-v1.5: 33 million parameters, 384 dimensions, a 129 MB download. On a desktop CPU with the GPU off, a question costs 12 ms: about 6 ms to embed it and 6 ms for the hybrid search. Keyword search alone takes 4 ms.

Bar chart of time per question on a CPU: keyword only 4 ms, all-MiniLM-L6 10 ms, bge-small 12 ms, e5-small, multilingual-e5-small and arctic-embed-s 14 ms, bge-base 22 ms, mxbai-large 54 ms, Qwen3-Embedding-0.6B 98 ms.

That is why bge-small stays the default. bge-base scores a little higher on text for twice the time; the larger models cost four to eight times as much and gain little on these benchmarks. The step that pays is a GPU-sized model, which is a different setup from a folder on a laptop.

Images use CLIP ViT-B/32, a 580 MB download, loaded only when the knowledge base has images.

Bar chart of how often the right Flickr30k photo came first: CLIP ViT-B/32 59%, ViT-B/16 62%, ViT-L/14 65%.

The larger CLIP models help a little: ViT-L/14 puts the right photo first 65% of the time against 59%, for 11 ms per question instead of 7.

Two details matter more than the choice of model. The model is part of your schema: vectors from different models are not comparable, so changing it means a fresh ingest. And bge's suggested query prefix depends on the data: it helped on mypy's error-code lookups and slightly hurt on SciFact (0.713 with it, 0.720 without). Measure it on yours.

Limits

  • One writer at a time. Two sessions saving notes to the same folder can race. To share it with a team, have one scheduled job ingest the docs and let everyone else read a copy of the folder.
  • Thousands of rows, not millions. The full-text index is rebuilt after every write and search is exact, with no vector index.
  • No scanned PDFs, code, audio or video yet.

None of that matters for a team's runbooks and decisions, which is the point: the smallest thing that works, in a folder you can read, copy and delete.

The short version

  • LanceDB keeps vectors, columns, full-text search and versions in one folder, with no server.
  • As an MCP server, it gives Claude search, notes, ingest of Markdown, PDFs and images, and version history.
  • On public benchmarks: nDCG@10 0.722 on SciFact with hybrid search, the right page of a 945-page PDF first for 74% of questions, and the right photo first for 59% of Flickr30k captions.
  • Every answer cites its file, page or lines, and every write can be undone.