1. 从零开发第一个 MCP:为什么我选 Comate + TaoToken 这套组合
MCP(Model Context Protocol)这两年被讨论得很多,但真正动手写过一个能跑起来的 MCP Server 的人其实没那么多。它本质上是一套让大模型能调用外部工具的协议:你写一个服务,把「工具」注册进去,模型在对话里就能按需调用它,比如查数据库、读文件、调接口。适合谁?适合已经会用 AI IDE 写业务代码、想把自己的重复操作封装成工具、又不想被某一家模型厂商绑死的开发者。
我这次的目标很具体:用 Comate 的 Zulu 智能体当开发环境,从零搭一个 MCP 服务,记录 Vibe Coding 过程中的踩坑经验,之后遇到类似问题能自动检索。听起来有点绕,但拆开就是三件事——初始化一个 MCP 项目、写服务端骨架、把模型调用端点统一到 TaoToken 的 Key 通道上。前两件是 MCP 本身的活,第三件是让这个工具真正能长期用下去的关键。
为什么强调「统一 Key 通道」?因为 MCP 服务里往往要调模型做意图识别、做向量检索、做结果总结。如果每个环节都单独配一家厂商的 Key,管理成本会爆炸,换模型时还要改一堆代码。TaoToken 提供的是 OpenAI 兼容的接口,Base URL 和 Key 配一次,模型 ID 按需切换,MCP 服务里所有模型调用都走同一个入口。官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api ,注意这个地址后面不加任何参数。
Comate 这边,Zulu 智能体负责读文档、拆任务、写代码,内置浏览器能直接选页面元素改前端,对 MCP 这种「服务端 + 调试客户端」的项目来说,省掉了不少来回切窗口的时间。下面我把整个流程拆成可复制的步骤,你跟着敲一遍就能跑通。
2. 前置准备:Comate 里配好 MCP 市场与 TaoToken 统一 Key
在写自己的 MCP 之前,先把开发环境里常用的两个 MCP 配好,这样 Zulu 在写代码时能直接查文档、查数据库。Comate 的配置入口在 AI 侧边栏右上角的 MCP 按钮,点进去就是 MCP 市场,搜索添加即可。Supabase 这类市场里没有的,点右上角手动配置,会打开一个 JSON 文件,按格式加进去就行。
这里有个容易踩的坑:Comate 目前对 StreamableHTTP 传输的支持还不完整。我一开始按最新规范写了 StreamableHTTP 的 MCP Server,结果 Comate 侧连不上,后来改成 stdio 传输才通。所以你在选传输方式时,如果目标是让 Comate 直接调用,优先用 stdio;如果只是自己用命令行调试,StreamableHTTP 也能跑。
接下来是 TaoToken 的 Key。打开 https://taotoken.net/api-keys ,登录后创建一个 API Key,复制出来。这个 Key 后面会写进 MCP 服务的环境变量里,不要硬编码到代码中。模型 ID 可以在模型对话页面 https://taotoken.net/models 查到,常用的有通用对话模型和代码模型,按你的场景选。
配置 MCP 的 JSON 文件时,路径和字段名要和 Comate 的原文一致,否则它读不到。一个典型的手动配置片段长这样:
{ "mcpServers": { "supabase": { "command": "npx", "args": ["-y", "@supabase/mcp-server-supabase@latest"], "env": { "SUPABASE_URL": "https://your-project.supabase.co", "SUPABASE_SERVICE_ROLE_KEY": "your-service-role-key" } }, "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"] } } }注意command和args的写法,npx -y是为了跳过安装确认。如果你本地没装 Node,先装一个 LTS 版本。配完之后重启 Comate,侧边栏的 MCP 列表里能看到这两个服务就算成功。
TaoToken 的 Key 先放在手边,下一步写 MCP 服务端代码时会用到。这里提醒一句:不要把 Key 提交到 Git 仓库,用.env文件管理,.gitignore里加上.env。
3. 可复制配置:MCP 服务端骨架与 TaoToken 端点接入
现在开始写自己的 MCP 服务。我用的是 Node.js + TypeScript,因为 MCP 官方 SDK 对 TS 支持最好,Comate 的 Zulu 对 TS 项目也熟。先初始化项目:
mkdir recall-kit-mcp && cd recall-kit-mcp npm init -y npm install @modelcontextprotocol/sdk zod dotenv openai npm install -D typescript tsx @types/node npx tsc --inittsconfig.json里把target改成ES2022,module改成NodeNext,moduleResolution改成NodeNext,outDir设成dist。这些是 MCP SDK 的推荐配置,不改的话 import 路径会报错。
然后是.env文件,把 TaoToken 的 Key 和 Base URL 写进去:
TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL=你的模型ID注意TAOTOKEN_BASE_URL就是https://taotoken.net/api,不要加/v1后缀,OpenAI SDK 会自动拼。模型 ID 从模型对话页面复制,别自己猜。
服务端骨架src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import OpenAI from "openai"; import dotenv from "dotenv"; dotenv.config(); const openai = new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL, }); const server = new McpServer({ name: "recall-kit", version: "0.1.0", }); server.tool( "save_pitfall", "记录一条开发踩坑经验", { title: z.string().describe("问题标题"), content: z.string().describe("问题描述与解决方案"), tags: z.array(z.string()).describe("标签"), }, async ({ title, content, tags }) => { const summary = await openai.chat.completions.create({ model: process.env.TAOTOKEN_MODEL!, messages: [ { role: "system", content: "把下面的踩坑记录压缩成一句话摘要" }, { role: "user", content: `${title}\n${content}` }, ], }); const digest = summary.choices[0].message.content; // 这里接你的存储逻辑,比如写 Supabase return { content: [{ type: "text", text: `已记录:${digest}` }], }; } ); server.tool( "search_pitfall", "按关键词检索历史踩坑经验", { query: z.string().describe("检索关键词") }, async ({ query }) => { // 这里接你的向量检索逻辑 return { content: [{ type: "text", text: `检索到与「${query}」相关的记录` }], }; } ); const transport = new StdioServerTransport(); await server.connect(transport);这段代码里有两个工具:save_pitfall负责记录,search_pitfall负责检索。模型调用统一走openai客户端,baseURL指向 TaoToken,这样以后换模型只改.env里的TAOTOKEN_MODEL,代码一行不动。
package.json里加两个脚本:
{ "scripts": { "build": "tsc", "dev": "tsx src/index.ts" } }到这里,MCP 服务端的骨架就搭好了。Zulu 在写这段代码时,我把它生成的初版和上面这版对比了一下,主要差异在错误处理上——初版没做 try/catch,模型调用失败会直接崩。我手动补了异常捕获,这个后面排障章节会讲。
4. 验证请求:本地调试与一次真实工具调用
代码写完,先本地跑起来。用npm run dev启动,如果没报错,进程会挂起等待 stdio 输入,这是正常的,因为 stdio 传输靠标准输入输出通信。
调试 MCP 最方便的方式是用官方的 Inspector:
npx @modelcontextprotocol/inspector npx tsx src/index.ts它会启动一个本地网页,左边列出你注册的工具,右边可以填参数调用。点save_pitfall,填上标题、内容、标签,点 Run,如果返回「已记录:xxx」,说明模型调用链路通了。这一步验证的是 TaoToken 的 Key 和 Base URL 配对了。
如果 Inspector 里调用成功,再回到 Comate 里配这个 MCP。在 MCP 配置 JSON 里加一段:
{ "mcpServers": { "recall-kit": { "command": "npx", "args": ["tsx", "/绝对路径/recall-kit-mcp/src/index.ts"], "env": { "TAOTOKEN_API_KEY": "sk-你的key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_MODEL": "你的模型ID" } } } }注意args里用绝对路径,相对路径在 Comate 启动子进程时容易找不到文件。配完重启,在 Zulu 对话框里输入「帮我记录一条踩坑:MCP 的 StreamableHTTP 在 Comate 里连不上,改用 stdio 解决」,如果 Zulu 调用了save_pitfall并返回摘要,整条链路就打通了。
实测下来,从 Inspector 验证到 Comate 里调用成功,中间最容易卡住的是环境变量没传进去。Comate 启动 MCP 子进程时,不会自动继承你 shell 里的环境变量,所以env字段必须显式写全。这一点和本地直接npm run dev的行为不一样,很多人在这里踩坑。
5. 本篇常见错排查:401、local proxy failed 与 reading choices
排障这块我按真实遇到的报错来写,你对照着看。
401 Unauthorized:最常见。原因通常是 Key 没传进去,或者传了但格式不对。检查.env里的TAOTOKEN_API_KEY是不是以sk-开头,Comate 的 MCP 配置里env字段有没有写全。还有一种情况是 Key 复制时带了空格,肉眼看不出来,用echo $TAOTOKEN_API_KEY | wc -c数一下长度对不对。
local proxy failed:这个报错一般出现在网络层。先确认TAOTOKEN_BASE_URL写的是https://taotoken.net/api,没有多余路径。如果本地有抓包工具或者自定义 DNS,先关掉再试。另外 Node 版本太低也会导致 TLS 握手失败,升到 18 以上。
reading 'choices' of undefined:这个报错说明模型返回的结构和预期不符。大概率是TAOTOKEN_MODEL填错了,模型 ID 不存在时接口返回的是错误对象,没有choices字段。去模型对话页面确认一下模型 ID 拼写,注意大小写。还有一种可能是baseURL多写了/v1,导致请求路径变成/api/v1/chat/completions,而正确路径是/api/chat/completions。
OAuth 相关报错:如果你在 MCP 配置里用了需要 OAuth 的服务(比如某些云厂商的 MCP),报错会提示 token 过期或 scope 不足。这类问题不在 TaoToken 侧,去对应服务的控制台重新授权即可。TaoToken 用的是 API Key 认证,不涉及 OAuth 流程。
工具调用没反应:Zulu 对话框里输入了指令,但没触发工具。检查 MCP 服务是否在 Comate 的 MCP 列表里显示为「已连接」。如果显示连接失败,看 Comate 的日志输出,通常是command路径不对或者npx找不到。把command改成node加绝对路径的dist/index.js试试,先npm run build编译出 JS 再跑。
排障时有个通用技巧:先在命令行单独跑 MCP 服务,确认它能启动、能响应 Inspector 的调用,再放进 Comate。这样能把「MCP 本身的问题」和「Comate 集成的问题」分开,定位快很多。
6. 长期编码与 Agent 场景:把 Key 通道固定下来
MCP 服务跑通只是第一步。如果你打算长期用它,比如每天记录踩坑、定期检索,那 Key 通道的稳定性就很重要。TaoToken 的 Coding Plan 适合这种长期编码和 Agent 场景,https://taotoken.net/coding-plan ,配一次 Key,模型按需切换,不用每次改代码。
我自己的做法是把 MCP 服务做成一个常驻进程,用pm2或者systemd管起来,Comate 通过 stdio 连它。这样即使 Comate 重启,MCP 服务也不用重新配。环境变量统一放在.env里,换模型时只改一行。
另外,MCP 的工具描述(description字段)写清楚很重要。Zulu 是靠描述来判断什么时候调用哪个工具的。我一开始把search_pitfall的描述写成「检索」,Zulu 经常不调用;改成「按关键词检索历史踩坑经验,当用户提到类似问题时使用」之后,触发率明显上来了。
最后留一个实用技巧:MCP 服务里所有模型调用都走同一个openai客户端实例,不要每个工具里 new 一个。这样连接池能复用,并发调用时不会因为频繁建连而超时。如果你要接多个模型,用model参数区分,客户端还是那一个。