☰
独立开发者如何用MCP协议让AI代理自动调用你的小产品
2026/10/1 5:34:47 网站建设 项目流程

1. 一个独立开发者为什么要给自己的小产品接上 MCP

先说结论:我给我的小产品写了一个 MCP server,现在 Claude、Cursor 这类 AI 代理在对话里就能直接「发现」它、读取它的能力清单,甚至自动完成一次报价请求。整个过程不需要我写一行前端对接代码,也不需要用户手动复制粘贴任何参数。

这件事的背景是这样的。我手上有一个很小的 SaaS 工具,功能很垂直——帮独立开发者做报价单的自动生成和成本估算。以前用户要用它,路径是:打开网页、注册、填表单、点生成、下载。这套流程对真人来说没问题,但对现在越来越多的「AI 代理工作流」来说就非常别扭。因为用户现在习惯在 Claude 或者 Cursor 里直接说一句「帮我给这个项目估个价,生成一份报价单」,然后希望代理自己去把这件事办完。

问题就出在「自己去办」这四个字上。AI 代理再聪明,它也没法凭空知道世界上有一个叫某某报价工具的东西,更不知道这个工具需要哪些参数、返回什么格式。它需要一个标准化的「自我介绍」入口。MCP(Model Context Protocol)就是干这个的。

MCP 本质上是一套让 AI 代理和外部工具、数据源对话的协议。你可以把它理解成「AI 世界的 USB-C 接口」——以前每个工具都要为每个 AI 客户端单独写适配,现在只要实现一次 MCP server,所有支持 MCP 的客户端(Claude Desktop、Cursor、各种 IDE 插件)都能即插即用。我这次做的事情,就是把我那个小产品包装成一个 MCP server,暴露出「发现能力」和「报价」两个核心动作。

适合谁看这篇?三类人。第一类是做小产品、小工具的独立开发者,想让自己的东西被 AI 代理调用;第二类是在用 Claude、Cursor 做 agent 工作流的人,想搞清楚 MCP server 到底怎么接;第三类是对 agent-to-agent commerce 这个方向好奇、想动手试一下的技术人。不需要你是协议专家,但最好有一点 Node 或 Python 基础,知道 JSON 长什么样。

下面我按「为什么这么设计 → 核心细节 → 完整实操 → 踩坑排查」的顺序,把我这次从零到跑通的完整过程拆开讲。中间涉及参数选择、协议字段、调试方法的地方,我都会把「为什么这么选」讲清楚,方便你直接抄作业或者改成自己的版本。

2. 整体设计思路:为什么是 MCP,而不是写个 API 文档

2.1 传统 API 对接和 MCP 对接的本质区别

在动手之前,我先想清楚了一件事:我到底是在解决「人调用工具」的问题,还是「代理调用工具」的问题。这两件事看起来像,实际上完全不同。

传统 API 的思路是给人看的。我写一份 REST 文档,说明POST /quote需要传project_name、hours、rate,返回一个 JSON。人看了文档,知道怎么填。但 AI 代理面对这份文档时,它得先「读到」文档,再「理解」字段含义,再「拼」出请求,中间任何一步都可能出错。而且每个 AI 客户端的工具调用格式还不一样,Claude 有 Claude 的 tool use 格式,Cursor 有 Cursor 的,我得为每个都适配一遍。

MCP 的思路是给代理看的。它把「工具清单」和「调用方式」标准化了。代理启动时,会向 MCP server 发一个「列出你有哪些工具」的请求,server 返回一份结构化的清单,每个工具带名字、描述、参数 schema。代理拿到这份清单,就知道自己能干什么、需要什么参数。调用的时候,代理按 schema 填参数,server 执行完返回结果。整个过程代理不需要读自然语言文档,全靠结构化数据。

这个区别带来的直接好处是:我只需要维护一份 MCP server,所有支持 MCP 的客户端自动就能用。这就是我选 MCP 而不是「再写一份 OpenAPI 文档」的核心理由。

2.2 为什么把「发现」和「报价」拆成两个工具

设计工具清单的时候,我一开始想做一个大而全的工具,叫handle_quote,参数里塞一个action字段,根据 action 决定是查询还是报价。后来我把它拆成了两个独立工具:discover_capabilities和create_quote。

拆开的理由很实际。AI 代理在决定调用哪个工具时,是靠工具的名字和描述来判断的。如果只有一个handle_quote,代理得先理解action字段的取值含义,多了一层认知负担,出错概率上升。拆成两个之后,discover_capabilities的描述是「返回本服务支持的所有报价能力、计价维度和限制」,create_quote的描述是「根据项目参数生成一份报价单」,代理一看名字就知道什么时候该用哪个。

