1. 为什么要在 LangChain Agent 里接入 MCP如果你已经用 LangChain 写过几版 Agent大概率会遇到一个很现实的问题每接一个新工具就要重新写一遍tool装饰器、重新定义参数 schema、重新调 prompt。工具一多代码里全是重复的样板改一个参数名要翻好几个文件。MCPModel Context Protocol想解决的就是这件事——它把「工具怎么描述、怎么调用、怎么返回」抽象成一套标准协议工具提供方按协议写一个 MCP ServerAgent 侧只要写一个 MCP Client 就能把 Server 里的所有工具一次性挂进来。放到 LangChain 的语境里这件事的价值更明显。LangChain 本身有成熟的 Agent 抽象create_openai_tools_agent、AgentExecutor也有统一的模型接入层init_chat_model但它缺一个「工具即插即用」的标准化入口。MCP 正好补上这一环langchain-mcp-adapters这个包会把 MCP Server 暴露的工具自动转换成 LangChain 的BaseTool对象你拿到的就是一堆可以直接塞进 Agent 的工具函数不用手写转换逻辑。这篇是系列第八篇前七篇从 LangChain 核心概念、链、记忆、多轮对话一路讲到 Agent API 和 Playwright 工具调用。这一篇聚焦一个具体目标用 LangChain 写 MCP Client把外部 MCP Server 的工具接进 Agent并且用 TaoToken 统一 Key/API 通道做模型鉴权与调用入口。适合已经跑通过基础 Agent、想把手上的工具链标准化、又不想在多个模型供应商之间来回切 Key 的开发者。整条链路我拆成四步先准备 TaoToken 的通道配置再写 MCP Server 注册文件然后写 LangChain Agent 初始化代码最后跑一个端到端验证。每一步都给可复制的片段你照着改路径和 Key 就能跑。2. TaoToken 统一通道前置配置在写 MCP 之前先把模型这一侧的通道理顺。LangChain 的init_chat_model支持base_url参数只要目标服务兼容 OpenAI 的/v1/chat/completions协议就能直接接。TaoToken 提供的就是这样一个统一入口一个 Base URL、一个 Key背后可以路由到不同的模型省掉你在代码里维护多套api_key和base_url的麻烦。先拿到 Key。打开控制台在 API Keys 页面创建一个新 Key复制出来。这个 Key 只显示一次建议直接写进环境变量别硬编码进代码。# Linux / macOS export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api # Windows PowerShell $env:TAOTOKEN_API_KEYsk-你的Key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你更习惯用.env文件在项目根目录建一个TAOTOKEN_API_KEYsk-你的Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELclaude-sonnet-4-5这里TAOTOKEN_MODEL填你要用的模型 ID。TaoToken 的模型列表在文档里有选一个支持 tool calling 的因为 Agent 要靠 function calling 来驱动 MCP 工具。选模型的时候注意一点不是所有模型都稳定支持并行工具调用做 MCP 场景建议优先选工具调用能力标注明确的。装依赖。除了 LangChain 本体还需要 MCP 适配器和 dotenvpip install langchain langchain-mcp-adapters langchain-openai python-dotenvlangchain-mcp-adapters是官方维护的桥接包核心就两个东西MultiServerMCPClient负责连多个 MCP Serverload_mcp_tools负责把 Server 的工具转成 LangChain Tool。版本上建议用较新的早期版本对 stdio 传输的支持不太稳。注意TaoToken 的 Base URL 是https://taotoken.net/api不要在后面手动加/v1SDK 会自己拼路径。加了反而会 404。到这一步模型通道就准备好了。接下来写 MCP 这一侧。3. 可复制的 MCP Server 注册与 Agent 初始化配置MCP Server 的注册用一个 JSON 文件描述LangChain 侧读这个文件就知道要连哪些 Server、用什么传输方式。在项目根目录建servers_config.json{ mcpServers: { playwright: { command: npx, args: [playwright/mcplatest], transport: stdio }, filesystem: { command: npx, args: [-y, modelcontextprotocol/server-filesystem, /tmp/mcp-workspace], transport: stdio } } }这段配置里playwright是浏览器自动化 Serverfilesystem是文件系统 Server。transport填stdio表示通过标准输入输出通信command和args是启动 Server 的命令。npx会自动下载对应的包第一次跑会慢一点。filesystem那个args最后一项是允许访问的目录按你的实际路径改。如果你用的是 Cline 或 Claude Code 这类客户端它们的 MCP 配置格式和这个基本一致可以直接把mcpServers这一段搬过去。区别在于客户端可能用mcpServers作为顶层键而 LangChain 的适配器读的也是这个键所以能复用。然后是 Agent 初始化代码。新建mcp_agent.pyimport asyncio import json import logging import os from dotenv import load_dotenv from langchain import hub from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain.chat_models import init_chat_model from langchain_mcp_adapters.client import MultiServerMCPClient load_dotenv() class Config: def __init__(self): self.api_key os.getenv(TAOTOKEN_API_KEY) self.base_url os.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api) self.model os.getenv(TAOTOKEN_MODEL, claude-sonnet-4-5) staticmethod def load_servers(pathservers_config.json): with open(path, r, encodingutf-8) as f: return json.load(f).get(mcpServers, {}) async def build_agent(): cfg Config() servers Config.load_servers() mcp_client MultiServerMCPClient(servers) tools await mcp_client.get_tools() logging.info(已加载 %d 个 MCP 工具: %s, len(tools), [t.name for t in tools]) llm init_chat_model( modelcfg.model, model_provideropenai, api_keycfg.api_key, base_urlcfg.base_url, ) prompt hub.pull(hwchase17/openai-tools-agent) agent create_openai_tools_agent(llm, tools, prompt) return AgentExecutor(agentagent, toolstools, verboseTrue) async def main(): executor await build_agent() print(MCP Agent 已启动输入 quit 退出) while True: user_input input(\n你: ).strip() if user_input.lower() quit: break try: result await executor.ainvoke({input: user_input}) print(f\nAI: {result[output]}) except Exception as exc: print(f\n出错: {exc}) if __name__ __main__: logging.basicConfig(levellogging.INFO) asyncio.run(main())几个关键点解释一下。model_provideropenai是因为 TaoToken 走 OpenAI 兼容协议base_url指向 TaoToken 的 API 地址api_key用你创建的那个 Key。MultiServerMCPClient(servers)接收的就是上面 JSON 里的mcpServers字典它会为每个 Server 起一个子进程通过 stdio 通信。get_tools()是异步的返回的是 LangChain 的BaseTool列表直接能塞进create_openai_tools_agent。如果你之前用过 Codex 的auth.json或者 Cline 的 MCP 配置会发现三件套永远是那三样Base URL、Key、Model ID。TaoToken 把前两样统一成一个入口Model ID 按需换代码里只改一个环境变量。4. 端到端验证从工具发现到 Agent 自主调用配置写完跑起来验证。先确认工具能被发现python mcp_agent.py启动日志里应该能看到类似这样一行INFO - 已加载 12 个 MCP 工具: [browser_navigate, browser_click, browser_type, browser_snapshot, read_file, write_file, ...]工具数量取决于你配了几个 Server。如果这里是 0说明 Server 没起来往下看第五节。工具加载成功后在交互里输入一个需要多步工具调用的任务比如你: 打开 https://taotoken.net 这个页面把页面标题和主要导航项列出来Agent 的执行链路是这样的模型先看到工具列表判断需要browser_navigate调用它打开页面拿到返回后再判断需要browser_snapshot抓取页面结构最后把结果整理成自然语言输出。verboseTrue会把每一步的思考、工具名、参数、返回值都打出来你能清楚看到 Agent 是怎么一步步决策的。一个典型的成功输出片段 Entering new AgentExecutor chain... Invoking: browser_navigate with {url: https://taotoken.net} ... Invoking: browser_snapshot with {} ... 页面标题是 TaoToken主要导航项包括模型对话、Coding Plan、控制台、API Keys、接入文档。 Finished chain.再试一个文件操作的你: 在 /tmp/mcp-workspace 下创建一个 hello.txt写入当前时间Agent 会调用write_file参数里带上路径和内容。执行完你去目录里cat hello.txt能看到结果。这一步验证的是 MCP Server 的写权限和路径映射是否正确。如果你想单独验证模型通道是否通不经过 MCP可以直接用模型对话页面发一条消息确认 Key 和 Base URL 没问题。这样排障时能把「模型通道问题」和「MCP 问题」分开。验证通过后这套代码就可以作为模板复用了。换工具只需要改servers_config.json换模型只需要改TAOTOKEN_MODELAgent 逻辑一行不用动。5. 常见报错排查401、local proxy failed、reading choices跑 MCP LangChain 这套组合报错基本集中在几个地方。下面按我实际遇到的频率排。401 Unauthorized。最常见的原因是 Key 没读到。检查.env文件是否在项目根目录、load_dotenv()是否在读取环境变量之前调用。另一个原因是 Key 复制时带了空格或换行用print(repr(os.getenv(TAOTOKEN_API_KEY)))打出来看看。还有一种情况是 Base URL 写成了https://taotoken.net/api/v1多加了/v1导致路径拼接错误去掉即可。local proxy failed / connection refused。这个报错通常出现在 MCP Server 启动阶段不是模型通道的问题。原因是npx拉包失败或者 Node.js 没装。先确认node -v和npx -v能正常输出再手动跑一次npx playwright/mcplatest看能不能起来。如果卡在下载检查网络和 npm 源。另外filesystemServer 的路径参数如果指向一个不存在的目录也会启动失败先mkdir -p建好。Error reading choices / 返回结构解析失败。这个多半是模型返回的 tool call 格式和 LangChain 预期不一致。TaoToken 走 OpenAI 兼容协议正常情况下不会出现但如果你的模型 ID 选了一个不支持 function calling 的就会返回纯文本而不是 tool callLangChain 解析时就会报这个。换一个支持工具调用的模型 ID 即可。判断方法在模型对话页面直接问「调用工具」看返回里有没有tool_calls字段。OAuth / 鉴权跳转。如果你接的某个 MCP Server 需要 OAuth 授权比如某些云服务stdio 模式下没法弹浏览器会卡住。这类 Server 建议先用支持 OAuth 的客户端如 Claude Code完成一次授权把 token 缓存下来再让 LangChain 侧复用。或者改用支持 HTTP 传输的 Server。工具加载为 0。日志里显示已加载 0 个 MCP 工具说明MultiServerMCPClient没连上任何 Server。逐个排查JSON 格式是否合法用python -m json.tool servers_config.json验证、transport字段是否拼写正确、command是否在 PATH 里。把servers_config.json里只留一个 Server跑通再加第二个能快速定位是哪个配置有问题。排障时记住一个原则先分离模型通道和 MCP 通道。模型通道用模型对话页面验证MCP 通道用单独启动 Server 验证两边都通了再合起来跑 Agent。这样报错信息指向明确不用猜。6. 把 MCP 工具链接进你的 Agent 工作流跑通这一套之后你会发现 MCP 带来的最大变化不是「多接了几个工具」而是工具的组织方式变了。以前每个工具是代码里的一个函数现在每个工具是一个独立的 Server可以单独开发、单独测试、单独部署。你的 LangChain Agent 代码变成了一个纯粹的「编排层」只负责把模型和工具连起来不关心工具内部怎么实现。实际用的时候我建议把servers_config.json按环境拆成多份比如servers_dev.json和servers_prod.json通过环境变量切换。开发环境可以挂一堆调试用的 Server生产环境只留必要的。TaoToken 的 Key 也建议按环境分开创建方便在控制台看调用量和排查问题。如果你要把这套跑在长期运行的服务里而不是 CLI 交互把main()里的while循环换成你的请求处理逻辑就行AgentExecutor本身是无状态的每次ainvoke传入新的 input 即可。需要多轮记忆的话在AgentExecutor外面套一层RunnableWithMessageHistory。下一步可以试试把 MCP Server 换成你自己写的。MCP 协议本身不复杂用官方 SDK 写一个暴露几个工具函数的 Server几十行代码就能跑起来然后挂到servers_config.json里Agent 立刻就能用。这才是 MCP 真正省事的地方——工具开发和 Agent 开发彻底解耦了。模型通道这边TaoToken 的接入文档里有不同语言的示例Python 之外还有 Node 和 curl 的版本换语言的时候可以直接参考。API Keys 页面可以管理多个 Key按项目或环境分配配合控制台的用量统计能清楚看到每个 Agent 的调用情况。