1. 为什么我要手写一个 MCP server
MCP server 说白了就是一段 Python 程序,它把「工具」「资源」「提示词」这三类能力暴露给大模型客户端,让模型能真正去调函数、读数据,而不是只会在对话框里编。你平时用的 Cherry Studio、Claude Desktop、Cline 这些客户端,背后连的就是一个个 MCP server。理解它最好的方式不是看文档,而是自己从零写一个,跑通 stdio 和 SSE 两种传输模式,再把它接到统一的 API 通道上。
这篇面向的是已经会一点 Python、想搞清楚 MCP 到底怎么跑起来的人。我会用 uv 管环境,用官方mcp[cli]SDK 写一个带加法工具、动态资源、提示词模板的最小 server,然后分别用 stdio 和 SSE 两种方式验证连通性。最后给出 TaoToken 的config.toml和settings.json骨架,让请求走统一 Key 通道转发。全程本地可复现,不需要你去折腾网络环境。
我试过把 stdio 和 SSE 混在一个文件里反复切,最容易踩的坑是端口没释放和客户端配置路径写错,后面排障章节会逐个说。
2. TaoToken 前置:统一 Key 与 API 通道准备
在写 server 之前,先把「请求往哪发」这件事定下来。TaoToken 提供统一的 API 通道,你只需要一个 Key,就能在模型对话、编码、Agent 这些场景里复用同一套接入参数,不用每个客户端单独配一遍。
你需要做两件事:拿到 Key,记住两个地址。
| 项目 | 地址 | 用途 |
|---|---|---|
| 官网 | https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= | 注册、看文档、进控制台 |
| API Base | https://taotoken.net/api | 所有请求的统一入口,不加 UTM |
Key 在控制台的 API Keys 页面生成,格式一般是一串sk-开头的字符串。生成后先复制到本地一个安全的地方,后面config.toml和settings.json都要用。
注意:Key 只显示一次,页面刷新后就看不到了。建议生成后立刻写进本地配置文件,别只放在聊天窗口里。
如果你后面要长期跑编码类 Agent,可以顺带了解 Coding Plan,它把常用的编码模型额度打包,配合 MCP server 做工具调用会更顺。但这一篇的重点是骨架,先把最小链路跑通。
3. 用 uv 初始化工程并写最小 MCP server
3.1 安装 uv 与 Python 3.13
uv 是目前管 Python 环境最省心的工具,装完它连虚拟环境都不用你手动建。Windows 下用 PowerShell 一行搞定:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"装完确认一下版本和已有的 Python:
uv --version uv python list然后装目标版本,我选 3.13:
uv python install 3.133.2 初始化项目并加依赖
新建文件夹并初始化,-p指定 Python 版本:
mkdir mcp_server cd mcp_server uv init . -p 3.13 uv add "mcp[cli]"执行完你会看到.venv虚拟环境目录和pyproject.toml。pyproject.toml里记录了项目名、Python 版本和依赖,.venv是隔离环境,两者配合保证换台机器也能复现。
3.3 写 server 主体
把main.py改成下面这样。这段代码定义了一个加法工具、一个动态问候资源、一个提示词模板:
from mcp.server.fastmcp import FastMCP mcp = FastMCP("Demo") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" return a + b @mcp.resource("greeting://{name}") def get_greeting(name: str) -> str: """Get a personalized greeting""" return f"Hello, {name}!" @mcp.prompt() def greet_user(name: str, style: str = "friendly") -> str: """Generate a greeting prompt""" styles = { "friendly": "Please write a warm, friendly greeting", "formal": "Please write a formal, professional greeting", "casual": "Please write a casual, relaxed greeting", } return f"{styles.get(style, styles['friendly'])} for someone named {name}." if __name__ == "__main__": mcp.run(transport='stdio')三个装饰器的语义要分清:@mcp.tool()相当于 HTTP 里的 POST,模型调它会触发副作用或计算;@mcp.resource()相当于 GET,只读数据,不该改状态;@mcp.prompt()是给模型用的提示词模板,客户端可以把它当快捷指令。理解这三者的区别,后面设计自己的 server 就不会乱。
4. 可复制配置:stdio 与 SSE 两种传输
4.1 stdio 模式配置
stdio 模式下,客户端把 server 当子进程拉起来,通过标准输入输出通信。Cherry Studio 这类客户端里配置 MCP 服务器时,命令和参数这样填:
{ "mcpServers": { "demo-stdio": { "command": "uv", "args": [ "--directory", "D:\\userApplication\\mcp_server", "run", "mcp", "run", "main.py" ] } } }--directory指向你的工程目录,uv run mcp run main.py是启动命令。客户端会自己拉起这个进程,你不需要手动开终端。
4.2 SSE 模式配置
SSE 模式要把 server 单独跑起来,客户端通过 URL 远程调用。先把main.py最后一行改掉:
mcp.run(transport='sse')然后手动启动:
uv run mcp run main.py默认监听http://127.0.0.1:8000,SSE 端点是/sse。客户端里配置成 URL 形式:
{ "mcpServers": { "demo-sse": { "url": "http://127.0.0.1:8000/sse" } } }4.3 TaoToken 统一通道骨架
如果你希望 server 内部调用模型时走 TaoToken 的统一通道,用config.toml存接入参数:
[taotoken] api_base = "https://taotoken.net/api" api_key = "sk-你的Key" default_model = "你的模型名" [mcp] transport = "stdio"对应的settings.json给客户端用:
{ "taotoken": { "apiBase": "https://taotoken.net/api", "apiKey": "sk-你的Key" }, "mcp": { "transport": "stdio", "sseUrl": "http://127.0.0.1:8000/sse" } }提示:
api_base不要带 UTM 参数,保持https://taotoken.net/api干净,避免某些客户端拼接路径时出错。
5. 验证请求:stdio 与 SSE 连通性实测
5.1 stdio 验证
在 Cherry Studio 里添加 MCP 服务器,按 4.1 的 JSON 填好命令和参数,保存后客户端会尝试启动进程。启动成功的话,工具列表里会出现add,资源里会出现greeting://{name}。
调用add,传a=3, b=5,返回8就说明 stdio 链路通了。这一步的本质是客户端把 JSON-RPC 请求写进子进程的 stdin,server 处理后把结果写回 stdout。
5.2 SSE 验证
先确认 server 在跑:
uv run mcp run main.py终端会打印监听地址。然后在客户端里添加 URL 类型的 MCP 服务器,填http://127.0.0.1:8000/sse。添加后同样调add,返回8即成功。
你也可以用 curl 直接探一下 SSE 端点是否活着:
curl -N http://127.0.0.1:8000/sse-N关闭缓冲,能看到服务端持续推送的事件流就说明 SSE 通道正常。
5.3 两种模式的区别
| 维度 | stdio | SSE |
|---|---|---|
| 部署位置 | 客户端本机 | 可单独部署 |
| 通信方式 | 标准输入输出 | HTTP 长连接 |
| 距离 | 近,进程级 | 远,网络级 |
| 适用场景 | 本地工具、单机 | 远程服务、多客户端共享 |
stdio 适合本地一次性工具,SSE 适合把 server 放到一台机器上给多个客户端用。选哪个取决于你的 server 要不要被共享。
6. 本篇常见错排查
端口被占用:SSE 模式启动报Address already in use,说明 8000 端口有别的进程。换端口可以在FastMCP初始化时传port参数,或者先netstat -ano | findstr 8000找到进程结束掉。
客户端拉不起 stdio 进程:多半是--directory路径写错,或者uv不在系统 PATH 里。把路径换成绝对路径,并确认终端里uv --version能正常输出。
SSE 连不上:检查 server 是否真的在跑,以及 URL 是不是/sse结尾。有些客户端要求填完整端点,只填http://127.0.0.1:8000会失败。
Key 无效或 401:确认api_key复制完整,没有多余空格。如果走 TaoToken 通道报错,去控制台 API Keys 页面核对 Key 状态,必要时重新生成。
改了 transport 没生效:mcp.run(transport=...)是启动时读的,改完必须重启 server,客户端也要重新连接。
排障时优先看 server 端终端的输出,大部分错误信息会直接打在那里,比客户端日志清楚。
7. 下一步:把骨架接到真实场景
骨架跑通后,你可以按同样的结构加自己的工具。比如加一个读本地文件的工具,或者加一个查数据库的只读资源。工具用@mcp.tool(),只读数据用@mcp.resource(),提示词模板用@mcp.prompt(),三者别混用。
接入参数统一走 TaoToken 的 API 通道,Key 和 Base 只维护一份,换客户端时改settings.json就行。需要生成新 Key 或查看额度,进控制台 API Keys 页面;接入细节看接入文档;想先验证模型对话是否正常,用模型对话页面发一条测试消息;长期跑编码 Agent 的话,Coding Plan 能把额度管得更省心。
把add换成你真正需要的函数,这个 server 就从 demo 变成你自己的工具了。