这其实是一个通用的 MCP 设计经验:工具粒度要匹配代理的决策粒度。代理做决策时是「我现在要干这件事」,那工具就应该对应「这件事」,而不是对应「这一大类事」。粒度太粗,代理要自己做二次判断;粒度太细,工具数量爆炸,代理选择困难。两个到五个工具,通常是一个 MCP server 比较舒服的区间。

2.3 报价逻辑放在 server 端还是暴露给代理

还有一个关键决策:报价的计算逻辑,是放在 MCP server 里算好返回,还是把原始数据返回给代理让它自己算。

我选择放在 server 端算。原因是报价涉及我的业务规则——不同项目类型的基础费率、加急系数、复杂度加成、折扣门槛,这些是我的核心资产,也是我产品差异化的地方。如果我把原始参数返回给代理,等于把定价逻辑暴露了,而且代理每次算出来的结果可能不一致,用户体验反而差。

放在 server 端还有一个好处:结果可复现。同样的输入,永远得到同样的报价,这对商业场景很重要。代理拿到的是一个确定的数字和一份结构化的明细,它可以直接展示给用户,也可以继续拿去做后续操作(比如生成 PDF、发邮件)。

提示:涉及商业逻辑、计费规则、权限判断的部分,强烈建议放在 server 端。MCP server 不只是「数据搬运工」,它应该是「业务能力的封装」。

3. 核心细节解析:MCP server 到底长什么样

3.1 MCP 的三种核心原语:Tools、Resources、Prompts

MCP 协议里,server 能向客户端暴露三类东西,理解这三类的区别是设计的基础。

Tools(工具)是代理可以主动调用的动作,比如「生成报价」「查询库存」。它有输入参数和输出结果,是「做事情」的。Resources(资源)是代理可以读取的数据,比如「当前费率表」「历史报价记录」,它是「读数据」的,通常不产生副作用。Prompts(提示模板)是预定义的提示词模板,客户端可以把它作为快捷入口展示给用户,比如「帮我做一份标准报价」这种一键触发的场景。

我这次主要用了 Tools,因为核心诉求是「让代理能执行报价动作」。Resources 我也加了一个,暴露当前的费率表,方便代理在报价前先了解计价维度。Prompts 暂时没加,因为我的场景里用户更习惯直接对话,不太需要预设模板。

这里有个容易混淆的点:Resources 和 Tools 都能返回数据,区别在于「谁发起」。Tools 是代理决定要调用才调用,Resources 是客户端可以主动加载进上下文。如果你希望代理「随时知道」某些信息,用 Resources;如果希望代理「按需执行」,用 Tools。

3.2 工具描述(description)为什么比代码还重要

写 MCP server 的时候,我花在工具description字段上的时间,比写实际业务逻辑还多。这不是夸张。

因为代理选择工具、填参数,全靠这段描述。描述写得含糊,代理就会用错工具,或者参数填错。我一开始写的描述是「生成报价单」,测试时发现代理经常在用户只是「问价格」的时候就调用它,其实用户只是想了解计价方式。后来我把描述改成「根据明确的项目参数(工时、费率、复杂度)生成一份正式报价单,仅在用户确认要出报价时调用」,误触发率立刻降下来了。

参数描述同样重要。每个参数的description要写清楚「这是什么、单位是什么、取值范围、给个例子」。比如hours参数,我写的是「预估工时,单位为小时,必须是正数,例如 40 表示 40 小时」。代理看到这个,就知道不能填「两天」这种模糊值。

提示:把工具描述当成「写给一个聪明但完全不了解你业务的实习生看的说明书」。它不会猜,你写多清楚它就理解多清楚。

3.3 参数 schema 的设计:用 JSON Schema 约束代理行为

MCP 的工具参数用 JSON Schema 定义。这个 schema 不只是给代理看的文档,它还是运行时的校验规则。代理填的参数如果不符合 schema,server 可以直接拒绝,避免脏数据进入业务逻辑。

我这次的核心参数大概是这样设计的:

参数名类型是否必填说明约束
project_namestring是项目名称长度 1-100
project_typestring是项目类型枚举:web/app/design/consulting
hoursnumber是预估工时大于 0,小于 10000
complexitystring否复杂度枚举:low/medium/high,默认 medium
rushboolean否是否加急默认 false

用枚举(enum)约束project_type和complexity是关键。如果我用自由字符串,代理可能填「网站」「网页」「web 项目」各种变体,我后端就得做一堆容错。用枚举之后,代理只能从固定值里选,数据干净很多。

必填和选填的划分也有讲究。必填项越少,代理调用成功率越高,但业务信息可能不全。我的原则是:没有它就算不出结果的,才设为必填。complexity和rush都有合理默认值,所以设为选填。

4. 完整实操:从零跑通一个可被代理发现的 MCP server

4.1 环境准备与依赖选择

我选的是 Node.js + 官方 MCP SDK。理由有两个:一是官方 SDK 对协议细节封装得比较完整,不用自己处理握手、能力协商这些底层东西;二是 Node 生态里 JSON 处理很顺手,我的业务逻辑本来就是 JS 写的,复用成本低。

环境要求不复杂:

  • Node.js 18 或以上(我用的是 20 LTS)
  • npm 或 pnpm
  • 一个支持 MCP 的客户端,我用 Claude Desktop 和 Cursor 各测了一遍

初始化项目:

mkdir quote-mcp-server cd quote-mcp-server npm init -y npm install @modelcontextprotocol/sdk

装完之后,package.json里把type设成module,因为 SDK 用的是 ESM 风格。这一步如果漏了,import 会报错,我第一次就栽在这。

4.2 搭建 server 骨架与注册工具

核心代码结构其实很清晰。先创建 server 实例,声明自己的能力,然后注册工具,最后接上传输层。

import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; const server = new Server( { name: "quote-server", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } } );

capabilities这里声明我支持 tools 和 resources。如果只声明了 tools 却去响应 resources 请求,客户端可能不认。声明什么就实现什么,这是协议的基本礼貌。

接下来注册工具清单。ListToolsRequestSchema对应的 handler 返回工具数组:

server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "discover_capabilities", description: "返回本报价服务支持的项目类型、计价维度和限制条件。在报价前调用可了解可用选项。", inputSchema: { type: "object", properties: {} }, }, { name: "create_quote", description: "根据明确的项目参数生成正式报价单。仅在用户确认需要出报价时调用。", inputSchema: { type: "object", properties: { project_name: { type: "string", description: "项目名称,1-100 字符" }, project_type: { type: "string", enum: ["web", "app", "design", "consulting"], description: "项目类型", }, hours: { type: "number", description: "预估工时,单位小时,正数" }, complexity: { type: "string", enum: ["low", "medium", "high"], description: "复杂度,默认 medium", }, rush: { type: "boolean", description: "是否加急,默认 false" }, }, required: ["project_name", "project_type", "hours"], }, }, ], }; });

注意discover_capabilities的inputSchema是空对象,因为它不需要参数。这个设计让代理可以「零成本」地先探一下服务能力,再决定要不要报价。

4.3 实现报价计算与参数校验

工具调用的 handler 里,我先做参数校验,再走业务逻辑。校验这一步不能省,因为代理填的参数虽然受 schema 约束,但边界值(比如 hours 填了 0 或者负数)还是可能漏进来。

server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === "discover_capabilities") { return { content: [{ type: "text", text: JSON.stringify({ project_types: ["web", "app", "design", "consulting"], complexity_levels: ["low", "medium", "high"], base_rates: { web: 800, app: 1000, design: 600, consulting: 1200 }, rush_multiplier: 1.5, complexity_multiplier: { low: 0.9, medium: 1.0, high: 1.3 }, }), }], }; } if (name === "create_quote") { const { project_name, project_type, hours, complexity = "medium", rush = false } = args; if (typeof hours !== "number" || hours <= 0 || hours > 10000) { return { content: [{ type: "text", text: "参数错误:hours 必须是 0 到 10000 之间的正数" }], isError: true, }; } const baseRate = { web: 800, app: 1000, design: 600, consulting: 1200 }[project_type]; const complexityMultiplier = { low: 0.9, medium: 1.0, high: 1.3 }[complexity]; const rushMultiplier = rush ? 1.5 : 1.0; const subtotal = baseRate * hours * complexityMultiplier; const total = subtotal * rushMultiplier; return { content: [{ type: "text", text: JSON.stringify({ project_name, project_type, hours, complexity, rush, base_rate: baseRate, subtotal: Math.round(subtotal), total: Math.round(total), currency: "CNY", }, null, 2), }], }; } return { content: [{ type: "text", text: `未知工具:${name}` }], isError: true, }; });

报价公式是基础费率 × 工时 × 复杂度系数 × 加急系数。这个公式本身很简单,但每个系数的取值是我根据实际业务定的。比如加急系数 1.5,是因为加急项目通常要占用额外资源、压缩其他排期,成本确实高出一截。复杂度系数 low 是 0.9 而不是 1.0 以下更多,是因为再简单的项目也有基础沟通成本,不能无限打折。

4.4 接上传输层并在客户端里验证

最后一步是把 server 接上 stdio 传输层,让它能被客户端启动:

const transport = new StdioServerTransport(); await server.connect(transport);

stdio 传输的意思是,客户端会把这个 server 当成一个子进程启动,通过标准输入输出通信。这是本地 MCP server 最常见的模式,配置简单,不需要开端口。

