使用说明
Docs MCP服务器-技术文档的混合搜索
高性能 模型上下文协议(MCP)服务器 其提供混合搜索功能,结合了:
- 语义搜索 使用FastEmbed(BAAI/bge-small-en-v1.5)+Qdrant矢量数据库
- 关键词搜索 使用带有倒排索引的Elasticsearch BM25
- 加权融合 (默认为0.4 BM25+0.6语义)
专为跨Django、Django REST Framework、Psycopg和其他技术文档的本地开发人员文档搜索而设计。
先决条件
此服务器需要 弹性搜索 和 Qdrant 跑步。最简单的方法是使用Docker:
选项1:使用Docker Compose(推荐)
# Start services (Elasticsearch + Qdrant)
./start-services.sh
# Stop services
./stop-services.sh
脚本将:
- 在端口9200上启动Elasticsearch
- 在端口6333上启动Qdrant
- 等待两种服务都健康
- 在Docker卷中持久化数据
选项2:手动Docker命令
# Elasticsearch
docker run -d \
--name docs-elasticsearch \
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
elasticsearch:8.11.0
# Qdrant
docker run -d \
--name docs-qdrant \
-p 6333:6333 \
-p 6334:6334 \
qdrant/qdrant
选项3:本机安装
- 弹性搜索: https://www.elastic.co/downloads/elasticsearch
- Qdrant: https://qdrant.tech/documentation/quick-start/
特性
- 🔍 混合搜索:将语义理解与关键字精度相结合
- 🏷️ 元数据:按技术、组件、主题、版本自动标记文档
- 📊 结构化结果:具有个人和组合分数的Pydantic模型
- 🚀 双接口:MCP工具+REST API
- 💾 持久索引:Elasticsearch+Qdrant,带基于磁盘的元数据缓存
- 📝 智能分块:散文语义(约100个标记),代码结构(80-150行)
- 🔒 仅限本地主机:默认情况下安全,无外部访问
- ⚡ 快速嵌入:CPU优化的FastEmbed(无需GPU)
快速开始
1.安装依赖项
# Using uv (recommended)
uv sync
# Or using pip
pip install -e .
2.构建搜索索引
首次设置-索引您的文档:
# Use the build script (recommended)
uv run scripts/build_index.py
# Or index programmatically
uv run python -c "
import asyncio
from pathlib import Path
from search_engine import IndexManager
async def main():
manager = IndexManager()
stats = await manager.index_documents([Path('docs')])
print(f'Indexed {stats["chunks_created"]} chunks from {stats["files_processed"]} files')
asyncio.run(main())
"
或者在启动服务器后使用MCP工具(见下文)。
3.运行服务器
作为MCP服务器(stdio)
uv run server.py
然后在您的MCP客户端(Claude Desktop、VSCode等)中进行配置:
{
"mcpServers": {
"docs-server": {
"command": "uv",
"args": ["run", "server.py"],
"cwd": "/path/to/docs-mcp-server"
}
}
}
作为HTTP API服务器
uv run server.py --http
访问地址:
- API
http://localhost:8000 - 文件:
http://localhost:8000/docs
4.验证设置
# Run validation script
uv run scripts/test_setup.py
5.使用MCP检查员进行测试
# In one terminal: start server
uv run server.py
# In another terminal: start inspector
npx @modelcontextprotocol/inspector uv run server.py
引导到 http://localhost:5173 并测试工具。
MCP功能
此服务器通过以下方式实现了完整的MCP规范 工具, 资源,以及 提示.
工具
search
语义+关键字匹配的混合搜索。
参数:
query(必填):搜索查询文本tech(可选):按技术筛选(django/drf/psycopg)component(可选):按组件/模块筛选top_k(默认值10):结果数量
例子:
search(
query="How does Django auth middleware work?",
tech="django",
top_k=5
)
退货: 列表 SearchResultModel 与:
chunk_id:唯一标识符content:块状文本(截断为500个字符)source_path:原始文件路径tech,component,topic,version,file_type:元数据bm25_score,semantic_score,hybrid_score:个人和综合得分
retrieve
按ID获取特定块。
参数:
chunk_id(必填):来自搜索结果
退货: 满的 SearchResultModel 内容完整。
reindex_docs
构建或重建搜索索引。使用校验和跳过未更改的文件。
参数:
paths(可选):要索引的特定文件/目录路径tech(可选):索引特定技术(django/drf/psycopg)
示例:
# Reindex all docs (incremental - skips unchanged)
reindex_docs()
# Index only Django docs
reindex_docs(tech="django")
# Index specific paths
reindex_docs(paths=["docs/django-6.0/ref"])
退货: 统计数据 files_processed, chunks_created, errors.
注: 默认情况下,这将执行增量更新。要强制完全重建,请使用带有 --force 旗帜。
get_index_stats
获取指数统计数据和健康状况。
退货: IndexStats 包括块计数、模型信息、技术等。
list_sources
列出所有索引源文件。
参数:
tech(可选):按技术筛选
退货: 列表 SourceInfo 包含文件元数据和块计数。
search_docs (已弃用)
仅搜索传统关键字。使用 search 而是为了获得更好的结果。
list_docs
显示技术的目录结构。
list_technologies
列出可用的文档集。
资源
MCP资源提供对文档的直接访问,以用作上下文:
doc://{tech}/{path}
访问原始文档文件:
doc://django/ref/models/fields.md
doc://drf/api-guide/serializers.md
chunk://{chunk_id}
从搜索结果中访问索引块:
chunk://middleware_3_1234
VS代码中的用法:选择 添加上下文 > MCP资源 在聊天视图中。
提示
预配置的搜索提示以斜线命令形式提供:
/mcp.docsSearch.find_authentication_docs-身份验证和授权文档/mcp.docsSearch.find_database_docs-数据库和ORM文档/mcp.docsSearch.find_api_docs-API和序列化文档
每个提示接受一个 tech 参数(django/drf/psycopg)。
VS代码中的用法:类型 / 在聊天输入中查看可用的MCP提示。
REST API端点
所有端点都绑定到 127.0.0.1:8000 (仅限本地主机)。
POST /search
curl -X POST http://localhost:8000/search \
-H "Content-Type: application/json" \
-d '{
"query": "Django middleware authentication",
"tech": "django",
"top_k": 10
}'
GET /retrieve/{chunk_id}
curl http://localhost:8000/retrieve/middleware_3_1234
GET /status
curl http://localhost:8000/status
GET /sources?tech=django
curl http://localhost:8000/sources?tech=django
POST /reindex
curl -X POST http://localhost:8000/reindex \
-H "Content-Type: application/json" \
-d '{"tech": "django"}'
配置
环境变量
所有配置都通过环境变量进行管理。在运行服务器或生成脚本之前设置这些。
核心设置
| 变量 | 默认值 | 描述 |
|---|---|---|
INDEX_DIR |
.index |
元数据缓存目录(校验和、缓存数据) |
EMBEDDING_MODEL |
BAAI/bge-small-en-v1.5 |
语义嵌入的FastEmbed模型 |
CHUNK_SIZE_TOKENS |
100 |
语义拆分令牌中的目标块大小 |
搜索权重
| 变量 | 默认值 | 描述 |
|---|---|---|
BM25_WEIGHT |
0.4 |
BM25关键词得分权重(0.0-1.0) |
SEMANTIC_WEIGHT |
0.6 |
语义相似度得分权重(0.0-1.0) |
注: 权重之和应为1.0。对于关键字较多的查询,增加BM25,增加概念匹配的语义。
Elasticsearch配置
| 变量 | 默认值 | 描述 |
|---|---|---|
ES_HOST |
http://localhost:9200 |
Elasticsearch服务器URL |
ES_INDEX_NAME |
docs_index |
BM25文档的索引名称 |
Qdrant配置
| 变量 | 默认值 | 描述 |
|---|---|---|
QDRANT_HOST |
localhost |
Qdrant服务器主机 |
QDRANT_PORT |
6333 |
Qdrant gRPC端口 |
QDRANT_COLLECTION |
docs_collection |
向量嵌入的集合名称 |
Redis配置(已准备好,尚未使用)
| 变量 | 默认值 | 描述 |
|---|---|---|
REDIS_HOST |
localhost |
Redis服务器主机(用于未来缓存) |
REDIS_PORT |
6379 |
Redis服务器端口 |
REDIS_ENABLED |
false |
启用Redis缓存(当前未使用) |
生成脚本标志
这 scripts/build_index.py 脚本支持以下命令行标志:
建立索引
# Index all documentation
uv run scripts/build_index.py
# Index specific technology
uv run scripts/build_index.py --tech django
uv run scripts/build_index.py --tech drf
uv run scripts/build_index.py --tech psycopg
# Force reindex (ignore checksums, rebuild everything)
uv run scripts/build_index.py --force
# Index specific paths
uv run scripts/build_index.py --paths docs/django-6.0/ref docs/django-6.0/topics
清除索引
# Clear ALL documentation from index
uv run scripts/build_index.py --clear
# Clear specific technology only (keeps others intact)
uv run scripts/build_index.py --tech django --clear
uv run scripts/build_index.py --tech drf --clear
警告:
--clear没有--tech将删除所有索引文档。使用--tech标记以选择性清除。
联合作战
# Clear Django docs and reindex them
uv run scripts/build_index.py --tech django --clear
uv run scripts/build_index.py --tech django
# Force rebuild specific technology
uv run scripts/build_index.py --tech drf --force
配置示例
概念查询的语义权重高:
export BM25_WEIGHT=0.3
export SEMANTIC_WEIGHT=0.7
uv run server.py
BM25权重高,可实现精确的关键字匹配:
export BM25_WEIGHT=0.7
export SEMANTIC_WEIGHT=0.3
uv run server.py
自定义嵌入模型:
export EMBEDDING_MODEL="sentence-transformers/all-mpnet-base-v2"
uv run server.py
大块以获得更多上下文:
export CHUNK_SIZE_TOKENS=200
uv run scripts/build_index.py --force # Rebuild with new chunk size
自定义Elasticsearch实例:
export ES_HOST="http://192.168.1.100:9200"
export ES_INDEX_NAME="my_docs"
python server.py
查询示例
根据以下常见的开发人员查询调整混合权重:
"How does Django auth middleware work?"
"What is the DRF request lifecycle?"
"Configure Nuxt SSR"
"Redis cache invalidation flow"
"psycopg transaction patterns"
"Django model field options"
"DRF serializer validation"
建筑
┌─────────────────┐
│ MCP Client │ (Claude, VSCode, etc)
│ or HTTP Client │
└────────┬────────┘
│
v
┌─────────────────┐
│ server.py │ FastMCP + FastAPI
│ (Tools/API) │ • Lifespan context manager
│ │ • Context[ServerSession, AppContext]
│ │ • Structured Pydantic outputs
└────────┬────────┘
│
v
┌────────────────────────────────────────┐
│ search_engine.py │
│ (IndexManager) │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ Elasticsearch │ │ Qdrant │ │
│ │ BM25 Index │ │ Vector Index │ │
│ │ (Inverted) │ │ (HNSW) │ │
│ └──────────────┘ └───────────────┘ │
│ │ │ │
│ v v │
│ ┌────────────────────────┐ │
│ │ FastEmbed (CPU) │ │
│ │ bge-small-en-v1.5 │ │
│ │ 384-dim vectors │ │
│ └────────────────────────┘ │
└────────────────────────────────────────┘
│
v
┌─────────┐
│ .index/ │ (Metadata cache)
└─────────┘
实施说明:
- 寿命管理:IndexManager通过初始化一次
@asynccontextmanager并注入到工具上下文中 - 上下文模式:工具接收
Context[ServerSession, AppContext]用于访问共享状态 - 结构化输出:所有结果均作为经过验证的Pydantic模型返回
- 安全:仅本地主机绑定,用于监控的查询日志记录
文档来源
当前文档集 docs/:
- Django 6.0 (约300个文件):模型、视图、ORM、中间件、管理
- Django REST框架3.16.1 (约30个文件):序列化程序、视图、身份验证
- 心理学第3.3.1页 (约20个文件):PostgreSQL适配器,连接池
支持: .md, .txt, .rst, .py, .json, .yaml, .yml, .html
演出
初始索引
- 350-400个文件→ 2-5 分钟
- 生成嵌入(384维FastEmbed,CPU优化)
- 构建Elasticsearch BM25倒排索引+Qdrant HNSW向量索引
- 内存使用情况: 嵌入生成时峰值约500MB-1GB
- 磁盘使用情况: ~50-100MB
.index/缓存+Qdrant向量
增量更新
系统使用校验和来检测文件更改,并跳过对未更改文件的重新索引:
# First run: indexes all files
uv run scripts/build_index.py
# Processing 350 files...
# Second run: skips unchanged files
uv run scripts/build_index.py
# Skipped 350 files (unchanged)
# After modifying one file
uv run scripts/build_index.py
# Skipped 349 files (unchanged)
# Processing 1 file...
校验和行为:
- 每10个文件增量保存一次,以防止崩溃时丢失
- 存储在
.index/checksums.json - 使用
--force标记以绕过校验和验证并重建所有内容
搜索延迟
- 语义搜索(Qdrant): ~50-100ms(HNSW索引查找)
- BM25搜索(Elasticsearch): ~10-20ms(倒置指数)
- 混合融合: ~5ms(分数归一化和组合)
- 每次查询总计: ~100-150ms
已实施优化
-
批量处理:
- 嵌入:每批50个块(防止OOM)
- Qdrant上传:每批100分
- Elasticsearch索引:每批500个文档
-
并行操作:
- 单独的读/写锁允许在索引期间进行搜索
- 所有网络操作的异步I/O
-
资源管理:
- 缓存写入前的磁盘空间检查(最小1GB)
- 批之间的速率限制(0.1秒延迟)
- 增量检查点保存
-
内存效率:
- 流式文件处理
- 处理后批量清理
- 无需GPU(FastEmbed CPU优化)
系统要求
- CPU: 现代多核(建议使用4+核)
- 猛撞: 最低2GB,建议4GB
- 磁盘: 500MB可用于索引和缓存
- 服务:
- 本地主机上的Elasticsearch 8.x+:9200
- 本地主机上的Qdrant 1.x+:6333
- GPU: 不需要(FastEmbed使用CPU)
故障排除
Docker服务未运行
症状: Elasticsearch或Qdrant的连接错误
解决方案:
# Check service status
docker ps
# Start services if not running
docker-compose up -d
# Check logs
docker-compose logs elasticsearch
docker-compose logs qdrant
索引为空
症状: get_index_stats 显示0个块
解决:
# Check status via REST API
curl http://localhost:8000/status
# Rebuild index manually
uv run scripts/build_index.py
# Or via Python
uv run python -c "
import asyncio
from pathlib import Path
from search_engine import IndexManager
asyncio.run(IndexManager().index_documents([Path('docs')]))
"
构建脚本崩溃(退出143/SIGTERM)
症状: 索引过程终止于 Exit Code 143 或内存不足
原因:
- 系统OOM(内存不足)杀手终止进程
- RAM不足,无法进行大批量处理
解决:
-
减少批量大小 (已在当前版本中优化):
- 嵌入:每批50块
- Qdrant:每批100分
- Elasticsearch:每批500个文档
-
关闭内存密集型应用程序 索引期间
-
监控内存使用情况:
# Watch memory in real-time watch -n 1 free -h # Monitor indexing process ps aux | grep build_index -
小批量指标:
# Index one technology at a time uv run scripts/build_index.py --tech django uv run scripts/build_index.py --tech drf uv run scripts/build_index.py --tech psycopg
语义搜索返回所有分数=0
症状: 搜索结果显示 semantic_score: 0.0 对于所有结果
原因:
- Qdrant向量未正确索引
- MCP客户端缓存陈旧
- 重新索引后服务器未重新启动
解决:
-
重新启动服务器:
# Stop server (Ctrl+C) # Restart uv run server.py -
验证Qdrant是否有向量:
curl http://localhost:6333/collections/docs_collection # Check "points_count" > 0 and "vectors_count" > 0 -
直接测试语义搜索:
uv run python -c "
导入异步 从search_engine导入IndexManager
异步定义测试(): manager=索引管理器() wait manager初始化() results=等待管理器.搜索('Django身份验证',top_k=5) 对于结果中的r: 打印(f’语义:{r.Semantic_score},BM25:{r.BM25_score}’)
asyncio.run(test()) "
4. **Rebuild Qdrant collection:**
```bash
uv run scripts/build_index.py --clear
uv run scripts/build_index.py
意外清除所有文件
症状: 用过的 --tech X --clear 预计只清除技术X,但所有文档均已删除
原因: 旧版本的脚本有不正确的清晰逻辑
当前行为(固定):
--clear单独:清除所有文档--tech django --clear:仅清除Django文档,保留DRF和Psycopg--tech drf --clear:仅清除DRF文档
恢复:
# Reindex everything
uv run scripts/build_index.py
# Or reindex specific techs
uv run scripts/build_index.py --tech django
uv run scripts/build_index.py --tech drf
uv run scripts/build_index.py --tech psycopg
搜索质量差
症状: 结果与预期不符或缺少相关文件
解决:
-
检查查询日志 在
.index/search_queries.log查看查询的处理方式 -
调整混合搜索权重:
- 关键字密集型查询 (确切术语、函数名称):增加
BM25_WEIGHTexport BM25_WEIGHT=0.7 export SEMANTIC_WEIGHT=0.3 - 概念查询 (如何,最佳做法):增加
SEMANTIC_WEIGHTexport BM25_WEIGHT=0.3 export SEMANTIC_WEIGHT=0.7
- 关键字密集型查询 (确切术语、函数名称):增加
-
尝试不同的嵌入模型:
# Larger, more accurate (slower) export EMBEDDING_MODEL="sentence-transformers/all-mpnet-base-v2" # Smaller, faster (less accurate) export EMBEDDING_MODEL="sentence-transformers/all-MiniLM-L6-v2" # Rebuild with new model uv run scripts/build_index.py --force -
增加结果计数:
search(query="...", top_k=20) # Default is 10
磁盘空间问题
症状: 索引构建失败,出现磁盘写入错误
原因: 磁盘空间不足,无法写入元数据缓存
解决方案:
- 在写入缓存之前,系统会检查至少1GB的可用空间
- 如果需要,请释放磁盘空间:
df -h # Check available space docker system prune # Clean Docker artifacts
嵌入时间过长
解决:
- FastEmbed已经进行了CPU优化和批处理(一次50个块)
- 尝试较小的型号:
export EMBEDDING_MODEL="sentence-transformers/all-MiniLM-L6-v2" - 验证Elasticsearch和Qdrant是否正在运行:
docker ps - 检查CPU使用率是否没有瓶颈:
top或htop
缺少等待错误
症状: RuntimeWarning: coroutine 'get_stats' was never awaited
原因: 异步函数调用时没有 await
解决方案: 已在当前版本中修复。所有异步调用都已正确等待:
# Correct (current version)
stats = await manager.get_stats()
# Incorrect (would cause warning)
stats = manager.get_stats()
发展
本地缓存目录(.index/)
这 .index/ 目录包含本地元数据缓存,应该 非 致力于Git:
.index/
├── checksums.json # File checksums for incremental indexing
├── metadata_cache.pkl # Pickled chunk metadata
└── search_queries.log # Query history for debugging
Git配置:
# Already in .gitignore
echo ".index/" >> .gitignore
为什么不承诺?
- 包含特定于机器的路径
- 大型二进制pickle文件
- 首次运行时自动重建
- Elasticsearch和Qdrant保存实际索引
在新机器上重建:
# Clone repo
git clone <repo-url>
cd docs-mcp-server
# Start services
docker-compose up -d
# Build index (will create .index/ automatically)
uv run scripts/build_index.py
运行测试
# Quick sanity check (30 seconds)
uv run tests/smoke_test.py
# Comprehensive test suite (8-10 minutes)
uv run tests/run_comprehensive_tests.py
# Individual test suites
uv run tests/test_search_engine.py # Core functionality
uv run tests/test_score_accuracy.py # Score validation
uv run tests/test_integration.py # Integration tests
uv run tests/test_mcp_accuracy.py # 50+ query tests
# See tests/README.md for full documentation
测试覆盖范围:
- ✅ 核心搜索功能(20+测试)
- ✅ 分数准确性和阈值(15+次测试)
- ✅ 集成工作流程(7+测试)
- ✅ 真实世界查询(50+个查询)
- ✅ 混合评分验证
- ✅ 技术/部件过滤器
- ✅ 边缘情况和错误处理
调试日志记录
启用调试输出:
# Set environment variable
export PYTHONVERBOSE=1
# Or add to Python code
uv run python -c "import logging; logging.basicConfig(level=logging.DEBUG)"
调试语义搜索:
# Already enabled in search_engine.py
# Check logs for:
# - "Semantic search returned X results"
# - "Normalized semantic scores: [...]"
# - "Final hybrid scores: [...]"
检查查询日志
# Watch queries in real-time
tail -f .index/search_queries.log
# Analyze query patterns
grep "query=" .index/search_queries.log | head -20
测试更改
修改搜索权重后:
# Update environment
export BM25_WEIGHT=0.7
export SEMANTIC_WEIGHT=0.3
# Restart server (no reindex needed)
uv run server.py
修改块大小后:
# Update environment
export CHUNK_SIZE_TOKENS=200
# Must rebuild index with new chunks
uv run scripts/build_index.py --force
修改嵌入模型后:
# Update environment
export EMBEDDING_MODEL="sentence-transformers/all-mpnet-base-v2"
# Must rebuild with new embeddings
uv run scripts/build_index.py --force
未来的增强功能
MVP后的改进:
- []用于top-k细化的本地重新排序器(BAAI/bge重新排序器)
- []增量更新的文档差异
- []自动重新索引的文件监视器
- []版本感知过滤(Django 4 vs 5)
- []带同义词的查询扩展
- []使用情况分析仪表板
贡献
- 分叉存储库
- 创建要素分支
- 进行更改
- 使用MCP检查员进行测试
- 提交拉取请求
支持
- GitHub问题:[报告错误或请求功能]
- MCP文件:https://modelcontextprotocol.io/
- 句子转换:https://www.sbert.net/
内置: