
1. LangGraph 接 MCP 的真实痛点工具一多Key 就乱LangGraph 做 Agent 编排时最舒服的写法是把「模型推理」和「工具执行」拆成两个节点用tools_condition做条件路由。但一旦工具来源从本地函数变成 MCP 服务问题就来了每个 MCP Server 背后可能挂着不同的模型供应商有的走本地 stdio有的走 streamable-httpKey 分散在.env、config.toml、settings.json三四个地方改一次要翻半天。这篇聚焦的场景很具体你有一个 LangGraph 工作流需要同时接入本地数学工具 MCP 和远程 BMI 计算 MCP并且希望所有模型调用统一走一个 Key不再为每个供应商单独配环境变量。适合已经在写 Agent、被多工具调用链折腾过的开发者。读完你能拿到config.toml和settings.json的可复制骨架、TaoToken 统一 Key 的填写位置以及一次本地调用链的验证动作和预期返回。我试过把 Key 硬编码在ChatDeepSeek(api_key...)里短期能跑但换模型或加工具时就得改代码。下面这套配置把 Key 抽到统一入口LangGraph 只负责编排MCP 只负责暴露工具职责清晰。2. TaoToken 前置统一 Key 与 MCP 配置入口TaoToken 在这里扮演的角色是「模型调用的统一出口」。你不需要在每个 MCP Server 里塞不同的供应商 Key而是让 LangGraph 里的 LLM 客户端指向同一个 API 地址和 Key。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。配置分两层一层是 MCP Server 的注册信息写在config.toml一层是 LangGraph 运行时的模型参数写在settings.json。两层都通过环境变量读取同一个 Key避免明文散落。先看config.toml的骨架它负责声明有哪些 MCP Server、各自用什么 transport# config.toml [llm] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model deepseek-chat [mcp_servers.math] transport stdio command python args [mcp_servers/math_mcp_server.py] [mcp_servers.bmi] transport streamable_http url http://localhost:8000/mcp再看settings.json它给 LangGraph 运行时提供模型和会话参数{ llm: { base_url: https://taotoken.net/api, api_key: ${TAOTOKEN_API_KEY}, model: deepseek-chat, temperature: 0 }, graph: { thread_id: 1234, checkpointer: memory } }Key 的填写位置只有一个环境变量TAOTOKEN_API_KEY。在 Windows 的 cmd 里可以这样设set TAOTOKEN_API_KEY你的KeyLinux/macOS 用export TAOTOKEN_API_KEY你的Key。这样config.toml和settings.json都引用同一个变量换 Key 只改一处。如果你还没有 Key可以在控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 具体 Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。注意不要把 Key 直接写进config.toml或提交到 Git。用环境变量引用是底线。3. 可复制配置LangGraph 多 MCP Server 完整骨架这一节给出能直接跑的代码。先准备两个 MCP Server一个本地 stdio一个远程 streamable-http。本地数学 MCPmath_mcp_server.py# math_mcp_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(Math) mcp.prompt() def system_prompt() - str: return You are an AI assistant, use the tools if needed. mcp.tool() def add(a: int, b: int) - int: Add two numbers return a b mcp.tool() def multiply(a: int, b: int) - int: Multiply two numbers return a * b if __name__ __main__: mcp.run()远程 BMI MCPbmi_mcp_server.py用 streamable-http 启动# bmi_mcp_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(BMI) mcp.tool() def calculate_bmi(weight: int, height: int) - str: Calculate BMI, height in meters return BMI: str(weight / (height * height)) if __name__ __main__: mcp.run(transportstreamable-http)启动 BMI 服务它会监听 8000 端口python bmi_mcp_server.py看到Uvicorn running on http://127.0.0.1:8000就说明远程 MCP 起来了。接下来是 LangGraph 客户端用MultiServerMCPClient同时挂两个 ServerLLM 走 TaoToken 统一入口# langgraph_mcp_client.py import asyncio import os from typing import Annotated, List from typing_extensions import TypedDict from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.prompts import load_mcp_prompt from langchain_mcp_adapters.tools import load_mcp_tools from langchain_openai import ChatOpenAI from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import StateGraph, START, END from langgraph.graph.message import AnyMessage, add_messages from langgraph.prebuilt import tools_condition, ToolNode client MultiServerMCPClient( { math: { command: python, args: [mcp_servers/math_mcp_server.py], transport: stdio, }, bmi: { url: http://localhost:8000/mcp, transport: streamable_http, }, } ) async def create_graph(math_session, bmi_session): llm ChatOpenAI( modeldeepseek-chat, base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY], ) math_tools await load_mcp_tools(math_session) bmi_tools await load_mcp_tools(bmi_session) tools math_tools bmi_tools llm_with_tool llm.bind_tools(tools) system_prompt await load_mcp_prompt(math_session, system_prompt) prompt_template ChatPromptTemplate.from_messages([ (system, system_prompt[0].content), MessagesPlaceholder(messages), ]) chat_llm prompt_template | llm_with_tool class State(TypedDict): messages: Annotated[List[AnyMessage], add_messages] def chat_node(state: State) - State: state[messages] chat_llm.invoke({messages: state[messages]}) return state graph_builder StateGraph(State) graph_builder.add_node(chat_node, chat_node) graph_builder.add_node(tool_node, ToolNode(toolstools)) graph_builder.add_edge(START, chat_node) graph_builder.add_conditional_edges( chat_node, tools_condition, {tools: tool_node, __end__: END} ) graph_builder.add_edge(tool_node, chat_node) return graph_builder.compile(checkpointerMemorySaver()) async def main(): config {configurable: {thread_id: 1234}} async with client.session(math) as math_session, client.session(bmi) as bmi_session: agent await create_graph(math_session, bmi_session) while True: message input(User: ) response await agent.ainvoke({messages: message}, configconfig) print(AI: response[messages][-1].content) if __name__ __main__: asyncio.run(main())这里的关键点ChatOpenAI的base_url指向 TaoToken APIapi_key从环境变量读。两个 MCP Server 的工具被合并成一个tools列表ToolNode统一执行。load_mcp_prompt从 math session 拿系统提示保证 Agent 知道要用工具。4. 验证请求一次本地调用链的预期返回配置写完后跑一次最小闭环。先确保 BMI 服务在另一个终端运行然后执行客户端python langgraph_mcp_client.py依次输入三个问题观察返回User: 45等于多少 AI: 4 5 等于 9。 User: 3*5 AI: 3 * 5 等于 15。 User: 体重80kg身高1.7mBMI多少 AI: 您的 BMI 值为 27.68。同时 BMI 服务的终端会打印请求日志Processing request of type ListToolsRequest Processing request of type CallToolRequest INFO: 127.0.0.1:56947 - POST /mcp/ HTTP/1.1 200 OK看到CallToolRequest就说明 LangGraph 的tool_node确实调用了远程 MCP 工具。如果只看到ListToolsRequest没有CallToolRequest说明模型没有触发工具调用检查bind_tools是否绑定了合并后的tools列表。验证模型对话是否走通 TaoToken可以单独发一个请求from openai import OpenAI import os client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY], ) resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 只回复OK}], ) print(resp.choices[0].message.content)返回OK就说明 Key 和 API 地址配置正确。这一步能快速排除「是模型没通还是 MCP 没通」的问题。5. 本篇常见错排查报错一ModuleNotFoundError: No module named langchain_mcp_adapters这是适配器包没装。执行pip install langchain-mcp-adapters mcp langgraph langchain-openai注意包名是langchain-mcp-adapters导入时用langchain_mcp_adapters。报错二Connection refused或httpx.ConnectError远程 BMI MCP 没启动或者端口不对。检查bmi_mcp_server.py是否在运行url是否写成http://localhost:8000/mcp。注意末尾的/mcp不能少少了会 404。报错三401 Unauthorized或invalid api keyTAOTOKEN_API_KEY没设或设错。在 cmd 里用echo %TAOTOKEN_API_KEY%确认Linux 用echo $TAOTOKEN_API_KEY。如果 Key 里有特殊字符确保没有多余空格。报错四模型不调用工具直接回答检查llm.bind_tools(tools)里的tools是否是合并后的列表。如果只绑了 math 的工具BMI 问题就不会触发远程调用。另外system_prompt里要明确「use the tools if needed」否则模型可能选择直接算。报错五stdio 路径问题args里的路径建议用相对路径或绝对路径不要在 VSCode 的集成终端里跑路径解析可能不一致。直接在 cmd 或 PowerShell 里执行python langgraph_mcp_client.py。提示如果工具调用链较长可以在chat_node里加日志打印state[messages]观察每一步的 tool_calls 字段。6. 下一步把统一 Key 用到长期编码与 Agent 场景跑通最小闭环后你可以把config.toml和settings.json的骨架直接复制到新项目只改 MCP Server 的注册信息。Key 始终从TAOTOKEN_API_KEY读LangGraph 的 LLM 客户端始终指向 https://taotoken.net/api 。如果你要长期跑编码类 Agent或者需要多轮工具调用的稳定会话可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各语言 SDK 的配置示例。模型对话调试可以用 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 快速验证 Key 是否可用。实际用下来把 Key 抽到环境变量、MCP 注册信息抽到config.toml是让 LangGraph 工作流可维护的关键一步。工具越多这个习惯越值钱。