☰
别再手动复制粘贴文档了:用 TaoToken 统一通道 + MCP 构建本地全量知识库检索系统,实现毫秒级上下文注入
2026/10/1 14:45:13 网站建设 项目流程

1. 文档散落一地的痛,我懂:为什么需要本地知识库检索系统

你有没有过这种体验:写代码时想查自己三个月前写的接口文档,得先打开文件管理器,翻三层目录,找到那个api-spec-v3-final-真的最终版.md,复制一段,切回 AI 对话框,粘贴,然后问“这个字段什么意思”。问完发现少复制了一段,又切回去找。一天下来,复制粘贴的次数比敲代码还多。

这就是典型的碎片化信息黑洞。你的知识散落在 Markdown 笔记、PDF 报告、Word 文档、代码注释、README 里,AI 看不到它们,只能靠你当“人肉搬运工”。更麻烦的是,当你把一堆文档拖进对话框,模型受限于上下文长度,要么读不完,要么“迷失在中部”——开头结尾记得住,中间的关键信息被忽略。

MCP(Model Context Protocol)解决的就是这个问题。它让 AI 工具能够以标准化方式调用你本地的检索能力。你不再需要手动复制,而是让模型自己决定“我需要查一下知识库”,然后通过 MCP 协议调用你写的搜索工具,毫秒级拿到最相关的文档片段,自动注入到当前对话上下文里。

这套方案适合谁?适合所有本地文档超过 50 篇、经常需要跨文档查资料的人。开发者、产品经理、研究人员、写作者都算。你不需要把文档上传到任何云端,所有索引和检索都在本机完成,隐私可控。

我试过把 200 多篇技术笔记和 PDF 报告接入这套系统,现在问 AI “我之前记录的 Redis 持久化配置注意事项”,它直接返回我半年前写的那段笔记,连文件名和行号都标出来了。整个过程不到 300 毫秒。

下面我会从零开始,带你搭一套可运行的本地知识库检索系统。核心组件有三个:TaoToken 统一 API 通道负责模型调用,MCP Server负责检索逻辑,本地向量库负责存储和语义匹配。每一步都有可复制的配置和代码。

2. TaoToken 前置准备:统一 Key 与 API 通道配置

在动手写 MCP Server 之前,先把模型调用的通道打通。为什么需要 TaoToken?因为你的 MCP Server 在检索到文档片段后,可能需要调用 Embedding 模型把文本转向量,或者调用对话模型做 Reranking。如果每个模型都单独配 Key、单独管 Base URL,维护成本很高。TaoToken 提供统一的 API 入口,一个 Key 走通所有模型调用。

2.1 获取 API Key 与确认 Base URL

首先访问 TaoToken 官网注册账号,然后在控制台的 API Keys 页面创建一个新 Key。建议给这个 Key 起个名字叫mcp-knowledge-local,方便后续排查。

创建完成后,你会拿到一串以sk-开头的密钥。把它保存到本地环境变量里,不要硬编码在代码中:

# Linux / macOS export TAOTOKEN_API_KEY="sk-你的密钥" # Windows PowerShell $env:TAOTOKEN_API_KEY="sk-你的密钥"

TaoToken 的 API Base URL 是:

https://taotoken.net/api

注意这个地址后面不加任何路径,具体端点由 SDK 或 HTTP 客户端拼接。比如调用对话模型时,完整地址是https://taotoken.net/api/v1/chat/completions。

2.2 验证 Key 是否可用

在写复杂代码之前,先用一条 curl 命令确认 Key 能正常工作:

curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "回复 OK"}], "max_tokens": 10 }'

如果返回的 JSON 里有choices字段且内容包含OK,说明通道正常。如果返回 401,检查 Key 是否复制完整、环境变量是否生效。如果返回local proxy failed,说明网络层有问题,检查你的网络配置是否能访问taotoken.net。

2.3 在 MCP Server 中读取 Key

