本地RAG知识检索工具

local-rag 是一款运行在 macOS 菜单栏的本地 RAG 个人知识检索工具,将 Obsidian、邮件、电子书、RSS、代码仓库和项目文档索引到 SQLite,通过向量+全文混合搜索与关系图谱扩展结果,并提供内置 MCP 服务器供 Claude 等客户端在本地隐私环境中查询知识库。

  • by sebastianhutter
  • Go
  • 混合部署
  • 11 星标
  • 更新于
在 GitHub 查看 sebastianhutter/local-rag

使用说明

local-rag

A fully local, privacy-preserving RAG (Retrieval Augmented Generation) system for macOS. Indexes personal knowledge from multiple sources into a single SQLite database with hybrid vector + full-text search, plus a relation graph so a result can be expanded into what it is connected to — not only into what resembles it. Runs as a menu bar app with built-in MCP server so Claude Desktop and Claude Code can query your personal knowledge base directly.

Supported Sources

Source Collection Type What Gets Indexed
Obsidian system Vault files — .md, .pdf, .docx, .html, .txt, .epub
eM Client system Emails — subject, body, sender, recipients, date, folder
Calibre system Ebook metadata + content — EPUB/PDF with author, tags, series
NetNewsWire system RSS articles — title, author, content, feed name
Code Repos code Git repos — paths can be direct repos or parent dirs (auto-discovered recursively). Tree-sitter AST split-then-merge (cAST) chunking + commit history
Project Docs project Any folder of documents dispatched to the correct parser by extension

Installation

From GitHub Releases

Download the latest .dmg from Releases, open it, and drag local-rag to Applications.

Since the app is not code-signed, bypass Gatekeeper on first launch:

xattr -cr /Applications/local-rag.app

Build from Source

# Prerequisites
brew install ollama go
ollama pull bge-m3

# Optional: OCR support for scanned PDFs
brew install tesseract tesseract-lang

# Build
git clone https://github.com/sebastianhutter/local-rag.git
cd local-rag
make build            # binary at bin/local-rag
make app              # macOS .app bundle at bin/local-rag.app
make dmg              # DMG installer at bin/local-rag.dmg

Requires Go 1.26+, CGO enabled (for SQLite), and macOS (for sips/iconutil/hdiutil). Tesseract is optional — only needed for OCR on scanned/image-only PDFs.

Quick Start

  1. Configure sources — edit ~/.local-rag/config.json (see configs/config.example.json for a fully annotated template) or use the Settings GUI:
{
  "obsidian_vaults": ["~/Documents/MyVault"],
  "calibre_libraries": ["~/CalibreLibrary"],
  "repositories": {
    "my-org": ["~/Repository/my-org"]
  },
  "projects": {
    "client-docs": ["~/Documents/client-project/specs"]
  }
}
  1. Index your content:
local-rag index obsidian
local-rag index email
local-rag index calibre
local-rag index rss
local-rag index code my-org --history
local-rag index all                        # everything at once
  1. Search:
local-rag search "kubernetes deployment strategy"
local-rag search "invoice from supplier" --collection email
local-rag search "API specification" --type pdf --top 20
local-rag search "s3 bucket policy" --collection code --path infrastructure/modules
  1. Prune — indexing file-backed collections (obsidian, code, project) removes stale entries automatically, so deleted files disappear from search without extra steps. To prune on demand:
local-rag prune                 # all collections, including email/calibre/rss
local-rag prune obsidian        # a single collection

GUI

Launch local-rag with no arguments (or local-rag gui) to start the menu bar app. There is no Dock icon — it lives entirely in the macOS menu bar.

Menu bar features:

  • MCP server toggle — start/stop the built-in MCP server (Streamable HTTP, default port 31123)
  • Status display — collection and chunk counts, indexing progress
  • Index menu — trigger indexing for individual collections or all at once
  • Settings — configure sources, embedding model, Ollama hosts (ordered endpoints; first reachable one that has the model is used, else local) and embedding batch size, search weights, MCP port, auto-reindex interval, start-on-login
  • Log viewer — live scrolling log output with auto-scroll toggle
  • Auto-reindex — periodically re-index all sources on a configurable interval
  • macOS notifications — notifies when indexing completes or errors occur

MCP Integration

local-rag exposes 5 MCP tools: rag_search, rag_list_collections, rag_collection_info, rag_index, and rag_prune.

The rag_search tool supports a metadata_filter parameter — a JSON object of key-value pairs for filtering by arbitrary metadata fields (e.g. {"source": "jira", "issue_key": "PROJ-123"}) — and a path parameter to scope results to a subfolder or repo by a case-insensitive substring of the source path (e.g. "infrastructure/modules"). Its collection parameter accepts either a collection name or a collection type (system, project, code), so a search can be scoped to all code repos at once.

GUI Mode (Streamable HTTP)

When running as a menu bar app, the MCP server uses the Streamable HTTP transport on http://127.0.0.1:31123/mcp. The older SSE transport (/sse) was removed: it is deprecated in the MCP spec, and clients that speak only Streamable HTTP get a 405 from it. Requests carrying a non-localhost Origin header are rejected, so a web page in your browser cannot query the server.

Claude Code — add to your project's .mcp.json:

{
  "mcpServers": {
    "local-rag": {
      "type": "http",
      "url": "http://127.0.0.1:31123/mcp"
    }
  }
}

Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "local-rag": {
      "command": "/path/to/local-rag",
      "args": ["serve"]
    }
  }
}

Standalone Mode (stdio)

For use as a subprocess without the GUI:

{
  "mcpServers": {
    "local-rag": {
      "command": "/path/to/local-rag",
      "args": ["serve"]
    }
  }
}

CLI Reference

Indexing

local-rag index obsidian [--vault/-V PATH]...   Index Obsidian vaults
local-rag index email                            Index eM Client emails
local-rag index calibre [--library/-l PATH]...   Index Calibre ebook libraries
local-rag index rss                              Index NetNewsWire RSS articles
local-rag index code [NAME] [--history]          Index code repositories; omit NAME for all
local-rag index project [NAME]                   Index project(s) from config; omit NAME for all
local-rag index all                              Index all configured sources

All index commands accept --force to re-index everything regardless of change detection, --no-prune to skip the automatic pruning pass, and --no-graph to skip the relation-graph rebuild that otherwise runs afterwards.

Indexing obsidian, code, project and all prunes stale sources first — entries whose file no longer exists on disk are removed before the run, so deleted and moved files do not linger in search results.

Pruning

local-rag prune [COLLECTION]     Remove sources whose originals are gone; omit COLLECTION for all
local-rag prune --vectors        Remove orphaned embeddings (vectors with no surviving document)

Pruning covers deleted files, emails removed from eM Client, articles purged from NetNewsWire, books removed from Calibre, and files deleted from repositories. -y skips confirmation.

Searching

local-rag search QUERY [flags]

Flags:
  -c, --collection STRING   Filter by collection name
      --type STRING         Filter by source type (markdown, pdf, email, code, ...)
      --path STRING         Filter by source path (case-insensitive substring, e.g. a subfolder or repo)
      --from STRING         Filter by email sender
      --author STRING       Filter by book author
      --after YYYY-MM-DD    Only results after this date
      --before YYYY-MM-DD   Only results before this date
  -m, --meta KEY=VALUE      Filter by metadata field (repeatable)
      --top INT             Number of results (default: 10)

Collections

local-rag collections list                       List all collections with counts
local-rag collections info NAME                  Show collection details
local-rag collections delete NAME [-y]           Delete a collection and all its data
local-rag collections export NAME                Export collection metadata as JSON
local-rag collections paths list NAME            List configured paths for a collection
local-rag collections paths add NAME PATH...     Add paths to a collection in config
local-rag collections paths remove NAME PATH...  Remove paths from a collection in config
local-rag collections paths update NAME \        Rewrite path prefixes in-place
  --old-prefix OLD --new-prefix NEW              (config paths + source paths in DB)

All collection paths are stored in config.json. The paths commands work for all collection types — obsidian vaults, calibre libraries, repositories, and projects.

If you move files to a new location, use paths update to rewrite all paths without re-indexing:

local-rag collections paths update "Project Alpha" \
  --old-prefix ~/docs/specs --new-prefix ~/new-location/specs

Relation Graph

local-rag graph rebuild                  Derive relations from indexed content
local-rag graph rebuild --origin NAME    Rebuild one origin, leaving the others alone
local-rag graph stats                    Edge counts by origin and relation, plus top hubs
local-rag neighbors SOURCE               What a source is connected to (id or path substring)

neighbors accepts --rel, --origin, --hops (1 or 2), --hub-cap and --top.

The graph records relations parsed from the content — Obsidian wikilinks and frontmatter properties, Confluence page hierarchy, Jira issue hierarchy, Terraform module dependencies, and mentions of a ticket key across mail, notes and commits. It exists to close a gap search cannot: a note that never uses your query's wording but links to one that does is unreachable at any top_k, and one hop away here. Replaying 60 real queries, 78% gained at least one document that was unreachable at top_k 60.

Nothing calls a model — a rebuild is a scan of data already in the database, about a minute for ~28,000 edges — and it runs automatically after each index. Agents use it through the rag_neighbors MCP tool.

See docs/relation-graph.md for the design, the measurements, and why hub suppression is a precondition rather than a refinement.

Other

local-rag status            Database stats, collection counts, Ollama status
local-rag graph rebuild     Rebuild the relation graph (see above)
local-rag prune [NAME]      Remove stale sources (see Pruning above)
local-rag serve [--port N]  Start MCP server (stdio, or Streamable HTTP at /mcp on given port)
local-rag gui               Start menu bar app (default when no subcommand)
local-rag --version         Print version
local-rag -v, --verbose     Enable debug logging (global flag)

Configuration

Config file: ~/.local-rag/config.json

Key Default Description
db_path ~/.local-rag/rag.db SQLite database location
embedding_model bge-m3 Ollama embedding model
embedding_dimensions 1024 Embedding vector dimensions
embedding_hosts (unset → localhost) Ordered Ollama hosts; first reachable one that has the model is used (else local). OLLAMA_HOST env overrides.
embedding_batch_size 32 Texts per Ollama embedding request; higher (e.g. 128) improves GPU throughput.
embedding_workers 4 Embedding requests in flight at once; raise for a fast remote host, 1 to serialise.
embedding_num_batch 0 (server default) Ollama num_batch option. Raise to the model context (e.g. 8192) if the log shows inputs rejected as too large to process.
chunk_size_tokens 500 Chunk size in whitespace-separated words
chunk_overlap_tokens 50 Overlap between chunks
obsidian_vaults [] Paths to Obsidian vaults
obsidian_exclude_folders [] Folders to skip in vaults
emclient_db_path ~/Library/Application Support/eM Client eM Client database path
calibre_libraries [] Paths to Calibre libraries
netnewswire_db_path (auto-detected) NetNewsWire database path
repositories {} Map of collection name to repo/directory paths (directories are scanned recursively for git repos)
projects {} Map of project name to document paths
disabled_collections [] Collection names to skip during indexing
skip_cloud_placeholders true Skip cloud-only files (OneDrive/iCloud/Google/Synology on-demand placeholders) instead of downloading them
git_history_in_months 6 How far back to index commit history
git_commit_subject_blacklist [] Skip commits whose subject starts with any of these strings (prefix match)
search_defaults.top_k 10 Default number of search results
search_defaults.rrf_k 60 Reciprocal Rank Fusion parameter
search_defaults.vector_weight 0.7 Weight for vector similarity
search_defaults.fts_weight 0.3 Weight for full-text search
ocr.enabled false Enable OCR fallback for scanned PDFs
ocr.languages ["eng"] Tesseract languages (e.g. ["eng","deu"])
ocr.max_pages 50 Skip OCR if PDF exceeds this page count
ocr.max_file_size_mb 100 Skip OCR if file exceeds this size
ocr.min_word_count 10 OCR pages with fewer extracted words
gui.auto_start_mcp true Start MCP server when GUI launches
gui.mcp_port 31123 MCP HTTP server port
gui.auto_reindex false Enable periodic re-indexing
gui.auto_reindex_interval_minutes 60 Minutes between auto-reindex runs
gui.start_on_login false Register as login item via launchd

Tech Stack

Component Choice Notes
Language Go 1.26+ CGO required for SQLite
Database SQLite + sqlite-vec + FTS5 Single file, no server
Embeddings Ollama + bge-m3 (1024d) Fully local, no API keys
GUI Fyne v2 + systray macOS menu bar app
MCP mcp-go Streamable HTTP and stdio transports
PDF go-pdfium (WASM/Wazero) No CGO needed for PDF
PDF OCR tesseract (optional) Fallback for scanned/image-only PDFs
DOCX archive/zip + encoding/xml Word document extraction (.docx, .dotx)
Code parsing go-tree-sitter 13 languages; AST split-then-merge (cAST) chunking
CLI Cobra Subcommands, flags, help
HTML cleanup golang.org/x/net/html Strip tags from email/RSS

Tree-sitter Languages

Structural splitting (functions, classes, methods): Python, Go, HCL/Terraform, TypeScript, TSX, JavaScript, Rust, Java, C, C++, C#, Ruby, Bash.

Full-file parsing: YAML, TOML, SQL, HTML, CSS, Dockerfile, Markdown.

Plaintext fallback: JSON, TXT, CSV, RST, XML, SCSS, Makefile.

Building & Development

make build       # Build binary to bin/local-rag
make test        # Run tests (requires -tags sqlite_fts5)
make test-v      # Verbose tests
make lint        # golangci-lint
make tidy        # go mod tidy
make app         # Build macOS .app bundle
make dmg         # Build DMG installer
make clean       # Remove bin/

Pass VERSION to inject version at build time:

make build VERSION=1.2.3
bin/local-rag --version    # local-rag version 1.2.3

Architecture

cmd/local-rag/       CLI entry point (Cobra) — one cmd_*.go per command group
internal/
  config/            Configuration loading and defaults
  db/                SQLite + sqlite-vec + FTS5 setup and migrations
  embeddings/        Ollama embedding client and host resolution
  chunker/           Text chunking strategies
  search/            Hybrid search engine (vector + FTS + RRF)
  parser/            File parsers (markdown, pdf, docx, epub, html, code, ...)
  indexer/           Source indexers, shared batching, pruning
  mcp/               MCP server (tools, Streamable HTTP, stdio)
  gui/               Fyne menu bar app, settings, log viewer
configs/
  config.example.json  Annotated configuration template
docs/                Architecture, hybrid search/RRF, relation graph, Ollama, eM Client schema
scripts/
  build-app.sh       Create macOS .app bundle
  build-dmg.sh       Create DMG installer
.github/workflows/
  release.yml        Tagged release build

Deeper notes live in docs/: architecture, hybrid search and RRF, the relation graph, Ollama and embeddings, eM Client schema.

License

This project is licensed under the MIT License.

All dependencies use permissive licenses (MIT, BSD, Apache 2.0). PDF parsing uses go-pdfium (MIT) with the PDFium library running in a WASM sandbox (BSD 3-Clause).