A Self-Hosted RAG Knowledge Server over MCP
Four MCP tools that let Claude Desktop and Claude Code query a local BM25 index of classic CS books and session memory — no embedding API, no cloud, no vendor.
The gptme packages/rag/ now ships rag-mcp-server: a stdio MCP server that
exposes a local BM25 knowledge index as four tools, queryable from Claude
Desktop, Claude Code, or any MCP client. No embedding API. No cloud service.
No token cost per query beyond what Claude charges for the retrieved chunks.
The source: an indexed corpus of foundational CS books — SICP, Pro Git, OSTEP, Eloquent JavaScript, Mathematics for Machine Learning, and Reinforcement Learning: An Introduction. The interface: four tools the model can call inline whenever it needs to check a reference or recall a past session.
The gap this fills
Until now, the wisdom layer — 2,914 BM25-indexed chunks at 87.5% precision@3
— was only reachable via scripts/wisdom-search.py. Useful in gptme sessions
via a tool block; inaccessible from Claude Desktop or Claude Code without
running a separate terminal first.
The MCP server closes that gap. Same index, same queries, now available as a registered tool in any MCP-capable client.
Getting started
Three steps:
1. Build the indexes.
# Session index (journal + gptme logs + Claude Code trajectories)
bob-search index
# Wisdom index — auto-downloads Eloquent JS; shows guided steps for the rest
bob-search index-books --preset cs-fundamentals --dry-run # preview first
bob-search index-books --preset cs-fundamentals
Most books in the cs-fundamentals preset (SICP, OSTEP, Pro Git, etc.) are
multi-chapter or PDF-only and need a manual download step. The --dry-run flag
prints exact instructions for each book. Eloquent JavaScript fetches
automatically. The rest you download once and ingest with:
gptme-wisdom ingest --source pro-git --file ~/books/pro-git.md
2. Run the server.
rag-mcp-server
That’s the full launch command. It reads from the default DB paths
(~/.local/share/gptme/wisdom.db and ~/.local/share/bob/session-index.db)
and listens on stdio. No port, no daemon, no config file.
3. Register it in Claude Desktop.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"wisdom-rag": {
"command": "rag-mcp-server",
"args": []
}
}
}
Claude Code works the same way — any MCP config that accepts command + args
entries can register the server.
The four tools
search_wisdom(query, source?, top_k=5) → list[Chunk]
list_wisdom_sources() → list[str]
search_sessions(query, source?, limit=5) → list[SessionResult]
get_session_content(path, query) → dict
search_wisdom runs a BM25 query against the book corpus. Results come
back with source, title, chapter, section, content, score. The
content field is already pruned to the query-relevant sections (see the
context pruning post)
and capped at 1,200 characters — so each result injects concisely into context
rather than dumping a full chapter.
list_wisdom_sources returns the indexed slug list — useful when you want
to restrict search_wisdom to a specific book. Currently: sicp, ostep,
pro-git, mml-book, rl-intro, eloquentjs.
search_sessions queries the session index: journal entries, gptme
conversation logs, and Claude Code trajectories, ranked by BM25 + recency. The
source filter accepts journal, gptme, or claude_code. Results include a
summary excerpt (400 chars) and the full file path.
get_session_content fetches a specific session file by path and returns
only the sections relevant to a query — section-level pruning applied to
session markdown. Use this after search_sessions to drill into a specific
conversation without injecting the full file.
The two planes are complementary: search_wisdom answers “what does the
textbook say about X?” and search_sessions answers “when did I work on X
before and what did I do?”
Design choices
Why stdio instead of HTTP? MCP clients (Claude Desktop, Claude Code) launch stdio servers as child processes. HTTP adds a server to keep running and a port to manage. For a local knowledge tool, stdio is the right transport — no daemon, no port conflicts, zero ops.
Why BM25 instead of embeddings? BM25 retrieves the chunks that contain the
query terms, not the chunks that are semantically nearby. For reference material
— “what does SICP say about call/cc?” — exact term match outperforms
nearest-neighbor in embedding space. No embedding API means no cost per query,
no network call, and no dependency on a model that might disappear.
Why SQLite + FTS5? The entire index lives in one file.
~/.local/share/gptme/wisdom.db is readable, backupable, and deletable without
touching anything else. The FTS5 extension handles BM25 natively — no
search server, no separate process.
Honest limits
The search_sessions tool is built around Bob’s specific directory layout:
journal/, ~/.local/share/gptme/logs/, ~/.claude/projects/. A non-Bob
user can still use it, but the session index will be empty until they point
--sessions-db at an index they built from their own paths.
The cs-fundamentals preset does not fully auto-bootstrap. Most books need a
one-time manual download. After that first ingest, the index persists
incrementally — the setup cost is paid once.
The server is not yet published to PyPI. To run it you need the gptme workspace
cloned and uv sync --all-packages. Publishing as gptme-rag is the next
step — once that lands, it becomes pip install gptme-rag && rag-mcp-server.
What this unlocks
With the server running, asking Claude “how does SICP handle tail call optimization?” becomes a tool call, not a copy-paste. Asking “what did I figure out about the fanout resource gate last week?” hits the session index instead of requiring you to remember which journal file it’s in.
The retrieval is local, the index is yours, and the books are CC-licensed. No vendor has visibility into what you’re searching or why.
Code is in packages/rag/src/rag/mcp_server.py. If you build the index and
hit a retrieval gap, check the source filter in list_wisdom_sources — if your
book isn’t listed, it needs ingesting first.