PKB — MCP-based personal RAG knowledge base
A personal knowledge base RAG that Claude searches and indexes directly through MCP tools
Background
Years of career notes, an Obsidian vault, study material, and resume drafts were scattered across several places, so every lookup meant grepping for them or relying on memory. Working with Claude meant repeatedly pulling up the relevant material and pasting it in by hand. Relational exploration - finding what else connects to a given concept - was not something grep could solve. What was needed was a way to bind personal knowledge into a RAG and an interface Claude could query and explore through MCP tools. RAG (retrieval-augmented generation) retrieves external documents to ground an LLM answer.
System architecture
- Hybrid search: BM25 and multilingual MiniLM embedding kNN are combined through RRF (reciprocal rank fusion), then reranked with BGE Reranker v2-m3 to pick the top documents.
- Categories: five categories -
about · career · study · writing · obsidian- across more than 10,000 chunks. - MCP server:
src/pkb/mcp_server.pyexposes 14 MCP tools:search_knowledge,list_documents,get_document,reindex_document,add_document,write_file,sync_obsidian,search_concepts,explain_concept,related_concepts,graph_store_concepts,graph_list_chunks,doctor,convert_and_ingest. - Concept graph: a LangGraph-based LLM extraction pipeline (
graph/{builder, extract, store, schema}) pulls concepts and relations out of chunks and stores them, queried throughsearch_concepts,related_concepts, andexplain_concept.
How it was built
Designing hybrid search. BM25 (traditional keyword-frequency search) is weak on semantic similarity, and kNN (embedding vector distance) is weak on exact keyword matching. The two result sets are merged with RRF (reciprocal rank fusion, summing the reciprocals of each result's rank), and the top 20 candidates are reranked with BGE Reranker v2-m3. scripts/reranker_model_benchmark.py records the basis for the reranker model choice.
Ingestion by category. ingest.py runs parsers per file extension (markdown, PDF, docx, pptx, xlsx). The script then chunks the output, embeds it with multilingual MiniLM, and loads it into the Elasticsearch pkb_documents index. Chunks are cut at 500 tokens with 100 tokens of overlap while preserving the markdown section path (H1 > H2 > H3), so search results show where in the source a hit came from. Storage is confined to paths under data/ to keep the Obsidian vault and the working directory from mixing.
MCP server. mcp_server.py, built on FastMCP, exposes the 14 tools. Claude Code queries with search_knowledge, adds notes with write_file, and reindexes edited documents with reindex_document. A pipeline like this portfolio work, flowing from code to PKB to portfolio mdx, completes entirely through MCP tool calls.
Extending to a concept graph. Beyond searching chunk bodies directly, graph/extract.py uses LangGraph to extract concepts and relations per chunk. search_concepts finds semantically similar concepts and related_concepts finds other concepts tied to a given one.
Quality checks as tools. The doctor MCP tool returns the ES connection, index, chunk count, and embedding and reranker configuration. scripts/golden_retrieval_eval.py tracks retrieval quality regressions as numbers.
Results
- Five categories and more than 10,000 chunks indexed in a single Elasticsearch 8.17 index, with average response times in the hundreds of milliseconds.
- Retrieval quality ceiling set by BM25 plus kNN hybrid search, RRF fusion, and BGE Reranker v2-m3 reranking.
- 14 MCP tools exposed to Claude Code and Claude Desktop, callable directly from a working pipeline.
- Relational exploration supported by a LangGraph-based concept graph pipeline (
graph/{builder, extract, store}). - The
write_fileplusreindex_documentcombination completes a PKB document workflow where writing indexes immediately.