ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MCP是什么及如何开发一个MCPServer:从Node.js到Python的SSE实战与TaoToken统一Key接入

MCP是什么及如何开发一个MCPServer:从Node.js到Python的SSE实战与TaoToken统一Key接入 1. MCP 到底是什么从 stdio 到 SSE 的 MCPServer 职责拆解MCP 全称 Model Context Protocol直译是「模型上下文协议」。你可以把它理解成 AI 大模型的标准化工具箱接口模型本身只会生成文本它没法直接读你的数据库、查你的 Git 仓库、调你的内部 API。MCP 就是给模型装上一排标准化的插座让模型通过统一的消息格式去调用外部能力。一个 MCPServer 本质上就是一段 Node.js 或者 Python 程序。它对外暴露若干「工具tool」「资源resource」「提示prompt」模型侧通过 JSON-RPC 2.0 消息和它通信。消息长这样{ jsonrpc: 2.0, id: 129, method: tools/call, params: { name: search_repositories, arguments: { query: user:tech-shrimp } } }MCPServer 收到这条消息后用自己的代码逻辑或再发一次外部 API 请求完成任务然后把结果按同样格式回传。整个过程对模型来说是透明的它只需要知道「有个叫 search_repositories 的工具参数是 query」。传输层目前主流有两种stdio 和 SSE。stdio 是标准输入输出客户端把 MCPServer 当子进程启动通过管道收发消息适合本地单机场景。SSE 全称 Server-Sent EventsMCPServer 跑成一个 HTTP 服务客户端通过GET /sse建立长连接接收事件再通过POST /messages发送请求。SSE 的好处是跨进程、跨机器、可远程部署也是本文实战的重点。谁适合读这篇如果你第一次听说 MCP想搞清楚「协议是什么、Server 负责什么」并且想亲手跑通一个能被本地客户端调用的 SSE 版 MCPServer那这篇就是给你写的。我会先用 Node.js 写一个最小可用的 SSE Server再用 Python 的官方 SDK 复刻一遍最后讲清楚模型调用凭证怎么通过 TaoToken 统一管理避免每个项目到处散落 Key。先说清楚职责边界避免后面踩坑。MCPServer 只负责「暴露能力 执行 返回结果」它不负责模型推理也不负责决定模型该调哪个工具——那是客户端比如 Cherry Studio、Cline、Claude Code和模型之间的事。所以调试 MCPServer 时你要盯的是连接建没建上、工具列表有没有正确返回、参数有没有按 schema 传进来、返回值格式对不对。这四点搞定了Server 就算通了。2. 前置准备TaoToken 统一 Key 与 API 通道配置在写 Server 之前先把模型调用凭证这件事理顺。很多人的痛点是Node.js 项目一个 Key、Python 项目又一个 Key、客户端里再填一个改起来到处找。TaoToken 的思路是提供一个统一的 API 通道你只需要维护一份 Key各个项目和客户端都指向同一个 Base URL。TaoToken 官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于代码里的 Base URL。第一步登录后在控制台创建 API Key。控制台地址带归因参数https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建完把 Key 复制出来形如sk-xxxx先存到环境变量里别硬编码进代码。第二步如果你用的是 Claude Code 这类编码工具可以直接用它的接入文档配置https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。文档里会给出 Base URL 和 Key 的填法。第三步验证 Key 是否可用。最直接的方式是打开模型对话页面发一条消息https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。能正常返回就说明 Key 和通道没问题。这里要强调一个概念TaoToken 是模型调用的统一入口不是 MCPServer 本身。MCPServer 里如果某个工具需要调用大模型比如做摘要、做意图识别它就可以用这份统一 Key 去请求 TaoToken 的 API 通道而不是自己再维护一套凭证。这样你的 Server 代码里只需要读一个环境变量干净很多。把 Key 写进环境变量Linux/macOS 下export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell$env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api后面 Node.js 和 Python 的示例都会从这两个环境变量读取保证凭证不落盘、不进 Git。如果你打算长期跑编码类 Agent也可以了解下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合高频调用场景。3. Node.js 实现 SSE 版 MCPServer可复制配置与启动先建项目结构。我习惯这样组织mcp-node-sse/ ├── package.json ├── tsconfig.json └── src/ └── server.ts初始化并装依赖mkdir mcp-node-sse cd mcp-node-sse npm init -y npm install modelcontextprotocol/sdk express zod npm install -D typescript tsx types/express types/nodepackage.json里加上启动脚本和 ESM 声明{ name: mcp-node-sse, version: 1.0.0, type: module, scripts: { dev: tsx src/server.ts, build: tsc, start: node dist/server.js }, dependencies: { modelcontextprotocol/sdk: ^1.0.0, express: ^4.19.2, zod: ^3.23.8 }, devDependencies: { typescript: ^5.5.0, tsx: ^4.16.0, types/express: ^4.17.21, types/node: ^20.14.0 } }tsconfig.json{ compilerOptions: { target: ES2022, module: ES2022, moduleResolution: bundler, outDir: dist, rootDir: src, strict: true, esModuleInterop: true, skipLibCheck: true }, include: [src] }核心的src/server.ts用官方 SDK 的McpServer加SSEServerTransportimport express from express; import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { SSEServerTransport } from modelcontextprotocol/sdk/server/sse.js; import { z } from zod; const app express(); const PORT 3001; // 每个会话对应一个 transport const transports new Mapstring, SSEServerTransport(); function buildServer() { const server new McpServer({ name: demo-sse-server, version: 1.0.0, }); // 工具一加法 server.tool( add, 计算两个数字之和, { a: z.number(), b: z.number() }, async ({ a, b }) ({ content: [{ type: text, text: String(a b) }], }) ); // 工具二调用 TaoToken 通道做文本摘要示例 server.tool( summarize, 对给定文本做一句话摘要, { text: z.string() }, async ({ text }) { const apiKey process.env.TAOTOKEN_API_KEY; const baseUrl process.env.TAOTOKEN_BASE_URL ?? https://taotoken.net/api; if (!apiKey) { return { content: [{ type: text, text: 缺少 TAOTOKEN_API_KEY }] }; } const resp await fetch(${baseUrl}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey}, }, body: JSON.stringify({ model: gpt-4o-mini, messages: [ { role: system, content: 用一句话总结用户输入。 }, { role: user, content: text }, ], }), }); const data await resp.json(); const summary data?.choices?.[0]?.message?.content ?? 无结果; return { content: [{ type: text, text: summary }] }; } ); return server; } // SSE 连接入口 app.get(/sse, async (req, res) { const transport new SSEServerTransport(/messages, res); transports.set(transport.sessionId, transport); res.on(close, () transports.delete(transport.sessionId)); const server buildServer(); await server.connect(transport); }); // 客户端 POST 消息入口 app.post(/messages, express.json(), async (req, res) { const sessionId req.query.sessionId as string; const transport transports.get(sessionId); if (!transport) { res.status(400).send(unknown session); return; } await transport.handlePostMessage(req, res); }); app.listen(PORT, () { console.log(MCP SSE server on http://localhost:${PORT}/sse); });启动npm run dev看到MCP SSE server on http://localhost:3001/sse就说明服务起来了。注意 SSE 的路径是/sse消息回传路径是/messages这两个路径客户端配置时要对应上写错一个就连不通。4. Python 复刻与 SSE 连通性验证从启动到工具调用成功Python 侧用官方mcp包先装环境。推荐用 uv 管理pip install uv uv init mcp-python-sse cd mcp-python-sse uv add mcp[cli] httpxserver.pyimport os import httpx from mcp.server.fastmcp import FastMCP mcp FastMCP(demo-python-sse, host0.0.0.0, port3002) mcp.tool() def add(a: int, b: int) - int: 计算两个数字之和 return a b mcp.tool() async def summarize(text: str) - str: 对给定文本做一句话摘要走 TaoToken 统一通道 api_key os.environ.get(TAOTOKEN_API_KEY) base_url os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) if not api_key: return 缺少 TAOTOKEN_API_KEY async with httpx.AsyncClient(timeout30) as client: resp await client.post( f{base_url}/v1/chat/completions, headers{Authorization: fBearer {api_key}}, json{ model: gpt-4o-mini, messages: [ {role: system, content: 用一句话总结用户输入。}, {role: user, content: text}, ], }, ) data resp.json() return data[choices][0][message][content] if __name__ __main__: mcp.run(transportsse)启动uv run server.py默认监听 3002 端口SSE 入口是http://localhost:3002/sse。现在验证连通性。先确认 SSE 端点能建立连接curl -N http://localhost:3002/sse正常的话你会看到类似event: endpoint加一行data: /messages/?session_idxxxx的输出说明长连接建立成功并且服务端告诉了你回传消息的地址。接着在客户端里配置。以 Cherry Studio 为例添加一个 MCP 工具类型选 SSEURL 填http://localhost:3002/sse。保存后客户端会自动拉取工具列表你应该能看到add和summarize两个工具。然后让模型调用add输入「帮我算 3 加 5」。模型会发起tools/call参数{a:3,b:5}Server 返回8。如果客户端里能看到工具调用记录和返回结果验收就通过了。Node.js 版同理把 URL 换成http://localhost:3001/sse即可。两个版本我都跑过Python 版启动更快Node.js 版在需要复用前端生态时更顺手按你的技术栈选。5. 常见报错排查401、local proxy failed 与 reading choices调试阶段最容易卡在几个固定报错上我按实际遇到的整理一遍。401 Unauthorized。这个几乎都是 Key 的问题。检查三处环境变量有没有真的 export 成功echo $TAOTOKEN_API_KEY看输出、请求头是不是Authorization: Bearer sk-xxx格式、Base URL 有没有多写或少写/v1。TaoToken 的 API 入口是https://taotoken.net/api拼 chat completions 时是/api/v1/chat/completions别把/v1漏了。local proxy failed / connection refused。客户端连不上 Server先确认服务真的在跑curl -N http://localhost:3002/sse有没有输出。如果 Server 跑在容器或远程机器上localhost在客户端侧指向的是客户端自己要换成实际 IP。另外 SSE 路径写错也会报这个比如把/sse写成/sse/某些客户端不兼容尾斜杠。reading choices of undefined。这是解析响应时data.choices不存在。原因通常是请求根本没成功返回的是错误对象或者模型名写错导致返回结构不同。加一层防御if (!resp.ok) { const errText await resp.text(); return { content: [{ type: text, text: 请求失败 ${resp.status}: ${errText} }] }; } const data await resp.json(); const summary data?.choices?.[0]?.message?.content ?? 无结果;Python 侧同理先判断resp.status_code。OAuth / 认证相关报错。如果你用的是 Claude Code 这类工具配置时出现 OAuth 提示说明它默认走了自己的登录流程。这时候要在配置里显式指定 Base URL 和 Key参考接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。Claude Code 的配置通常涉及三件套Base URL、API Key、Model ID缺一不可。工具列表为空。连接建上了但看不到工具多半是server.tool()注册时 schema 写错或者工具名重复。检查启动日志有没有报错把 zod schema 简化成最基础的类型先跑通再逐步加复杂参数。SSE 连接频繁断开。长连接对超时敏感某些反向代理会掐掉空闲连接。本地调试一般没事如果部署到服务器记得给 SSE 路径关掉缓冲、拉长超时。排查顺序建议固定成先 curl 确认 SSE 端点活着再确认客户端 URL 和路径再看 Key 和 Base URL最后看响应解析。按这个顺序走九成问题能定位。6. 把统一 Key 用起来模型对话、接入文档与 Coding Plan 分流Server 跑通之后真正省心的地方在于凭证管理。你不需要在每个 MCPServer 里各写一套 Key而是统一从环境变量读 TaoToken 的 Key 和 Base URL。这样换 Key、换通道只改一处所有 Server 和客户端一起生效。如果你只是想快速验证某个模型能不能用直接开模型对话页面发消息最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。想管理或新建 Key 就去控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。需要看具体接入参数和示例代码翻文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。Key 列表页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 方便你集中查看和轮换。如果你主要在写代码、跑 Agent调用频率高Coding Plan 会更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。而 Claude Code 用户可以直接看专门的接入说明https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 。最后留一个实用技巧把TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL写进项目的.env文件用dotenv加载同时把.env加进.gitignore。这样本地开发方便又不会把 Key 提交上去。Node.js 里import dotenv/config一行搞定Python 里from dotenv import load_dotenv; load_dotenv()即可。凭证管理这件事越早规范越省事。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进