ask-your-library
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Ask your own library — textbooks, guidelines, lecture notes and your notes — and get answers that cite the page. Local-first search hub: MCP server for Claude Code + Obsidian plugin.
ask-your-library
English · 繁體中文
Ask your own library — textbooks, guidelines, lecture notes and your notes — and get answers that cite the page. Local-first search hub with an MCP server for Claude Code and an Obsidian plugin.
Formerly vault-search.
Three tools that turn your Obsidian vault into a semantically searchable, AI-chattable knowledge base — running entirely on your own machine. No cloud indexing, no notes leaving your computer for search, no per-query API bill for retrieval.
Built by a physician to study from a vault of thousands of clinical notes, but domain-agnostic: point it at any vault of markdown notes (research, law, engineering, a personal wiki) and it works.

Two ways to use it (one shared index)
This repo ships two front-ends over the same local LanceDB index + retrieval core — use either or both:
vault-search-obsidian— the Obsidian plugin (Search / Related Notes / Chat in the sidebar), talking to a small local API server. → Quickstartvault-search-mcp— an MCP server exposing the same search as tools (vault_search,vault_similar,vault_related,vault_stats,textbook_search, andlecture_searchwhen a lecture corpus is configured) to Claude Code or any MCP client. No Obsidian required. → Quickstart
Both reuse the same server/ core (indexer · scoring · Personalized-PageRank · knowledge graph), so you index once and query from wherever you like.
The three-piece set (三套組)
| Tool | What it does | Backed by |
|---|---|---|
| 🔍 Vault Search | Type a question in natural language (any language) and get the most semantically relevant note sections — not keyword matches. | LanceDB vector search + local embeddings |
| 🔗 Related Notes | A live sidebar that, as you read or select text, surfaces related notes you forgot you wrote — surfacing hidden links across your vault. | Same index, "find similar" mode |
| 💬 Vault Chat | Chat with your notes (RAG). Three modes: Vault (notes only, 100% local & free), Hybrid (notes + AI knowledge), Free (open chat + literature search). | Local LLM or your Claude subscription |
All three live in the Obsidian sidebar. The same index also exposes an MCP server, so coding agents like Claude Code can search your vault as a tool.
Screenshots
| 🔍 Vault Search | 🔗 Related Notes |
|---|---|
![]() |
![]() |
| 💬 Vault Chat | 👁 Context preview |
|---|---|
![]() |
![]() |
Why it exists
Built-in Obsidian search is lexical — it finds the words you typed. But you rarely remember the exact words in a note you wrote months ago. Semantic search finds notes by meaning: search "how to manage a flaccid bladder after spinal injury" and it finds your note titled Neurogenic Bladder even if those exact words never appear.
The design priority is local-first and private:
- Embeddings run locally via Ollama (
bge-m3, bilingual, CPU-friendly). Your note text is never sent anywhere to be indexed or searched. - Vault-mode chat is fully local and free — a local LLM answers using retrieved notes, zero tokens billed.
- Hybrid/Free chat optionally shells out to the
claudeCLI so you can use a Claude subscription you already pay for, instead of wiring up (and paying per-token for) a raw API key.
How it works
┌─────────────────────────────────────────┐
Obsidian ◄────► │ Plugin (sidebar): Search · Related · Chat│
└───────────────────┬─────────────────────┘
│ HTTP (localhost:3789)
▼
┌─────────────────────────────────────────┐
│ api_server.py (FastAPI) │
│ /search /similar /chat /reindex │
└──────┬───────────────────────┬───────────┘
│ │
embed query RAG context
▼ ▼
┌────────────────────────┐ ┌──────────────────────────┐
│ Ollama (bge-m3) │ │ Vault mode → local LLM │
│ → vector │ │ Hybrid/Free → `claude` CLI │
└───────────┬────────────┘ └──────────────────────────┘
▼
┌────────────────────────┐
│ LanceDB vector index │ ◄── indexer.py (chunks notes by heading,
│ (~/.vault-search) │ embeds, stores; incremental updates)
└────────────────────────┘
Same index + scoring is also served to coding agents via mcp_server.py (MCP).
indexer.pywalks your vault, splits each note into chunks by heading, embeds them withbge-m3, and stores them in a local LanceDB table. Re-runs are incremental (content-hash cache).scoring.pyreranks raw vector hits with optional path weighting (trust some folders more), recency (newer notes rank higher), and relation boosts.ppr.pyranks notes by Personalized PageRank over the[[wiki-link]]graph — a HippoRAG / LinearRAG-style random-walk-with-restart. It powers Related Notes and multi-hop query expansion (seeding the walk on the top semantic hits), surfacing relevant notes that pure vector similarity misses. On the author's vault this lifted sparse-bridge recall@10 from ~60% to ~84% versus naive 1-hop / shared-link expansion.api_server.pyserves the plugin: semantic search, find-similar, single-note reindex, and chat (with retrieval-augmented context).mcp_server.pyexposes the same search to MCP clients (e.g. Claude Code) asvault_search,vault_related,vault_similar,vault_stats,textbook_search(andlecture_searchwhen a lecture corpus is configured).- Optional add-ons: a second long-form reference corpus (
textbook_indexer_v3.py, parent-child chunking, hybrid search, optional reranker) and a knowledge graph of[[wikilinks]]+ entities (graph_builder.py).
Prerequisites
| Requirement | Notes |
|---|---|
| Python 3.10+ | For the server. |
| Ollama running locally | Provides embeddings (and, optionally, local chat). bge-m3 runs fine on CPU — a GPU only speeds up bulk indexing. |
| Obsidian | To use the sidebar plugin. The server can also be used headless via the MCP/HTTP API. |
| Claude CLI (optional) | Only for Hybrid/Free chat. Vault-mode chat uses a local model instead. |
| A machine that stays on (optional) | If you want search/chat available to your phone or other devices, run the server on an always-on PC/NAS. For single-machine use, just start it when you open Obsidian. |
Pull the embedding model once:
ollama pull bge-m3
# optional, for zero-cost local Vault-mode chat:
ollama pull gemma2:9b
Installation
Step 0 — shared core (do this once, for either front-end)
git clone https://github.com/drpwchen/ask-your-library.git
cd ask-your-library
python -m venv .venv
source .venv/bin/activate # Windows Git Bash: source .venv/Scripts/activate · PowerShell: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
cp env.example .env # then edit .env — uncomment VAULT_PATH and point it at your vault
cd server
python indexer.py # build the index (incremental on later runs)
ollama pull bge-m3 first if you haven't (see Prerequisites). Now pick one or both front-ends below.
vault-search-obsidian — the Obsidian plugin
Start the local API server, then install the plugin:
python server/api_server.py # serves http://localhost:3789
cp -r plugin "<YOUR_VAULT>/.obsidian/plugins/vault-search-plugin"
Memory note.
api_server.pyandmcp_server.pysetOPENBLAS_NUM_THREADS=1at import time. numpy bundles OpenBLAS, which pre-commits one scratch buffer per CPU core as soon as numpy loads — 373 MB of private bytes against 19 MB capped, measured on a 12-thread machine — and these processes do no linear algebra themselves (Ollama embeds, LanceDB does the vector math). Export your ownOPENBLAS_NUM_THREADSif you need the default back.
In Obsidian: Settings → Community plugins → enable "Vault Semantic Search". Open its settings and confirm API Server URL is http://localhost:3789. Three ribbon icons appear: 🔍 Search, 💬 Chat, 🔗 Related Notes.
The plugin ships a
data.json.example. Obsidian writes its realdata.jsonon first run; never commit adata.jsonthat contains an API key.
vault-search-mcp — use it from Claude Code (or any MCP client)
No Obsidian, no API server — just register the MCP server against the index you built in Step 0:
# macOS / Linux
claude mcp add vault-search -- /abs/path/to/ask-your-library/.venv/bin/python /abs/path/to/ask-your-library/server/mcp_server.py
# Windows
claude mcp add vault-search -- C:\path\to\ask-your-library\.venv\Scripts\python.exe C:\path\to\ask-your-library\server\mcp_server.py
Give the absolute path of the Python in the venv from Step 0 (python -c "import sys; print(sys.executable)" inside the activated venv prints it). The MCP client starts the server itself, outside your shell: a bare python there can be another interpreter without lancedb, and the server then fails to connect.
Now Claude Code (or any MCP client) can call vault_search, vault_related, vault_similar, vault_stats, textbook_search and — if you indexed a lecture corpus — lecture_search, all backed by the same local index, Personalized-PageRank expansion, and reranking.
If you keep several sessions open, register mcp_thin.py instead. Each mcp_server.py session opens its own copy of the index. Measured here: 105 MB at startup, 367 MB after one vault_search, and 2.2 GB once a textbook_search loads the tokenizer and parent maps — so a few concurrent sessions cost gigabytes for a single index. mcp_thin.py is a stdlib-only client that forwards tool calls to a running api_server.py, which then holds the index alone:
python server/api_server.py # must be running
claude mcp add vault-search -- /abs/path/to/ask-your-library/.venv/bin/python /abs/path/to/ask-your-library/server/mcp_thin.py
Same tools, same arguments, same output — the API server dispatches to the very same handle_tool_call() the standalone MCP server uses. The trade-off is that the API server must be up; if it is down, the client still starts (it serves the last cached tool schema) and tool calls return a readable error instead of killing your session.
Configuration
Everything is driven by environment variables (see env.example for the full annotated list). None is required: VAULT_PATH is needed only to search your notes, and a corpus-only install runs without it. Highlights:
| Variable | Default | Purpose |
|---|---|---|
VAULT_PATH |
— (unset = corpus only) | Absolute path to your vault. |
VAULT_SEARCH_DATA_DIR |
~/.vault-search |
Where indexes/caches/logs live. |
OLLAMA_HOST |
http://127.0.0.1:11434 |
Ollama endpoint. |
VAULT_SEARCH_EMBEDDING_MODEL |
bge-m3 |
Vault embedding model. |
VAULT_SEARCH_API_PORT |
3789 |
API server port. |
CLAUDE_CMD |
claude |
CLI used for Hybrid/Free chat. |
VAULT_SEARCH_CHAT_MODEL |
gemma2:9b |
Local model for Vault-mode chat. |
VAULT_SEARCH_PERSONA / _LANGUAGE |
generic | Customize the assistant's role & reply language. |
VAULT_SEARCH_PATH_WEIGHTS |
{} |
Boost/demote folders in ranking. |
VAULT_SEARCH_EXCLUDE_PATTERNS |
(none) | Hide derivative notes (drafts, scratch) from search by default. |
Add a corpus (textbooks, guidelines, lectures)
Reference material reaches ask-your-library as a corpus folder: plain folders of Markdown laid out as docs/corpus-format.md describes (format t2n-corpus/1). textbook-to-note writes one from PDF textbooks and guidelines, lecture-to-notes from recorded lectures, and any script of your own can. One command makes it searchable:
ollama pull qwen3-embedding:0.6b # embedding model for corpora (a GPU is optional)
python server/corpus.py check /path/to/corpus # validate the layout (reads files only)
python server/corpus.py add /path/to/corpus # check → index → audit → print the .env line
add reads the kind from the folder's corpus.json and indexes into ~/.vault-search/textbook_db (textbooks, guidelines) or ~/.vault-search/lecture_db_v3 (lectures) — or --db PATH. It ends by printing the one setting the server needs, e.g.
VAULT_SEARCH_TEXTBOOK_DB_PATH=/home/you/.vault-search/textbook_db
Put that line in .env and restart api_server.py (and start a new MCP session if you run mcp_server.py directly): textbook_search — or lecture_search — now answers from it. To add a book later, put its folder under the same root and run add again; only changed files are re-embedded. --dry-run prints the indexer commands without running them. One database holds one corpus root: add refuses a database built from another root, and — like the indexer itself — any database the server uses for something else.
- Guidelines go in
<root>/_Guidelines/<Name>/, one sub-folder each: every guideline is its own source, taggedguideline, and gets a small ranking prior. books.json(VAULT_SEARCH_BOOKS_META, default~/.vault-search/books.json; seeexamples/books.example.json) adds what a folder name cannot say — edition, tier,corpus_type, andstatus(superseded/derivative/low_trustsources are not indexed and are hidden from search). Keys are the source folder names;checkflags keys that match no folder.- Lectures: one folder per course with a
_meta.md, and entries that start with an H1 and aSource: YYYY-MM-DD Speaker《Course title》· …line — see §4 of the format.
Try it on the bundled fixtures: python server/corpus.py add examples/corpus-fixture/textbook --db ./fixture_db.
Corpus only, no Obsidian vault. Nothing else is required: skip indexer.py and leave VAULT_PATH unset. The minimal setup is the venv from Step 0, ollama pull qwen3-embedding:0.6b, corpus.py add, and the one line it prints in .env (create the file with just that line, or export it). Register the MCP server with the venv's Python as above; textbook_search / lecture_search answer, and the vault_* tools reply "No vault configured — set VAULT_PATH …".
Optional add-ons
Secondary reference corpus (textbooks / manuals)A corpus of long-form reference docs (converted PDFs, manuals, guidelines) is indexed with parent-child chunking for precise hits with surrounding context. Build it with corpus.py add; what that runs is
cd server
python textbook_indexer_v3.py --corpus /path/to/corpus --db ~/.vault-search/textbook_db
python textbook_indexer_v3.py --corpus /path/to/corpus --db ~/.vault-search/textbook_db --audit
followed by VAULT_SEARCH_TEXTBOOK_DB_PATH=~/.vault-search/textbook_db in .env.
textbook_indexer_v3.py writes its own database (it refuses one the server uses for anything else, such as the vault's), records the corpus root, page provenance and table units per chunk, skips reference lists, and builds the full-text index hybrid search needs. Re-runs only re-embed changed files; --book <folder> re-embeds one book, --audit checks every chapter made it in. The older textbook_indexer.py (no FTS, writes into the vault DB) still works but is not where new behaviour lands.
Adds a textbook_search tool (MCP + HTTP). Its output says which corpus root the file paths are relative to, and its description carries a source-precedence rule for passages that disagree: a newer edition of the same work wins, a guideline not older than a textbook edition outranks it, a single study never overrides a guideline or textbook consensus — name the disagreement and cite both. After reranking, guidelines and recent sources get a small prior (VAULT_SEARCH_RERANK_GUIDELINE_PRIOR, VAULT_SEARCH_RERANK_YEAR_PRIOR; 0 turns either off). Optionally rank trusted sources higher with a boost file — see examples/source_boost.example.json and VAULT_SEARCH_SOURCE_BOOST.
Book table. A books.json (VAULT_SEARCH_BOOKS_META, see examples/books.example.json) is joined onto textbook results at query time: sources marked superseded or derivative are hidden unless you pass include_superseded, corpus_type restricts a search to e.g. guidelines, and tier/year break similarity ties. No reindex needed.
Hybrid search and reranking. textbook_search fuses vector and full-text results (RRF) by default (VAULT_SEARCH_TEXTBOOK_RETRIEVAL=hybrid; needs the full-text index textbook_indexer_v3.py builds, otherwise it falls back to vector). With a CUDA GPU you can add a cross-encoder reranker: set VAULT_SEARCH_RERANK_PYTHON to a Python that has CUDA torch + transformers, and each search takes a pool of 50, rescores it with BAAI/bge-reranker-v2-m3 and keeps the top n. The model runs in a separate process (server/reranker_service.py) that starts on the first query (a few seconds), exits after 10 idle minutes, and exits at once when another job claims the GPU lease — searches meanwhile are simply not reranked. On the author's 435-question textbook eval this added about +0.08 MRR at ~0.5 s per query (RTX 3070 Ti); similarity stays the cosine, the cross-encoder score is returned as rerank_score.
Lecture corpus. A lecture corpus (format) goes into its own database: corpus.py add runs textbook_indexer_v3.py --corpus /path/to/lecture-corpus --db ~/.vault-search/lecture_db_v3 (--corpus is the folder whose subfolders are the courses) and tells you to point VAULT_SEARCH_LECTURE_DB_PATH at it. Then a lecture_search tool appears, ranked separately from textbook_search. Each course folder's _meta.md is left out of the results, and every hit carries the date, speaker and course_title parsed from its entry's Source: line (Source: 2025-02-15 Speaker《Course title》· …); a field that cannot be read is empty, never guessed.
Step 0's indexer.py embeds whole notes. The parent-child index matches a small chunk and quotes the whole section around it; its v3 profile also reads real YAML frontmatter, skips backup copies and template folders, caps chunk length and writes a scope column. It needs ollama pull qwen3-embedding:0.6b and is built into a new database, so the old index keeps serving until you switch:
cd server
python vault_indexer_v2.py --profile v3 --db ~/.vault-search/lance_db_v3 # build
python vault_indexer_v2.py --profile v3 --db ~/.vault-search/lance_db_v3 --incremental # later: changed notes only
To switch the server to it:
- in
.env:VAULT_SEARCH_DB_PATH=~/.vault-search/lance_db_v3 - create the flag file
~/.vault-search/vault_v2.enabled(touchit; delete it to fall back to the whole-note index) - restart
api_server.py(and anymcp_server.pysession).
If your textbook tables lived in the vault database (no VAULT_SEARCH_TEXTBOOK_DB_PATH set), set that variable to the old database first — it defaults to VAULT_SEARCH_DB_PATH.
A JSON map (VAULT_SEARCH_SCOPE_MAP, see examples/scope_map.example.json) names the top-level folders of each scope. It is fail-closed: a folder not listed under a scope is outside it, so a new folder never leaks into a scoped search. personal is everything not listed anywhere, all turns the filter off, and the default scope is set on the server — a caller that never mentions scope gets the safe default. Applies to vault_search, vault_similar, vault_related and the chat context.
eval/ measures retrieval (hit@k, MRR, page-window hit, contamination by outdated sources) and claim-level answers, with paired tests (McNemar, cluster bootstrap) for A/B decisions. See eval/README.md. Your real question sets stay outside the repo.
cd server
python graph_builder.py # parse [[wikilinks]] into an adjacency graph
python graph_builder.py --ner # optional: scispaCy/NER entity extraction
When present, the wiki-link graph drives the Personalized PageRank retrieval in ppr.py (Related Notes + query expansion), and search results are enriched with linked notes and extracted relations. The NER step needs scispacy (see requirements.txt). PPR itself is pure-Python and needs only the wiki-link graph (Phase A) — no NER required.
Adapting it to your setup (alternatives)
This project was built around an always-on PC with a GPU + a Claude subscription, but none of that is required. Pick the row that matches you:
| You don't have… | Do this instead |
|---|---|
| A GPU | Nothing — bge-m3 embeds on CPU. Indexing is slower for huge vaults but search is instant. Smaller models (nomic-embed-text) are even lighter; set VAULT_SEARCH_EMBEDDING_MODEL (and _DIM) accordingly and re-index. |
| A Claude subscription | Use Vault mode for chat — it runs entirely on a local Ollama model (VAULT_SEARCH_CHAT_MODEL), zero cost. Set CLAUDE_CMD= empty to disable Claude-backed modes. |
| An always-on machine | Just start api_server.py when you open Obsidian (or wrap it in a login item / systemd --user / Task Scheduler entry). Search only needs the server while you're using it. |
| A second device to reach it | Run the server on a home PC or NAS, set the plugin's API Server URL to that host, and set an API key (VAULT_API_KEY env on the server + matching key in plugin settings) so only you can query it. |
| Want to avoid running a server at all | Use just the MCP server with Claude Code from the terminal — no FastAPI process, no plugin. |
Want a cloud embedder instead of Ollama? The embedding call is isolated in indexer.py / api_server.py (client.embed(...)); swap it for any provider and keep the same LanceDB pipeline.
Security notes
- The API server supports an API key (
X-API-Key). SetVAULT_API_KEY(env) or~/.vault-search/api_key.txton the server and the matching key in plugin settings. Always set one if the server is reachable beyondlocalhost. .env,plugin/data.json, and the whole~/.vault-search/data dir are git-ignored. Never commit secrets or your index.- Hybrid/Free chat runs the
claudeCLI as a subprocess with a deny-list of tools (no file write, no shell) — it can only read the context you pass it and call read-only literature tools.
Repository layout
server/ indexer · scoring · ppr · api_server · mcp_server (core three-piece set)
mcp_thin.py (low-memory MCP client)
vault_indexer_v2 · vault_v3 (parent-child vault index, v3 profile)
corpus.py (check / add a corpus folder)
textbook_indexer_v3 · chunker_v3 · provenance · table_units (corpus indexer)
books_meta · lecture_meta · scope · reranker · reranker_service (search-side helpers)
textbook_indexer.py (older corpus indexer, kept for v2 DBs)
graph_builder (optional knowledge graph)
config.py (all settings, env-driven)
tests/ (pytest; no index, Ollama or GPU needed)
plugin/ main.js · manifest.json · styles.css (Obsidian plugin)
eval/ retrieval / answer evaluation harness
examples/ books · scope_map · source_boost examples; corpus-fixture/ (t2n-corpus/1 samples)
docs/ corpus-format.md (corpus folder spec) · images/ (screenshots)
env.example copy to .env
The Obsidian plugin carries its own version number in plugin/manifest.json, and it moves only when something inside plugin/ changes — it is deliberately not kept in step with the repository tag. Obsidian offers users an update whenever that number rises, so matching it to a server-only release would advertise an update that changes nothing on their side.
Author
Built by Dr. P.W. Chen — a physician who needed to actually find things in his own notes.
🌐 drpwchen.com · 🐙 github.com/drpwchen
License
MIT — see LICENSE. Contributions and issues welcome.
🧋 Support
If this tool saves you time, you can buy me a boba — it keeps the servers running 🧡
🌱 Start here if you're new to AI agents / AI agent 新手起點
This tool is one piece of my personal AI workflow. If you want to learn how to use AI agents like Claude Code from zero (no programming background needed), I wrote a beginner series (in Traditional Chinese):
這個工具是我個人 AI 工作流的一部分。想從零開始學怎麼用 Claude Code 這類 AI agent(不需要程式背景),可以從我的入門系列開始:
Full map of my tools and posts / 所有工具與文章的全貌 → drpwchen.com/map
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi


