local-rag

local-rag

Enables read-only semantic search over a local document corpus with on-device embeddings and a local Chroma store, featuring symlink-hardened file access and structured error handling.

Category
访问服务器

README

local-rag-mcp

ci

A read-only MCP server for semantic search over a local document corpus — on-device embeddings (Ollama), a local Chroma store, nothing leaves the host. Built for environments where the corpus content cannot go to a cloud API, and served identically to every MCP client (Claude Code, Codex, anything speaking the protocol).

This is the MCP-served sibling of claude-code-session-memory: same embedding model, same instruction-prefix regime, same measurement methodology — one retrieval substrate, two consumers. The session-memory README carries the full eval story (pre-committed bars, adversarial query sets, regression attribution); this repo applies the same discipline to a server instead of a hook.

Tools

Tool What it does
search_corpus(query, k=4) Semantic search: up to k chunks with source path, heading path, cosine score, text
get_file(path) Text of an indexed document (capped at 50 k chars) — deliberately not a general filesystem reader

Both are annotated read-only. Failures return structured {"error": ...} payloads — a down dependency degrades the tool, never the session.

Quickstart

git clone https://github.com/wesglockzin/local-rag-mcp
cd local-rag-mcp
python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
ollama pull embeddinggemma

# Index the included sample corpus (or point RAG_CORPUS_DIR at your own)
./.venv/bin/python ingest.py

# Register with Claude Code — ABSOLUTE paths on both sides: the MCP client
# launches the server from its own working directory, so relative paths are
# the #1 install failure.
claude mcp add local-rag -- "$PWD/.venv/bin/python" "$PWD/server.py"

Then ask Claude Code something the corpus knows — "who gets paged for a sev-1?" — and watch it call search_corpus.

Configuration is three environment variables: RAG_CORPUS_DIR (default: ./sample-corpus), RAG_STORE_DIR (default: ~/.local-rag-mcp/store), OLLAMA_HOST.

Design decisions that earn their keep

  • The server is read-only and never creates stores. Ingestion owns creation. A read-only server that quietly initializes an empty store turns "you forgot to ingest" into "search returns nothing" — the worse failure, because it looks like an answer.
  • Embed-then-swap ingest. A file's old chunks are deleted only after every new chunk embedded successfully; an Ollama failure mid-file never leaves that file missing from the index.
  • Retired documents are pre-filtered, not post-filtered. A document with lifecycle: superseded in its frontmatter is excluded by a where clause before the vector search, so it never occupies a result slot. Ingest writes the lifecycle key explicitly on every chunk — on some versions of the store a missing key slips through $ne, so absence is not a safe default. (The original of this rule exists because a re-ingest once silently erased the marker and a retired document resurfaced in results; a regression test now pins it.)
  • get_file is symlink-hardened. Only indexed paths are readable, and a path that resolves somewhere different than it did at ingest time is refused — otherwise anyone who can swap a corpus file for a symlink reads outside the corpus through the server. If the file is absent on disk (moved corpus, different machine), the indexed chunk text is served instead, in chunk order.
  • The store is machine-local, always. It's a live SQLite-backed database; cloud sync does whole-file replacement with no transactional awareness, and the failure mode is a silently corrupted index on the machine that didn't write it. Sync the corpus and this recipe; every machine builds its own store.
  • Every ingest stamps the corpus git commit into its output, so an index build can be pinned to exactly the corpus state that produced it ("uncommitted changes present" is itself a warning label).
  • Asymmetric embedding prefixes (EmbeddingGemma's documented query/doc instruction prefixes) on both sides of the search, matching the companion project's measured regime — prefixed beat raw retrieval by double digits there, and mixed prefixed/raw vectors score in an uncalibrated band.

Corpus conventions

Any directory of *.md files works. Three optional frontmatter keys:

rag: false            # exclude this file from the index entirely
rag_chunk: headings   # heading-split a long document (default: whole-file)
lifecycle: superseded # keep the file, hide it from search

The committed sample-corpus/ exercises all three plus a plain file — six fictional platform-team documents, generated by tools/gen_sample_corpus.py (CI verifies the committed corpus matches the generator).

Tests

pip install pytest && python -m pytest -q

No Ollama, no store: the embedder is stubbed and the collection is a fake that records calls. Under test are the contracts — argument validation, the lifecycle pre-filter reaching the store as a where clause, the read-only no-create guarantee, symlink refusal, embed-then-swap ordering (including the embedder-down path), the mtime tolerance skip, and the chunker's merge and oversize-split behavior.

Known limitations

  • Trust model: the server reads whatever corpus you point it at, and clients inject retrieved text into model context. Index only content you trust — a hostile document is a prompt-injection vector; the server retrieves, it doesn't sanitize. Stdio MCP has no authentication layer; it inherits the trust of the process that launched it.
  • Scores are comparable only within one embedding regime; a calibrated "weak match" floor is corpus-specific (the companion repo documents the calibration method).
  • One store, one collection — multi-corpus routing is out of scope here.
  • No hybrid keyword+vector stage; paraphrase headroom is measured and documented in the companion repo.

License

MIT — see LICENSE.

Author

Wes Glockzin

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选