1. 为什么你的 Agent 需要一个 MCP 中间层
如果你正在做 AI Agent,大概率遇到过这种局面:Agent 要读本地文件、要查数据库、要调内部 HTTP 接口、还要操作浏览器,每接一个能力就写一套适配代码,工具一多,主流程里全是 if-else 和胶水逻辑。MCP(Model Context Protocol,模型上下文协议)要解决的就是这件事——它把「Agent 怎么连外部资源」抽象成统一协议,Agent 只跟 MCP Server 对话,具体连的是文件、数据库还是某个 API,全部下沉到 Server 里。
一句话概括:MCP 是 Agent 和外部工具之间的统一插座。Agent 是电器,MCP Server 是插线板,你换电器不用重装修电路。
它适合谁?适合已经跑通单轮对话、准备把 Agent 做成可维护工程的开发者;也适合手上有一堆内部系统、想让模型安全调用又不想把密钥散落各处的人。这篇不聊概念空转,直接交付一套可运行的 MCP 服务端骨架、Agent 侧配置片段,以及用 TaoToken 统一 Key/API 通道接入的示例,最后给出启动、连通性和性能验证动作。源码结构我会拆到你能直接复制粘贴的程度。
需要先明确一个边界:MCP 负责「工具怎么被描述和调用」,模型推理仍然走大模型 API。所以你会看到两条链路——一条是 MCP 的 stdio 进程通信,一条是 Agent 到模型服务的 HTTP 请求。把这两条链路分清楚,后面排障会轻松很多。
2. TaoToken 前置:把模型通道统一成一条
在搭 Agent 之前,先把模型调用这条链路固定下来。我试过在多个项目里分别维护不同厂商的 Key 和 endpoint,工具一多,环境变量就乱成一团。TaoToken 的价值在于提供一个统一的 API 通道,OpenAI 兼容格式,Agent 侧只认一个 base_url 和一个 Key,换模型不用改业务代码。
你需要先拿到 Key。进入控制台创建 API Key,建议按项目维度建,别所有环境共用一个。地址是 https://taotoken.net/api ,Key 管理在 https://taotoken.net/console/api-keys 。拿到之后,把它写进环境变量,不要硬编码进源码:
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"如果你用的是 Claude Code 这类编码 Agent,TaoToken 也提供了对应的接入方式,可以参考 https://taotoken.net/doc 里的说明,Anthropic 兼容通道在 https://taotoken.net/ClaudeCodeAnthropic 。模型对话调试可以直接用 https://taotoken.net/models 页面验证 Key 是否可用,省得在代码里反复试错。
注意:Key 只放服务端环境变量,前端和 MCP Server 的日志里都不要打印完整 Key。MCP Server 如果要把模型能力暴露给 Agent,也应该由 Agent 侧持有 Key,Server 只做工具执行。
这一步做完,你手上应该有两个东西:一个可用的模型通道,一个待搭建的 MCP 工具层。接下来进入正题。
3. 可复制配置:MCP Server 骨架与 Agent 接入
3.1 环境与依赖
Python 3.10+ 即可,MCP 官方 SDK 安装很轻:
pip install mcp openaimcp提供 Server/Client 能力,openai用来走 TaoToken 的兼容接口。目录结构建议这样,别把所有东西塞一个文件:
agent-mcp/ ├── server/ │ ├── main.py # MCP Server 入口 │ └── tools/ │ ├── calc.py # 计算工具 │ └── files.py # 文件读取工具 ├── agent/ │ └── runner.py # Agent 主循环 └── .env3.2 MCP Server 骨架
先写一个带两个工具的 Server,一个做四则运算,一个读受限目录下的文本文件。工具注册用装饰器,参数用类型注解,SDK 会自动生成 schema 给模型看:
# server/main.py from mcp.server.fastmcp import FastMCP import os mcp = FastMCP("agent-tools") SAFE_DIR = os.path.abspath("./workspace") @mcp.tool() def calculate(expression: str) -> float: """计算四则运算表达式。 参数 expression: 形如 '188*23-34' 的字符串。 返回: 计算结果。""" allowed = set("0123456789+-*/(). ") if not set(expression) <= allowed: raise ValueError("表达式包含非法字符") return eval(expression, {"__builtins__": {}}, {}) @mcp.tool() def read_text(path: str) -> str: """读取 workspace 目录下的文本文件。 参数 path: 相对 workspace 的路径。""" full = os.path.abspath(os.path.join(SAFE_DIR, path)) if not full.startswith(SAFE_DIR): raise ValueError("路径越界") with open(full, "r", encoding="utf-8") as f: return f.read()[:4000] if __name__ == "__main__": mcp.run(transport="stdio")这里有两个工程细节值得说。第一,eval前做了字符白名单,别直接裸 eval,Agent 传进来的参数不可信。第二,文件工具做了路径越界检查,这是 MCP 工具最容易踩的安全坑——模型可能被诱导去读../../etc/passwd这类路径。
3.3 Agent 侧接入 MCP
Agent 主循环要做三件事:启动 MCP Server 会话、把工具列表转成模型能理解的 function schema、拿到模型返回的 tool_call 后路由到对应 MCP 工具。下面这段是核心:
# agent/runner.py import asyncio, json, os from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], ) server_params = StdioServerParameters( command="python", args=["./server/main.py"], env=None, ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() schema = [{ "type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.inputSchema, }, } for t in tools.tools] messages = [{"role": "user", "content": "帮我算一下 188*23-34"}] resp = client.chat.completions.create( model="gpt-4o-mini", messages=messages, tools=schema, ) msg = resp.choices[0].message if msg.tool_calls: call = msg.tool_calls[0] args = json.loads(call.function.arguments) result = await session.call_tool(call.function.name, args) print("工具返回:", result.content) asyncio.run(main())跑起来后,你会看到工具返回4288.0。这条链路打通,意味着你的 Agent 已经能通过 MCP 调用外部能力,而且新增工具只需要在 Server 里加一个@mcp.tool(),Agent 侧零改动。
3.4 上下文管理的关键参数
高性能 Agent 的瓶颈往往不在模型,而在上下文膨胀。三个参数建议显式控制:工具返回内容截断(上面read_text的 4000 字符)、历史消息窗口(只保留最近 N 轮 + 工具结果摘要)、以及工具 schema 的按需注入(工具超过 20 个时,先让模型选工具类别再注入具体 schema)。这些不是 MCP 协议强制的,但决定了你的 Agent 能不能长期稳定跑。
4. 验证请求与成功结果
启动验证分两步。先单独验证 MCP Server 能不能被 Inspector 拉起,这是最快的排障手段:
mcp dev server/main.py浏览器打开提示的本地地址,能看到calculate和read_text两个工具,手动传参调用,返回正常就说明 Server 本身没问题。这一步能把「Server 写错」和「Agent 接错」两类问题分开。
再验证完整链路:
python agent/runner.py预期输出类似:
工具返回: [TextContent(type='text', text='4288.0')]如果模型没有触发 tool_call,先检查tools=schema是否传进去了,再检查模型是否支持 function calling。TaoToken 通道下换个支持工具调用的模型即可,模型列表在 https://taotoken.net/models 可以查。
性能验证给一个可量化的动作:连续调用 50 次calculate,统计 P95 延迟。MCP 本地 stdio 通信本身通常在毫秒级,如果你看到单次超过 200ms,大概率是 Server 里做了阻塞 IO 或每次调用重建了会话。会话要复用,别在工具函数里重新stdio_client。
5. 本篇常见错排查
报错ModuleNotFoundError: No module named 'mcp':确认 pip 装在了当前 Python 环境,虚拟环境激活了吗。用python -c "import mcp; print(mcp.__file__)"定位。
Agent 启动后卡住无输出:stdio 模式下 Server 的 stdout 被协议占用,任何print调试都会污染通信。调试信息一律走 stderr,或者用 Inspector。
工具调用返回Invalid arguments:模型生成的参数和 schema 不匹配。检查类型注解是否准确,expression: str别写成expression。schema 是模型唯一的依据。
路径越界报错:这是安全机制生效,不是 bug。把要读的文件放进workspace目录,或者调整SAFE_DIR。
模型不调用工具,直接编答案:prompt 里明确要求「需要计算时必须调用工具」,同时确认tool_choice没被设成none。
Key 报 401:检查TAOTOKEN_API_KEY是否带上了Bearer前缀重复,OpenAI SDK 会自动加,环境变量里只放sk-开头的原始值。
6. 把通道和工具层分开维护
搭完这套骨架,你会发现工程上真正省心的地方在于职责分离:MCP Server 只管工具怎么执行和安全边界,Agent 只管编排和模型交互,模型通道交给 TaoToken 统一收口。三者独立演进,加工具不动 Agent,换模型不动工具。
下一步可以做的:把 Server 拆成多个按领域划分的 MCP 进程(文件、数据库、内部 API 各一个),Agent 侧维护多个 Session;给工具加调用审计日志;把高频工具结果做本地缓存。这些都是在当前骨架上增量加,不用重构。
如果你还没配好模型通道,先去 https://taotoken.net/api-keys 建 Key,接入文档在 https://taotoken.net/doc ,编码类 Agent 的长期使用可以看 https://taotoken.net/coding-plan 。工具层跑通之后,模型对话调试用 https://taotoken.net/models 验证,整条链路就闭环了。