Context-Mode:基于SQLite FTS5的智能体轻量路由机制 1. 项目概述Context-Mode 不是玄学而是智能体系统里“知道该问谁”的底层逻辑最近在多个技术社区和开发者群聊里“context-mode”这个词出现频率陡增——它既不是某个新发布的开源框架也不是某家大厂刚推出的SaaS服务而是一个正在悄然重塑智能体Agent交互范式的运行时语义机制。我第一次在调试一个本地知识库检索服务时撞见它当时后端日志里反复打印出context-mode: mcp而前端调用方只传了一个普通 query 字符串。起初以为是配置项写错了结果翻遍文档才发现这不是 bug是 design —— 它标志着整个请求不再由单一模块硬编码处理而是进入了一种“上下文感知的路由模式”。简单说context-mode 是一种轻量级、声明式的服务调度协议核心目标就一个让智能体在面对用户输入时能自动识别“当前这句话到底该交给哪个能力模块来处理”而不是靠人工写 if-else 或硬编码规则链。它不依赖大模型实时推理做路由决策那太重也不靠预设关键词匹配那太死而是通过结构化上下文元数据 轻量索引引擎比如 SQLite FTS5做毫秒级语义路由。你看到的mcp正是这个机制背后最主流的协议实现层——MCPModel Control Protocol它定义了能力模块Skill如何注册、如何被发现、如何被安全调用。而 SQLite FTS5 BM25 的组合则是目前落地最稳、部署最轻、调试最透明的本地化 context-mode 实现基座。适合谁看如果你正卡在这些场景里这篇就是为你写的用 Dify / LangChain / LlamaIndex 搭智能体但每次加新 Skill 都要改一堆路由逻辑在本地跑 RAG发现关键词检索不准、语义召回率低又不想上 Elasticsearch看到 “蓝湖 MCP”、“Figma MCP 插件”、“Cursor 连接 MCP” 却搞不清它们怎么协同工作想给 Delphi / Unity / Blender 写插件但苦于没有统一的能力注册与发现机制甚至只是好奇为什么现在连剪映、WorkBuddy、MasterGo 都在悄悄接入 MCP它解决的不是“怎么让大模型更聪明”而是“怎么让整个系统更懂分派任务”。接下来我会从设计本质、SQLite 实现细节、BM25 参数调优、MCP 协议对接实操到真实踩坑记录一层层拆给你看。所有内容基于我过去三个月在三个生产级智能体项目中的落地经验包括一个嵌入式设备上的离线 MCP Server、一个支持 20 Skill 的桌面端知识助手以及一个对接蓝湖设计系统的前端 MCP 代理层。不讲虚概念只讲你打开终端就能复现的步骤。2. Context-Mode 的底层设计哲学为什么不用大模型做路由而用 SQLiteFTS52.1 传统智能体路由的三大死穴Context-Mode 全部绕开我先说结论Context-Mode 的本质是一次对“智能体架构中‘控制流’与‘数据流’解耦”的工程实践。过去我们总想让大模型自己决定“下一步该调哪个工具”这听起来很 AI但实际落地全是坑。我在一个客户现场亲眼见过他们用 Claude 3 Sonnet 做 Tool Calling 路由单次 query 平均耗时 2.8 秒其中 2.1 秒花在等待 LLM 输出 JSON 格式 tool name —— 而真正执行工具只用了 300ms。更糟的是当 Skill 数量超过 15 个LLM 开始频繁 hallucinate 工具名比如把search_codebase错写成search_code_base导致整个链路中断。为什么不用 LLM 做路由不是不能而是不该。原因有三延迟不可控LLM 推理受 token length、batch size、GPU 显存影响极大。一个 50 行的 prompt在 A10 上可能 800ms在 T4 上可能 2.3s而你的 MCP Server 要支撑 50QPS这种波动根本没法做 SLA 保障。语义漂移无感知LLM 对 Skill 描述的理解会随温度值、top_p、模型版本变化。今天get_user_profile被识别为“查用户信息”明天可能被归类为“获取身份凭证”而你根本不知道它什么时候变了。调试黑洞当路由失败你只能看 LLM 的 output log无法像调试 SQL 一样 explain plan、看索引命中率、查 term frequency。问题定位靠猜修复靠试。Context-Mode 的破局点就是把“路由决策”这件事从黑盒 LLM 推理变成白盒数据库查询。它不否认 LLM 的价值而是把它严格限定在“生成结果”环节把“该找谁干活”这个动作交给更可靠、更可测、更可审计的本地索引引擎。2.2 为什么选 SQLite 而不是 PostgreSQL 或 LiteDB很多人第一反应是“SQLite就这生产环境敢用”—— 我去年也这么想直到在一台 4GB RAM 的树莓派 4 上用 SQLite FTS5 实现了每秒 120 次 context-mode 路由P99 延迟稳定在 8ms 以内。关键不在 SQLite 本身而在FTS5 的倒排索引设计 BM25 排序算法 mmap 内存映射优化的组合拳。我们对比下主流选项方案启动开销内存占用查询延迟万级 Skill可调试性部署复杂度PostgreSQL pg_trgm120MB300MB15~40mswarm cache中需 explain analyze高需 DBA 配置LiteDB全文索引25MB80MB35~90ms.NET GC 影响大低无 explain中.NET runtime 依赖SQLite FTS55MB40MB3~12msmmap 启用后高fts5info、fts5vocab 可查极低单文件零配置重点说 SQLite 的不可替代性单文件即服务.db文件复制即部署无需 daemon、无需 port、无需用户权限。你在 Windows 上双击mcp.db用 DB Browser for SQLite 打开就能直接看到所有 Skill 的注册元数据——这是任何网络数据库做不到的透明度。FTS5 原生支持 BM25SQLite 3.30 内置 FTS5其bm25()函数是经过工业验证的排序算法参数可调后面详述不像某些 ORM 封装的“伪 BM25”只是 cosine similarity 换了个名字。mmap 模式榨干 I/O在PRAGMA mmap_size268435456;256MB设置下SQLite 将.db文件直接映射到进程虚拟内存避免 read() 系统调用开销。实测比默认 mode 快 3.2 倍且内存占用反而更低——因为 OS page cache 复用率极高。提示Delphi 开发者常遇到的sqlite 亂碼问题90% 源于未设置PRAGMA encoding UTF-8;。FTS5 对编码极其敏感一旦建表时没指定后续插入的中文 Skill 描述全变乱码且无法 recover。务必在CREATE VIRTUAL TABLE ... USING fts5(...)前执行该 pragma。2.3 Context-Mode 的三层元数据模型Skill、Capability、Context SchemaContext-Mode 的核心不是代码而是三张表构成的元数据契约。我用一个真实案例说明我们在蓝湖设计系统里接入 MCP让设计师能语音说“把按钮组件的圆角改成 12px”系统自动调用update_component_styleSkill。这个过程背后是三张表的协同skills表能力本体存储每个 Skill 的静态描述id: UUID如skill-bluehole-update-style-001name: 机器可读名update_component_styledescription: 自然语言描述“修改设计稿中指定组件的 CSS 样式属性”tags: JSON 数组[ui, component, style]schema: JSON Schema定义 input/output 结构供 type-safe 调用capabilities表能力接口定义 Skill 如何被调用skill_id: 外键关联skills.idprotocol:http,local,websocketendpoint:http://localhost:8001/api/v1/update-styleauth_method:none,api_key,oauth2timeout_ms:5000context_schemas表上下文契约声明 Skill 期望的输入上下文结构capability_id: 外键field_name:component_idfield_type:stringrequired:trueexample_value:btn-primary-001semantic_hint:design component identifier供 FTS5 索引用这三张表的关系决定了 context-mode 的路由精度。比如用户说“把首页的登录按钮圆角调大”系统提取出实体首页、登录按钮、圆角、调大然后构造一个 FTS5 查询SELECT s.id, s.name, s.description, bm25(c) AS score FROM skills s JOIN capabilities c ON s.id c.skill_id WHERE s.description MATCH 登录按钮 OR 首页 OR 圆角 AND c.protocol http ORDER BY score DESC LIMIT 3;注意MATCH子句里的登录按钮 OR 首页 OR 圆角不是简单字符串拼接而是经过tokenizeunicode61分词器处理后的词干。SQLite FTS5 默认用unicode61它对中文支持良好按 Unicode 字符切分但对英文缩写如UI会误切为U I需额外配置tokenizeunicode61 remove_diacritics 0并自定义 stopword list。3. SQLite FTS5 BM25 实战从建库到毫秒级路由的完整链路3.1 初始化 MCP 数据库5 行命令搞定可调试基座别被“FTS5”“BM25”吓住实际初始化比你想象中简单。我提供一个零依赖、跨平台的初始化脚本bash sqlite3 CLIWindows 用户可用 WSL 或直接下载 DB Browser for SQLite 图形化操作。# 1. 创建空数据库 sqlite3 mcp.db EOF PRAGMA encoding UTF-8; PRAGMA mmap_size 268435456; EOF # 2. 创建 skills 表普通表存结构化元数据 sqlite3 mcp.db EOF CREATE TABLE skills ( id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, tags TEXT, schema TEXT, created_at INTEGER DEFAULT (strftime(%s, now)) ); EOF # 3. 创建 FTS5 虚拟表核心全文索引在此 sqlite3 mcp.db EOF CREATE VIRTUAL TABLE skills_fts USING fts5( name, description, tags, contentskills, content_rowidrowid, tokenizeunicode61 remove_diacritics 0 ); EOF # 4. 创建 triggers 同步数据关键确保 skills 更新时 FTS5 自动刷新 sqlite3 mcp.db EOF CREATE TRIGGER skills_ai AFTER INSERT ON skills BEGIN INSERT INTO skills_fts(rowid, name, description, tags) VALUES (new.rowid, new.name, new.description, new.tags); END; CREATE TRIGGER skills_ad AFTER DELETE ON skills BEGIN INSERT INTO skills_fts(skills_fts, rowid) VALUES(delete, old.rowid); END; CREATE TRIGGER skills_au AFTER UPDATE ON skills BEGIN INSERT INTO skills_fts(skills_fts, rowid) VALUES(delete, old.rowid); INSERT INTO skills_fts(rowid, name, description, tags) VALUES (new.rowid, new.name, new.description, new.tags); END; EOF # 5. 插入一个测试 Skill蓝湖组件样式更新 sqlite3 mcp.db EOF INSERT INTO skills (id, name, description, tags, schema) VALUES ( skill-bluehole-update-style-001, update_component_style, 修改设计稿中指定组件的 CSS 样式属性支持圆角、边框、阴影等, [ui, component, style, bluehole], {type:object,properties:{component_id:{type:string},style:{type:object}}} ); EOF执行完这 5 步你的mcp.db就具备了 context-mode 的基础能力。验证方法# 查看 FTS5 索引状态 sqlite3 mcp.db SELECT * FROM skills_fts WHERE skills_fts MATCH 圆角; # 应返回一行score 值约 12.7BM25 分数越高越相关 # 查看分词效果调试必备 sqlite3 mcp.db SELECT * FROM skills_fts WHERE skills_fts MATCH 圆角 AND skills_fts vocab; # 返回类似圆角|1|1 term|doc_count|global_count注意contentskills和content_rowidrowid是 FTS5 同步的关键。它告诉 SQLite “这个虚拟表的数据源是skills表主键是rowid”。如果漏掉insert trigger 会失效FTS5 索引永远为空。3.2 BM25 参数调优不是调参玄学而是业务语义校准BM25 公式score IDF(q) * (f(q, D) * (k1 1)) / (f(q, D) k1 * (1 - b b * |D|/avgdl))其中f(q, D)是词频IDF(q)是逆文档频率k1控制词频饱和度b控制文档长度归一化强度。SQLite FTS5 的bm25()函数允许传入k1和b参数默认k11.2, b0.75。但默认值在 Skill 检索场景下往往不适用。我们实测了 3 类 Skill 的最优参数Skill 类型示例最佳 k1最佳 b原因分析短描述 Skill50 字get_user_profile0.80.3描述极短词频易爆炸需降低 k1 抑制高频词权重文档长度方差小b 可调低长描述 Skill200 字generate_api_documentation1.80.9描述冗长需更强词频激励k1↑和长度惩罚b↑避免长文档碾压短文档多标签 Skilltags 数 5export_to_figma1.20.5tags 提供强语义信号b 中等即可平衡 description 与 tags 贡献调优方法不是盲试而是用fts5vocab表分析-- 查看所有词的文档频率DF找出高频干扰词 SELECT term, doclist FROM skills_fts WHERE skills_fts vocab ORDER BY doclist DESC LIMIT 10; -- 如果看到 api, tool, service 等泛化词 DF 80%说明需要加入 stopword添加停用词stopword的正确姿势-- 创建停用词表必须叫 fts5_vocab CREATE VIRTUAL TABLE fts5_vocab USING fts5vocab(skills_fts, row); -- 插入停用词SQLite 会自动过滤 INSERT INTO fts5_vocab(term) VALUES (api), (tool), (service), (function);实操心得我在一个含 127 个 Skill 的库中加入 19 个停用词后BM25平均分提升 23%且 P95 延迟下降 1.8ms。但停用词不能贪多——删掉button这种 UI 组件词会导致“修改按钮样式”查询完全失效。原则是只删在所有 Skill 描述中高频出现、且无区分度的通用词。3.3 构建 context-mode 路由函数Python SQLite 的 20 行核心逻辑有了数据库下一步是写路由函数。以下是我在线上环境稳定运行 6 个月的 Python 版本兼容 PyPy无外部依赖import sqlite3 import json from typing import List, Dict, Optional def route_context(query: str, db_path: str mcp.db, top_k: int 3, k1: float 1.2, b: float 0.75) - List[Dict]: Context-Mode 核心路由函数 :param query: 用户原始输入如“把按钮圆角调大” :param db_path: SQLite 数据库路径 :param top_k: 返回最匹配的 Skill 数量 :param k1, b: BM25 参数按 Skill 类型动态传入 :return: [{skill_id, name, description, score, capability}] conn sqlite3.connect(db_path) conn.row_factory sqlite3.Row # 支持字典访问 # Step 1: 提取查询中的关键实体简化版实际用 spaCy 或 LTP # 这里用正则模拟提取中文名词 英文单词避免过度分词 import re tokens re.findall(r[\u4e00-\u9fff]|[a-zA-Z_][a-zA-Z0-9_]*, query) # Step 2: 构造 FTS5 MATCH 查询OR 连接避免 AND 导致无结果 match_clause OR .join([f{t} for t in tokens]) # Step 3: 执行带 BM25 排序的查询 cursor conn.cursor() cursor.execute(f SELECT s.id AS skill_id, s.name, s.description, bm25(s_fts, {k1}, {b}) AS score, c.endpoint, c.protocol, c.timeout_ms FROM skills s JOIN skills_fts s_fts ON s.rowid s_fts.rowid JOIN capabilities c ON s.id c.skill_id WHERE s_fts MATCH ? ORDER BY score DESC LIMIT ? , (match_clause, top_k)) results [] for row in cursor.fetchall(): results.append({ skill_id: row[skill_id], name: row[name], description: row[description], score: round(row[score], 3), capability: { endpoint: row[endpoint], protocol: row[protocol], timeout_ms: row[timeout_ms] } }) conn.close() return results # 使用示例 if __name__ __main__: # 模拟用户输入 user_query 把登录按钮的圆角改成 12px candidates route_context(user_query, k10.8, b0.3) # 短描述 Skill用低 k1 print(json.dumps(candidates, ensure_asciiFalse, indent2))这段代码的精妙之处在于不依赖 NLP 库用正则提取 token避免引入 spaCy/LTP 等重量级依赖启动快、内存省动态 BM25 参数k1/b作为函数参数可按 Skill 类型从skills.tags字段判断实时切换返回完整 capability不只是 Skill ID还包含 endpoint、protocol、timeout下游可直接发起调用score 可解释BM25 分数是绝对值不同 query 间可比如 12.7 vs 8.3方便做阈值过滤score 5.0 则 fallback 到 LLM。注意s_fts MATCH ?中的?是参数化查询防止 SQL 注入。不要用 f-string 拼接match_clause否则query; DROP TABLE skills; --会直接删库。4. MCP 协议对接实战从注册 Skill 到响应客户端的全流程4.1 MCP Server 的最小可行实现30 行 GoMCP 协议的核心是 HTTP RESTful API定义在 MCP Spec v0.3 。我们用 Go 写一个极简 Servermain.go它只做三件事接收 Skill 注册、响应 context-mode 路由、转发调用请求。package main import ( database/sql encoding/json log net/http time _ github.com/mattn/go-sqlite3 ) type Skill struct { ID string json:id Name string json:name Description string json:description Tags []string json:tags Schema string json:schema } func main() { db, _ : sql.Open(sqlite3, ./mcp.db) http.HandleFunc(/mcp/register, func(w http.ResponseWriter, r *http.Request) { if r.Method ! http.MethodPost { http.Error(w, Method not allowed, http.StatusMethodNotAllowed) return } var skill Skill json.NewDecoder(r.Body).Decode(skill) // 插入 skills 表触发 FTS5 同步 _, err : db.Exec(INSERT INTO skills(id, name, description, tags, schema) VALUES(?, ?, ?, ?, ?), skill.ID, skill.Name, skill.Description, json.Marshal(skill.Tags), skill.Schema) if err ! nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{status: ok}) }) http.HandleFunc(/mcp/route, func(w http.ResponseWriter, r *http.Request) { if r.Method ! http.MethodPost { http.Error(w, Method not allowed, http.StatusMethodNotAllowed) return } var req struct{ Query string } json.NewDecoder(r.Body).Decode(req) // 调用前面的 route_context 逻辑此处简化为 SQL 查询 rows, _ : db.Query( SELECT s.id, s.name, s.description, bm25(s_fts) as score FROM skills s JOIN skills_fts s_fts ON s.rowid s_fts.rowid WHERE s_fts MATCH ? ORDER BY score DESC LIMIT 3, req.Query) var results []map[string]interface{} for rows.Next() { var id, name, desc string var score float64 rows.Scan(id, name, desc, score) results append(results, map[string]interface{}{ skill_id: id, name: name, description: desc, score: score, }) } w.Header().Set(Content-Type, application/json) json.NewEncoder(w).Encode(results) }) log.Println(MCP Server listening on :8000) log.Fatal(http.ListenAndServe(:8000, nil)) }编译运行go build -o mcp-server main.go ./mcp-server现在你可以用 curl 测试# 注册一个 Skill curl -X POST http://localhost:8000/mcp/register \ -H Content-Type: application/json \ -d {id:skill-test-001,name:test_skill,description:用于测试的技能,tags:[test],schema:{}} # 路由查询 curl -X POST http://localhost:8000/mcp/route \ -H Content-Type: application/json \ -d {query:测试技能}这个 Server 的价值在于它把 context-mode 的能力封装成标准 HTTP 接口任何语言Python/JS/Java/Delphi都能调用。比如 Cursor 编辑器的插件只需在fetch(http://localhost:8000/mcp/route)就能获得 Skill 建议Figma 插件同理。4.2 前端 MCP 客户端TypeScript 的 15 行智能提示以 Figma 插件为例我们用 TypeScript 实现一个 context-mode 驱动的命令面板Command Palette// figma-mcp-client.ts interface SkillCandidate { skill_id: string; name: string; description: string; score: number; } async function getSkillSuggestions(query: string): PromiseSkillCandidate[] { try { const res await fetch(http://localhost:8000/mcp/route, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ query }), }); return await res.json(); } catch (err) { console.error(MCP route failed:, err); return []; // fallback to static list } } // 在 Figma UI 中调用 figma.showUI(__html__, { width: 300, height: 400 }); figma.ui.onmessage async (msg) { if (msg.type GET_SUGGESTIONS) { const candidates await getSkillSuggestions(msg.query); // 渲染到 UI显示 name description score figma.ui.postMessage({ type: SUGGESTIONS, data: candidates.map(c ({ id: c.skill_id, label: c.name, detail: c.description, score: c.score.toFixed(1), })) }); } };关键点本地回环调用Figma 插件运行在沙箱环境但localhost:8000可访问需在 manifest.json 中声明permissions: [http://localhost:8000/]score 可视化把score显示为8.7让用户直观感受匹配强度避免“为什么推荐这个”的困惑fallback 机制网络失败时返回空数组UI 自动降级为静态命令列表体验不中断。实操心得在蓝湖 MCP 集成中我们发现设计师输入“导出 PNG”时export_to_pngSkill 的 BM25 分数常低于export_to_pdf因 PDF 描述里也含“导出”。解决方案是给export_to_png的tags字段加权重[export, png, raster]并在 FTS5 查询中 boosttags字段MATCH 导出 PNG OR tags:导出。SQLite FTS5 支持字段前缀field:term这是提升精准度的隐藏技巧。4.3 MCP 认证与安全OAuth2 与 API Key 的轻量实现MCP Server 不能裸奔。我们用最简方式实现认证对高危 Skill如delete_database要求 OAuth2对普通 Skill如get_weather用 API Key。API Key 方案SQLite 存储-- 新增 api_keys 表 CREATE TABLE api_keys ( key_hash TEXT PRIMARY KEY, -- bcrypt hash of raw key owner TEXT NOT NULL, -- e.g., figma-plugin-v1 scopes TEXT, -- JSON array: [read:skills, invoke:update_style] created_at INTEGER DEFAULT (strftime(%s, now)) ); -- 在 /mcp/route 请求头中检查 -- Authorization: Bearer abc123...OAuth2 方案Stateless JWTClient 用蓝湖 OAuth2 Endpoint 获取 tokenMCP Server 用公钥验签RS256payload 包含scope: [mcp:invoke]关键JWT 不存服务端无 session 状态扩展性极好。注意mcp oauth认证搜索热度高但多数人卡在“如何让 Figma 插件拿到蓝湖 token”。答案是Figma 插件无法直接调蓝湖 OAuth2CORS 限制必须走自己的代理层。我们用 Cloudflare Workers 写了一个 20 行代理接收 Figma 插件的/auth/start请求重定向到蓝湖授权页回调时Workers 用 client_secret 换取 access_token再返回给插件。全程不暴露 client_secret。5. 真实项目踩坑实录从 Delphi 乱码到 Kali 权限12 个问题速查表5.1 SQLite 相关高频问题占全部问题的 63%问题现象根本原因解决方案验证命令Delphi SQLite 亂碼sqlite3.dll编译时未启用SQLITE_ENABLE_RTREE和SQLITE_ENABLE_FTS5且连接字符串缺UTF-8下载预编译版 SQLite3 DLL for Delphi 连接字符串加;CharacterSetUTF8SELECT hex(description) FROM skills WHERE idxxx;应返回合法 UTF-8 hexFTS5 查询无结果CREATE VIRTUAL TABLE时漏contentskills或INSERT未触发 trigger检查skills_fts表是否为空SELECT count(*) FROM skills_fts;若为 0重建表并确认 trigger 存在SELECT * FROM sqlite_master WHERE typetrigger AND tbl_nameskills;BM25 分数异常低k1/b参数超出合理范围k10.5 或 3.0或文档平均长度avgdl估算错误用SELECT avg(length(description)) FROM skills;得到 avgdl设b0.75k1从 1.0 开始试SELECT bm25(s_fts, 1.0, 0.75) FROM skills_fts WHERE s_fts MATCH test;多线程写入报错 database is lockedSQLite 默认 WAL mode 未开启PRAGMA journal_modeWAL;执行一次即可WAL 模式允许多读一写PRAGMA journal_mode;应返回wal5.2 MCP 协议对接问题28%问题现象根本原因解决方案关键检查点Figma 插件调用 MCP 403Figma manifest.json 未声明http://localhost:8000/权限在manifest.json的permissions数组加http://localhost:8000/Figma DevTools Console 查 Network tab 的 preflight OPTIONS 请求响应头Cursor 连接蓝湖 MCP 超时蓝湖 MCP Server 绑定127.0.0.1而 Cursor 在 sandbox 中访问localhost失败Server 改为0.0.0.0:8000并加防火墙规则仅允许 127.0.0.1netstat -an | findstr :8000确认监听地址Skill 注册后路由不生效capabilities表未插入对应记录或protocol字段值非http/local检查SELECT * FROM capabilities WHERE skill_idxxx;protocol必须小写且值在 MCP Spec 定义范围内5.3 环境与部署问题9%问题现象根本原因解决方案一句话总结Kali Linux 上 MCP Server 启动失败Kali 默认禁用systemd-resolved导致localhost解析失败sudo systemctl enable systemd-resolved sudo systemctl start systemd-resolvedKali 的网络栈和 Ubuntu 有差异localhost不一定通Windows SQLite 驱动找不到Python 的pysqlite3未编译 FTS5 支持pip install pysqlite3-binary预编译版或从源码编译需 VS Build ToolsWindows 上用二进制包最省事Docker 部署 MCP 后 Skill 注册丢失Docker volume 未挂载.db文件容器重启后数据清空docker run