1. 为什么你的第一个 MCP Server 值得认真写
MCP Server 这个词最近出现频率很高,但很多人第一次接触时容易把它想复杂。说白了,模型上下文协议(Model Context Protocol,简称 MCP)就是给大模型装了一套标准插座:以前你想让模型查天气、读文件、调数据库,得为每个工具单独写一套接口,模型换个平台就得重写一遍;现在只要按 MCP 的格式把工具注册好,任何支持 MCP 的客户端都能直接调用。MCP Server 就是这套插座背后的服务端,负责暴露工具、接收请求、返回结果。
这篇文章面向第一次接触 MCP 的开发者,目标很明确:从零写出一个最小可运行的 MCP Server,本地启动,注册一个工具,然后用一次完整调用链路验证它真的通了。同时我会把模型请求统一走 TaoToken 的 Key 通道,这样你后面换模型、加工具时不用到处改配置。适合谁看?会一点 Python、装过 pip、能看懂 JSON 的人就够了,不需要你之前碰过 MCP。
我试过把整个流程拆成六步:先讲清楚问题和场景,再准备 TaoToken 的统一 Key,然后给出可复制的 server 配置,接着用 curl 和日志双重验证,再列一遍新手最容易踩的报错,最后把入口整理给你。跟着做,半小时内你能看到自己的 MCP Server 返回第一条真实响应。
2. TaoToken 统一 Key 准备与 MCP 环境依赖清单
在写代码之前,先把两件事准备好:一个是模型调用的统一入口,一个是本地依赖。很多人卡在第一步不是因为不会写 MCP,而是 Key 散落在各个平台,调试时根本分不清请求到底走了哪条通道。用 TaoToken 的好处是 Base URL 和 Key 固定,模型 ID 按需切换,MCP Server 里只认这一套配置。
先拿 Key。打开 https://taotoken.net/api-keys ,登录后创建一个 API Key,复制下来存到环境变量里,别硬编码进代码。我习惯这样写:
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"注意 Base URL 是https://taotoken.net/api,不带任何多余路径。模型 ID 你可以先在 https://taotoken.net/models 看一眼当前可用的列表,选一个你熟悉的,比如gpt-4o-mini或claude-3-5-sonnet这类通用对话模型,MCP 工具调用对模型的要求主要是支持 function calling。
依赖清单很轻,Python 3.10 以上即可:
pip install "mcp[cli]" httpxmcp[cli]是官方 SDK,自带命令行调试工具;httpx用来在工具内部发 HTTP 请求。如果你打算用 Node.js 写,换成npm install @modelcontextprotocol/sdk也行,但本文以 Python 为主线,因为它的 SDK 对新手最友好,报错信息也直白。
目录结构建议这样:
my_mcp_server/ ├── server.py ├── tools/ │ └── weather_tool.json └── .env.env里放 Key,tools/weather_tool.json放工具描述符,server.py是主逻辑。这样拆的好处是工具描述和实现分离,后面加第二个、第三个工具时不用动主文件。
有一点要提醒:MCP Server 本身不负责模型推理,它只负责暴露工具。模型调用工具的那一步,是由 MCP Client(比如 Claude Desktop、Cline、或者你自己写的客户端)发起的。所以你的 Server 里不需要写任何大模型相关的代码,只需要把工具注册好、把请求转发到 TaoToken 的统一通道即可。这个边界搞清楚,后面调试会省很多事。
3. 可复制的 MCP Server 配置与工具注册代码
现在进入核心部分。先写工具描述符tools/weather_tool.json:
{ "name": "get_weather", "description": "查询指定城市的当前天气", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,例如 北京" } }, "required": ["city"] } }这个文件的作用是告诉客户端「我有哪些工具、每个工具要什么参数」。MCP 的标准化就体现在这里:不管你的工具背后是查数据库还是调第三方 API,描述格式都一样。
接着写server.py。官方 SDK 的写法比早期版本简洁很多,用FastMCP装饰器注册工具:
import os import httpx from mcp.server.fastmcp import FastMCP mcp = FastMCP("weather-server") TAOTOKEN_BASE_URL = os.getenv("TAOTOKEN_BASE_URL", "https://taotoken.net/api") TAOTOKEN_API_KEY = os.getenv("TAOTOKEN_API_KEY") @mcp.tool() async def get_weather(city: str) -> dict: """查询指定城市的当前天气""" # 这里用统一通道做一次模型侧确认,实际项目可替换为真实天气 API async with httpx.AsyncClient(timeout=30) as client: resp = await client.post( f"{TAOTOKEN_BASE_URL}/v1/chat/completions", headers={ "Authorization": f"Bearer {TAOTOKEN_API_KEY}", "Content-Type": "application/json", }, json={ "model": "gpt-4o-mini", "messages": [ {"role": "user", "content": f"用一句话描述{city}今天的天气,不要解释"} ], }, ) resp.raise_for_status() data = resp.json() summary = data["choices"][0]["message"]["content"] return {"city": city, "summary": summary, "source": "taotoken"} if __name__ == "__main__": mcp.run(transport="stdio")几个关键点。第一,@mcp.tool()装饰器会自动读取函数的类型注解和 docstring,生成工具描述,所以你不需要手动再写一遍 JSON——上面那个weather_tool.json是给不支持自动发现的客户端用的备份。第二,transport="stdio"表示用标准输入输出通信,这是本地开发最省事的方式,客户端启动这个进程后直接通过管道对话。第三,模型调用走的是{TAOTOKEN_BASE_URL}/v1/chat/completions,这是 OpenAI 兼容格式,TaoToken 的通道直接支持。
如果你更习惯用配置文件而不是环境变量,可以写一个settings.toml:
[taotoken] base_url = "https://taotoken.net/api" api_key = "sk-你的key" default_model = "gpt-4o-mini" [mcp] transport = "stdio" name = "weather-server"然后在server.py里用tomllib读进来。这样团队协作时配置一目了然,也不会因为某个人忘了 export 环境变量而报 401。
启动命令:
python server.py如果一切正常,进程会安静地挂在那里等待输入,不会有花哨的输出。这是 stdio 模式的正常表现,别以为它卡死了。想确认它活着,用官方自带的调试器:
mcp dev server.py这会打开一个本地调试界面,能看到已注册的工具列表和调用日志。第一次跑通时,看到get_weather出现在工具列表里,基本就成功一半了。
4. 用 curl 与日志双重验证 MCP 调用链路
Server 起来了,怎么确认它真的能工作?我习惯用双重验证:一次 curl 直接打模型通道,确认 Key 和 Base URL 没问题;一次通过 MCP 客户端调用工具,确认整条链路通。
先验证 TaoToken 通道本身:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "回复两个字:通了"}] }'正常返回类似:
{ "choices": [ { "message": { "role": "assistant", "content": "通了" } } ] }如果这一步就报 401,说明 Key 有问题;报连接错误,说明 Base URL 写错了。先把这一步跑通,再往下走。
接着验证 MCP 工具调用。用mcp dev server.py打开调试界面,在工具列表里点get_weather,参数填{"city": "北京"},执行。你会看到返回:
{ "city": "北京", "summary": "北京今天晴,气温约 25 摄氏度,适合外出。", "source": "taotoken" }同时在终端日志里能看到类似这样的记录:
INFO Received request: tools/call get_weather INFO POST https://taotoken.net/api/v1/chat/completions 200 INFO Response sent: 1 result这两条日志很关键:第一条证明 MCP 协议层收到了调用请求,第二条证明请求确实走了 TaoToken 的统一通道并成功返回。如果只有第一条没有第二条,说明工具内部逻辑出错;如果两条都没有,说明客户端根本没连上 Server。
想更贴近真实场景,可以写一个最小 MCP Client 来调:
import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params = StdioServerParameters(command="python", args=["server.py"]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print("可用工具:", [t.name for t in tools.tools]) result = await session.call_tool("get_weather", {"city": "上海"}) print("调用结果:", result.content) asyncio.run(main())跑通后输出里会同时出现工具列表和天气结果。到这一步,你的第一个 MCP Server 就算真正跑通了:本地启动、工具注册、完整调用链路,三件事都验证过了。
5. 新手必踩的 MCP 报错与排查对照表
这一节是我踩过的坑合集。MCP 刚上手时,报错信息往往不直观,下面按真实报错对照排查。
401 Unauthorized。最常见,九成是 Key 没读到。检查echo $TAOTOKEN_API_KEY有没有输出,或者.env有没有被正确加载。注意 stdio 模式下 Server 是子进程,父进程的环境变量不一定继承,稳妥做法是在server.py开头显式load_dotenv()。
local proxy failed / connection refused。这个报错通常出现在客户端连不上 Server 时。stdio 模式下检查command和args路径对不对,比如python是不是虚拟环境里的那个。如果你用的是 SSE 或 WebSocket 传输,检查端口有没有被占用,lsof -i :8000看一眼。
reading choices 报错 / KeyError: 'choices'。说明请求发出去了但返回结构不对。大概率是 Base URL 写成了https://taotoken.net/api/v1又在代码里拼了一次/v1,变成/v1/v1/chat/completions。统一用https://taotoken.net/api作为 Base URL,路径里只写一次/v1。
OAuth 相关报错。如果你用的是 Claude Code 或某些需要 OAuth 的客户端,报 OAuth 失败时先确认客户端版本,再检查是不是把 API Key 和 OAuth 混用了。MCP Server 本身不处理 OAuth,它只认请求里的工具调用。
工具列表为空。list_tools返回空数组,通常是装饰器没生效。检查@mcp.tool()有没有漏写括号,或者函数是不是 async。另外 docstring 不能为空,SDK 靠它生成描述。
调用超时。工具内部请求 TaoToken 时没设 timeout,默认可能很长。显式加timeout=30,并在客户端侧也设一个合理的超时。
对照表:
| 报错 | 最可能原因 | 处理 |
|---|---|---|
| 401 | Key 未加载 | 检查环境变量或 .env |
| local proxy failed | 进程路径/端口错 | 核对 command 与端口 |
| reading choices | Base URL 重复拼接 | 统一用 /api |
| OAuth 失败 | 客户端认证方式混用 | 区分 Key 与 OAuth |
| 工具列表空 | 装饰器或 docstring 问题 | 补括号、补描述 |
排查时记住一个原则:先确认通道(curl 打 TaoToken),再确认协议(mcp dev 看工具),最后确认业务(工具内部逻辑)。分层定位,比盲目改代码快得多。
6. 把 MCP Server 接入你的日常开发流
跑通最小示例只是起点。真正让 MCP Server 有价值,是把它接进你每天用的工具里。如果你用 Cline 或 Claude Code 这类支持 MCP 的编辑器,配置方式基本一致,核心三件套是 Base URL、Key、Model ID:
{ "mcpServers": { "weather": { "command": "python", "args": ["/绝对路径/my_mcp_server/server.py"], "env": { "TAOTOKEN_API_KEY": "sk-你的key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }这段配置放到客户端的 MCP 设置里,重启后就能在对话中直接让模型调用get_weather。注意args用绝对路径,相对路径在不同工作目录下会失效。
如果你打算长期做编码类 Agent,把多个工具注册到同一个 Server 里,统一走 TaoToken 通道,模型切换时只改一个 Model ID 就行。需要看当前可用模型和额度,去 https://taotoken.net/models ;要管理多个 Key 或做团队隔离,去 https://taotoken.net/console 。接入文档在 https://taotoken.net/doc ,里面有各语言 SDK 的完整示例。
调试阶段遇到通道问题,优先回 https://taotoken.net/api-keys 确认 Key 状态;想快速验证某个模型能不能正常对话,用 https://taotoken.net/chat 直接试一句,比在代码里反复改快得多。长期跑编码任务的话,Coding Plan 在 https://taotoken.net/coding-plan 有更省心的额度方案。
最后给一个实用建议:把工具描述写清楚。模型能不能选对工具,八成取决于 description 写得够不够具体。get_weather写成「查询指定城市的当前天气」就比「获取天气」强很多。工具多了以后,描述质量直接决定调用准确率。你的第一个 MCP Server 不用追求功能多,把一个工具打磨到模型每次都能正确调用,比堆十个半成品有用。