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