1. 从零理解 MCP 服务器:它到底是什么、能做什么、适合谁
如果你最近在折腾大模型应用,大概率听过 MCP 这个词。MCP 全称 Model Context Protocol,翻译过来叫“模型上下文协议”,说白了就是一套让模型和外部世界对话的标准接口。你可以把它想象成 USB-C:以前每个设备都有自己的充电口,现在统一成一个标准,谁都能插。MCP 干的就是这件事——把工具调用、数据读取、提示词模板这三类能力,用统一的协议暴露给模型客户端。
那 MCP 服务器又是什么?简单讲,它是一个独立运行的服务进程,对外声明“我有哪些工具可以调、哪些资源可以读、哪些提示词可以用”,然后等客户端来连。客户端可能是 Claude Desktop、Cursor、Cline,也可能是你自己写的 Agent。服务器不关心谁来连,只负责把能力描述清楚、把请求处理正确。
这篇文章适合谁?如果你已经会用 Node.js 写点脚本,听说 MCP 但还没亲手跑通过一个服务器,或者你照着官方示例抄了一遍却发现 tools 列表是空的、resources 读不出来、prompts 调用报错,那这篇就是写给你的。我会带你从 SDK 初始化开始,一步步把 tools、resources、prompts 三类能力都注册上,最后用 TaoToken 统一 Key 通道完成本地联调验证。全程可复制,不需要你提前理解 JSON-RPC 的细节。
我试过直接啃协议文档,结果被 method 名和 schema 转换绕晕。后来发现只要抓住一个核心:MCP SDK 帮你把协议层全包了,你只需要用 registerTool、registerResource、registerPrompt 这三个方法把业务逻辑挂上去。剩下的握手、发现、路由、序列化,SDK 自己处理。理解这一点,后面就顺了。
2. TaoToken 前置准备:统一 Key 通道怎么配、为什么需要它
在动手写代码之前,先把“通道”这件事说清楚。MCP 服务器本身不绑定任何模型厂商,它只负责暴露能力。但你要验证这些能力是否真的能被模型调用,就需要一个能连上模型的客户端。问题来了:不同厂商的 API Key 格式不一样,Base URL 不一样,模型 ID 也不一样。如果你每换一个客户端就重新配一遍,光记这些参数就够头疼。
TaoToken 在这里扮演的角色是统一入口。它提供一个兼容 OpenAI 风格的 API 通道,你拿一个 Key,配一个 Base URL,就能在多个客户端里复用。对于 MCP 联调来说,这意味着你可以把精力放在服务器逻辑上,而不是反复折腾鉴权配置。
具体怎么拿 Key?访问 https://taotoken.net/api-keys 这个 deep link,登录后创建一个 API Key。注意保存好,页面关闭后不会再完整显示。然后你需要记下三个核心参数:Base URL 填https://taotoken.net/api,API Key 填你刚创建的那串,Model ID 根据你要用的模型填,比如claude-sonnet-4-20250514或gpt-4o这类。这三个参数在后面配置 MCP 客户端时会反复用到。
注意:Base URL 不要加 UTM 参数,直接写
https://taotoken.net/api就行。带参数的链接是给网页访问用的,API 调用不需要。
如果你用的是 Claude Code 这类工具,它有自己的配置文件。以 Claude Code 为例,配置文件通常在~/.claude/settings.json或项目级的.claude/settings.json。你需要把 Base URL 和 Key 写进去,格式类似这样:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key" } }如果你用的是 Cline 或 Roo Code 这类 VS Code 插件,配置入口在插件的设置面板里。选择 “OpenAI Compatible” 作为 Provider,Base URL 填https://taotoken.net/api,API Key 填你的 Key,Model ID 填你要用的模型。保存后插件会发一个测试请求,通了就说明通道没问题。
对于 Codex 用户,配置文件在~/.codex/auth.json。你需要把OPENAI_BASE_URL指向https://taotoken.net/api,OPENAI_API_KEY填你的 Key。这个文件是 JSON 格式,改完保存即可。
提示:如果你不确定自己的工具该改哪个文件,先去 https://taotoken.net/doc 看接入文档,里面按客户端分类列了配置路径和示例。别凭记忆改,改错了排查起来很费时间。
配好通道之后,你的 MCP 服务器就可以专注于“暴露能力”这件事了。客户端通过 TaoToken 通道连上模型,模型决定调用哪个工具,请求打到你的 MCP 服务器,服务器返回结果,模型再组织语言回复用户。整条链路里,TaoToken 负责的是模型侧的统一接入,MCP 服务器负责的是能力侧的标准暴露。两者各司其职,你只需要把中间那层业务逻辑写对。
3. 可复制配置:SDK 初始化与 tools/resources/prompts 三类能力注册
现在进入正题。先建项目。我习惯把 MCP 服务器放在独立目录里,和前端应用平级,这样职责清晰,部署也灵活。
mkdir mcp-server && cd mcp-server npm init -y npm install typescript ts-node-dev zod express @modelcontextprotocol/sdk npx tsc --inittsconfig.json用最普通的配置就行,target设es2022,module设esnext,moduleResolution设node,strict打开。这些是 TypeScript 项目的常规设置,不展开。
接下来建两个文件:src/gifts.ts放业务逻辑,src/server.ts放 MCP 服务器入口。先写业务逻辑,它完全不依赖 MCP,纯函数,方便测试和复用。
// src/gifts.ts export type GiftIdea = { id: string; title: string; price: number; occasion: string; }; export type SuggestGiftsInput = { age: number; relationship: "friend" | "partner" | "child" | "coworker"; budget: number; }; export function suggestGifts(input: SuggestGiftsInput): GiftIdea[] { return [ { id: "book-1", title: "与对方爱好相关的书", price: Math.min(input.budget, 30), occasion: "generic", }, { id: "game-1", title: "适合聚会的桌游", price: Math.min(input.budget, 50), occasion: "party", }, ]; } export const giftCatalog: GiftIdea[] = [ { id: "book-1", title: "编程类图书", price: 25, occasion: "learning" }, { id: "lego-1", title: "LEGO 积木套装", price: 60, occasion: "fun" }, ];然后是服务器入口。这里的关键是理解 SDK 的注册模式:每个能力都对应一个 register 方法,传入名称、描述对象、以及处理函数。
// src/server.ts import express from "express"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { z } from "zod"; import { suggestGifts, giftCatalog } from "./gifts.js"; const app = express(); app.use(express.json()); const mcpServer = new McpServer({ name: "gift-assistant-mcp", version: "0.1.0", }); // 注册工具 const suggestGiftsInputSchema = z.object({ age: z.number().int().min(0).max(120), relationship: z.enum(["friend", "partner", "child", "coworker"]), budget: z.number().min(0), }); mcpServer.registerTool( "suggest_gifts", { title: "Suggest gift ideas", description: "根据年龄、关系类型与预算推荐礼物创意。", inputSchema: suggestGiftsInputSchema, }, async ({ input }) => { const ideas = suggestGifts(input); const text = ideas .map((g) => `• ${g.title} — ~${g.price} USD (occasion: ${g.occasion}, id: ${g.id})`) .join("\n"); return { content: [{ type: "text", text }], structuredContent: { ideas }, }; } ); // 注册资源 mcpServer.registerResource( "gift_catalog", { title: "Gift catalog", description: "用于演示与调试的简单礼物目录。", mimeType: "application/json", }, async () => { return { contents: [ { uri: "mcp://gift-catalog", mimeType: "application/json", text: JSON.stringify(giftCatalog, null, 2), }, ], }; } ); // 注册提示词 mcpServer.registerPrompt("birthday_gift", { title: "Birthday gift helper", description: "用于挑选生日礼物的请求模板。", messages: [ { role: "system", content: "你是一名礼物搜索助手。请提出澄清性问题,并提供若干备选方案。", }, { role: "user", content: "我需要一份生日礼物。请先询问必要的细节并帮助我选择。", }, ], }); // 接入 HTTP 传输 const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); app.post("/mcp", async (req, res) => { await transport.handleRequest(req, res, req.body); }); app.get("/mcp", async (req, res) => { await transport.handleRequest(req, res); }); await mcpServer.connect(transport); const PORT = process.env.PORT ?? 4000; app.listen(PORT, () => { console.log(`MCP server listening on http://localhost:${PORT}/mcp`); });这段代码里有几个点值得展开。第一,inputSchema用的是 Zod 模式,SDK 会自动把它转成 JSON Schema 给模型看。你不需要手写 JSON Schema,Zod 的类型推导和校验能力直接复用。第二,处理函数的返回值里,content是模型可见的文本,structuredContent是给前端组件消费的结构化数据,两者可以同时返回。第三,资源注册的返回值必须是contents数组,每项包含uri、mimeType、text,这是 MCP 资源的标准格式,别自己发明字段。
package.json里加个脚本:
{ "scripts": { "dev": "ts-node-dev --esm src/server.ts" } }到这里,三类能力都注册完了。tools 有suggest_gifts,resources 有gift_catalog,prompts 有birthday_gift。服务器启动后,客户端连接时会自动发现这些能力。
4. 验证请求与成功结果:用 curl 和 MCP Inspector 确认三类能力可用
代码写完了,得验证。最直接的方式是先跑起来,看控制台有没有报错。
npm run dev看到MCP server listening on http://localhost:4000/mcp就说明进程起来了。接下来用 curl 发一个发现请求,看看 tools 列表里有没有suggest_gifts。
curl -X POST http://localhost:4000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'如果返回的 JSON 里能看到suggest_gifts及其 inputSchema,说明工具注册成功。同样的方式可以查 resources 和 prompts:
curl -X POST http://localhost:4000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":2,"method":"resources/list","params":{}}' curl -X POST http://localhost:4000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":3,"method":"prompts/list","params":{}}'curl 适合快速 smoke test,但真正调试建议用 MCP Inspector。它是官方提供的可视化调试工具,能列出所有能力、手动调用工具、查看原始请求响应。启动方式:
npx @modelcontextprotocol/inspector然后在 Inspector 界面里填你的服务器地址http://localhost:4000/mcp,传输类型选 “Streamable HTTP”。连上之后,左侧会显示 Tools、Resources、Prompts 三个面板。点开 Tools,能看到suggest_gifts,点进去填参数,比如 age 填 30、relationship 选 friend、budget 填 100,点运行。如果返回了礼物列表文本,说明工具链路通了。
Resources 面板里点gift_catalog,应该能看到 JSON 格式的礼物目录。Prompts 面板里点birthday_gift,能看到预设的 system 和 user 消息。这三样都正常,你的第一个 MCP 服务器就算跑通了。
提示:Inspector 默认连的是 stdio 传输,如果你用 HTTP 传输,记得在连接设置里切换。这个坑我踩过,连了半天连不上,后来发现是传输类型选错了。
验证通过后,你可以把 MCP 服务器接到实际客户端里。以 Cline 为例,在插件设置里添加 MCP Server,类型选 “Streamable HTTP”,URL 填http://localhost:4000/mcp。保存后 Cline 会自动发现工具,你在对话里说“帮我推荐一个给朋友的生日礼物,预算 100 以内”,模型就会调用suggest_gifts工具。这时候模型侧的请求走的是 TaoToken 通道,能力侧的请求走的是你的 MCP 服务器,两条链路各走各的,互不干扰。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 报错对照
联调过程中最容易卡住的不是业务逻辑,而是配置和协议层的报错。我把几个高频错误和排查路径列出来,你对照着看。
401 Unauthorized。这个通常出现在模型侧,不是 MCP 服务器侧。如果你在客户端里调用工具时报 401,先检查 TaoToken 的 API Key 有没有填对、有没有过期。Base URL 是不是写成了https://taotoken.net/api,注意结尾没有斜杠。如果 Key 没问题还是 401,去 https://taotoken.net/api-keys 重新生成一个,排除 Key 被误删的情况。
local proxy failed。这个报错一般出现在客户端尝试连接 MCP 服务器时。原因可能是服务器没启动、端口被占用、或者 URL 路径写错了。先确认npm run dev还在跑,然后curl http://localhost:4000/mcp看有没有响应。如果端口冲突,改PORT环境变量换个端口。路径一定要带/mcp,别只写http://localhost:4000。
reading choices 报错。这个通常和模型返回格式有关。如果你用的模型 ID 不对,或者客户端把非 OpenAI 兼容的响应当 OpenAI 格式解析,就会在读取choices字段时失败。检查 Model ID 是不是 TaoToken 支持的模型,比如claude-sonnet-4-20250514或gpt-4o。别填一个不存在的模型名,那样请求会直接失败。
OAuth 相关报错。MCP 规范里定义了鉴权流程,但本地开发阶段你大概率不需要 OAuth。如果你在客户端里看到 OAuth 相关的错误,先确认是不是误开了鉴权选项。本地联调时,MCP 服务器不加鉴权是正常的,客户端也不应该强制走 OAuth。把客户端的鉴权开关关掉,或者选 “None” 作为认证方式。
tools 列表为空。服务器启动了,但客户端发现不到工具。先确认registerTool的调用在mcpServer.connect(transport)之前。如果顺序反了,连接建立时工具还没注册,客户端自然看不到。另外检查一下inputSchema有没有语法错误,Zod 模式写错了会导致注册失败,但控制台不一定报错。
resources 读取返回空。检查contents数组的格式。每项必须有uri、mimeType、text三个字段,缺一个都可能导致客户端解析失败。uri用mcp://开头是惯例,但具体格式客户端可能不校验,关键是字段齐全。
prompts 调用没反应。确认messages数组里的role和content字段拼写正确。role只能是system、user、assistant这几个值,写错了客户端会忽略。另外 prompts 是客户端主动获取的,不是模型自动调用的,你需要在客户端界面里手动选择或触发。
排查的时候记住一个原则:先隔离,再联调。MCP 服务器的问题用 Inspector 查,模型通道的问题用 curl 直接打 TaoToken API 查。两边都单独通了,再合起来跑。这样出问题时你能快速定位是哪一侧的锅。
6. 语义一致 CTA:把统一 Key 通道用起来,继续深入 MCP 开发
跑通第一个 MCP 服务器之后,你手里已经有了一个可扩展的骨架。tools、resources、prompts 三类能力的注册模式你都摸过了,接下来无非是往里填更多业务逻辑。但要让这个服务器真正用起来,模型侧的通道得配好。
如果你还没配 TaoToken 的 Key,现在是个好时机。访问 https://taotoken.net/api-keys 创建一个,然后在你的客户端里把 Base URL 和 Key 填上。具体每个客户端怎么填,https://taotoken.net/doc 里有分门别类的说明,照着改就行。
配好之后,你可以把 MCP 服务器接到 Cline、Claude Code 或者自己写的 Agent 里,让模型真正调用你注册的工具。如果只是想先试试模型对话效果,https://taotoken.net/chat 可以直接体验。如果你打算长期做编码类 Agent 开发,https://taotoken.net/coding-plan 里有更完整的方案说明。
MCP 的生态还在快速演进,SDK 的类名和方法签名可能会变,但核心模式不会变:注册能力、处理请求、返回标准格式。把这篇文章里的代码跑通,你就有了一个能继续迭代的起点。后面加鉴权、加日志、加流式响应,都是在这个骨架上做增量。遇到报错先回来看第 5 节,大部分坑我都替你踩过了。