很多维护内容产品线的朋友应该都有类似经历:手里同时跑着好几个站点,有的是知识库,有的是帮助中心,有的是数据报表,有的是开放接口文档。站点之间互不相通,AI 助手根本不知道该调哪个。看到“MCP”这个词后,我第一反应是把所有接入都收拢到这一个通道里,于是有了这次实践——把六个站的内容,真正塞进一个 MCP server 里。
简单说,MCP(Model Context Protocol)是当前 AI 应用与外部数据、工具之间互操作的标准协议,它能让 Claude、Cursor、Codex 这类 AI 客户端以同一套方式调用你自定义的服务能力。这次我把六个站点的查询、检索、资源读取统一收拢成一个本地服务,并在不同 AI 工具里跑通了基于自然语言的交互。如果你也想把一堆“内容入口”整合进 AI 工作流,这篇文章的思路和代码可以直接抄。
1. 为什么把六个独立站点压进同一个 MCP
1.1 内容运维里的真实痛点
我先交代一下这六个站点的背景。它们不是同一套系统的不同页面,而是六个相对独立的 Web 服务:第一个是团队的知识库,存内部技术沉淀和项目总结;第二个是用户帮助中心,有大量 FAQ;第三个是产品公告站,发布版本更新和变更记录;第四个是开放接口文档站,承载开发者的 API 参考;第五个是数据看板站,里面有核心指标和运行报告;第六个是活动落地页站,包含线上活动的规则、排期和素材下载。
以前要想让 AI 回答“帮我把这个月的产品更新整理成摘要”,我得先把产品公告、知识库、数据看板里的内容手动拉出来,再丢给大模型。这个过程既不实时,也容易漏。换句话讲,真正的痛点不是“没有内容”,而是“每一个内容源都是一个孤岛”。AI 工具再聪明,也只能看到你塞进去的那一段聊天窗口。
1.2 MCP 是在给 AI 开一扇可以自主进出的门
MCP 解决的就是连接问题。MCP server 启动后,AI 客户端可以通过协议发现我暴露出的工具、资源和提示词模板。AI 需要确认某个问题涉及哪个站,就会主动去调用对应工具,而不是等我人工复制粘贴。本质上相当于把原先需要人肉完成的数据检索和拼接动作,变成标准化的服务调用。
为什么不是普通 HTTP API?普通 API 当然也能让 AI 请求,但还缺一层“发现”和“权限确认”。MCP 的 client-server 结构,让 AI 应用在启动时就知道有哪些可用能力,以及每个能力接收什么参数。让我更省心的是,AI 会在推理过程中自行选择一个合适的工具,再根据返回值继续加工内容。这比“我写好接口、再手写 prompt 让 AI 调”要顺滑得多。
1.3 把“复制粘贴”变成一次内部编排
这次实践里我把六个站点重构成一个逻辑层:它们的数据源仍然保留自己原本的存储结构,但这个 MCP server 作为统一出入口。六个站的运维人员不需要改自己站内的技术框架,只需要把内容制作为可查询的索引,或提供内部查询接口。最终的效果就是,AI 对话工具里所有跨站问题都变成了对这一个 MCP 服务器的常见调用。
如果你现在维护的站点或内容源不止一个,那么这种做法普遍适用。你不需要从一开始就做很重的“数据中台”,只需要在 MCP server 里以适配层的方式去接各站的数据库、文件或内部接口,就能让 AI 以一个统一视角来处理分散的站点数据。
2. 动手之前先搞懂 MCP 里的三类能力
2.1 Tools、Resources、Prompts 各管什么
MCP 约定俗成地把能力分为三种:Tool、Resource、Prompt。Tool 是让 AI 执行动作或获取结果的函数,比如查询某个文章、保存某个草稿;Resource 是可供读取的结构化内容,强调“内容可以被读取并参与上下文构建”;Prompt 是预设的提示模板或指令,用来规范 AI 以特定方式处理问题。
实操中,我建议在头脑里把三者对应为命令、文件和模板。Tool 是命令,AI 只能通过参数执行并拿到返回值;Resource 是文件,可以在对话中直接引用,需要按 URI 协议读取;Prompt 是模板,当用户的目标符合模板时自动套用。这三者可以同时存在于同一个 server 中。
2.2 六个站的资源如何映射成这些能力
我的映射逻辑是这样的:知识库和帮助中心的内容都以文章为主,但它们面向的场景不同,所以我把知识库文章做成 Resource,路径类似sixhub://wiki/{article_id};帮助中心的问答更适合做 Tool,因为用户的问题是搜索式、会话式的。产品公告站和活动落地页的更新频率不高,但会在固定时间发生内容变动,做成 Resource 更合适。开放接口文档站和数据看板站的数据结构很规整,但通常要求带参数按需查询,所以我把它们做成 Tool。
这样的拆分不是唯一答案,但它符合一个直觉:凡是需要“取出某一段固定内容”的就用 Resource,凡是“根据条件动态计算结果”的就用 Tool。活动站里的素材下载地址和排期,如果是一张固定的表,我甚至直接做成 Resource,让 AI 不需要额外调工具就能读懂。
2.3 为什么“一个 Server 装六个站”而不是跑六个 Server
很多刚接触 MCP 的人容易反过来:给每个站点各起一个 MCP server,然后在客户端里全部接入。听起来模块化,但实际使用中常有三个坑:一是 AI 客户端需要启动的连接数量变多,故障排查麻烦;二是同一轮问答里,AI 在多个工具之间切换时,若不手动声明来源,容易用错上下文;三是对权限边界的管理会散落多处。
我统到一个 server 中后,就可以在同一个进程里完成六站的数据聚合、统一鉴权和日志记录。遇到问题只需要盯一个服务的日志。而且一次连接即可让 AI 具备六个站点的能力。如果你的站点更多情况更极端,拆成“后台服务域+前端展示域”两个 server 或许更合理,但我的建议永远是:优先减少 AI 上下文配置的复杂度,减少无效的横向分散。
3. 核心工程:用 Python FastMCP 搭出六站统一服务
3.1 技术栈与目录设计
我用的是 Python 的 FastMCP,因为它的抽象层非常简单,天然支持用装饰器把函数暴露成 Tool,也支持把某个目录或资源注册为 Resource。当然你也可以用官方 TypeScript SDK,语言无所谓,只要遵循 MCP 协议即可。
工程目录大致这么放:
sixhub-mcp/ server.py sync_jobs.py config.yaml sources/ wiki/ help_center/ release_notes/ api_docs/ data_report/ campaign/ storage/ index.db其中sources/下六个目录分别放对应站点的内容快照或抓取脚本生成的 JSON。server.py负责注册工具、资源和 prompt;sync_jobs.py负责定时从各站拉取数据;storage/index.db是一个 SQLite 数据库,存全站内容的检索索引。
3.2 注册检索工具:让 AI 自己能跨站找内容
我做的第一个核心动作,是把六个站的搜索能力抽象成一个search_all工具。它接收三个参数:查询关键词、时间范围、限定站点名。先用一个 SQLite 全文索引把所有站点的正文汇聚起来,再按布尔逻辑过滤。这个方案比真正实时去每个站请求轻量很多,因为从外部站点实时抓全文会极大拖慢 AI 决策速度,也很容易触发对方的安全策略。
代码大致是这样:
from mcp.server.fastmcp import FastMCP import sqlite3 mcp = FastMCP("sixhub") def query_index(keyword, start_date=None, site=None): conn = sqlite3.connect("storage/index.db") base = """ SELECT site, title, url, publish_date, snippet FROM content_index WHERE content_index MATCH ? """ params = [keyword] if start_date: base += " AND publish_date >= ?" params.append(start_date) if site: base += " AND site = ?" params.append(site) base += " ORDER BY publish_date DESC LIMIT 20" result = conn.execute(base, params).fetchall() conn.close() return result @mcp.tool() def search_all(keyword: str, start_date: str = "", site: str = "") -> list: """跨六个站点统一检索文章、问答、公告、API 文档或活动信息。""" return query_index(keyword, start_date, site)这里要注意一个细节:content_index MATCH ?使用的是 SQLite FTS5 语法,如果你传入的 keyword 带引号或特殊符号,需要先做转义。否则搜索引号会直接报错,AI 不知道该怎么办。可以写一个小函数把输入中的"、(、)替换掉。
def sanitize_query(keyword): for ch in ['"', "'", "(", ")", "*"]: keyword = keyword.replace(ch, " ") return keyword.strip()3.3 配置按站点读取详情的资源方法
搜索是第一步,命中之后 AI 还要能读取详情。对知识库、帮助中心这类内容,我选择用 Resource URI。FastMCP 支持以@mcp.resource方式暴露一个动态资源,比如/wiki/{article_id}。它跟 Tool 的区别是:Resource 的调用方通常把它视为内容本身,可以直接填充到上下文中;而 Tool 更像是一个操作命令。
来看示例:
@mcp.resource("sixhub://wiki/{article_id}") def read_wiki(article_id: str) -> str: path = f"sources/wiki/{article_id}.md" try: with open(path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: return "知识库中没有找到对应文章,请确认 ID 是否正确。"实际配置中,客户端会把这个 URI 当作一个可访问的资源地址,而不是“先传函数再等回显”的接口。这种资源很适合知识沉淀场景,因为知识库文章的正文本身就是一个长文档,如果每次都让 AI 用工具去查,它只能拿到返回值,难以在推理中自然地复述或引用,Resource 模式更贴近阅读本身。
3.4 用 Prompt 模板把跨站摘要变成半成品
我还注册了一个 Prompt,名字叫summarize_site_updates。它做的事情比较“轻”,只给定一个输出框架,让 AI 调用前几步的工具,然后把结果填入框架。
@mcp.prompt() def summarize_site_updates(site: str, since: str) -> str: return f"""请帮助我把站点「{site}」自 {since} 以来的重要更新整理成周报。 要求: 1. 先通过 search_all 搜索该站点最近更新内容。 2. 按照功能新增、问题修复、文档变更、运营活动四类归纳。 3. 每条必须标注来源标题。 4. 最后加一段“对当前项目的影响”分析。"""这里的价值在于,它不让每次都需要我自己写一套繁琐的 prompt。AI 用户只需要简单说“更新周报”,Prompt 模板就会被选中,模型知道要调用哪些工具、以什么格式输出。对习惯用自然语言的人来说,这个设计很直观。
4. 数据同步、索引更新和六站一致性维护
4.1 内容源定时同步,而不是让 AI 实时爬页面
前面已经提过,我把六个站的内容都同步到本地索引中。那具体怎么做?我写了一个sync_jobs.py,针对不同的源采用不同策略。知识库我直接读取底层数据库导出接口;帮助中心和开放接口文档站提供 JSON 快照;产品公告站我通过内部 RSS 拉取;活动站排期则读运营同学维护的 Google Sheets 导出 CSV;数据看板站的指标数据则每天凌晨调报表系统的内部 API,存成一份 day_snapshot。
这和我一开始设想的差别很大。最开始我以为靠 MCP 运行时现查就能解决问题,但现实告诉我,AI 工具的行为特点是“能并行就不要串行,能快速拿到结果就不要等待外部网络”。如果 MCP 工具每次搜索都请求远程 URL,响应延迟很容易突破 10 秒,AI 客户端很可能判定工具调用超时,返回幻觉。
4.2 全文检索索引怎么建立
对六个站来说,一个可靠的做法是统一建一张表。我每次同步后会生成每篇文章的title、url、site、publish_date、snippet以及正文全文。然后利用 SQLite FTS5 建全文检索表。
建表语句大概是:
CREATE VIRTUAL TABLE content_index USING fts5( site, title, url, publish_date, snippet, content, tokenize = 'unicode61' );如果你没有那么多站,也不需要做到海量搜索,那么这个层面的全文索引就足够。如果你要处理的站点内容里有大量代码块、表格,直接建一个 HTML 正文清洗流程,把标签去掉再入库,搜索准确率会高很多。
这里有一个很妙的点:为了返回更适合 AI 上下文的结果,我在search_all里不会返回完整正文,只返回snippet和 URL。让 AI 先通过摘要判断该读哪一篇,再通过资源读取完整内容。这也正好利用 MCP 本身的工具分工,让检索成本和阅读成本分开。
4.3 同步失败和脏数据的处理
数据处理上踩过的坑比写代码多。第一个坑是内容源里会有大量重复页面,比如同一个 FAQ 在不同站点都放了一份。我在索引表里加了一个dedup_key,以“标题+来源URL”为唯一键。第二次同步时,如果标题相同但正文版本更新,就更新时间戳,而不是插入新行。
第二个坑是公告站删除的文章,索引里还在。我在同步进程里引入了一个软删除标记。同步时遇到已删除的文章会更新一个is_deleted字段,查询处默认过滤。如果直接物理删除,那历史数据引用的旧链接就不可追踪了。
4.4 索引快照与实时查询之间的取舍
虽然数据同步是定时执行的,但有些验证类查询不适合走旧索引。比如活动站,报名人数、剩余名额这类实时数据绝对不能缓存。我另外做了一批“实时型 Tool”,这些工具在调用时直接访问运营服务提供的只读接口或 websocket 状态接口,并设置短超时 2 秒。这样就形成了基础内容走缓存、状态数据走实时的混合模式。
这个混合模式非常关键。六个站点的性质不同,冷热数据差异极大。把所有数据都做成实时查询,系统扛不住且没必要;把所有数据都做成同步缓存,又可能导致 AI 给出过期答案。最好按更新频率和业务容忍度区分,只要把规则写清楚,AI 的决策基本不会错。
5. 在 Claude Desktop、Cursor、Codex 等端的接入与实测
5.1 标准 JSON 配置接入所有工具
MCP server 写好后,接入客户端的方式大同小异。以 Claude Desktop 为例,在配置文件里加上:
{ "mcpServers": { "sixhub": { "command": "uv", "args": ["run", "--directory", "/path/to/sixhub-mcp", "server.py"], "env": { "PYTHONUNBUFFERED": "1" } } } }Cursor 的配置也差不多,只是去对应菜单里选 MCP servers 入口,把同样的配置粘贴进去。Codex 这类集成度不高的端,一般会提供命令行模式,可以先用官方 CLI 验证。
5.2 第一次实测:一个自然语言问题跨了三个站
接好后,我第一个测试问题是:“最近两周,知识库里有没有关于部署的教程,另外产品公告有没有相关更新?如果有,请整合到 300 字以内的说明。”
这一步让我很惊讶,AI 确实自动先调用了search_all,看到返回结果里同时有知识库文章和产品公告站内容。随后它又问自己:知识库里的文章需要阅读详情吗?于是它直接读取了 Resource URI,再结合公告站的返回结果,生成了整合回答。
也就是说,AI 不是死板地按预设链路顺序执行,而是根据返回内容动态决策。我在日志里能看到,它一共调用了三次:第一次搜知识库关键词,第二次读详情资源,第三次搜产品公告站。看起来毫无压力。
5.3 排查频率很高的几类问题
接入完成后,我也在几个工具间来回切,遇到了几个比较典型的问题,整理在表格里供你速查:
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 工具一直注册不上 | MCP server 启动报错,或 URL 填错 | 先在终端直接运行python server.py看输出,确认没有 import 错误 |
| 资源 URI 读取返回空 | 配置里没有设置正确的 resource 前缀,或文件路径不对 | 在 server 内写一个 self-test,用mcp.call_resource方法自行验证 |
| 工具响应超时 | 实时接口或索引库锁定过久 | 给每个待调用的 HTTP 接口设置更短的超时,并减少返回字段 |
| AI 搜索时一直在转圈 | 返回内容过大,或关键词第一次没匹配 | 缩小每个输出字段长度,把长正文拆到详情资源接口里 |
| 搜索特殊字符报错 | FTS5 查询语法问题 | 增加sanitize_query对引号和括号过滤 |
| 并发多个查询会串结果 | SQLite 连接被多线程共用 | 使用threading.local或每次请求新建独立连接 |
5.4 给命令式工具设置“最小权限”习惯
MCP server 里可以写很多工具,但不建议给 AI 一个“万能执行器”。我这次实践里有一个很强的体会:注册给 AI 的工具越具体,越不容易出错。如果某个接口可以修改删除操作,我宁可在函数定义里把参数白名单写死,也不要在注释里让 AI 自己判断参数是否合法。
比如我在活动站里做了一个“查询活动报名状态”的工具,就只接收活动 ID,不接收任何 SQL 字符串;又比如知识库写入工具,我没有把它放进 public 的工具集里。MCP 工具的能力越原子化,AI 决策的可控性就越高。
6. 跑通一个月后的复盘与工具边界认知
这个 MCP server 已经稳定跑了一段日子。最实际的收益是,我每周写站点运营周报的时间从原来的一小时缩减到了十五分钟。以前我要去产品公告站复制更新内容、去知识库翻文章阅读量、去数据看板截指标,如今一句“生成六站运营周报,按内容更新和效果数据两条线组织”,AI 就会自己完成整个检索链,我只需要审核它生成内容里的数据是否来源于正确站点。
不过也要泼点冷水:MCP 并不是万能的。它生效的前提是你真的把内容源的复杂度控制住。如果六个站各自维护的数据库、文本格式、API 参差不齐,光适配层就会写得又臭又长。我先给每个站点统一下发了一个轻量的 JSON 快照规范,然后再接入 MCP,速度就快多了。
第二个体会是,MCP 工具的数量不宜贪多。最初我把六站里的几十个功能接口全部暴露成 Tool,结果 AI 在每次任务里花大量时间去匹配工具,甚至会出现看起来选择很多但每个都不是最优的情况。后来我克制地收敛到十几个核心工具,把低频需求合并成一个通用查询,AI 的准确率和速度反而提高不少。
最后分享一个我很受用的小技巧:给每一个 MCP 工具或资源都写清楚简短的功能描述。因为 AI 判断“该不该调用这个工具”时,主要依据是函数的description和参数名,而不是看内部代码。描述写得越清晰,工具命中率越高。不要觉得这是文档负担,它其实是 MCP 使用质量的决定性因素之一。
如果你现在也维护着多个独立网站或内容服务,可以试着先从一个最小闭环开始:确认三个信息源、落地一个检索工具、把一个客户端接通过。等跑顺后再把更多站点加入进来。这比我一开始就想一步到位把六个站全部接进来更稳妥,也能让你更快理解 MCP 协议能为工作流带来的这种“一键集成”潜力。