MCP Server 通常以子进程方式运行,环境变量需要显式传递。在后续的配置文件中,我们会把TAOTOKEN_API_KEY写进env字段。这样 Server 启动时就能读到,不需要额外加载.env文件。

如果你用的是 Claude Code 或 Cline 这类支持 MCP 的客户端,它们的配置文件里都有env段落,直接填进去即可。下面第三节会给出完整配置。

3. 可复制配置:MCP Server 与知识库索引脚本

这一节是核心。我会先给出 MCP Server 的完整配置片段,再给出知识库索引脚本,最后说明如何把两者串起来。

3.1 MCP Server 配置文件(JSON 格式)

假设你用的是 Claude Code 或 Cline,它们的 MCP 配置通常放在~/.claude/claude_desktop_config.json或项目根目录的.mcp.json里。以下是一个完整的配置片段,路径和字段名与官方文档一致:

{ "mcpServers": { "local-knowledge": { "command": "node", "args": ["/Users/yourname/mcp-knowledge-bridge/dist/index.js"], "env": { "TAOTOKEN_API_KEY": "sk-你的密钥", "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "KNOWLEDGE_DIR": "/Users/yourname/Documents/knowledge-base", "LANCEDB_PATH": "/Users/yourname/mcp-knowledge-bridge/.lancedb" } } } }

三个关键点:command指向 Node 可执行文件,args指向编译后的 Server 入口,env里放 TaoToken 的 Key 和 Base URL,以及知识库目录和向量库路径。KNOWLEDGE_DIR是你存放所有文档的根目录,LANCEDB_PATH是向量数据落盘的位置。

如果你用的是 Codex,它的auth.json配置方式不同,但核心三件套一样:Base URL 填https://taotoken.net/api,Key 填sk-开头的密钥,Model ID 填你实际调用的模型名(如gpt-4o-mini或text-embedding-3-small)。

3.2 知识库索引脚本(TypeScript)

这个脚本负责扫描KNOWLEDGE_DIR下的所有.md、.txt、.pdf文件,分块后调用 TaoToken 的 Embedding 接口生成向量,写入 LanceDB。

import * as fs from "fs"; import * as path from "path"; import * as lancedb from "@lancedb/lancedb"; import pdf from "pdf-parse"; const KNOWLEDGE_DIR = process.env.KNOWLEDGE_DIR || "./knowledge-base"; const LANCEDB_PATH = process.env.LANCEDB_PATH || "./.lancedb"; const API_KEY = process.env.TAOTOKEN_API_KEY!; const BASE_URL = process.env.TAOTOKEN_BASE_URL || "https://taotoken.net/api"; const CHUNK_SIZE = 500; const CHUNK_OVERLAP = 50; async function getEmbedding(text: string): Promise<number[]> { const res = await fetch(`${BASE_URL}/v1/embeddings`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "text-embedding-3-small", input: text, }), }); const data = await res.json(); return data.data[0].embedding; } function chunkText(text: string): string[] { const chunks: string[] = []; let start = 0; while (start < text.length) { const end = Math.min(start + CHUNK_SIZE, text.length); chunks.push(text.slice(start, end)); start += CHUNK_SIZE - CHUNK_OVERLAP; } return chunks; } async function indexFile(filePath: string) { const ext = path.extname(filePath).toLowerCase(); let content = ""; if (ext === ".pdf") { const buffer = fs.readFileSync(filePath); const parsed = await pdf(buffer); content = parsed.text; } else if ([".md", ".txt"].includes(ext)) { content = fs.readFileSync(filePath, "utf-8"); } else { return; } const chunks = chunkText(content); const db = await lancedb.connect(LANCEDB_PATH); const table = await db.openTable("documents").catch(() => null); const records = []; for (const chunk of chunks) { const vector = await getEmbedding(chunk); records.push({ filename: path.basename(filePath), filepath: filePath, text: chunk, vector, }); } if (table) { await table.add(records); } else { await db.createTable("documents", records); } console.log(`Indexed ${records.length} chunks from ${filePath}`); } async function walkDir(dir: string) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { await walkDir(fullPath); } else { await indexFile(fullPath); } } } walkDir(KNOWLEDGE_DIR).then(() => console.log("Indexing complete."));

