五一花了 1 天,我把个人知识库做成了可"对话检索"的 MCP 服务
一句话:pip install 一个包,你的 Markdown 笔记就能被 AI 助手自动检索并引用。
起因
我平时在本地写了大量 Markdown 笔记------前端面试题、CSS 知识、Python 性能优化、学习计划......但每次和 AI 助手(Claude Code)对话时,它根本不知道我有这些笔记。我想问"我笔记里的 CSS 第一题是什么",它只能瞎猜。
于是我给自己定了个五一假期的目标:做一个能把自己的私有笔记喂给 AI 助手的 RAG 系统。而且要简单------装个包、配一行配置就能用。
总体设计思路
整个系统的核心链路只有 7 步:
bash
本地 .md/.txt 文件
→ Markdown 解析器(markdown-it-py)
→ 按标题分块(Chunker)
→ 文本向量化(BGE-small-zh-v1.5)
→ 存入向量数据库(LanceDB)
→ 封装成 MCP 服务(FastMCP)
→ Claude Code 自动调用
→ 检索结果注入上下文,生成回答
其中,MCP(Model Context Protocol)是 AI 助手和外部工具之间的标准通信协议------想象一下,它就像 USB 接口,你的工具只要实现这个协议,任何 AI 助手都能"即插即用"。这就是为什么 Claude Code、Cursor 都能直接调用我的知识库。
为什么选这些技术
| 环节 | 选型 | 理由 |
|---|---|---|
| Embedding | BGE-small-zh-v1.5 | 本地运行,512 维向量,中英文检索效果好 |
| 向量库 | LanceDB | 嵌入式数据库,无需额外部署,像 SQLite 一样简单 |
| MD 解析 | markdown-it-py | Token Stream 解析,正确处理嵌套标题和代码块 |
| 分块 | 标题感知 + 代码块原子保护 | 按 h2/h3 切分,代码块绝对不切割 |
| 集成框架 | FastMCP | MCP 官方 Python SDK,三行代码注册一个 Tool |
这些选型有一个共同的考量:零外部依赖部署。除了 Python 本身,用户不需要装 Docker、不需要起数据库、不需要配云服务。
把一篇 Markdown 变成可检索的知识------核心实现
1. 解析(你猜我绕过了哪个坑)
最初我用 markdown-it-py 的 SyntaxTreeNode 构建文档树,调试了好久一直返回 token=None。后来才搞清楚:markdown-it-py 的树形节点会在非叶子节点上把 token 设为 None------所以你直接读 token 属性就崩了。
解决方案是放弃高层 API,直接遍历底层 Token Stream:
python
# 不靠谱的高层 API
tokens = md.parse(content)
node = SyntaxTreeNode(tokens) # parent nodes 的 .token 是 None!
# 靠谱的底层实现
for token in md.parse(content):
if token.type == "heading_open":
# 手动追踪标题层级,构建 Section 树
踩过这个坑之后,"别信框架给你包装好的结构"成了我写解析器的一条原则。
2. 分块(别把代码块切碎了)
分块的核心矛盾:块太小则丢失上下文,块太大则检索精度下降。
我的策略:
- 按 h2/h3 标题边界切分:一个标题及其内容尽量在同一个 chunk 里
- 代码块原子保护 :用正则
(\``[\s\S]*?```)` 识别代码块,分块时绕开它们,绝不切割
python
# 核心逻辑
if len(section_content) <= 1000:
chunks.append(section_content) # 整个 Section 作为一个 chunk
else:
# 按段落切分,但代码块保持完整
parts = re.split(r'(\`\`\`[\s\S]*?\`\`\`)', section_content)
for part in parts:
if part.startswith('```'):
chunks.append(part) # 代码块原封不动
else:
# 长文本按段落补充切分
3. 向量化与增量索引
BGE 模型有一个设计细节很多人不知道:文档向量和查询向量应该用不同的方式生成。
- 文档文本:直接
model.encode(text) - 查询文本:加一个指令前缀
"为这个句子生成表示以用于检索相关文章:"再编码
这个前缀告诉 BGE 模型"接下来的是查询,不是文档",能显著提升中文检索质量。
增量索引的实现很简单------算文件 SHA256,跟库里存的 content_hash 比对,一样就跳过:
python
file_hash = hashlib.sha256(file_bytes).hexdigest()
if db.has_hash(file_hash):
return "skipped" # 文件没变,跳过
MCP 协议------让 AI 助手"看懂"你的工具
我注册了 5 个 Tool:
| Tool | 功能 | 标签 |
|---|---|---|
knowledge_search |
搜索知识库 | readOnly |
knowledge_index |
索引文件/目录 | destructive, idempotent |
knowledge_list |
列出已索引文档 | readOnly |
knowledge_remove |
移除索引 | destructive |
knowledge_stats |
全局统计 | readOnly |
标签系统是 FastMCP 内置的------readOnlyHint 告诉 AI "这个操作是安全的";destructiveHint 告诉 AI "这个会改数据"。Claude Code 会根据这些标签决定要不要弹出确认框。
一个 MCP Server 注册多个 Tool 的概念,类似于一个 HTTP 服务暴露多个 API 端点------启动的是同一个进程,通过同一根 stdio 管道通信,但 5 个 Tool 各自独立可调用。
为什么装好了 MCP,问问题却不自动检索?
这是上线后遇到的一个有意思的问题。我在新环境装好包后,问了一个知识库相关的问题,Claude Code 直接用自己的训练数据回答了------完全没有调用 knowledge_search。
原因有二:
- 没索引就没有内容可搜------向量库是空的
- AI 不知道什么时候该用你的工具------Tool 描述告诉它"我能做什么",但没告诉它"什么时候该用我"
解决方案是创建一个 CLAUDE.md:
markdown
当用户询问与个人笔记、学习记录相关的问题时:
1. 优先使用 knowledge_search 检索知识库
2. 即使用户提到了具体文件名,也先用 knowledge_search 搜索
这个规则文件是给 Claude Code 看的"行为指南",告诉它遇到哪类问题该走知识库路线。
发布到 PyPI------踩过的坑
-
license = {text = "MIT"}是新版 setuptools 不支持的旧写法 ,必须改成 SPDX 表达式license = "MIT",同时删除旧的License :: OSI Approved :: MIT Licenseclassifier------两者共存直接报InvalidConfigError -
torch/torchvision 版本不匹配 ------
sentence-transformers会拉 torch 2.11.0,但旧版 torchvision 0.22.1 不兼容,报RuntimeError: operator torchvision::nms does not exist。pip install torch torchvision --force-reinstall解决 -
包名和模块名是两回事 ------改
pyproject.toml的name字段改了pip install的名字,但 Python 模块名永远是src/下的目录名knowledge_mcp
成果
- 总代码量:约 640 行 Python(9 个源文件)
- 安装方式 :
pip install lxd-knowledge-test-mcp - 发布在 PyPI :lxd-knowledge-test-mcp
- 支持格式:Markdown + 纯文本(后续可扩展 PDF、DOCX)
从零到发布,五一假期 3 天搞定。核心体感是:MCP 协议把"AI 调用工具"这件事做得极其丝滑------你写完 Tool 的定义,AI 就能自动发现并调用,不用写任何对接代码。
后续方向
- 支持 PDF、DOCX 文档格式
- 文件监听自动增量索引
- 混合检索(向量 + BM25 关键词)
- 查询重写(HyDE 等策略提升召回率)