在 Claude Desktop 里配置,找到配置文件(macOS 一般在~/Library/Application Support/Claude/claude_desktop_config.json),加上:

{ "mcpServers": { "quote-server": { "command": "node", "args": ["/绝对路径/quote-mcp-server/index.js"] } } }

路径一定要用绝对路径,相对路径在客户端启动子进程时经常找不到文件,这是我踩过的第一个坑。配置完重启客户端,在对话里问「你有哪些报价相关的工具」,如果代理能列出discover_capabilities和create_quote,说明接上了。

Cursor 的配置类似,在 MCP 设置里添加 server,命令和参数一样。我两个客户端都测了,行为基本一致,说明协议层的标准化确实到位。

5. 常见问题与排查技巧实录

5.1 代理「看不见」我的工具怎么办

这是最高频的问题。表现是:配置写好了,客户端也重启了,但代理就是说自己没有报价工具。

排查顺序我总结成一张表:

现象可能原因排查方法
完全看不到工具配置文件路径错检查绝对路径,手动node index.js看能否启动
看不到工具server 启动即崩溃看客户端日志,通常是依赖缺失或语法错误
看到工具但调用失败schema 不合法用 JSON 校验工具检查 inputSchema
调用返回空handler 没匹配到工具名打印 request.params.name 确认拼写

我遇到过一次,原因是package.json没设type: module,导致 ESM import 报错,server 启动就挂了,但客户端日志里只显示「server disconnected」,不告诉你具体原因。后来我养成习惯:改完代码先在终端手动跑一遍node index.js,确认能正常启动再配到客户端里。

5.2 代理填错参数、乱调工具的应对

代理不是每次都听话。我测试时遇到过代理在用户只是「问一下大概多少钱」的时候,就直接调用了create_quote,还自己编了个 hours 值。

应对方法有两个层面。第一层是描述优化,前面说过,把「仅在用户确认要出报价时调用」写进 description,能挡掉大部分误触发。第二层是 server 端兜底,对关键参数做合理性检查,比如 hours 如果明显是代理瞎填的(比如 99999),直接返回错误提示,让代理重新问用户。

还有一个技巧:在返回结果里带上「下一步建议」。比如报价成功后,返回文本里加一句「如需调整参数,可重新调用本工具」。代理看到这句话,会更倾向于在参数不确定时先跟用户确认,而不是硬编一个值。

5.3 日志与调试:stdio 模式下怎么看输出

stdio 模式下有个坑:你不能用console.log打日志,因为标准输出被协议占用了,打日志会污染通信,导致客户端解析失败。

正确做法是用console.error,它走标准错误,不会干扰协议通信,客户端日志里也能看到。我调试阶段所有关键节点都加了console.error,比如「收到工具调用请求:xxx」「参数校验通过」「报价计算完成」。

提示:MCP server 里,console.log是禁忌,console.error才是你的朋友。这个坑不踩一次很难记住。

5.4 报价结果不稳定、每次不一样

如果发现同样的输入,代理展示的报价每次不同,大概率是代理在「转述」你的结果时自己做了加工。解决办法是让 server 返回结构化的、带明确字段名的 JSON,并在描述里说明「请原样展示 total 字段」。

我一开始返回的是纯文本「总价是 3200 元」,代理有时会四舍五入成「约 3000 元」。改成返回 JSON 并强调字段后,代理就老实了,直接引用total的值。这也说明一个原则:给代理的数据越结构化,它转述时越不容易失真。

6. 关于 agent-to-agent commerce 的一点个人判断

跑通这个 MCP server 之后,我最大的感受是:agent-to-agent commerce 这件事,门槛比想象中低,但设计比想象中重要。

低在于,协议层的东西官方 SDK 已经封装得很好了,一个下午就能让代理发现并调用你的服务。重要在于,代理是个「不会猜」的调用方,你的工具描述、参数 schema、返回结构,每一个细节都直接决定它能不能用对。以前写给人用的 API,文档写得糙一点,人还能靠常识补;写给代理用的 MCP,描述含糊一点,它就直接用错。

我现在把这个 server 当成产品的「代理入口」在维护,和网页入口、API 入口并列。后续我打算再加一个list_recent_quotes工具,让代理能读取历史报价,这样用户在对话里说「把上次那份报价改一下工时」,代理就能自己找到并更新。这个扩展方向我觉得挺有意思,等跑通了再单独写一篇。

如果你也在做小产品,我建议尽早把 MCP server 加上。不是因为它是风口,而是因为它真的能让你的产品出现在用户和 AI 对话的「第一现场」,这个位置的曝光价值,比多做一个落地页高得多。

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

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

立即咨询