1. 为什么 Agent 开发总卡在工具接入这一层
如果你最近在写 Agent,大概率遇到过这种局面:模型本身跑得挺顺,Function Calling 也能触发,但一到「真正调用外部工具」就开始出问题。比如你想让 Agent 查一下 ArXiv 上的论文、读一下本地数据库结构、或者调一个内部 CRM 接口,结果发现每个工具都要单独写适配层,参数格式不一样、鉴权方式不一样、返回结构也不一样。写三个工具还能忍,写到第八个的时候,代码里全是胶水逻辑,维护成本直接爆炸。
MCP(Model Context Protocol)就是为了解决这件事出现的。你可以把它理解成「AI 应用和外部工具之间的 USB-C 接口」:以前每个工具都要配一根专用线,现在统一成一个标准插口,插上就能用。MCP Server 负责把外部资源包装成标准能力,MCP Client 负责在 Agent 侧连接这些 Server,双方通过统一协议通信。对开发者来说,最大的好处是你不用再为每个工具写一套适配代码,Agent 侧只需要维护一个 MCP Client 会话,就能动态发现和调用工具。
但真正落地的时候,还有第二个坑:模型通道。很多第三方 MCP Server 本身不绑定模型,它只提供工具能力,真正做推理和决策的还是你 Agent 背后的 LLM。如果你用的是多个模型供应商,或者团队里有人用 Claude、有人用 GPT、有人用国产模型,Key 管理、Base URL 切换、额度分配就会变成新的麻烦。我试过在一个 LangGraph 项目里同时接三个模型通道,光是环境变量就维护了四套,换一次模型要改五个文件。
这篇要讲的就是把这两件事一起解决:用 TaoToken 作为统一的模型 API 通道,用 LlamaIndex / LangGraph 作为 Agent 框架,接入第三方 MCP Server。整条链路跑通之后,你换模型只需要改一个 Base URL 和 Key,MCP Server 的注册配置不用动,Agent 代码也不用动。下面从环境准备开始,一步步给可复制的配置和验证动作。
2. TaoToken 统一通道与 MCP 接入前置准备
在正式写 Agent 之前,先把「模型通道」这一层理清楚。TaoToken 在这里扮演的角色是统一 API 网关:你拿到一个 Key,就可以通过兼容 OpenAI 协议的接口调用不同模型,Agent 框架侧只需要配置一个 Base URL。这样做的直接好处是,MCP Server 负责工具,TaoToken 负责模型,两边解耦,互不影响。
2.1 拿到 Key 和 Base URL
先到 TaoToken 控制台创建一个 API Key。地址是 https://taotoken.net/api-keys ,登录后点创建,复制出来的 Key 形如sk-xxxxxxxx。这个 Key 就是后面所有配置里要填的凭证。
Base URL 统一用 https://taotoken.net/api ,注意这个地址不带任何查询参数,直接作为 OpenAI 兼容接口的base_url使用。如果你用的是 OpenAI SDK,配置大概是这样:
from openai import OpenAI client = OpenAI( api_key="sk-你的TaoTokenKey", base_url="https://taotoken.net/api" )如果你用的是 LlamaIndex,它内部也是走 OpenAI 兼容层,配置方式类似。LangGraph 侧如果用ChatOpenAI,同样把base_url指过去就行。
2.2 模型 ID 怎么选
TaoToken 支持多个模型,具体可用列表可以在模型对话页面查看: https://taotoken.net/models 。选模型的时候注意两点:第一,Agent 场景建议选 Function Calling 能力强的模型,因为 MCP 工具调用依赖模型输出结构化的 tool call;第二,如果你要做长链路 Agent,建议选上下文窗口大一点的,避免工具返回结果太长被截断。
我一般会在环境变量里把模型 ID 单独拎出来,方便切换:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL="你的模型ID"这样 Agent 代码里读环境变量就行,不用硬编码。
2.3 MCP 相关依赖安装
MCP 的 Python SDK 是mcp,LlamaIndex 侧需要llama-index和llama-index-tools-mcp,LangGraph 侧需要langgraph和langchain-mcp-adapters。一次性装齐:
pip install mcp llama-index llama-index-tools-mcp langgraph langchain-mcp-adapters如果你打算用uvx方式跑第三方 MCP Server,还需要装uv:
pip install uv装完之后可以用mcp --version和uv --version确认一下。这一步看起来简单,但后面很多报错都跟依赖版本有关,建议在虚拟环境里操作,避免污染全局。
2.4 第三方 MCP Server 怎么找
第三方 MCP Server 的生态现在挺活跃,常见的有文件系统、数据库、浏览器自动化、ArXiv 论文检索等。你可以从社区维护的列表里挑,也可以直接看某个 Server 的 README。挑选的时候重点看三件事:启动命令是什么、需要哪些参数、有没有依赖外部凭证。比如 ArXiv 那个 Server,启动命令是uv tool run arxiv-mcp-server,带一个--storage-path参数指定下载目录,不需要额外 Key,这种就适合拿来练手。
选好之后先别急着写 Agent,单独把 Server 跑起来确认能启动,再进下一步。很多问题其实是 Server 本身没跑通,而不是 Agent 代码写错了。
3. 可复制的 MCP Server 注册配置与 Agent 调用链路
这一节是核心,给完整的配置片段和 Agent 代码。分两部分:先写 MCP Server 的注册配置,再写 LlamaIndex 和 LangGraph 两种 Agent 的调用链路。配置里的 Base URL、Key、Model ID 三件套都会写全,你直接替换成自己的就行。
3.1 MCP Server 注册配置(JSON 片段)
MCP Server 的注册本质上就是告诉 Client「用什么命令启动哪个 Server」。以 ArXiv MCP Server 为例,配置片段如下:
{ "mcpServers": { "arxiv": { "command": "uv", "args": [ "tool", "run", "arxiv-mcp-server", "--storage-path", "./storage" ], "env": { "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_MODEL": "你的模型ID" } } } }这个 JSON 结构是 MCP 生态里比较通用的格式,Claude Desktop、Cline、CC Switch 这类工具都认。注意env里我把 TaoToken 的三件套也放进去了,因为有些 MCP Server 内部会自己调模型做预处理,带上这些环境变量能避免它去读全局配置。
如果你用的是 Cline 或者 CC Switch,配置路径一般在工具的 MCP 设置里,把上面这段粘进去就行。Codex 用户如果走auth.json,结构类似,把command和args对应填好即可。
3.2 LlamaIndex 侧 Agent 调用链路
LlamaIndex 提供了McpToolSpec,可以把 MCP Server 的 tools 直接转成 Agent 可用的工具列表。完整代码如下:
import asyncio import os from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from llama_index.core.agent import FunctionCallingAgent from llama_index.llms.openai import OpenAI from llama_index.tools.mcp import McpToolSpec server_params = StdioServerParameters( command="uv", args=[ "tool", "run", "arxiv-mcp-server", "--storage-path", "./storage" ], env={**os.environ} ) llm = OpenAI( model=os.environ["TAOTOKEN_MODEL"], api_key=os.environ["TAOTOKEN_API_KEY"], api_base=os.environ["TAOTOKEN_BASE_URL"] ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write, sampling_callback=None) as session: await session.initialize() tools_resp = await session.list_tools() for tool in tools_resp.tools: print("可用工具:", tool.name) mcp_tool_spec = McpToolSpec(session) tools_list = await mcp_tool_spec.to_tool_list_async() agent = FunctionCallingAgent.from_tools( tools_list, llm=llm, verbose=True, system_prompt="你是一个论文检索助手,请使用工具回答问题。" ) response = await agent.achat("帮我搜索关于 MCP 协议的论文") print(response) asyncio.run(main())这段代码的关键点有三个:第一,StdioServerParameters里的command和args必须和 JSON 配置一致;第二,OpenAI的api_base指向 TaoToken 的 Base URL;第三,McpToolSpec负责把 MCP tools 转成 LlamaIndex 的 Tool 对象,Agent 侧不用关心底层协议。
3.3 LangGraph 侧 Agent 调用链路
LangGraph 侧用langchain-mcp-adapters把 MCP tools 转成 LangChain Tool,再喂给create_react_agent。代码如下:
import asyncio import os from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from langchain_mcp_adapters.tools import load_mcp_tools from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent server_params = StdioServerParameters( command="uv", args=[ "tool", "run", "arxiv-mcp-server", "--storage-path", "./storage" ], env={**os.environ} ) llm = ChatOpenAI( model=os.environ["TAOTOKEN_MODEL"], api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"] ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write, sampling_callback=None) as session: await session.initialize() tools = await load_mcp_tools(session) agent = create_react_agent(llm, tools) result = await agent.ainvoke({ "messages": [("user", "搜索关于 Agent 的论文")] }) print(result["messages"][-1].content) asyncio.run(main())LangGraph 的好处是状态管理更清晰,适合多轮工具调用的场景。注意ChatOpenAI的base_url参数名和 LlamaIndex 的api_base不一样,别写错。
3.4 三件套对照表
| 配置项 | LlamaIndex 参数名 | LangGraph 参数名 | 值 |
|---|---|---|---|
| Base URL | api_base | base_url | https://taotoken.net/api |
| API Key | api_key | api_key | sk-你的Key |
| Model ID | model | model | 你的模型ID |
这张表建议存下来,换框架的时候对照着改,能省不少排查时间。
4. 端到端工具调用验证与成功结果
配置写完不算完,得跑一次完整的工具调用,确认从 Agent 到 MCP Server 再到外部资源的链路是通的。这一节给一个具体的验证动作,以及成功时应该看到什么输出。
4.1 验证动作:让 Agent 搜索一篇论文
用上面 LlamaIndex 的代码,把最后一行改成:
response = await agent.achat("帮我搜索标题包含 'Model Context Protocol' 的论文,返回前三条") print(response)运行命令:
python agent_arxiv.py4.2 成功时的输出特征
如果链路通了,你会看到类似这样的输出顺序:
可用工具: search_papers 可用工具: download_paper 可用工具: list_papers 正在调用工具: search_papers 工具返回: [{"title": "...", "authors": [...], "summary": "..."}] 最终回答: 找到以下三篇论文...关键看两个地方:第一,list_tools能列出 Server 提供的工具名;第二,Agent 的 verbose 日志里能看到search_papers被实际调用,并且有返回结果。如果只看到模型在「编」答案,没有工具调用日志,说明 Function Calling 没触发,大概率是模型 ID 选得不对,或者 Base URL 配错了。
4.3 验证模型通道是否走 TaoToken
想确认模型请求确实走了 TaoToken,可以在代码里加一行日志,打印llm的配置:
print("Base URL:", llm.api_base) print("Model:", llm.model)输出应该是:
Base URL: https://taotoken.net/api Model: 你的模型ID如果 Base URL 显示的是其他地址,说明环境变量没生效,检查一下export有没有在当前 shell 里执行,或者代码里是不是硬编码了别的地址。
4.4 一次完整的工具调用链路
把整个链路串起来看是这样的:Agent 收到用户问题 → 模型(走 TaoToken)决定调用search_papers→ MCP Client 通过 stdio 把请求发给 MCP Server → MCP Server 调用 ArXiv API 拿到结果 → 结果回传给模型 → 模型生成最终回答。整个过程里,TaoToken 只负责模型推理这一段,MCP Server 负责工具执行,两边通过 Agent 框架解耦。这也是为什么换模型不用改 MCP 配置,换 MCP Server 也不用改模型配置。
5. 本篇常见错误排查
跑不通的时候别慌,大部分问题集中在几个固定位置。下面按报错现象分类,给排查路径。
5.1 401 Unauthorized
这是最常见的报错,说明 Key 没传对或者没生效。排查顺序:
第一,确认TAOTOKEN_API_KEY环境变量在当前 shell 里能打印出来:
echo $TAOTOKEN_API_KEY如果输出为空,说明export没执行,或者你在新的终端窗口里没重新 export。
第二,确认代码里读的是这个环境变量,而不是硬编码了别的 Key。LlamaIndex 的OpenAI和 LangGraph 的ChatOpenAI都支持从环境变量读,但参数名不一样,别混。
第三,确认 Key 没有多余空格。从控制台复制的时候容易带上换行,建议用strip()处理一下。
5.2 local proxy failed 或连接超时
这个报错通常出现在 MCP Server 启动阶段,说明 Client 没能拉起 Server 进程。排查:
第一,确认command指向的可执行文件在 PATH 里。比如uv没装或者不在 PATH,就会报这个。用which uv确认。
第二,确认args里的包名和参数正确。uv tool run arxiv-mcp-server如果包名拼错,会卡在下载阶段然后超时。
第三,如果是远程 MCP Server,检查网络连通性。本地 stdio 模式一般不会有网络问题,除非 Server 内部要访问外部 API。
5.3 reading choices 相关报错
这个报错说明模型返回的结构不符合预期,通常是 Function Calling 的输出格式问题。排查:
第一,确认模型 ID 支持 Function Calling。有些模型只支持纯文本对话,不支持 tool call,用在这种场景就会报reading choices相关的解析错误。
第二,确认 TaoToken 的 Base URL 没写错。如果 Base URL 指向了一个不兼容 OpenAI 协议的地址,返回结构会对不上。
第三,检查tools列表是不是空的。如果load_mcp_tools返回空列表,模型没有工具可调,也可能触发奇怪的解析错误。加一行print(len(tools))确认。
5.4 OAuth 或鉴权相关报错
有些第三方 MCP Server 需要 OAuth 或者额外的凭证。排查:
第一,看 Server 的 README,确认是否需要额外配置。比如某些数据库 MCP Server 需要连接字符串。
第二,如果 Server 内部要调模型,确认env里带上了 TaoToken 的三件套。有些 Server 会读OPENAI_API_KEY和OPENAI_BASE_URL,你可以把 TaoToken 的值映射过去:
"env": { "OPENAI_API_KEY": "sk-你的Key", "OPENAI_BASE_URL": "https://taotoken.net/api" }第三,如果是 OAuth 流程,按 Server 文档走一遍授权,拿到 token 后再填进配置。
5.5 工具调用没触发
Agent 回复了,但没调工具。排查:
第一,看 verbose 日志有没有tool call相关输出。没有的话,说明模型没决定调工具。
第二,检查 system prompt 有没有引导模型使用工具。加一句「请优先使用工具回答问题」通常能改善。
第三,确认工具描述清晰。MCP Server 返回的 tool description 如果太模糊,模型可能不知道什么时候该调。可以在 Agent 侧对 tools 做一层包装,补充描述。
6. 把通道和工具解耦之后的工作方式
整条链路跑通之后,你会发现开发方式变了。以前接一个新工具,要改模型配置、改 Agent 代码、改鉴权逻辑;现在只需要在 MCP 配置里加一段 JSON,Agent 侧重新list_tools就能发现新能力。模型侧更简单,换模型只改一个环境变量,MCP 配置和 Agent 代码都不用动。
如果你打算长期做 Agent 开发,建议把 TaoToken 的 Key 管理起来,不同项目用不同的 Key,方便追踪用量。模型对话页面可以快速验证某个模型是否支持 Function Calling,省得在代码里反复试。接入文档里有各框架的配置示例,遇到参数名不确定的时候可以直接查。
最后给一个实用技巧:把 MCP Server 的配置和 TaoToken 的环境变量分开管理。MCP 配置放项目目录下的mcp.json,环境变量放.env或者 shell profile,这样换机器的时候只需要重新配环境变量,MCP 配置可以直接复用。Agent 代码里读配置的时候做一层封装,比如写个load_mcp_config()函数,后面加新 Server 就不用改主逻辑了。