内容
# micro-rag-mcp: 最小化本地 RAG MCP 服务器
最小化的本地检索增强生成(Retrieval Augmented Generation)索引和搜索 MCP 服务器,适用于文档文件夹。通过 uvx 设计支持多个实例,每个实例使用不同的数据文件夹。在每个数据文件夹下的索引目录中存储持久的 FAISS 索引。
## 目标
- 最小且优雅:一个单一的 stdio MCP 服务器进程,提供两个工具:搜索和重新索引
- 仅限本地:不进行网络调用;一旦模型下载完成,离线工作
- 快速启动:通过文件的 mtime 进行增量索引,并进行内容校验
- 低占用:使用 sentence-transformers all-MiniLM-L6-v2、FAISS IndexFlatIP、numpy
## 默认行为
- 嵌入:sentence-transformers all-MiniLM-L6-v2
- 文件类型:.txt, .md, .pdf, .docx
- 分块:1000 个字符,200 个重叠,段落感知
- 相似度:通过 L2 归一化向量和 FAISS IndexFlatIP 计算余弦相似度
- 索引位置:<data_folder>/index
- 启动:扫描文件并对索引应用增量更改
- 并发:使用简单文件锁 index.lock 来序列化重新索引操作
- 日志记录:人类可读日志输出到 stderr;工具结果为结构化格式
## 多实例支持
- 通过为每个进程指定不同的 `--title` 和 `--inst` 参数,可以运行多个独立的 MCP 服务器实例。
- `--title` 和 `--inst` 都是 **必需** 的。
- `--title` 用作 FastMCP 服务器名称。
- `--inst` 原样传递作为 FastMCP 指令(可以是句子、多行等)。
- 默认索引位置为所有实例的 `<data_folder>/index`,除非通过 `--index-folder` 进行覆盖。
### 示例 MCP 配置,包含两个实例
```json
{
"mcpServers": {
"team_alpha": {
"command": "uvx",
"args": [
"micro-rag-mcp@0.1.4",
"--data-folder", "/docs/teamA",
"--title", "Secret Islands",
"--inst", "Team Alpha read-only mode with nightly refresh"
]
},
"team_beta": {
"command": "uvx",
"args": [
"micro-rag-mcp@0.1.4",
"--data-folder", "/docs/teamB",
"--title", "Secret Islands",
"--inst", "Team Beta write-enabled with manual refresh"
]
}
}
}
```
- 要使用多行指令,请在字符串内使用 `\n`:
- `"--inst", "Team Alpha\nRead-only\nRefresh: 02:00 IST"`
- `args` 数组中的每个项都是一个单独的参数;空格和换行符作为字符串的一部分被保留。
## 最小架构
### 计划的源文件
- [src/micro_rag_mcp/server.py](src/micro_rag_mcp/server.py)
- [src/micro_rag_mcp/index.py](src/micro_rag_mcp/index.py)
- [src/micro_rag_mcp/embedder.py](src/micro_rag_mcp/embedder.py)
- [src/micro_rag_mcp/ingest.py](src/micro_rag_mcp/ingest.py)
- [src/micro_rag_mcp/types.py](src/micro_rag_mcp/types.py)
- [src/micro_rag_mcp/utils.py](src/micro_rag_mcp/utils.py)
- [src/micro_rag_mcp/__main__.py](src/micro_rag_mcp/__main__.py)
- [src/micro_rag_mcp/__init__.py](src/micro_rag_mcp/__init__.py)
### 数据流
```mermaid
flowchart LR
C[Client] --> S[Micro RAG MCP]
S --> FS[Data folder]
S --> E[Embedder]
S --> I[Index manager]
I --> FI[FAISS index]
I --> MF[Manifest jsonl]
```
### 启动序列
```mermaid
sequenceDiagram
participant C as Client
participant S as MCP server
participant I as Index manager
C->>S: connect via stdio
S->>I: open or create index
I->>I: scan data folder and apply incremental changes
I-->>S: ready with index
S-->>C: tools ready
```
## 存储布局在 <data_folder>/index 下
- faiss.index: 序列化的 FAISS 索引
- manifest.jsonl: 每个块一个 JSON 记录
- files.json: 每个文件的摘要和最后处理状态
- version: 架构版本字符串
- index.lock: 重新索引期间的锁文件
### manifest.jsonl 记录
```json
{
"id": 12345,
"path": "/abs/or/relative/path/to/file.md",
"ext": ".md",
"mtime": 1739650000.123,
"checksum": "sha256:ab...cd",
"chunk_index": 7,
"char_start": 4200,
"char_end": 5200,
"text": "chunk text...",
"embedding_dim": 384,
"deleted": false
}
```
### files.json 记录
```json
{
"path": "/abs/or/relative/path/to/file.md",
"mtime": 1739650000.123,
"checksum": "sha256:ab...cd",
"chunk_ids": [1201, 1202, 1203],
"ext": ".md"
}
```
## 增量重新索引算法
- 获取 index.lock;如果存在,则以明确的错误失败工具调用
- 将 files.json 和 manifest.jsonl 加载到内存映射中
- 遍历数据文件夹以查找允许的扩展名
- 对于每个文件
- 如果是新文件,进行摄取、分块、嵌入,添加向量和清单记录
- 如果 mtime 不同或 checksum 不同,删除旧块,添加新块
- 对于 files.json 中未在磁盘上存在的文件,标记其块为已删除,并可选择稍后回收 ID
- 持久化 FAISS、manifest.jsonl 附加、files.json
- 释放锁并返回计数
## 搜索算法
- 将查询编码为嵌入
- L2 归一化查询和索引向量
- 通过 FAISS 进行前 k 搜索
- 对于每个命中,获取清单记录,计算片段,返回结果
## MCP 工具
### 工具名称:search
- 参数
- query: 字符串,必需
- top_k: 整数,默认 5,最小 1,最大 50
- score_threshold: 浮点数,默认 0.2,范围 0..1
- 返回
- results: 数组
- path: 字符串
- score: 浮点数
- snippet: 字符串
- chunk_index: 整数
- char_start: 整数
- char_end: 整数
- mtime: 浮点数
- ext: 字符串
- 错误
- 如果索引缺失,服务器返回空列表并记录警告
### 工具名称:reindex
- 参数
- force: 布尔值,默认 false
- path_glob: 字符串,数据文件夹的可选 shell 模式
- 返回
- files_added: 整数
- files_updated: 整数
- files_removed: 整数
- chunks_total: 整数
- elapsed_ms: 整数
- 错误
- 如果索引正在进行中,返回忙碌错误
## 通过 uvx 的 CLI 使用
- 一次性运行
```bash
uvx micro-rag-mcp --data-folder /some/hr_docs --title "HR RAG" --inst "HR instance for confidential search"
```
- 覆盖默认值
```bash
uvx micro-rag-mcp --data-folder /some/docs --index-folder /some/docs/index --exts .txt,.md,.pdf,.docx --title "Docs RAG" --inst "Docs instance"
```
## 在上传到 PyPI 之前进行本地测试
在上传到 PyPI 之前,先在本地测试您的包,以确保它与 `uvx` 正常工作:
### 1. 开发安装
```bash
# 以开发模式安装以便快速迭代
uv pip install -e .
micro-rag-mcp --help
```
### 2. 本地 uvx 测试
```bash
# 使用本地包测试 uvx
uvx --from ./ micro-rag-mcp --help
# 创建测试数据并测试功能
mkdir -p test_data
echo "This is a test document about Python programming." > test_data/doc1.txt
echo "Another document about machine learning and AI." > test_data/doc2.md
# 使用真实数据测试
uvx --from ./ micro-rag-mcp --data-folder test_data
```
### 3. 本地构建和安装
```bash
# 构建包
uv build
# 安装构建的 wheel
uv pip install dist/micro_rag_mcp-0.1.0-py3-none-any.whl
# 使用已安装的包测试 uvx
uvx micro-rag-mcp --help
```
### 4. 清洁环境测试
```bash
# 在隔离的虚拟环境中测试
uv venv test_env
source test_env/bin/activate
uv pip install dist/micro_rag_mcp-0.1.0-py3-none-any.whl
micro-rag-mcp --help
deactivate
```
### 5. 包验证
```bash
# 检查包元数据
uv pip show micro-rag-mcp
# 验证入口点
uv pip show -f micro-rag-mcp | grep -A5 "Entry-points"
# 测试导入
python -c "import micro_rag_mcp; print('Import successful')"
```
## MCP 客户端配置示例
请参见上面的“多实例支持”部分,了解推荐的配置及所需的 `--title` 和 `--inst` 参数。
## 在 [pyproject.toml](pyproject.toml) 中声明的依赖项
- mcp
- sentence-transformers
- faiss-cpu
- numpy
- pypdf
- python-docx
- pydantic
- charset-normalizer
## 实施说明
- 使用来自 Python mcp 包的 stdio 传输
- 始终将人类可读日志写入 stderr,以避免污染工具响应
- 在添加到 IndexFlatIP 之前对嵌入进行归一化,以近似余弦
- 批量处理嵌入以管理内存;建议批量大小为 64
- 对于 pdf 提取,如果页面失败则回退到文本;跳过图像
- 对于 docx,用双换行符连接段落
- 对于文本文件使用 UTF-8 和错误替换
- 对归一化文本使用 SHA256 进行校验和
## 安全与隐私
- 仅在明确传递的本地文件夹上操作
- 运行时没有网络调用,除非首次使用时下载模型
- 没有个人身份信息(PII)离开机器
## 测试计划
- 创建一个包含混合文件类型的示例文件夹
- 运行 uvx micro-rag-mcp --data-folder sample
- 使用客户端的搜索工具验证结果和分数
- 修改文件并重新运行重新索引工具;确保更新反映出来
- 删除文件并重新运行重新索引;确保应用了墓碑
## 路线图
- 背景压缩以重建 faiss 并删除墓碑
- 可选的重新排序器以提高质量
- 额外的读取器支持 html 和 csv
- 使用 BM25 进行 markdown 标题的混合搜索
## 仓库布局目标
- [README.md](README.md)
- [pyproject.toml](pyproject.toml)
- [src/micro_rag_mcp/__main__.py](src/micro_rag_mcp/__main__.py)
- [src/micro_rag_mcp/server.py](src/micro_rag_mcp/server.py)
- [src/micro_rag_mcp/index.py](src/micro_rag_mcp/index.py)
- [src/micro_rag_mcp/ingest.py](src/micro_rag_mcp/ingest.py)
- [src/micro_rag_mcp/embedder.py](src/micro_rag_mcp/embedder.py)
- [src/micro_rag_mcp/types.py](src/micro_rag_mcp/types.py)
- [src/micro_rag_mcp/utils.py](src/micro_rag_mcp/utils.py)
## 批准
- 如果此设计符合预期,下一步是更新依赖项、搭建文件和实现这两个工具。请参见此工作区中的待办事项列表。
## 首次运行和模型初始化行为
- 在服务器启动时,sentence-transformer 模型在后台下载。
- 模型准备好后,会自动触发后台初始重新索引(首次运行时无需手动调用重新索引)。
- 如果在模型设置之前或期间调用搜索,它将等待最多 5 秒以使模型准备就绪。如果模型在 5 秒内未准备好,搜索将返回一个空结果集(没有错误)。
- 一旦模型准备就绪且后台重新索引完成,搜索将按预期返回结果。
- 这确保了搜索不会失败或无限期阻塞,即使在服务器启动后立即调用。