
1. 从一个没人愿意手动报价的小产品说起我手上有个很小的产品小到只有我一个人维护功能单一但确实有一小撮人在用。它的商业模式也很朴素按调用量收费用户想用就自己去看价格表、自己下单。问题出在自己去看价格表这一步——我陆续收到过几封邮件问的都是同一类问题你这个东西到底怎么收费能不能给我一个报价我这边有个自动化流程能不能让你的服务直接告诉我价格前两个问题我还能手动回第三个问题让我意识到一件事当调用方从人变成AI 代理的时候人肉报价这条路就走不通了。代理不会打开你的定价页面不会读你的 FAQ它只会做一件事——按照它能理解的协议去问、去拿、去比价。如果你的产品没有暴露一个机器可读的接口那在代理眼里你这个产品等于不存在。这就是我给自己的小产品写 MCP server 的直接动机。MCP 是 Model Context Protocol 的缩写简单说它是一套让 AI 客户端比如 Claude、Cursor 这类工具能够以标准化方式调用外部能力的协议。你写一个 MCP server就等于给你的产品装了一个AI 可读的说明书 操作面板。代理连上来之后能发现你有哪些能力、每个能力要多少钱、怎么调用甚至能直接完成一次报价请求。关键词里提到的MCP server、AI 代理、agent-to-agent commerce、Claude、Cursor基本就是这件事的全部拼图。这篇文章我会把整个过程的来龙去脉讲清楚为什么小产品也需要 MCP、MCP server 到底暴露了什么、报价这个动作怎么设计成工具、代理是怎么发现你的、以及我在实测中踩到的那些坑。适合手上有小产品、小服务、小 API想让 AI 代理能自动对接的独立开发者看也适合单纯想搞明白 MCP 到底怎么落地的人。先说结论这件事的技术门槛比想象中低难的是想清楚你要暴露什么给代理。代码可能两百行设计要想两天。2. MCP server 到底给代理暴露了什么2.1 把 MCP 理解成给 AI 用的 USB 接口很多人第一次接触 MCP 会懵因为它听起来像又一个协议标准。我用一个类比USB 接口出现之前每个外设都有自己的插头和驱动键盘一个口、打印机一个口、鼠标一个口。USB 出现之后只要设备符合 USB 规范主机就能识别它、枚举它的能力、按标准方式通信。MCP 对 AI 客户端做的事是一样的。在 MCP 之前你想让 Claude 或 Cursor 调用你的服务得针对每个客户端写适配、写提示词、写函数定义客户端一升级你就得跟着改。MCP 把这些统一了你写一个 server声明你有哪些tools工具、哪些resources资源、哪些prompts提示模板任何支持 MCP 的客户端连上来都能自动发现这些能力。这里有个关键点容易被忽略MCP 的核心价值不是调用而是发现。调用这件事 HTTP API 早就能做代理也能通过 function calling 调你的 API。但发现不一样——代理不需要预先知道你的存在它连上 MCP server就能拿到一份自描述的能力清单。这才是 agent-to-agent commerce 能成立的前提代理 A 想找某个能力它去问一圈 MCP server谁有、多少钱、怎么调一目了然。2.2 tools、resources、prompts 三件套的分工MCP server 对外暴露的东西主要分三类我按自己的理解重新解释一遍不照搬文档tools可执行的动作有副作用。比如下单报价查询余额。代理调用 tool 相当于按了一个按钮会改变状态或产生结果。这是报价场景的主战场。resources只读的数据没有副作用。比如当前价格表服务状态使用文档。代理读 resource 相当于翻了一页说明书不会改变任何东西。prompts预置的提示模板帮代理更好地使用你的能力。这个我一开始觉得多余后来发现对报价场景挺有用——你可以预置一个如何为批量调用询价的模板代理照着填参数就行。我自己的产品只用了 tools 和 resources 两类。报价走 tool因为要按参数计算有逻辑价格表走 resource静态数据只读。这个划分不是拍脑袋而是有实际考虑的如果报价也做成 resource代理就没法传参它只能拿到一个固定价格没法根据调用量、调用类型给出差异化报价。而差异化报价恰恰是 agent-to-agent commerce 里最有价值的部分。2.3 为什么小产品反而更适合先上 MCP大厂上 MCP 是战略动作要考虑生态、要考虑兼容、要走一堆流程。小产品没这些包袱反而更适合先试。原因有三个第一小产品的能力边界清晰。我的产品就三四个功能暴露成三四个 tool 就够了不需要设计复杂的权限体系。大产品动辄几百个接口光是想清楚哪些该暴露就得开会开一个月。第二小产品的报价逻辑简单。我按调用量阶梯计价规则写死在代码里代理一问就能算出来。大产品的定价涉及合同、折扣、区域差异机器算不明白。第三小产品输得起。MCP 生态还在早期协议在演进客户端支持程度参差不齐。大厂不敢拿核心业务试小产品试错了改一改就行。我第一版 MCP server 写完到跑通前后不到一周中间推翻重来两次成本几乎为零。提示如果你手上有个小 API 或者小工具想验证 MCP 这条路别一上来就想着完整暴露所有能力。先挑一个最独立、最没有副作用的能力做成 tool跑通链路再说。3. 报价这个动作怎么设计成代理能用的工具3.1 报价 tool 的输入参数设计报价 tool 看起来简单——输入调用量输出价格。但真设计起来参数怎么定很讲究。我第一版只设计了两个参数volume调用量和tier套餐类型。跑通之后发现代理用起来很别扭因为它不知道该传什么 tier。后来我改成三个参数volume预计调用量整数必填。usage_type调用类型枚举值比如standard、batch、realtime必填。duration使用周期枚举值monthly、yearly可选默认monthly。为什么加usage_type因为不同调用类型的成本不一样实时调用要占用更多资源批量调用可以错峰。代理如果能明确告诉我要哪种我就能给出更准确的价格。为什么duration可选因为大部分代理询价时只关心月度价格年度折扣可以后面再谈不必强制。这里有个经验参数设计要站在代理的角度想而不是站在人的角度想。人询价时会说我大概每个月用十万次主要是实时查询代理不会这么模糊它会精确传参。所以你的参数要足够精确让代理能表达清楚但也不能太细细到代理不知道该填什么。我试过加一个region参数结果代理每次都传默认值因为我的产品根本没有区域差异这个参数纯属多余后来删了。3.2 报价结果的返回结构返回结构比输入参数更重要因为代理要拿这个结果去做决策——比价、下单、汇报给用户。我第一版返回的是一个纯文本字符串类似您的报价是每月 99 元。跑通之后发现代理处理起来很费劲它得从自然语言里解析数字。第二版我改成结构化返回{ currency: CNY, base_price: 99, discount: 0, final_price: 99, unit: monthly, breakdown: [ { item: standard calls, quantity: 100000, unit_price: 0.00099 } ], valid_until: 2025-01-31T23:59:59Z, quote_id: q_abc123 }这个结构里几个字段是有讲究的currency必须明确代理可能同时对接多个服务货币不明确它没法比价。breakdown让代理能看到价格构成如果它想优化成本可以据此调整调用类型。valid_until是报价有效期避免代理拿着过期价格去下单。quote_id是报价单号后续下单时带上服务端可以校验报价是否还有效。注意quote_id这个设计我强烈建议加上。没有它代理可能拿一个三天前的报价来下单而你的价格早就变了。有了它服务端一查就知道报价是否过期直接拒绝干净利落。3.3 报价逻辑里那些不能明说的边界报价 tool 有个微妙的地方它既是能力展示也是商业策略。你暴露的报价规则越细代理越容易比价也越容易找到你的价格漏洞。我在这上面纠结了很久最后定了几条边界第一不暴露成本结构。breakdown 里只展示对外的计价项不展示我的实际成本。代理不需要知道我的服务器多少钱一个月。第二不暴露折扣触发条件。我有个年付九折的规则但我不在报价 tool 里主动说只在代理传duration: yearly时才体现。这样代理不会为了拿折扣而故意传年度参数。第三设置报价频率限制。代理可能疯狂询价来试探你的价格曲线我在服务端加了限流同一个来源每分钟最多询价 10 次。这个数字是拍脑袋定的实测下来正常代理根本用不到这么多次。这几条边界不是技术问题是商业判断。MCP server 暴露的是能力不是底牌。想清楚哪些能说、哪些不能说比写代码重要得多。4. 代理是怎么发现你的 MCP server 的4.1 发现机制的现实目前还没有自动发现标题里我写了自动发现这里得诚实一点目前的 MCP 生态里还没有真正意义上的全网自动发现。代理不会自己扫描互联网找到你的 MCP server它需要被配置。配置方式通常是用户在客户端里手动添加你的 server 地址或者通过某种目录服务注册。我实测下来Claude 和 Cursor 的配置方式略有不同但本质一样告诉客户端这里有个 MCP server地址是 X启动方式是 Y。配置好之后客户端启动时会去连接这个 server拉取能力清单之后代理就能看到你的 tools 了。所以自动发现准确的说法是配置一次之后代理自动发现你的能力。用户不需要每次告诉代理你可以报价代理连上 server 就知道有个get_quote工具可用。这个一次配置、持续发现的体验已经比传统的每个功能都要手动写函数定义好太多了。4.2 能力清单长什么样代理连上你的 MCP server 后会拿到一份能力清单。这份清单里每个 tool 都有名字、描述、参数 schema。我贴一段我自己的简化版{ name: get_quote, description: Get a price quote for using the service. Returns structured pricing based on volume, usage type, and duration., inputSchema: { type: object, properties: { volume: { type: integer, description: Estimated number of calls }, usage_type: { type: string, enum: [standard, batch, realtime] }, duration: { type: string, enum: [monthly, yearly], default: monthly } }, required: [volume, usage_type] } }这份清单里description 的写法直接决定代理会不会用、用得对不对。我踩过一个坑第一版 description 写的是获取报价结果代理经常不传usage_type因为它不知道这个参数是干嘛的。后来我把 description 改成明确说明每个参数的用途代理的调用准确率明显提升。提示description 不是写给人看的文档是写给代理看的使用说明。要具体、要说明参数含义、要说明返回什么。含糊的描述会让代理瞎猜。4.3 代理调用报价 tool 的完整链路我把一次完整的报价调用链路拆开讲这样你能看清每一步发生了什么用户在 Claude 或 Cursor 里说帮我问问那个服务每月十万次实时调用多少钱。客户端把这句话连同当前可用的 tools 清单一起发给模型。模型看到有个get_quote工具参数匹配决定调用它生成调用参数{volume: 100000, usage_type: realtime, duration: monthly}。客户端把调用请求转发给你的 MCP server。你的 server 执行报价逻辑返回结构化结果。客户端把结果回传给模型。模型把结构化结果转成自然语言每月十万次实时调用报价是 199 元有效期到本月底。这条链路里第 3 步是模型自己决策的第 5 步是你的代码控制的。你能优化的主要是第 5 步——返回结构越清晰第 7 步模型转述得越准确。我实测发现返回纯文本时模型转述经常出错返回结构化 JSON 后准确率大幅提升。5. 实测中踩到的坑和排查过程5.1 坑一代理死活不调用我的 tool第一个坑出现在刚跑通的时候。我配置好 server在 Claude 里问帮我询个价结果模型压根没调用我的 tool而是自己编了一段话回答。我一开始以为是配置没生效检查了半天配置发现 server 确实连上了能力清单也拉到了。排查过程是这样的我先确认 server 日志发现根本没有收到调用请求说明问题出在模型决策阶段不是通信阶段。然后我把 tool 的 description 打印出来看发现写得太笼统——获取报价四个字模型根本判断不出什么时候该用。改法很直接把 description 写具体明确说明当用户询问服务价格、需要报价、需要比价时使用此工具。改完之后模型调用率明显上升。这个坑的本质是模型判断要不要调用一个 tool主要看 description不看 tool 名字。名字起得再好description 含糊也没用。5.2 坑二参数类型不匹配导致调用失败第二个坑更隐蔽。我的volume参数定义成 integer但模型有时候会传字符串100000。服务端校验失败返回错误模型收到错误后不是重试而是直接告诉用户报价失败。这个问题的根因是不同模型对参数类型的处理不一致。有的模型严格按 schema 传有的模型会把数字转成字符串。我的处理方式是在服务端做一次宽松转换——收到字符串就尝试转成整数转不了再报错。这样兼容性好了很多。顺带说一句参数校验失败时返回的错误信息也很重要。我第一版返回的是invalid parameter模型看不懂。后来改成volume must be a positive integer, received: string 100000模型就能理解并重试了。5.3 坑三报价有效期没校验代理拿旧价下单第三个坑是我自己设计疏忽。报价返回了valid_until但下单接口没校验这个字段。结果测试时发现代理可以拿一个过期报价去下单服务端照单全收。修复方式是在下单接口里加校验根据quote_id查出报价记录检查valid_until是否过期过期就拒绝。这个改动很小但很关键。报价和下单必须是两个独立的校验环节不能因为报价时算过就信任下单时的价格。排查这个坑的过程让我意识到一件事MCP server 的每个 tool 都不是孤立的它们之间有状态关联。报价 tool 产生的quote_id下单 tool 要能识别。这种跨 tool 的状态管理是设计 MCP server 时容易忽略的地方。5.4 坑四并发询价把服务打挂最后一个坑是压力问题。我做了个测试脚本模拟多个代理同时询价结果服务端很快就响应变慢。查下来是报价逻辑里有几次数据库查询并发一高就顶不住。处理方式有两个一是加缓存相同参数的报价结果缓存几分钟二是加限流单来源每分钟最多 10 次询价。缓存这个事要注意报价可能随时间变化缓存时间不能太长我设的是 5 分钟。限流这个事要注意别把正常代理也限了10 次这个数字是我根据实际调用模式定的你可以根据自己的情况调整。6. 把 MCP server 跑起来的关键配置细节6.1 本地启动 vs 远程托管的选择MCP server 有两种部署方式本地启动客户端拉起一个进程和远程托管客户端连一个 URL。我两种都试过说说区别。本地启动的优点是简单不需要服务器不需要考虑鉴权客户端直接拉起你的进程通过标准输入输出通信。缺点是代理只能在装了你的代码的机器上用没法跨设备。适合个人开发者自用或者小范围测试。远程托管的优点是任何客户端都能连适合真正对外提供服务。缺点是要考虑鉴权、要考虑并发、要考虑稳定性。我现在用的是远程托管因为我的目标是让外部代理能发现并询价本地启动做不到这一点。选择建议先本地跑通再迁移到远程。本地跑通能帮你快速验证 tool 设计是否合理迁移到远程只是换个传输层逻辑不用改。6.2 鉴权别让任何人都能询你的价远程托管必须考虑鉴权。我的做法是给每个接入方发一个 API key请求时带上服务端校验。MCP 协议本身支持在配置里带 header客户端配置时把 key 填进去就行。这里有个细节鉴权失败要返回明确的错误而不是静默拒绝。代理收到明确错误后能告诉用户你的凭证无效收到静默拒绝只会一脸懵。我第一版鉴权失败返回空结果代理以为是没有报价闹了笑话。6.3 日志排查问题的唯一抓手MCP server 跑在远程出问题时你没法像本地调试那样打断点。日志是唯一的抓手。我记录这几类信息每次 tool 调用的入参和出参每次鉴权的结果每次报价的计算过程用了哪个阶梯、算了多少异常堆栈日志格式我用的是结构化 JSON方便后续检索。这里提醒一句日志里不要记录敏感信息比如完整的 API key。我见过有人把 key 打进日志结果日志泄露等于 key 泄露。6.4 版本管理能力清单会变MCP server 的能力清单不是一成不变的。你可能会加新 tool、改参数、改返回结构。这些变化会影响已经接入的代理。我的做法是给 server 加版本号能力清单里带上版本重大变更时升版本并在 description 里说明变更内容。代理不会自动适配你的变更所以能不破坏兼容就不破坏。加参数可以删参数要谨慎加返回字段可以改字段含义要谨慎。这个原则和做 API 是一样的。7. 关于 agent-to-agent commerce 的一点个人判断写到这里我想聊聊为什么我觉得这件事值得做。agent-to-agent commerce 这个词听起来很宏大但落到我这个小产品上其实就是一句话让代理能自己完成问价—比价—下单的闭环不需要人介入。现在这个闭环还不完整因为下单环节还涉及支付而支付在代理场景下还没有成熟的方案。但问价—比价这一段已经能跑通了。我实测过让代理同时询价我的服务和另外两个类似服务它能拿到三份结构化报价然后给出对比。这个体验在一年前是不可想象的。我的判断是未来一两年会不会有 MCP server 会成为小产品的一个基础配置就像现在会不会有官网、会不会有 API 一样。现在做成本低、试错空间大等生态成熟了再做就得跟一堆已经占好位置的竞品抢代理的注意力了。当然这事也有不确定性。MCP 协议还在演进客户端支持程度参差不齐代理的决策能力也还在提升。我踩的那些坑有一部分就是生态早期特有的。但我觉得这些坑值得踩因为踩坑的过程本身就是对代理到底需要什么的理解过程。最后分享一个我自己的小技巧写完 MCP server 之后别自己测让代理测。你自己测会不自觉地按自己的思路传参代理传参的方式往往和你不一样。我那几个坑基本都是让代理实际跑一遍才暴露出来的。代理是最好的测试员因为它就是最终用户。