1. 为什么 MCP 是 AI Agent 工具集绕不开的一层
如果你最近在折腾 AI Agent,大概率会遇到一个很现实的问题:模型本身很聪明,但它碰不到你的文件、数据库、内部 API。Function Calling 能解决一部分,可每接一个新工具就要写一套适配代码,换个模型厂商还得重写一遍。MCP(Model Context Protocol,模型上下文协议)就是冲着这个碎片化问题来的——它把「模型怎么调用外部工具」这件事标准化了,你可以把它理解成 AI 世界的 USB-C 接口:工具服务端按统一协议暴露能力,Agent 客户端按统一协议发现和调用,两边不用互相认识。
MCP 能做什么?一句话:让一套工具服务被所有支持 MCP 的 Agent 框架复用。适合谁?正在做 AI Agent 工具集、想让模型安全访问本地文件或内网服务、又不想被单一模型厂商绑死的开发者。这篇我会从零搭一个 MCP Server,把计算器、文件读取这类工具注册进去,再用 TaoToken 统一 Key 承接模型调用,最后跑通「用户提问 → 模型决策 → 工具执行 → 二次推理」的完整闭环。全程代码可复制,踩过的坑我也会标出来。
先说清楚架构,不然后面配置容易懵。MCP 是三层:Host 是承载大模型的 Agent 主机,负责发起调用和整理上下文;Client 是 Host 内置的通信模块,负责和 Server 建连接、封装标准报文;Server 就是你自己写的工具服务端,把外部能力包装成标准 MCP 工具接口。通信流程是握手 → 工具发现 → 模型决策 → Server 执行 → 结果回传 → 二次推理。和原生 Function Calling 比,MCP 最大的区别是解耦:工具逻辑独立进程部署,本地资源权限由 Server 单独管控,还能通过 SSE 远程调用,工具热更新时 Agent 不用重启。
理解了这层,你就明白为什么我要把模型调用单独抽出来用统一 Key——工具服务是本地进程,模型调用是外部 API,两者解耦后,换模型只改一个 Base URL,工具代码一行不动。
2. TaoToken 前置准备:统一 Key 与 API 通道
在动手写 Server 之前,先把模型调用这条链路理顺。MCP Server 本身不负责调模型,它只暴露工具;真正调模型的是 Agent 客户端。所以我们需要一个稳定的、OpenAI 兼容的 API 通道来承接模型请求,这样客户端代码里那套tools参数和 Function Calling 流程才能直接复用。
TaoToken 在这里的角色就是统一 Key 和 API 通道。你注册后在控制台创建一个 API Key,之后所有模型调用都走同一个 Base URL,不用为每个模型厂商单独维护一套鉴权和地址。对 MCP 工具集这种「工具固定、模型可能换」的场景特别合适——工具注册代码写一次,模型侧只改 model 字段。
具体操作路径:先到官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册账号,然后进控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 创建 API Key。Key 只在创建时完整显示一次,复制下来存到环境变量里,别硬编码进代码提交到仓库。
API 通道地址是 https://taotoken.net/api ,这个地址不加任何 UTM 参数,直接作为 OpenAI SDK 的base_url使用。注意末尾不要多加/v1,SDK 会自己拼路径,多写一层会 404。
我建议用环境变量管理 Key,这样本地调试和后续部署都不用改代码:
# macOS / Linux export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" # Windows PowerShell $env:TAOTOKEN_API_KEY="sk-你的key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"验证 Key 是否可用,最直接的方式是发一个最小请求。你可以用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 先手动测一下,确认通道通了再写代码。命令行验证:
curl https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-chat", "messages": [{"role": "user", "content": "只回复两个字:通了"}] }'返回里有choices[0].message.content就说明 Key 和通道都正常。这一步别跳过,后面客户端报 401 十有八九是这里没通。如果你打算长期跑编码类 Agent,可以顺手看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,额度策略对高频工具调用更友好。
3. 可复制配置:MCP Server 与客户端 settings 片段
这一节给你能直接落地的配置。先装依赖,MCP 官方 Python SDK 要求 Python 3.10 以上:
python3 --version # 确认 >= 3.10 pip install mcp openai项目目录我按这个结构组织,后面所有路径都以此为准:
mcp-agent-demo/ ├── server/ │ ├── calc_server.py │ └── file_server.py ├── client/ │ └── agent_client.py └── requirements.txtrequirements.txt内容:
mcp>=1.0.0 openai>=1.30.0先写计算器 Server,这是最小可运行单元。核心是@server.list_tools()注册工具描述、@server.call_tool()处理调用:
#!/usr/bin/env python3 from mcp.server import Server from mcp.types import Tool, TextContent import asyncio server = Server("calc-mcp-server") @server.list_tools() async def handle_list_tools() -> list[Tool]: return [ Tool( name="calc_compute", description="四则运算计算器,支持加减乘除,输入数学表达式", inputSchema={ "type": "object", "properties": { "expr": {"type": "string", "description": "数学表达式,如 100*2+30/5"} }, "required": ["expr"] } ) ] @server.call_tool() async def handle_call_tool(name: str, arguments: dict) -> list[TextContent]: if name == "calc_compute": expr = arguments.get("expr") try: result = eval(expr, {"__builtins__": None}, {}) return [TextContent(type="text", text=f"表达式:{expr}\n结果:{result}")] except Exception as e: return [TextContent(type="text", text=f"计算失败:{str(e)}")] raise ValueError(f"未定义工具:{name}") async def main(): await server.run() if __name__ == "__main__": asyncio.run(main())注意eval这里清空了__builtins__,只是演示用,生产环境务必换成sympy这类安全解析库,别拿 eval 直接跑用户输入。
客户端这边,模型调用统一走 TaoToken。关键配置就三件套:Base URL、Key、Model ID。如果你用的是 Claude Code 或 Cline 这类工具,它们的 MCP 配置通常是一个 JSON,路径和字段名要对齐。以通用 MCP 客户端配置为例:
{ "mcpServers": { "calc-server": { "command": "python", "args": ["server/calc_server.py"], "env": { "TAOTOKEN_API_KEY": "sk-你的key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }如果你在 Claude Code 里接入,配置走~/.claude/settings.json或项目级.mcp.json,字段结构类似,command指向你的 Python 解释器绝对路径更稳。Codex 用户则是在auth.json里配 Base URL 和 Key,Model ID 填你实际要用的模型名。三件套缺一不可:Base URL 写错会 404,Key 错会 401,Model ID 错会报 model not found。
4. 端到端验证:跑通工具调用闭环
配置写完,现在验证整条链路。客户端要做四件事:启动 Server 子进程、建立 Stdio 会话、拉取工具列表、把工具转成模型能识别的格式发起调用。
#!/usr/bin/env python3 import asyncio import json import os import subprocess from mcp.client.stdio import stdio_client from mcp.client.session import ClientSession from openai import OpenAI llm = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"] ) async def run_agent(): proc = subprocess.Popen( ["python", "server/calc_server.py"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) async with stdio_client(proc.stdout, proc.stdin) as (read, write): async with ClientSession(read, write) as session: await session.initialize() print("MCP 握手成功") tools_resp = await session.list_tools() tools = tools_resp.tools print("工具列表:", [t.name for t in tools]) query = "计算 125 * 8 + 360 / 12 等于多少?" resp = llm.chat.completions.create( model="deepseek-chat", messages=[{"role": "user", "content": query}], tools=[{ "type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.inputSchema } } for t in tools] ) msg = resp.choices[0].message if msg.tool_calls: for call in msg.tool_calls: args = json.loads(call.function.arguments) print("模型发起调用:", call.function.name, args) result = await session.call_tool(call.function.name, args) text = "\n".join(c.text for c in result.content) print("工具返回:", text) final = llm.chat.completions.create( model="deepseek-chat", messages=[ {"role": "user", "content": query}, msg, {"role": "tool", "tool_call_id": call.id, "name": call.function.name, "content": text} ] ) print("最终回答:", final.choices[0].message.content) else: print("直接回答:", msg.content) if __name__ == "__main__": asyncio.run(run_agent())运行python client/agent_client.py,预期看到握手成功、工具列表里有calc_compute、模型发起调用、工具返回结果、最终回答拼装完成。到这一步,你的 MCP 工具集调用闭环就跑通了。
想扩展成多工具集,就再写一个file_server.py,用同样的list_tools/call_tool模式注册read_local_file,然后在客户端同时启动两个子进程、建两组ClientSession,把所有工具合并后一起传给模型。模型会根据工具描述自动选择调用哪个,服务之间数据完全隔离。文件读取记得做白名单目录校验,别让模型随便读系统文件。
5. 本篇常见报错排查
401 Unauthorized:Key 没读到或写错。先确认环境变量TAOTOKEN_API_KEY在当前 shell 里echo得出来,再确认请求头是Bearer sk-xxx格式。如果 Key 是从控制台复制的,注意别带多余空格。
local proxy failed / connection refused:客户端连不上 API 通道。检查base_url是不是https://taotoken.net/api,末尾别加/v1。网络层面确认能正常访问该域名,公司内网可能需要放行。
reading choices 报错 / KeyError: 'choices':返回体结构不对,通常是请求被网关拦截返回了错误页,或者 model 字段填了不存在的模型名。打印完整resp看原始返回,别只看choices。
OAuth / 鉴权失败:如果你在 Claude Code 或 Cline 里接入,确认 MCP 配置的env字段把 Key 传进去了,有些客户端不会继承系统环境变量,必须在配置里显式写。
MCP handshake failed:Server 进程启动就崩了。单独跑python server/calc_server.py看报错,常见是 Python 版本低于 3.10,或者mcp包没装对版本。
tool not found:工具名大小写不一致,或者list_tools返回的定义和调用时用的名字对不上。打印工具列表核对一遍。
SSE 远程连接超时:如果用了远程模式,确认端口开放、路由路径是/mcp/stream,防火墙别拦。
排查顺序建议:先单独验证 API 通道(curl 那步),再单独验证 Server 能启动,最后跑客户端。分层定位比一上来就 debug 客户端快得多。
6. 继续往下走:从单工具到工具集
跑通计算器只是起点。真实场景里你会有文件读写、数据库查询、内部 API 调用一堆工具,MCP 的价值就在于它们都能用同一套模式注册、被同一个 Agent 发现和调用。我的建议是先把工具描述写清楚——description和inputSchema直接决定模型能不能正确选工具,参数说明越具体,模型误调用越少。
模型侧继续用 TaoToken 统一 Key 承接,换模型只改model字段,工具代码零改动。需要看更多接入细节可以翻接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Key 管理在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。下一步你可以试着把文件服务和计算器串起来,让模型先读文件里的数字再计算,这就是多工具集协作的雏形。