
1. 内部知识库问答为什么总是慢半拍内部知识库做技术问答最让人抓狂的不是答不准而是等太久。你问一句“用户服务的鉴权接口怎么调”系统先做向量检索再重排序再把一堆片段塞进提示词让模型生成最后流式吐字。整条链路走完三五秒过去了。对于习惯了即时搜索的工程师来说这个等待时间足以让人直接放弃问答、回去翻文档。我试过把检索和生成拆开优化检索压到 200ms重排序砍掉生成换小模型端到端还是卡在 1.5 秒以上。问题不在某一环而在于传统 RAG 把“找知识”和“用知识”耦合在一条同步链路上每次提问都要重新走一遍完整流程。MCP Server 的思路不一样。它把文档检索封装成一个独立的工具服务AI 客户端通过标准协议直接调用。模型不需要在提示词里塞满检索结果而是像调用函数一样按需取用。协议层直接路由到目标 Skill省掉了“检索-判断-再检索”的来回。实测下来端到端延迟可以从 3 秒级压到 1 秒以内体感上接近“零延迟”。这篇文章面向的是正在搭内部知识库、被 RAG 延迟困扰的工程师。我会从场景拆解开始给出可复制的 MCP Server 配置片段、文档索引参数并演示一次从提问到命中的完整验证动作。你不需要先精通 MCP 协议跟着步骤走就能跑通第一个文档 MCP Server。核心检索词先明确RAG 负责“知道什么”MCP Server 负责“能做什么”文档是知识载体技术问答是目标场景零延迟是体验标准。把这五个词串起来就是本文要落地的完整方案。2. 把文档检索封装成 MCP Server 的前置准备在动手写配置之前先把架构想清楚。传统 RAG 是一个大而全的向量库所有文档混在一起检索时不同领域的知识互相干扰。技术方案和故障复盘混在一个集合里问“数据库连接池怎么配”可能召回一条三个月前的故障报告答非所问。RAG Skill 的思路是“一技能一知识库”。每个垂直领域独立成一个 MCP Server各自拥有专属的文档集合和检索策略。技术方案一个 Server故障复盘一个 ServerAPI 文档一个 Server。客户端根据问题类型路由到对应的 Server检索范围天然收窄召回精度和速度同时提升。2.1 技术选型与组件职责整套方案需要四个核心组件。MCP Server 本身用 Python SDK 实现负责暴露检索工具。向量库选 Qdrant支持过滤检索且性能稳定。元数据存 PostgreSQL记录集合配置、文档来源和权限信息。Embedding 用 Ollama 本地部署避免每次向量化都走网络请求。组件选型职责MCP SDKPython SDK v1.x暴露 search_docs、ask_with_context 等工具向量库Qdrant存储文档向量执行语义检索元数据PostgreSQL集合配置、文档来源、权限EmbeddingOllama mxbai-embed-large本地向量化无网络延迟生成模型Ollama gpt-oss:20b本地推理数据不出域选本地 Embedding 和本地生成模型核心目的是消除网络往返。向量化一次走本地 HTTP生成也走本地整条链路没有外部 API 调用延迟可控。2.2 文档索引参数怎么定分块策略直接决定检索质量。按章节标题分块保留层次结构块大小控制在 512 到 1024 tokens重叠 50 到 100 tokens。块太大检索精度下降块太小上下文断裂。技术文档适合按二级标题切分每个块带上前置标题路径作为元数据。向量化时把标题路径拼进文本再嵌入检索时命中率更高。比如“用户服务 / 鉴权 / Token 刷新”这样的路径比单纯一段正文更能表达语义。Qdrant 的 payload 里存原文、来源文件、标题、块序号检索结果返回时带上这些信息方便模型引用。2.3 获取接入凭证与配置入口MCP Server 本身不依赖外部模型服务但如果你想让客户端调用远程模型做生成或者用 Coding Plan 跑 Agent 任务需要先拿到接入凭证。访问 https://taotoken.net/api-keys 创建 API Key然后在 https://taotoken.net/doc 查看接入文档确认 Base URL 和 Model ID 的填写方式。这一步不是必须的本地 Ollama 方案可以完全离线跑通。但如果你希望把文档 MCP Server 接到云端模型上或者用 Claude Code 做代码相关的知识问答提前配好凭证会省事很多。模型对话入口在 https://taotoken.net/models Coding Plan 在 https://taotoken.net/coding-plan 按需选用。3. 可复制的 MCP Server 配置与索引脚本这一节给出可以直接复制运行的配置片段和索引脚本。路径和参数都按实际可跑通的标准写你只需要替换文档目录和数据库连接信息。3.1 MCP Server 配置文件在项目根目录创建mcp_config.json内容如下。这个配置同时适用于 Claude Desktop 和 Cursor放到对应的配置目录即可。{ mcpServers: { rag-skill-tech-docs: { command: python, args: [-m, rag_server], env: { DATABASE_URL: postgresql://mcp_user:secure_pass127.0.0.1:5432/mcp_rag, QDRANT_BASE_URL: http://127.0.0.1:6333, OLLAMA_BASE_URL: http://127.0.0.1:11434, EMBED_MODEL: mxbai-embed-large, CHAT_MODEL: gpt-oss:20b, COLLECTION_NAME: tech_docs } } } }Claude Desktop 的配置文件路径是~/Library/Application Support/Claude/claude_desktop_config.jsonmacOS或%APPDATA%\Claude\claude_desktop_config.jsonWindows。把上面的mcpServers对象合并进去重启客户端即可生效。Cursor 的配置在设置里的 MCP 面板或者直接编辑~/.cursor/mcp.json格式相同。三件套必须写全Base URL 指向本地 Qdrant 和 OllamaKey 这里用本地环境变量替代Model ID 明确指定mxbai-embed-large和gpt-oss:20b。3.2 文档索引脚本创建index_docs.py负责把 Markdown 文档解析、分块、向量化并写入 Qdrant。import os import hashlib from pathlib import Path import ollama from qdrant_client import QdrantClient from qdrant_client.models import PointStruct, VectorParams, Distance QDRANT_URL os.getenv(QDRANT_BASE_URL, http://127.0.0.1:6333) OLLAMA_URL os.getenv(OLLAMA_BASE_URL, http://127.0.0.1:11434) EMBED_MODEL os.getenv(EMBED_MODEL, mxbai-embed-large) COLLECTION os.getenv(COLLECTION_NAME, tech_docs) DOC_DIR Path(./docs) client QdrantClient(urlQDRANT_URL) ollama_client ollama.Client(hostOLLAMA_URL) def chunk_markdown(text, source, max_tokens800, overlap80): lines text.split(\n) chunks, current, title_path [], [], [] for line in lines: if line.startswith(#): level len(line) - len(line.lstrip(#)) title_path title_path[:level-1] [line.strip(# ).strip()] if current: chunks.append(( .join(title_path), \n.join(current))) current [] current.append(line) if len( .join(current).split()) max_tokens: chunks.append(( .join(title_path), \n.join(current))) current current[-overlap:] if current: chunks.append(( .join(title_path), \n.join(current))) return chunks def embed(text): resp ollama_client.embed(modelEMBED_MODEL, inputtext) return resp[embeddings][0] def ensure_collection(): cols [c.name for c in client.get_collections().collections] if COLLECTION not in cols: client.create_collection( collection_nameCOLLECTION, vectors_configVectorParams(size1024, distanceDistance.COSINE) ) def index_file(path): text path.read_text(encodingutf-8) for idx, (title, chunk) in enumerate(chunk_markdown(text, str(path))): doc_id hashlib.md5(f{path}-{idx}.encode()).hexdigest() vector embed(f{title}\n{chunk}) client.upsert( collection_nameCOLLECTION, points[PointStruct( iddoc_id, vectorvector, payload{ text: chunk, source: str(path), title: title, chunk_index: idx } )] ) if __name__ __main__: ensure_collection() for md in DOC_DIR.rglob(*.md): index_file(md) print(findexed: {md})运行前先启动 Qdrant 和 Ollamadocker run -d -p 6333:6333 qdrant/qdrant ollama pull mxbai-embed-large ollama pull gpt-oss:20b python index_docs.py索引完成后Qdrant 里就有了带标题路径的文档向量。每个向量携带原文和来源信息检索时可以直接返回。3.3 MCP Server 核心工具实现创建rag_server.py暴露两个核心工具search_docs做纯检索ask_with_context做检索加生成。import os import json import ollama from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent from qdrant_client import QdrantClient QDRANT_URL os.getenv(QDRANT_BASE_URL, http://127.0.0.1:6333) OLLAMA_URL os.getenv(OLLAMA_BASE_URL, http://127.0.0.1:11434) EMBED_MODEL os.getenv(EMBED_MODEL, mxbai-embed-large) CHAT_MODEL os.getenv(CHAT_MODEL, gpt-oss:20b) COLLECTION os.getenv(COLLECTION_NAME, tech_docs) client QdrantClient(urlQDRANT_URL) ollama_client ollama.Client(hostOLLAMA_URL) server Server(rag-skill-tech-docs) def embed(text): return ollama_client.embed(modelEMBED_MODEL, inputtext)[embeddings][0] server.list_tools() async def list_tools(): return [ Tool( namesearch_docs, description在技术文档库中进行语义检索返回相关文档片段, inputSchema{ type: object, properties: { query: {type: string, description: 查询内容}, limit: {type: integer, default: 5} }, required: [query] } ), Tool( nameask_with_context, description基于知识库回答技术问题返回带引用的答案, inputSchema{ type: object, properties: { question: {type: string, description: 技术问题} }, required: [question] } ) ] server.call_tool() async def call_tool(name, arguments): if name search_docs: query arguments[query] limit arguments.get(limit, 5) vector embed(query) hits client.search( collection_nameCOLLECTION, query_vectorvector, limitlimit ) results [{ text: h.payload[text], source: h.payload[source], title: h.payload[title], score: h.score } for h in hits] return [TextContent(typetext, textjson.dumps(results, ensure_asciiFalse))] if name ask_with_context: question arguments[question] vector embed(question) hits client.search( collection_nameCOLLECTION, query_vectorvector, limit5 ) context \n\n.join([ f[{h.payload[title]}] {h.payload[text]} for h in hits ]) prompt f基于以下文档回答问题并标注引用来源\n\n{context}\n\n问题{question} resp ollama_client.chat( modelCHAT_MODEL, messages[{role: user, content: prompt}] ) return [TextContent(typetext, textresp[message][content])] async def main(): async with stdio_server() as (read, write): await server.run(read, write, server.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())这个 Server 通过 stdio 与客户端通信启动后客户端就能在对话中直接调用search_docs和ask_with_context。检索走本地 Qdrant生成走本地 Ollama整条链路没有外部网络请求。4. 验证请求与成功结果演示配置写完了得实际跑一次确认链路通。这一节演示从提问到命中的完整验证动作包括 stdio 直连测试和客户端调用测试。4.1 stdio 直连测试不启动客户端直接用 echo 管道模拟一次 MCP 请求验证 Server 能正常响应。echo {jsonrpc:2.0,id:1,method:tools/call,params:{name:search_docs,arguments:{query:数据库连接池配置,limit:3}}} | python rag_server.py预期返回一个 JSON 数组包含三条文档片段每条带text、source、title、score字段。如果返回空数组说明索引没建好或者查询向量和文档向量不在同一空间检查 Embedding 模型是否一致。4.2 客户端调用测试把mcp_config.json合并到 Claude Desktop 配置后重启在对话里直接问用户服务的鉴权接口怎么调客户端会自动调用ask_with_context工具Server 内部先检索再生成返回带引用的答案。你会在响应里看到类似这样的结构根据 [用户服务 / 鉴权 / Token 刷新] 文档 鉴权接口使用 Bearer Token刷新时调用 POST /auth/refresh 请求体包含 refresh_token 字段... 来源docs/user-service/auth.md整个过程从提问到返回本地实测在 800ms 到 1.2 秒之间。相比传统 RAG 的三到五秒体感上已经是“秒回”。4.3 延迟拆解与优化点把一次完整调用的耗时拆开看向量化查询约 30msQdrant 检索约 20msOllama 生成约 700ms 到 1 秒。生成占了大头但这是本地 20B 模型的正常水平。如果换成更小的模型或者流式返回首字延迟可以压到 300ms 以内。检索环节的 50ms 是 MCP 方案的核心优势。传统 RAG 里检索完还要把结果塞进提示词、等模型判断用哪条这个“检索-路由”双重延迟被 MCP 的协议直连消除了。客户端知道该调哪个工具Server 知道该查哪个集合没有中间商。4.4 多 Skill 路由验证如果你部署了多个 MCP Server比如技术方案、故障复盘、API 文档各一个客户端会根据问题类型自动选择。问“上个月数据库宕机的原因”路由到故障复盘 Server问“鉴权接口怎么调”路由到 API 文档 Server。每个 Server 的集合独立检索范围收窄召回精度提升。验证方法在客户端连续问三个不同领域的问题观察返回的source字段是否指向对应的文档目录。如果路由错了检查每个 Server 的description是否写清楚了适用场景客户端靠这个做路由判断。5. 本篇常见错误排查配置和验证过程中容易踩的坑集中在几个地方。这一节按真实报错对照排查覆盖 401、local proxy failed、reading choices、OAuth 等典型问题。5.1 401 Unauthorized如果你在 MCP Server 里调用了远程模型服务报 401 通常是 API Key 没配或配错。检查环境变量TAOTOKEN_API_KEY是否设置Base URL 是否指向https://taotoken.net/api。注意 API 地址不带 UTM 参数直接写https://taotoken.net/api即可。本地 Ollama 方案不会出现 401因为不涉及远程认证。如果你混用了远程生成和本地检索确认两套凭证分开管理不要互相覆盖。5.2 local proxy failed这个报错通常出现在客户端尝试连接 MCP Server 时。原因可能是 Server 进程没启动或者 stdio 管道被占用。检查mcp_config.json里的command和args是否正确python -m rag_server能否在终端单独跑通。另一个常见原因是端口冲突。Qdrant 默认 6333Ollama 默认 11434如果这两个端口被其他服务占用Server 启动时会报连接失败。用lsof -i :6333确认端口状态必要时改配置里的端口号。5.3 reading choices 报错这个错误一般出现在模型返回格式不符合预期时。MCP 工具调用要求返回结构化的TextContent如果你在call_tool里直接返回字符串而不是[TextContent(...)]客户端解析会失败。检查返回语句是否包在列表里type字段是否为text。还有一种情况是 Ollama 返回的message字段结构变了。不同版本的 Ollama SDK 返回格式略有差异用resp[message][content]取内容如果报 KeyError打印完整resp看实际结构。5.4 OAuth 认证失败远程 MCP Server 如果配了 OAuth客户端首次连接会跳转授权。报错通常是回调地址不匹配或 token 过期。检查 OAuth 应用的回调 URL 是否和客户端配置一致token 有效期是否设置合理。本地 stdio 模式不涉及 OAuth如果你在本地开发时遇到 OAuth 报错说明配置里混入了远程 Server 的认证信息。把mcp_config.json里对应的env清理干净本地 Server 不需要 OAuth。5.5 检索结果为空或不准索引建好了但检索不到先确认 Embedding 模型一致。索引用mxbai-embed-large查询也必须用同一个模型否则向量空间不对齐相似度计算全是噪声。如果结果不准检查分块策略。块太大导致语义稀释块太小导致上下文断裂。技术文档建议按二级标题切分块大小 512 到 1024 tokens。另外确认标题路径是否拼进了嵌入文本这个对召回率影响很大。5.6 三件套配置遗漏无论用 CC Switch、Cline MCP 还是 Codex 的auth.json配置 MCP Server 时三件套必须写全Base URL、Key、Model ID。Base URL 指向服务地址Key 用于认证Model ID 明确指定用哪个模型。缺任何一个都会导致连接失败或调用报错。以 Codex 的auth.json为例{ base_url: https://taotoken.net/api, api_key: sk-your-key-here, model: gpt-oss:20b }Cline MCP 的配置在设置面板里三件套分别对应 Server URL、API Key、Model。CC Switch 同理切换配置时确认这三项都填了。6. 把文档 MCP Server 接到长期编码流里跑通第一个文档 MCP Server 之后下一步是把它接到日常编码流里。Cursor 里写代码时直接问内部 API 怎么调Claude Code 里让 Agent 查故障复盘再改代码这些场景都能用同一套 MCP Server 支撑。如果你需要长期跑 Agent 任务或者团队多人共用一套知识库Coding Plan 比按次调用更划算。入口在 https://taotoken.net/coding-plan 配置方式和单次调用一致三件套填对即可。模型对话调试在 https://taotoken.net/models 接入文档在 https://taotoken.net/doc API Key 管理在 https://taotoken.net/api-keys 。实际用下来文档 MCP Server 最大的价值不是“快”而是“准”。检索范围收窄到单个 Skill 之后召回的相关性明显提升模型生成时不容易被无关片段带偏。你可以先从 API 文档这一个垂直领域开始跑通之后再横向扩展到技术方案和故障复盘。每个 Skill 独立部署、独立更新互不影响。最后一个实用技巧在 MCP Server 的description里写清楚这个 Skill 覆盖哪些文档、适合回答什么问题。客户端靠这个描述做路由判断写得好路由准确率能到九成以上。