1. 从一个“没人发现”的小产品说起
去年年底我把自己做的一个小工具挂到了网上,功能很垂直——帮独立开发者批量检查落地页的 SEO 基础项,比如 title 长度、meta 描述缺失、H1 重复、图片 alt 为空这类琐碎但影响收录的问题。上线三个月,自然流量每天不到二十个 UV,转化率倒还行,但基数太小,基本等于自娱自乐。
问题出在哪?我复盘了很久。不是产品不行,是发现路径太长。用户得先知道有这么个东西,再打开浏览器,注册,粘贴网址,等结果。每一步都在流失。而与此同时,我注意到一个明显的变化:身边越来越多的开发者开始把日常任务交给 AI 代理去跑——写代码用 Claude Code,改 bug 用 Cursor,查资料用带联网能力的对话助手。这些代理已经能读文件、跑命令、调 API,但它们不知道我的产品存在,更没法主动去用它。
这就是我决定给小产品写一个 MCP server 的直接动机。MCP 是 Model Context Protocol 的缩写,简单说它是一套让 AI 代理和外部工具之间“对话”的约定。写完之后的效果是:当用户在 Claude、Cursor 这类支持 MCP 的客户端里提出“帮我检查一下这个落地页的 SEO 问题”时,代理能自动发现我的工具、调用它、拿到结构化结果,甚至根据我的定价规则给出报价。整个过程用户不需要离开对话窗口,也不需要知道我的产品叫什么名字。
这篇文章我会把整件事拆开讲清楚:MCP server 到底是什么、为什么它能让 AI 代理“发现”你的产品、agent-to-agent commerce 这个趋势意味着什么、我具体怎么实现的、踩了哪些坑、以及如果你也想给自己的小产品接上这条路,应该从哪一步开始。适合独立开发者、做 SaaS 小工具的人、以及对 AI 代理生态感兴趣但还没动手的读者。不需要你之前写过 MCP,我会把关键概念用生活化的方式讲明白。
2. MCP server 到底是什么,为什么它成了 AI 代理的“插座”
2.1 用一句话解释 MCP:AI 世界的 USB-C
你可以把 MCP 理解成 AI 代理和外部能力之间的标准插座。在 MCP 出现之前,每个 AI 客户端想接一个外部工具,都得自己写一套适配代码:Claude 有一套,Cursor 有一套,别的客户端又有一套。工具提供方要维护 N 份对接逻辑,累且容易出错。
MCP 把这个关系反过来了。工具方只需要按协议实现一个 server,暴露自己的能力(有哪些工具、每个工具接受什么参数、返回什么结构),任何支持 MCP 的客户端都能直接连上来用。就像 USB-C 接口统一了充电和数据传输,你不需要为每个设备准备不同的线。
具体到技术层面,一个 MCP server 通常暴露三类能力:
- Tools(工具):代理可以主动调用的函数,比如“检查 SEO”“生成报告”。这是最核心的一类,也是我这次主要实现的部分。
- Resources(资源):代理可以读取的数据,类似文件或数据库记录。
- Prompts(提示模板):预置的提示词模板,帮代理更好地使用这些工具。
对独立开发者来说,最有价值的是 Tools。因为它意味着你的产品能力可以被代理当成一个函数来调用,而不是让用户去学你的界面。
2.2 为什么代理需要“发现”能力,而不是硬编码
这里有个关键区别,值得单独说清楚。早期的 AI 工具集成,基本是硬编码的:开发者提前在客户端里写死“如果用户问 SEO,就调用某某 API”。这种方式的问题是,工具列表是静态的,新增一个工具就得改客户端代码,普通开发者根本没机会被集成进去。
MCP 的“发现”机制改变了这一点。代理在运行时可以查询 server 暴露了哪些工具、每个工具的用途描述是什么。当用户的请求和某个工具的描述匹配时,代理就会调用它。这意味着你的产品能不能被用上,取决于你的工具描述写得好不好,而不是取决于你有没有关系把它塞进某个客户端的白名单。
这对小产品是巨大利好。你不需要谈合作、不需要买流量,只需要把工具描述写清楚、参数设计合理,就有机会被代理在合适的场景下选中。我实测下来,工具描述里把“什么时候该用我”写明白,被调用的概率会明显提升。
2.3 agent-to-agent commerce 意味着什么
再往深一层看,这件事的意义不只是“被调用”,而是交易本身可以由代理之间完成。当代理能发现工具、调用工具、拿到结果,它自然也能处理付费环节:根据工具返回的报价信息,决定是否继续、是否升级到付费档、是否把结果转给用户确认。
这就是 agent-to-agent commerce 的雏形。传统的 SaaS 付费流程是:用户看到定价页、比较、注册、绑卡、使用。而在代理场景下,流程变成:代理发现工具、评估能力、读取报价、在授权范围内完成调用、把结果和费用一起汇报给用户。用户看到的只是一个结果和一笔小额支出,中间的摩擦被抹平了。
我这次给小产品加的报价能力,就是朝这个方向迈的一小步。代理调用检查工具时,免费档返回基础结果,同时附带一个结构化的报价对象,说明完整报告需要多少费用、包含哪些额外项。代理可以把这个信息呈现给用户,用户确认后代理再发起付费调用。整个链路是机器可读的,不需要人去点网页。
3. 动手之前:先想清楚你的产品该暴露什么
3.1 不是所有功能都适合做成 MCP 工具
我一开始的想法很贪心,想把产品的所有功能都暴露出去。后来发现这是错的。MCP 工具的设计原则和网页功能设计完全不同:网页可以有很多按钮、很多页面,用户自己探索;但代理调用工具时,每一次调用都要消耗上下文和推理成本,工具太多、太碎,代理反而不知道该用哪个。
我的做法是先问自己三个问题:
- 这个功能能不能用一句话说清楚它解决什么问题?
- 它的输入输出是不是结构化的、可预期的?
- 它是不是用户会在对话里自然提出的需求?
三个都满足的,才做成工具。比如“检查单个 URL 的 SEO 基础项”满足,“管理我的历史检查记录”就不满足——后者更适合做成 Resource 让代理读取,而不是做成一个需要参数的工具。
3.2 工具粒度:粗一点还是细一点
这是设计时最纠结的地方。粒度太细,比如把“检查 title”“检查 meta”“检查 H1”拆成三个工具,代理得调用三次,每次都往返一次网络,慢且费 token。粒度太粗,比如一个“全面检查并修复”工具,参数复杂、返回巨大,代理处理起来也吃力。
我最后选的是中等粒度:一个“检查”工具负责诊断,返回结构化的问题列表;一个“报价”工具负责根据问题数量给出费用估算。两个工具职责清晰,代理容易理解,调用次数也少。实测下来,代理在大多数场景下只需要调用一次检查工具就能给出有用回答。
提示:工具数量控制在 3 到 7 个之间是比较舒服的区间。太少显得能力单薄,太多会让代理的选择困难,调用准确率下降。
3.3 描述文案比代码更重要
这点我必须强调。MCP 工具的description字段,是代理决定要不要调用你的唯一依据。它不像网页有视觉设计帮你吸引点击,代理只能读文字。所以描述要写得像给一个新同事交代任务:说清楚这个工具做什么、什么时候用、输入是什么格式、返回什么。
我最初的描述写得很技术化,类似“执行 SEO 规则引擎并返回违规项”。结果代理很少调用它,因为它不知道这跟用户的“帮我看看网站有没有问题”有什么关系。改成“检查一个网页的 SEO 基础问题,当用户想知道页面为什么没被搜索引擎收录、或者想优化落地页时使用”之后,调用率明显上来了。
4. 核心实现:从零搭一个能被代理调用的 MCP server
4.1 技术选型与最小依赖
我选的是官方提供的 SDK,语言用 TypeScript。原因很实际:我的小产品后端本来就是 Node 生态,复用现有代码成本最低;而且 MCP 的官方示例和文档里 TypeScript 版本最完整,遇到问题好查。
最小依赖其实很少,核心就是 SDK 本身加一个传输层。传输方式有两种常见选择:
| 传输方式 | 适用场景 | 我的选择 |
|---|---|---|
| stdio | 本地运行,客户端直接拉起进程 | 开发调试阶段用 |
| HTTP/SSE | 远程服务,多客户端共享 | 正式上线用 |
我最终上线用的是 HTTP 方式,因为我的检查逻辑跑在服务器上,用户本地不需要装任何东西。stdio 方式适合那种纯本地工具,比如读写本地文件的场景。
4.2 定义工具:参数 schema 怎么写才不容易出错
工具的参数用 JSON Schema 描述。这里有个坑我踩过:参数类型和必填项一定要写严格。我一开始把 URL 参数写成可选,结果代理有时候不传,服务端报错,代理拿到错误后也不知道怎么补救,整个对话就卡住了。
正确的做法是把必填参数标成required,并且在描述里给出格式示例。比如 URL 参数我会写“完整的网页地址,必须以 http:// 或 https:// 开头,例如 https://example.com/landing”。代理看到示例后,传参的准确率会高很多。
另外,返回值的结构也要稳定。我定义了一个固定的返回格式:一个issues数组,每项包含type、severity、message三个字段。代理拿到这种结构后,能很自然地把它转述给用户,或者做进一步处理。如果返回值每次结构都不一样,代理的后续推理就会乱。
4.3 报价逻辑:让代理能读懂“多少钱”
报价这块是我花时间最多的地方。核心思路是:报价必须结构化,且包含足够信息让代理做决策。我返回的报价对象大概长这样:
currency:货币单位amount:金额tier:档位名称,比如 basic、fullincludes:这个价格包含哪些内容valid_until:报价有效期
代理拿到这个对象后,可以把它转述给用户,也可以根据用户之前的授权直接决定是否购买。我特意加了valid_until,因为价格可能会变,代理需要知道这个报价什么时候过期,避免拿着旧价格去下单。
注意:报价信息不要藏在自然语言里。我见过有的实现把价格写成“完整报告只需 9.9 元”,代理要解析这句话才能拿到数字,很容易出错。结构化字段才是正道。
4.4 错误处理:代理最怕“沉默失败”
代理调用工具失败时,如果服务端只返回一个 500 错误、没有说明,代理就完全不知道发生了什么,只能告诉用户“出错了”。体验很差。
我的做法是所有错误都返回结构化的错误对象,包含错误码和人类可读的说明。比如 URL 格式不对,返回INVALID_URL加一句“提供的地址格式不正确,请检查是否包含 http 前缀”。代理拿到这个信息后,可以自动重试或者提示用户修正,而不是直接放弃。
5. 联调实录:在 Claude 和 Cursor 里跑通全流程
5.1 本地调试:先用 stdio 把逻辑跑顺
正式部署前,我在本地用 stdio 方式把整个流程跑了一遍。这一步的价值在于快速验证工具定义和返回结构,不用每次都部署到服务器。调试时我会在客户端里输入各种刁钻的请求,比如“帮我看看这个页面”“这个网址有什么问题”“我的落地页收录不好”,观察代理是否能正确选中我的工具、参数是否传对、返回是否被正确解读。
这个阶段我发现了一个问题:代理有时候会把整个网页内容当成参数传进来,而不是只传 URL。原因是我的参数描述不够明确。改成“只传网页地址,不要传网页内容”之后,问题解决了。
5.2 部署到远程:HTTP 传输的注意事项
部署到服务器后,用 HTTP 传输。这里有几个实际要注意的点:
- 鉴权:远程 server 必须做鉴权,否则任何人都能调用你的付费工具。我用的是简单的 token 机制,代理在请求头里带上 token。
- 超时:检查逻辑如果跑得慢,要设置合理的超时,并且返回明确的超时错误,让代理知道可以重试。
- 并发:多个代理同时调用时,要保证报价和扣费逻辑是线程安全的,避免同一个报价被重复使用。
5.3 在 Cursor 里配置 MCP server
Cursor 对 MCP 的支持比较直接,在设置里找到 MCP 相关配置,填入 server 的地址和鉴权信息即可。配置完成后,Cursor 的代理就能在对话中调用我的工具。我实测的场景是:在 Cursor 里让代理帮我检查一个正在开发的落地页,代理自动调用了我的工具,返回了问题列表,还根据报价信息问我要不要生成完整报告。
5.4 在 Claude 里配置 MCP server
Claude 桌面版的配置方式类似,也是在设置里添加 MCP server。这里有个细节:不同客户端对工具描述的解析方式略有差异,同一个描述在 Cursor 里能被正确理解,在 Claude 里可能需要微调措辞。我的经验是描述里多用动词和场景词,比如“检查”“诊断”“当用户想……时使用”,跨客户端的兼容性会更好。
6. 踩过的坑与排查速查表
6.1 代理不调用我的工具,怎么办
这是最常见的问题。排查顺序我总结成一张表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全不调用 | 工具描述太技术化 | 改成场景化描述,加入“当用户想……” |
| 偶尔调用 | 描述和其他工具重叠 | 检查是否有功能相近的工具,明确差异化 |
| 调用但传参错 | 参数 schema 不清晰 | 加格式示例,标严格必填 |
| 调用后报错 | 返回结构不稳定 | 固定返回字段,错误也结构化 |
6.2 报价被代理误解的几种情况
我遇到过代理把报价金额当成字符串处理、或者把有效期忽略的情况。解决办法是在描述里明确字段类型,并且在返回示例里给出一个完整的报价对象。代理看到示例后,解析准确率会高很多。
6.3 性能与成本的实际感受
MCP 调用本身开销不大,主要成本在代理的推理上。工具返回的内容越长,代理处理越慢、越贵。所以我的原则是返回必要信息,不返回冗余内容。比如检查结果只返回问题项,不返回整个页面的 HTML。
7. 这条路接下来还能怎么走
写完这个 MCP server 之后,我的小产品确实多了一条被发现的路。虽然目前通过代理来的调用量还不大,但趋势很明显:越来越多的任务会由代理发起,而不是由人打开网页发起。对独立开发者来说,早点把自己的能力做成代理能调用的形式,相当于在一条新渠道上提前占了位置。
我接下来打算做的几件事:一是把报价逻辑做得更细,支持按问题严重程度分级定价;二是增加一个 Resource,让代理能读取我的产品文档,回答用户关于功能的问题;三是观察不同客户端对工具描述的偏好,持续优化文案。
如果你也想动手,我的建议是从一个最小的工具开始,先跑通“被发现、被调用、返回结果”这个闭环,再考虑报价和商业化。工具描述多改几版,观察代理的调用行为,比闷头写代码有用得多。这个领域变化很快,但底层逻辑很稳:让代理能理解你、调用你、信任你,你就多了一个不需要用户主动找上门的入口。