内容
# mcp-server-hybrid-search
[](https://github.com/wonder-soft/mcp-server-hybrid-search/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
MCP 服务器,用于混合文档搜索(Qdrant 向量搜索 + Tantivy BM25),使用 SSE 传输。
## 架构
- **MCP 服务器** (`mcp-server-hybrid-search`): 基于 SSE 的 MCP 服务器,监听 7070 端口,提供 `search` 和 `get` 工具
- **CLI** (`ragctl`): 文档索引器,将 md/txt/pdf/xlsx/docx 文件摄入 Qdrant 和 Tantivy
- **Qdrant**: 用于语义搜索的向量数据库
- **Tantivy**: 用于 BM25 排名的全文本搜索引擎
## 前置条件
- Rust 工具链(1.75+)
- Docker(用于 Qdrant)
- OpenAI API 密钥(用于嵌入式,不需要 `--features local-embed`)
- Python + `markitdown`(用于 PDF/Excel/Word 支持):`pip install markitdown`
## 快速开始
### 1. 启动 Qdrant
```bash
docker compose up -d
```
### 2. 设置环境
```bash
cp .env.example .env
# 编辑 .env 并设置您的 OPENAI_API_KEY
```
### 3. 构建
```bash
# 默认构建(OpenAI 嵌入式,基于空格的 BM25 分词器)
cargo build --release
# 使用日语分词器进行 BM25 全文本搜索
cargo build --release --features ja
# 使用本地嵌入式(不需要 OpenAI API 密钥)
cargo build --release --features local-embed
# 按需组合功能
cargo build --release --features "ja,local-embed"
```
### 4. 初始化
创建默认源目录和数据目录:
```bash
./target/release/ragctl init
```
这会创建:
- `~/.local/share/mcp-hybrid-search/` — 默认文档源目录
- `~/.mcp-hybrid-search/tantivy/` — Tantivy 索引目录
### 5. 放置文档
将您的文档复制或符号链接到默认源目录:
```bash
cp ~/my-docs/*.md ~/.local/share/mcp-hybrid-search/
cp ~/reports/*.pdf ~/.local/share/mcp-hybrid-search/
cp ~/data/*.xlsx ~/.local/share/mcp-hybrid-search/
```
### 6. 摄入文档
```bash
# 使用默认源目录(~/.local/share/mcp-hybrid-search)
./target/release/ragctl ingest
# 或显式指定源目录
./target/release/ragctl ingest \
--source /path/to/your/docs \
--source /path/to/more/docs
```
### 7. 启动 MCP 服务器
```bash
./target/release/mcp-server-hybrid-search
```
服务器将监听 `http://localhost:7070`。
> **注意:** 当使用 `embedding_provider = "openai"`(默认)时,服务器在运行时需要 `OPENAI_API_KEY`,因为每个搜索查询都通过 OpenAI API 转换为嵌入式向量。确保 `.env` 文件存在于工作目录中,或在启动服务器之前设置环境变量。
### 8. 从 Claude Code 连接
在您的 Claude Code MCP 配置中添加:
```json
{
"mcpServers": {
"hybrid-search": {
"url": "http://localhost:7070/sse"
}
}
}
```
## CLI 用法
### 初始化目录
```bash
ragctl init
```
创建默认源目录(`~/.local/share/mcp-hybrid-search/`)和 Tantivy 索引目录。在首次使用之前运行此命令。
### 摄入文档
```bash
# 默认源目录
ragctl ingest
# 自定义源目录
ragctl ingest \
--source /path/to/docs \
--source /path/to/converted \
--qdrant http://localhost:6334 \
--index-dir ~/.mcp-hybrid-search/tantivy \
--chunk-size 1000 \
--chunk-overlap 200
```
支持的文件类型:
- **直接**: `.md`, `.txt`
- **通过 markitdown**: `.pdf`, `.xlsx`, `.xls`, `.docx`, `.pptx`, `.csv`, `.html`
### 检查状态
```bash
ragctl status
```
### 导出数据
将所有索引块(带有嵌入式)导出到 JSON 文件中,以便与其他工程师共享:
```bash
ragctl export --output ./exported-data.json
```
导出的文件包含所有块有效载荷及其嵌入式向量。其他工程师可以在不需要 OpenAI API 密钥的情况下导入此数据。
### 导入数据
将预先导出的数据导入 Qdrant 和 Tantivy:
```bash
ragctl import --input ./exported-data.json
```
这将同时填充 Qdrant(向量)和 Tantivy(BM25 索引)。
### 搜索(调试)
```bash
ragctl search --query "your search query" --top-k 10
```
## 多项目支持
使用 `--project` 标志隔离每个项目的集合。指定后,Qdrant 集合名称和 Tantivy 索引目录将被覆盖:
```bash
# 摄入到项目 "my-proj"
ragctl --project my-proj ingest --source /path/to/docs
# 检查项目 "my-proj" 的状态
ragctl --project my-proj status
# 启动项目 "my-proj" 的 MCP 服务器
mcp-server-hybrid-search --project my-proj
```
当指定 `--project my-proj` 时:
- Qdrant 集合名称 → `"my-proj"`
- Tantivy 索引目录 → `~/.mcp-hybrid-search/tantivy/my-proj/`
如果不使用 `--project`,则使用 `config.toml` 中的默认值(向后兼容)。
### 列出项目
```bash
ragctl list-projects
```
列出所有 Qdrant 集合及其点数。
## MCP 工具
### 搜索
使用向量相似性 + BM25 排名与 RRF 融合,在索引文档中进行混合搜索。
**输入:**
- `query`(字符串,必填):搜索查询
- `top_k`(数字,可选):结果数量(默认:10)
- `filters`(对象,可选):
- `source_type`(字符串):按文件类型过滤(md/txt/pdf/xlsx)
- `path_prefix`(字符串):按路径前缀过滤
### 获取
检索文档块的完整内容。
**输入:**
- `chunk_id`(字符串,必填):块标识符
### 获取项目信息
获取有关当前项目配置和索引状态的信息。
**输入:** 无需
**输出:** JSON 对象,包含:
- `collection_name`(字符串):当前 Qdrant 集合名称
- `document_count`(数字):索引文档块数量
- `tantivy_index_dir`(字符串):Tantivy 索引目录路径
- `embedding_provider`(字符串):嵌入式提供商名称
- `embedding_model`(字符串):嵌入式模型名称
- `embedding_dimension`(数字):嵌入式向量维度
## 配置
编辑 `config.toml`:
| 键 | 默认值 | 描述 |
| --- | --- | --- |
| `qdrant_url` | `http://localhost:6334` | Qdrant gRPC URL |
| `collection_name` | `docs` | Qdrant 集合名称 |
| `tantivy_index_dir` | `~/.mcp-hybrid-search/tantivy` | Tantivy 索引目录 |
| `chunk_size` | `1000` | 块大小(字符) |
| `chunk_overlap` | `200` | 块重叠(字符) |
| `listen_port` | `7070` | MCP 服务器端口 |
| `embedding_provider` | `openai` | 嵌入式提供商(见下文) |
| `embedding_model` | `text-embedding-3-small` | OpenAI 嵌入式模型 |
| `embedding_dimension` | `1536` | 嵌入式向量维度 |
| `tokenizer` | `default` | BM25 分词器(见下文) |
默认源目录:`~/.local/share/mcp-hybrid-search/`
### 分词器
`tokenizer` 配置控制 Tantivy 如何拆分文本以进行 BM25 全文本搜索。默认分词器基于空格,适用于英语,但不适用于 CJK 语言(日语、韩语、中文),因为这些语言的单词之间没有空格。
| 值 | 功能标志 | 词典 |
| --- | --- | --- |
| `default` | *(无)* | 基于空格(内置) |
| `japanese` | `--features ja` | IPADIC |
| `korean` | `--features ko` | ko-dic |
| `chinese` | `--features zh` | CC-CEDICT |
语言词典通过 [Lindera](https://github.com/lindera/lindera) 在构建时嵌入到二进制文件中。只需启用您需要的功能 — 每个功能都会为二进制文件添加约 50MB。
> **注意:** 更改分词器需要重建 Tantivy 索引。在切换分词器后运行 `ragctl reset` 然后运行 `ragctl ingest`。
### 嵌入式提供商
| 提供商 | 功能标志 | 模型 | 维度 | 是否需要 API 密钥 |
| --- | --- | --- | --- | --- |
| `openai` | *(无)* | `text-embedding-3-small` | 1536 | 是(`OPENAI_API_KEY`) |
| `local` | `--features local-embed` | `intfloat/multilingual-e5-base` | 768 | 否 |
| `local` | `--features local-embed` | `intfloat/multilingual-e5-small` | 384 | 否 |
本地提供商使用 [fastembed](https://github.com/Anush008/fastembed-rs) 和 ONNX Runtime。模型在首次使用时会自动下载并缓存。
要使用本地嵌入式:
```toml
# config.toml — 多语言-e5-base(推荐)
embedding_provider = "local"
embedding_model = "multilingual-e5-base"
embedding_dimension = 768
```
```toml
# config.toml — 多语言-e5-small(更轻、更快)
embedding_provider = "local"
embedding_model = "multilingual-e5-small"
embedding_dimension = 384
```
```bash
cargo build --release --features local-embed
```
> **注意:** 切换嵌入式提供商会更改向量维度。在切换后运行 `ragctl reset` 然后运行 `ragctl ingest`。
## 环境变量
| 变量 | 是否必填 | 描述 |
| --- | --- | --- |
| `OPENAI_API_KEY` | 是(当 `embedding_provider = "openai"` 时) | 在摄入时间(CLI)和搜索时间(服务器)用于嵌入式生成。不需要 `local-embed`。 |
| `OPENAI_API_BASE` | 否 | 自定义 OpenAI 兼容 API 端点(默认:`https://api.openai.com/v1`) |
> **重要:** `OPENAI_API_KEY` 不仅在 `ragctl ingest` 时需要,在运行 MCP 服务器时也需要,因为每个搜索查询都通过 OpenAI API 实时嵌入。如果要避免此依赖,请使用本地嵌入式(`--features local-embed`)。
## 搜索算法
1. 查询使用配置的嵌入式提供商进行嵌入。
2. Qdrant 向量搜索返回前 30 个候选结果。
3. Tantivy BM25 搜索返回前 30 个候选结果。
4. 使用 RRF(Reciprocal Rank Fusion)与 k=60 合并结果。
5. 返回前 N 个结果(默认 10)。