1. 为什么要把 MCP 服务端从 stdio 切到 SSE
MCP(Model Context Protocol)是让本地工具、数据库、内部系统接入 AI 能力的一套协议。它目前主流有两种传输方式:stdio 和 HTTP SSE。stdio 适合本地开发,客户端把服务端当子进程启动,通过标准输入输出交换数据,延迟低、配置简单。但一旦你想让多个应用、多个工作流共享同一个工具服务,stdio 就力不从心了——每个客户端都得自己拉起一份进程,跨机器、跨容器更是麻烦。
SSE 模式解决的就是这个问题。服务端跑一次,暴露/sse和/messages/两个 HTTP 接口,任何能联通网络的客户端都能接入。SSE 是单向推送,服务端向客户端推流,客户端通过 POST 往/messages/发请求,握手和流式返回都走标准 HTTP。对于 Dify、Cherry Studio 这类支持远程 MCP 的客户端,SSE 几乎是默认选择。
这篇要做的,是把一个 Python 写的 MCP 服务端用 SSE 模式部署起来,然后把它的 endpoint 指向 TaoToken 的 API 网关,让工具调用真正落到模型侧。适合谁?手上有本地工具(数据库、内部系统、文件处理脚本),想让 AI 直接调用的开发者。全程可复制,命令和配置都给你。
我试过把 stdio 版本直接改成 SSE,踩了几个坑,下面一步步来。
2. TaoToken 前置准备:拿到 Base URL 和 Key
TaoToken 在这里扮演的是模型侧的统一入口。MCP 服务端本身只负责暴露工具,真正决定“用哪个模型、怎么调”的是客户端或网关。把 endpoint 改到 TaoToken,意味着你的 MCP 工具链最终请求的是 TaoToken 的兼容接口,而不是散落在各处的原生地址。
你需要准备三样东西:Base URL、API Key、Model ID。这三件套在 TaoToken 控制台都能拿到。
Base URL 固定为https://taotoken.net/api,注意这里不加任何查询参数。API Key 在控制台的 API Keys 页面创建,创建后只显示一次,复制保存好。Model ID 根据你要用的模型填,比如claude-sonnet-4-5这类标识,具体以控制台模型列表为准。
操作路径:打开 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 创建 Key;模型列表和接入说明在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 查看。想先在网页里验证模型通不通,可以用 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 直接对话测试。
如果你后面要长期跑编码类 Agent,比如 Claude Code 那种持续会话的场景,建议单独看下 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,它和按量调用是两条线。
这里强调一点:TaoToken 是合规的 API 聚合入口,不是任何形式的转发工具。你拿到的 Key 就是正常调用凭证,配置方式和任何标准 OpenAI 兼容接口一致。
准备好这三件套后,我们进入服务端部署。
3. 可复制配置:Python SSE 服务端 + endpoint 指向 TaoToken
先建目录,装依赖。依赖清单如下,直接复制:
pip install mcp pip install mysql-connector-python pip install uvicorn pip install python-dotenv pip install starlette pip install httpxhttpx是后面把工具调用转发到 TaoToken 时用的,先装上。
新建.env,把数据库和 TaoToken 配置都放进去:
MYSQL_HOST=192.168.20.128 MYSQL_PORT=3306 MYSQL_USER=root MYSQL_PASSWORD=abcd@1234 MYSQL_DATABASE=test TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_API_KEY=sk-你的Key TAOTOKEN_MODEL=claude-sonnet-4-5然后是server.py的核心部分。SSE 传输用SseServerTransport,路由挂/sse和/messages/:
import os import uvicorn import httpx from mcp.server.sse import SseServerTransport from mcp.server import Server from mcp.types import Tool, TextContent from starlette.applications import Starlette from starlette.routing import Route, Mount from dotenv import load_dotenv load_dotenv() app = Server("taotoken_mcp_demo") @app.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="ask_model", description="把问题转发给 TaoToken 上的模型并返回回答", inputSchema={ "type": "object", "properties": { "prompt": {"type": "string", "description": "要问模型的问题"} }, "required": ["prompt"], }, ), ] @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: if name != "ask_model": raise ValueError(f"未知工具: {name}") prompt = arguments.get("prompt") if not prompt: raise ValueError("缺少 prompt") base = os.getenv("TAOTOKEN_BASE_URL") key = os.getenv("TAOTOKEN_API_KEY") model = os.getenv("TAOTOKEN_MODEL") async with httpx.AsyncClient(timeout=60) as client: resp = await client.post( f"{base}/v1/chat/completions", headers={"Authorization": f"Bearer {key}"}, json={ "model": model, "messages": [{"role": "user", "content": prompt}], }, ) resp.raise_for_status() data = resp.json() text = data["choices"][0]["message"]["content"] return [TextContent(type="text", text=text)] sse = SseServerTransport("/messages/") async def handle_sse(request): async with sse.connect_sse( request.scope, request.receive, request._send ) as streams: await app.run(streams[0], streams[1], app.create_initialization_options()) starlette_app = Starlette( debug=True, routes=[ Route("/sse", endpoint=handle_sse), Mount("/messages/", app=sse.handle_post_message), ], ) if __name__ == "__main__": uvicorn.run(starlette_app, host="0.0.0.0", port=9000)启动:
python server.py看到Uvicorn running on http://0.0.0.0:9000就说明起来了。这里 endpoint 的关键点在于:MCP 服务端本身不直接连模型,它把ask_model这个工具的执行逻辑转发到TAOTOKEN_BASE_URL。也就是说,客户端调 MCP 工具 → MCP 服务端 → TaoToken API → 模型返回。链路清晰,替换模型只改.env里的TAOTOKEN_MODEL。
如果你用的是 Claude Code 这类客户端,配置三件套时同样填 Base URL、Key、Model ID,格式和上面一致。Cline 的 MCP 配置里也是这三项,别漏。
4. 验证请求:curl 与 MCP 客户端各跑一次
先验证 SSE 握手。开一个终端:
curl -N http://127.0.0.1:9000/sse-N关闭缓冲,你会看到服务端持续推流,类似:
event: endpoint data: /messages/?session_id=xxxx这说明 SSE 通道建立成功,服务端把消息接收地址推给了你。复制那个session_id,另开终端发一条 POST:
curl -X POST "http://127.0.0.1:9000/messages/?session_id=xxxx" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'正常会返回工具列表,包含ask_model。这一步确认了握手和消息通道都通。
接着验证工具真正调到 TaoToken。用 MCP 客户端(Cherry Studio 最新版)添加服务器:类型选 SSE,URL 填http://127.0.0.1:9000/sse,保存后能看到ask_model工具。新建助手,开启 MCP,输入“用 ask_model 问:你好,介绍一下你自己”。如果返回了模型回答,说明整条链路——客户端 → MCP SSE → TaoToken → 模型——全部打通。
实测下来,第一次调用可能慢几秒,因为要建 SSE 长连接和模型首包。后续流式返回会明显快。
5. 本篇常见错排查:401、local proxy failed、reading choices
报错一:401 Unauthorized。九成是TAOTOKEN_API_KEY没填对,或者.env没被load_dotenv()读到。检查 Key 有没有多余空格,检查.env和server.py是否同目录。另外确认请求头是Authorization: Bearer sk-xxx,少个空格都会 401。
报错二:local proxy failed或连接被拒。这通常是客户端填的 URL 不对。SSE 的 URL 必须是http://127.0.0.1:9000/sse,不是/messages/。/messages/是服务端内部用来收 POST 的,客户端只连/sse。如果你把端口写成 8000 而服务端跑在 9000,也会报这个。
报错三:reading choices相关,比如KeyError: 'choices'或解析失败。说明 TaoToken 返回的结构和你预期不一致。先单独用 curl 打一次模型接口确认返回:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}]}'如果这里正常返回choices,那问题在 MCP 服务端的解析代码;如果这里就报错,检查 Model ID 是否拼错、Key 是否有额度。
报错四:OAuth 相关提示。部分客户端在 SSE 模式下会尝试 OAuth 流程,但本地 MCP 服务端一般不需要。遇到就检查客户端里是否误开了鉴权选项,关掉即可。MCP 服务端自身的鉴权由 TaoToken 的 Key 承担,不需要额外 OAuth。
排障时优先看服务端终端日志,uvicorn会把每个请求和异常打出来,比客户端报错更直接。
6. 把工具链接到 TaoToken 之后能做什么
服务端跑通只是起点。真正有价值的是把你手头的内部能力包成 MCP 工具:查 CRM、读 GitLab issue、处理特定格式的 Excel、调内部业务系统。每个工具就是一个@app.call_tool()分支,执行逻辑里该连数据库连数据库,该调 TaoToken 调 TaoToken。
模型侧统一走 TaoToken 的好处是,你换模型不用改工具代码,只改.env一行。想验证某个模型适不适合你的工具场景,去 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 直接对话试。要长期跑 Agent 或编码任务,Coding Plan 那条线更划算:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。接入文档和更多示例在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
一个实用技巧:SSE 服务端部署到有公网的内网机器后,多个客户端可以共用同一个/sse地址,不用每个客户端各拉一份进程。端口记得在防火墙放行,但别暴露到公网无鉴权,TaoToken 的 Key 只放在服务端.env里,不要下发到客户端。