运行方式:

npx ts-node index.ts

首次运行会遍历所有文档并生成向量,200 篇文档大约需要 2-3 分钟。之后新增文档时重新运行即可,LanceDB 支持增量追加。

3.3 MCP Server 主逻辑(检索工具)

Server 的核心是暴露一个search_local_docs工具,接收查询字符串,返回最相关的文档片段。

import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import * as lancedb from "@lancedb/lancedb"; const LANCEDB_PATH = process.env.LANCEDB_PATH || "./.lancedb"; const API_KEY = process.env.TAOTOKEN_API_KEY!; const BASE_URL = process.env.TAOTOKEN_BASE_URL || "https://taotoken.net/api"; const server = new Server( { name: "local-knowledge-expert", version: "1.0.0" }, { capabilities: { tools: {} } } ); async function getEmbedding(text: string): Promise<number[]> { const res = await fetch(`${BASE_URL}/v1/embeddings`, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "text-embedding-3-small", input: text, }), }); const data = await res.json(); return data.data[0].embedding; } server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "search_local_docs", description: "在本地知识库中进行语义搜索,返回最相关的文档片段", inputSchema: { type: "object", properties: { query: { type: "string", description: "搜索关键词或问题描述" }, topK: { type: "number", description: "返回结果数量", default: 3 }, }, required: ["query"], }, }, ], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name !== "search_local_docs") throw new Error("Tool not found"); const query = args?.query as string; const topK = (args?.topK as number) || 3; const queryVector = await getEmbedding(query); const db = await lancedb.connect(LANCEDB_PATH); const table = await db.openTable("documents"); const results = await table .search(queryVector) .limit(topK) .toArray(); const formatted = results .map((r: any) => `[来源: ${r.filename}]\n内容: ${r.text}\n---`) .join("\n"); return { content: [{ type: "text", text: formatted }] }; }); const transport = new StdioServerTransport(); await server.connect(transport);

编译后把dist/index.js的路径填进第 3.1 节的配置文件,重启客户端即可。

4. 验证请求与成功结果:毫秒级上下文注入实测

配置完成后,怎么确认整套系统跑通了?分三步验证。

4.1 验证 MCP Server 是否被客户端识别

在 Claude Code 里输入/mcp命令,或者在 Cline 的 MCP 面板里查看,应该能看到local-knowledge这个 Server 处于 connected 状态。如果显示 failed,检查args路径是否正确、Node 版本是否 ≥ 18。

4.2 手动触发一次检索

在对话里直接问一个你知识库里有的问题,比如“我之前记录的 Redis 持久化配置注意事项”。模型会自动调用search_local_docs工具,你会在工具调用日志里看到类似输出:

[来源: redis-notes.md] 内容: RDB 持久化默认每 900 秒至少 1 个 key 变化时触发快照... --- [来源: redis-notes.md] 内容: AOF 持久化 appendfsync everysec 是折中方案... ---

从发起查询到返回结果,实测在 200-400 毫秒之间。这个延迟主要花在 Embedding 接口调用上,向量检索本身在 LanceDB 里是亚毫秒级的。

4.3 验证上下文注入效果

关键看模型是否真的用了检索到的内容。你可以问一个只有你文档里才有的细节,比如“我笔记里写的那个 Redis 最大内存配置是多少”。如果模型回答出具体数值,说明上下文注入成功。如果模型说“我不知道”,检查两个地方:一是search_local_docs是否被调用,二是返回的片段是否包含答案。

一个实用技巧:在 MCP Server 返回内容前加一行console.error打印查询和结果数量,这样在客户端日志里能看到每次检索的命中情况。注意用console.error而不是console.log,因为 Stdio 传输下console.log会污染协议消息。

5. 本篇常见错排查:401、local proxy failed、reading choices

搭建过程中最容易踩的坑集中在几个报错上,我逐个拆解。

5.1 401 Unauthorized

这是最常见的。原因通常是 Key 没传对。检查顺序:第一,env里的TAOTOKEN_API_KEY是否以sk-开头且没有多余空格;第二,MCP Server 启动时是否真的读到了这个环境变量,可以在代码开头加console.error(process.env.TAOTOKEN_API_KEY?.slice(0, 8))确认;第三,如果用的是 Codex 的auth.json,确认字段名是api_key而不是apiKey。

5.2 local proxy failed

这个报错说明请求根本没到达 TaoToken 的服务器。检查TAOTOKEN_BASE_URL是否写成了https://taotoken.net/api,注意末尾没有斜杠。如果你在代码里拼接路径时写成了${BASE_URL}/v1/embeddings,实际请求地址是https://taotoken.net/api/v1/embeddings,这是正确的。如果写成${BASE_URL}v1/embeddings就会变成https://taotoken.net/apiv1/embeddings,导致失败。

5.3 reading choices 报错

这个报错通常出现在解析模型返回时。如果你调用的是对话模型做 Reranking,返回结构里应该有choices数组。报错说明返回的 JSON 结构不符合预期,可能是模型名写错了,或者接口返回了错误信息。建议在解析前先打印完整响应体:

const data = await res.json(); console.error(JSON.stringify(data, null, 2));

这样能看到实际返回了什么。常见原因是模型 ID 拼写错误,比如把gpt-4o-mini写成了gpt-4-mini。

5.4 OAuth 相关报错

如果你用的是 Claude Code 的 OAuth 登录模式,MCP Server 的环境变量可能不会自动继承。解决方法是在配置文件的env里显式写全三件套:TAOTOKEN_API_KEY、TAOTOKEN_BASE_URL、KNOWLEDGE_DIR。不要依赖 shell 的export,因为 MCP Server 是客户端拉起的子进程,环境变量隔离。

5.5 检索结果为空

如果工具被调用了但返回空,检查 LanceDB 表里是否有数据。可以用一个简单的脚本查一下:

const db = await lancedb.connect(LANCEDB_PATH); const table = await db.openTable("documents"); console.log(await table.countRows());

如果行数为 0,说明索引脚本没跑成功。回头检查KNOWLEDGE_DIR路径是否正确、文件后缀是否在支持列表里。

6. 从检索到推理:让 AI 真正“读过你的笔记”

整套系统跑通后,你的工作流会变成这样:在 AI 对话框里直接问“帮我找一下之前写的那个关于消息队列选型的对比”,模型自动调用search_local_docs,300 毫秒内返回你三个月前写的笔记片段,然后基于这些片段给出回答。你不需要打开任何文件管理器,不需要复制任何东西。

如果想进一步优化,有两个方向。一是父子块检索:索引时同时存小片段和大段落,检索命中子块后返回父块,给模型更完整的上下文。二是Reranking:向量检索返回 10 条后,用 TaoToken 调一个轻量级对话模型对这 10 条做相关性排序,只取前 3 条注入。这样能显著降低噪音。

如果你需要长期在编码场景里用这套能力,可以考虑 TaoToken 的 Coding Plan,它针对高频 Agent 调用做了通道优化。模型对话调试可以在模型对话页面直接测试。API Key 管理在 API Keys 页面。接入文档在 doc 页面有更详细的参数说明。

最后说一个实用技巧:把KNOWLEDGE_DIR指向你的 Obsidian 或 Logseq 仓库根目录,每次写完笔记后跑一次索引脚本,AI 就永远读的是最新版本。索引脚本可以挂到 Git hook 或文件监听上,实现自动更新。这样你只管写,检索和注入交给系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询