1. 为什么我要把 MCP 服务端和模型调用拆开接
MCP(Model Context Protocol)说白了就是给 AI 应用装一个标准化的外设接口,让模型能通过统一协议去读文件、查数据库、调工具。你可以把它理解成 AI 世界的 USB-C:不管对面是 Claude Desktop、Cursor 还是你自己写的 Agent,只要插上这个口,工具就能被识别和调用。而 JSON-RPC 2.0 就是这根线里跑的电信号,负责把「调用哪个工具、传什么参数、返回什么结果」讲清楚。
但真正动手搭第一个 MCP 应用时,很多人会卡在一个很现实的地方:服务端骨架搭好了,工具也注册了,可一旦要让 MCP 服务端背后的逻辑去调用大模型,Key 的管理就乱了。每个工具函数里塞一个 API Key,环境变量散落各处,换模型要改一堆代码。我这次的做法是:MCP 服务端只负责协议和工具暴露,模型调用统一走 TaoToken 的 API 通道,用一个 Key 打通 Python SDK 和 JSON-RPC 两条链路。
这篇是「从 0 到 1 构建 MCP 应用」的上篇,目标很明确:用 Python SDK 初始化一个 MCP 服务端,配好 JSON-RPC 通信骨架,再通过 TaoToken 统一 Key 完成一次模型调用接入,最后跑通一次 JSON-RPC 握手验证。跟着做,你能得到一个最小可用的 MCP 应用雏形,而不是一堆看不懂的抽象概念。
适合谁看:写过一点 Python、听过 MCP 但没真正跑起来、想让自己的工具被 AI 调用的开发者。不需要你懂协议底层,但需要你愿意动手敲命令。
2. 前置准备:TaoToken 统一 Key 与 Python 环境
2.1 为什么用 TaoToken 统一 Key
MCP 服务端的一个典型场景是:工具函数内部需要调用模型做二次处理,比如「读取文件 → 让模型总结 → 返回摘要」。如果每个工具各自管 Key,配置会非常碎。TaoToken 提供的是 OpenAI 兼容的 API 通道,一个 Key 就能覆盖模型对话、编码等调用,Python SDK 直接指向它的 base_url 即可,不用为每个工具单独维护凭证。
你需要先去控制台创建一个 API Key。入口在这里:
控制台创建 Key:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console 接入文档(base_url 与参数说明):https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc
创建完把 Key 存到环境变量里,别硬编码进代码。API 基础地址是https://taotoken.net/api,这个地址在后面的 config.toml 和 Python SDK 里都会用到。
2.2 环境与依赖安装
Python 版本要求 3.9 以上,先确认一下:
python --version然后建项目目录和虚拟环境。虚拟环境这一步别省,MCP SDK 和模型 SDK 的依赖版本容易互相干扰:
mkdir mcp-demo && cd mcp-demo python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate安装核心依赖。mcp是官方 Python SDK,openai用来走 TaoToken 的兼容接口:
pip install mcp openai验证安装:
python -c "import mcp; print(mcp.__version__)"能打印版本号就说明 SDK 装好了。如果报ModuleNotFoundError,八成是虚拟环境没激活,重新source venv/bin/activate再试。
3. 可复制配置:config.toml 与 settings.json 骨架
MCP 应用通常有两类配置:一类是服务端自己的运行参数(比如模型通道、超时),一类是宿主应用(如 Claude Desktop)用来发现和启动 MCP 服务端的配置。我把它拆成两个文件,职责清晰,改起来不打架。
3.1 config.toml:服务端运行配置
在项目根目录建config.toml,把模型通道和 MCP 服务端的基础参数放进去:
# config.toml [model] # TaoToken 统一 API 通道 base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" # 从环境变量读取,不写死 default_model = "gpt-4o-mini" timeout = 30 [mcp] server_name = "mcp-demo-server" transport = "stdio" # 本地开发先用 stdio log_level = "INFO" [tools] # 工具级开关,方便调试时单独关掉某个工具 enable_time_tool = true enable_model_tool = true这里的关键点是api_key_env:配置文件里只存环境变量的名字,真正的 Key 通过export TAOTOKEN_API_KEY=你的Key注入。这样配置文件可以进版本库,Key 不会泄露。
3.2 settings.json:宿主应用发现配置
如果你要把这个 MCP 服务端挂到 Claude Desktop 或类似宿主里,需要一个settings.json(不同宿主文件名可能不同,这里给通用骨架):
{ "mcpServers": { "mcp-demo-server": { "command": "python", "args": ["/absolute/path/to/mcp-demo/server.py"], "env": { "TAOTOKEN_API_KEY": "从环境变量继承或在此填入" } } } }注意args里必须是绝对路径,相对路径在宿主启动子进程时经常找不到文件,这是新手最容易踩的坑之一。env字段用来把 Key 传给子进程,如果宿主支持继承系统环境变量,这里可以留空。
4. 用 Python SDK 初始化 MCP 服务端并接入模型调用
4.1 读取配置与初始化模型客户端
先写一个配置加载模块config_loader.py,把 config.toml 读进来,同时初始化 TaoToken 的模型客户端:
# config_loader.py import os import tomllib # Python 3.11+;3.9~3.10 用 tomli from openai import OpenAI def load_config(path: str = "config.toml") -> dict: with open(path, "rb") as f: return tomllib.load(f) def build_model_client(cfg: dict) -> OpenAI: api_key = os.environ.get(cfg["model"]["api_key_env"]) if not api_key: raise RuntimeError("未找到 TAOTOKEN_API_KEY,请先 export") return OpenAI( base_url=cfg["model"]["base_url"], api_key=api_key, timeout=cfg["model"]["timeout"], )base_url指向https://taotoken.net/api,api_key从环境变量取。这样模型客户端就统一了,后面所有工具要调模型都复用这一个 client。
4.2 初始化 MCP 服务端并注册工具
创建server.py,用 MCP Python SDK 初始化服务端,注册两个工具:一个纯本地的时间工具,一个走 TaoToken 模型调用的总结工具。
# server.py import asyncio from datetime import datetime from mcp.server import Server from mcp.server.stdio import stdio_server from config_loader import load_config, build_model_client cfg = load_config() model_client = build_model_client(cfg) app = Server(cfg["mcp"]["server_name"]) @app.tool() async def get_current_time() -> str: """获取当前精确时间,包含时区信息""" return datetime.now().isoformat() @app.tool() async def summarize_text(text: str) -> str: """调用模型对给定文本做一句话总结。 Args: text: 需要总结的原始文本 """ resp = model_client.chat.completions.create( model=cfg["model"]["default_model"], messages=[ {"role": "system", "content": "你是一个简洁的总结助手。"}, {"role": "user", "content": f"用一句话总结:{text}"}, ], ) return resp.choices[0].message.content async def main(): async with stdio_server() as (read_stream, write_stream): await app.run( read_stream, write_stream, app.create_initialization_options(), ) if __name__ == "__main__": asyncio.run(main())这段代码里,summarize_text就是「MCP 工具 + 模型调用」的结合点:工具通过 JSON-RPC 被外部调用,内部再用 TaoToken 通道请求模型。整个服务端只依赖一个 Key,配置集中在 config.toml。
4.3 JSON-RPC 通信骨架说明
MCP 底层跑的是 JSON-RPC 2.0。当宿主调用summarize_text时,实际发出的消息长这样:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "summarize_text", "arguments": { "text": "MCP 是 AI 应用的标准外设接口" } } }服务端处理完返回:
{ "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "MCP 是 AI 应用的标准外设接口。" }] } }你不需要手写这些 JSON,SDK 会帮你序列化和反序列化。但理解这个骨架,排障时看日志就能对上号。
5. 验证请求:跑通一次 JSON-RPC 握手
5.1 启动服务端
先注入 Key,再启动:
export TAOTOKEN_API_KEY=你的Key python server.py如果没有任何报错、进程挂起等待输入,说明 stdio 传输已经就绪。这一步没有输出是正常的,因为 stdio 模式下服务端在等 JSON-RPC 消息。
5.2 用 MCP Inspector 做握手验证
官方调试工具 MCP Inspector 可以直接连上服务端,验证协议握手和工具列表:
npx @modelcontextprotocol/inspector python server.py它会打开一个本地 Web 界面。左侧应该能看到get_current_time和summarize_text两个工具。点开summarize_text,填入一段文本,点击调用。
如果配置正确,你会看到模型返回的一句话总结。这一步同时验证了三件事:JSON-RPC 握手成功、工具注册成功、TaoToken 模型通道可用。任何一环断了,这里都会报错。
5.3 用 Python 客户端做程序化验证
Inspector 适合手动测,想写进自动化脚本可以用 SDK 的客户端:
# client_test.py 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() # JSON-RPC 握手 tools = await session.list_tools() print("可用工具:", [t.name for t in tools.tools]) result = await session.call_tool( "summarize_text", {"text": "MCP 让模型能标准化地调用外部工具。"}, ) print("模型返回:", result.content[0].text) if __name__ == "__main__": asyncio.run(main())运行python client_test.py,如果打印出工具列表和模型总结,说明整条链路通了。session.initialize()就是那次关键的 JSON-RPC 握手,握手失败后面全免谈。
6. 本篇常见错排查
报错一:ModuleNotFoundError: No module named 'mcp'虚拟环境没激活,或者装到了全局。重新source venv/bin/activate后pip install mcp。
报错二:RuntimeError: 未找到 TAOTOKEN_API_KEY环境变量没导出,或者导出后换了终端窗口。echo $TAOTOKEN_API_KEY确认一下,为空就重新 export。注意 Key 只在当前 shell 会话有效。
报错三:模型调用返回 401 或鉴权失败检查base_url是不是写成了https://taotoken.net/api,末尾不要多加斜杠或路径。Key 复制时别带空格。可以到模型对话页面手动发一条消息,确认 Key 本身可用:
模型对话验证:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat
报错四:Inspector 里看不到工具装饰器@app.tool()没加,或者函数定义在main()之后。工具必须在app.run()之前注册。另外确认server.py没有语法错误,python server.py能正常挂起。
报错五:宿主启动子进程时报「找不到文件」settings.json 里的args用了相对路径。改成绝对路径,Windows 下注意反斜杠转义。
报错六:JSON-RPC 握手超时stdio 模式下,服务端往 stdout 打印了非协议内容(比如调试用的 print),会污染 JSON-RPC 流。把所有调试输出改成写日志文件或 stderr。
排障时如果怀疑是接入参数问题,对照接入文档再核一遍 base_url 和鉴权头格式:
接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc
7. 下一步:把 Key 管好,把链路跑顺
到这一步,你已经有了一个能跑的最小 MCP 应用:Python SDK 初始化服务端、JSON-RPC 骨架通了、TaoToken 统一 Key 把模型调用接进来了。上篇的重点是「跑通」,下篇会在这个骨架上加更多工具、处理并发调用、以及把服务端部署成可远程访问的形态。
如果你打算长期做编码类或 Agent 类项目,工具会越加越多,模型调用也会越来越频繁,这时候建议直接看 Coding Plan,把额度管理和多模型切换一次性配好,省得后面每个工具单独折腾:
Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan
另外,Key 的管理建议一开始就规范起来:控制台里给不同项目建不同的 Key,方便按项目排查用量和吊销。入口在这:
API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys
先把今天这套 config.toml + settings.json + server.py 的组合跑顺,下篇我们在这个基础上继续加料。