私人 Markdown 信息索引系统,提供全文搜索、重复检测能力,支持 MCP Server 供 AI 工具调用。
功能特性
- 全文搜索:基于 SQLite FTS5 实现高效全文检索,支持中文搜索
- 文件管理:列出文件、获取文件内容和元数据
- 重复检测:自动识别精确重复和近似重复文档
- RAG 查询:为 AI 工具提供格式化的上下文检索
- MCP Server:通过 SSE 与 AI 工具(如 opencode)集成
- 定时扫描:自动检测文件新增/修改/删除/移动
- NFS 共享:索引数据存储在 NFS 上,支持多机共享
架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| ┌─────────────────────────────────────────────────────────────┐ │ 用户交互层 │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ opencode │ │ 浏览器 │ │ 其他AI │ │ CLI工具 │ │ │ │ (MCP) │ │ (Web UI) │ │ (RAG) │ │ (REST) │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ └───────┼──────────────┼──────────────┼──────────────┼─────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌───────────────────────────────────────────────────────────────┐ │ API 服务层 (FastAPI) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 全文搜索API │ 语义搜索API │ RAG查询API │ MCP Server │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────┬────────────────────────────────┘ ▼ ┌───────────────────────────────────────────────────────────────┐ │ 索引引擎层 (Indexer) │ │ ┌──────────────┐ ┌────────────┐ ┌──────────────────────┐ │ │ │ Scanner │ │ Parser │ │ SQLite FTS5 + Dedup │ │ │ │ (文件扫描) │ │ (内容解析) │ │ (索引+重复检测) │ │ │ └──────────────┘ └────────────┘ └──────────────────────┘ │ └───────────────────────────────────────────────────────────────┘ ▼ ┌───────────────────────────────────────────────────────────────┐ │ 存储层 (NFS) │ │ /mnt/nfs/md-files/ /mnt/nfs/md-index/ │ │ markdown源文件 index.db + metadata.json + .lock │ └───────────────────────────────────────────────────────────────┘
|
快速开始
安装依赖
1 2
| cd /root/projects/wiki-server pip3 install --break-system-packages -r requirements.txt
|
启动服务
1 2 3 4 5 6 7 8
| ./start.sh
nohup python3 api_server.py > /tmp/wiki-server.log 2>&1 &
curl http://localhost:8900/api/health
|
运行测试
停止服务
项目结构
1 2 3 4 5 6 7 8 9 10 11 12
| /root/projects/wiki-server/ ├── config.py ├── scanner.py ├── parser.py ├── indexer.py ├── dedup.py ├── api_server.py ├── config.yaml ├── requirements.txt ├── start.sh ├── test.sh └── k3s-deployment.yaml
|
API 接口
搜索接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| curl -G "http://localhost:8900/api/search" \ --data-urlencode "q=关键词" \ --data-urlencode "tags=标签" \ --data-urlencode "limit=10"
curl -G "http://localhost:8900/api/search" \ --data-urlencode "q=关键词" \ --data-urlencode "exclude_duplicates=true"
curl -G "http://localhost:8900/api/search" \ --data-urlencode "q=关键词" \ --data-urlencode "include_total=true"
curl -X POST http://localhost:8900/api/semantic-search \ -H "Content-Type: application/json" \ -d '{"query": "关键词", "top_k": 5, "exclude_duplicates": false, "include_total": false}'
|
文件接口
1 2 3 4 5 6 7 8
| curl "http://localhost:8900/api/files?tags=标签&sort=date&order=desc&limit=20"
curl "http://localhost:8900/api/files?include_total=true&exclude_duplicates=true"
curl "http://localhost:8900/api/files/文档路径.md"
|
RAG 接口
1 2 3 4 5 6 7 8 9 10
| curl -X POST http://localhost:8900/api/rag/query \ -H "Content-Type: application/json" \ -d '{ "query": "查询内容", "top_k": 3, "include_full_content": true, "max_context_length": 4000, "exclude_duplicates": false }'
|
重复检测接口
1 2 3 4 5
| curl "http://localhost:8900/api/duplicates"
curl -X POST "http://localhost:8900/api/duplicates/scan"
|
管理接口
1 2 3 4 5 6 7 8
| curl -X POST "http://localhost:8900/api/index/refresh"
curl "http://localhost:8900/api/index/status"
curl "http://localhost:8900/api/health"
|
API 文档
启动服务后访问:
- Swagger UI:
http://localhost:8900/docs
- ReDoc:
http://localhost:8900/redoc
添加文档
将 Markdown 文件放入 /mnt/nfs/md-files/ 目录:
1 2 3 4 5
| cp your-doc.md /mnt/nfs/md-files/
curl -X POST http://localhost:8900/api/index/refresh
|
MCP Server 配置
opencode 配置
在 opencode 配置文件中添加:
1 2 3 4 5 6 7
| { "mcpServers": { "md-index": { "url": "http://192.168.x.x:8900/mcp/sse" } } }
|
- 本地访问:
http://localhost:8900/mcp/sse
- 局域网访问:
http://<服务器IP>:8900/mcp/sse
MCP 工具列表
| 工具 |
说明 |
参数 |
search_markdown |
关键词搜索 |
query, tags, limit |
semantic_search |
语义搜索 |
query, top_k |
get_file_content |
获取文件内容 |
path |
list_files |
列出文件 |
tags, sort, limit |
rag_query |
RAG 查询 |
query, top_k, max_context_length |
check_duplicates |
检查重复文档 |
无 |
配置说明
编辑 config.yaml 自定义配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| storage: md_files_path: "/mnt/nfs/md-files" index_path: "/mnt/nfs/md-index"
scanner: interval_minutes: 5 file_extensions: [".md", ".markdown"] exclude_patterns: [".git/", "node_modules/", ".trash/"]
vector: enabled: auto model_path: "./models/all-MiniLM-L6-v2" embedding_api_url: "" chunk_size: 500 chunk_overlap: 50
dedup: enabled: true exact_hash: true similarity_threshold: 0.9 containment_threshold: 0.9
api: host: "0.0.0.0" port: 8900 cors_origins: ["*"] api_token: ""
mcp: enabled: true transport: "stdio"
|
部署
k3s 部署
1 2 3 4 5 6 7 8 9 10
| docker build -t 192.168.2.40:30500/wiki-server:latest . docker push 192.168.2.40:30500/wiki-server:latest
kubectl apply -f k3s-deployment.yaml
kubectl get pods -l app=wiki-server kubectl logs -f deployment/wiki-server
|
部署配置说明
k3s-deployment.yaml 关键配置:
| 配置项 |
值 |
说明 |
| 镜像 |
192.168.2.40:30500/wiki-server:latest |
Zot 私有镜像仓库 |
| 存储 |
NFS (nfs-pvc) |
挂载到 /mnt/nfs |
| 节点选择 |
amd64 |
仅在 k3s-master 运行 |
| 资源限制 |
CPU: 100m-500m, Memory: 256Mi-512Mi |
|
环境变量(可选覆盖 config.yaml):
| 变量名 |
默认值 |
说明 |
MD_FILES_PATH |
/mnt/nfs/md-files |
Markdown 文件目录 |
INDEX_PATH |
/mnt/nfs/md-index |
索引数据目录 |
访问方式:
- ClusterIP:
http://wiki-server-service.default.svc.cluster.local:8900
- Ingress:
http://wiki-server.local/ (需配置 hosts)
- NodePort: 通过 Service 改为 NodePort 类型后访问
资源预估
| 组件 |
CPU |
内存 |
| API 服务 |
50m |
100MB |
| 扫描器 |
100m (周期性) |
50MB |
| SQLite |
10m |
20MB |
| 总计 |
~200m |
~250MB |
技术栈
- Python 3.11
- FastAPI - Web 框架
- SQLite FTS5 - 全文索引
- MCP SDK - AI 工具集成
- PyYAML - 配置解析
- fcntl - NFS 并发锁
注意事项
- 中文搜索使用 LIKE 查询(FTS5 unicode61 tokenizer 对中文分词支持有限)
- 向量检索当前未启用,需要下载模型或配置云端 API
- NFS 并发写入通过 fcntl 文件锁保护
- 服务启动时自动全量扫描,之后定时增量扫描
故障排查
Pod 启动失败 (Read-only file system)
问题: OSError: [Errno 30] Read-only file system: '/mnt/nfs/md-files'
原因: NFS 存储卷挂载为只读
解决:
- 确保 Deployment 中 volumeMounts 未设置
readOnly: true
- NFS 服务器需允许客户端写入 (检查 NFS 导出权限)
- 如使用 Longhorn 存储,可创建新的 PVC 替换 nfs-pvc
查看日志
1 2
| kubectl logs -l app=wiki-server kubectl describe pod -l app=wiki-server
|
重新部署
1 2
| kubectl rollout restart deployment/wiki-server kubectl rollout status deployment/wiki-server
|
NFS 路径不一致导致索引不更新
问题: 在 /mnt/nfs/md-files 放入文件后,索引未生成
排查步骤:
1 2 3 4 5 6 7 8 9 10 11
| mount | grep nfs
showmount -e 192.168.2.20
kubectl exec deploy/wiki-server -- ls -la /mnt/nfs/md-files/
kubectl get pv -o yaml | grep -A5 nfs
|
原因: Kubernetes PV 的 NFS path 与主机挂载路径不一致,导致容器内看到空目录
解决: 确保 PV 的 nfs.path 与主机挂载的 NFS 路径一致