1. 为什么要在 LangGraph 里接 MCP 本地模式如果你正在用 LangGraph 搭 Agent大概率会遇到一个尴尬模型能聊天但一让它读本地文件、查数据库、算个公式就开始胡编。原因不复杂——Agent 本身没有手脚它需要一个标准化的工具层。MCPModel Context Protocol就是干这个的而 stdio 本地模式是它最轻的一种跑法不用起 HTTP 服务不用配端口父进程拉起子进程通过标准输入输出通信。这套组合适合谁三类人。第一类是做本地 Agent 原型的开发者机器上跑着 Ollama 或本地模型不想为了调个工具再搭一套服务第二类是想把已有 Python 函数快速暴露给 Agent 的人FastMCP 几行代码就能把普通函数变成工具第三类是已经在用 LangGraph 的create_react_agent想给它挂上外部能力但不想改架构的人。我试过把 MCP 的 stdio 模式和 LangGraph 的 prebuilt agent 拼在一起整个链路跑通之后Agent 的决策路径会变得很清晰模型先判断要不要调工具调哪个参数是什么工具返回结果后再组织语言。这个过程在日志里能完整看到调试起来比黑盒调用舒服得多。本文用一个利息计算器做例子定义两个工具函数——单利和复利然后让 LangGraph Agent 根据自然语言问题自动选择调用。你会拿到完整的 MCP Server 代码、LangGraph Client 代码、依赖安装命令以及跑通后的日志长什么样。中间涉及模型接入的部分我会用 TaoToken 的 API 做演示因为它兼容 OpenAI 接口格式换模型只需要改 Base URL 和 Key。先说清楚一个前提stdio 模式下MCP Server 是被 Client 以子进程方式拉起的所以 Server 脚本必须能被python直接执行路径要对依赖要装在同一个环境里。这是后面排障部分最常见的坑。2. TaoToken 前置准备与 MCP stdio 环境搭建在写代码之前先把两件事搞定模型接入和 Python 环境。模型这块本地 Ollama 和云端 API 都可以我两种都试过。本地 Ollama 的好处是离线、免费缺点是 llama3.2 这类小模型在工具调用上的稳定性一般偶尔会漏掉 tool_call。云端 API 的好处是工具调用格式更规范适合做正式一点的 Agent。如果你走云端路线TaoToken 的接入方式很直接。它的 API 地址是https://taotoken.net/api兼容 OpenAI 的/v1/chat/completions格式。你需要先去控制台创建一个 API Key然后把它写进环境变量。模型 ID 按你实际要用的填比如gpt-4o-mini或者claude-3-5-sonnet这类支持 function calling 的模型。创建 Key 的入口在控制台的 API Keys 页面拿到之后不要硬编码在代码里用.env管理。下面是我实际用的.env文件你可以直接复制改# .env LLM_TEMPERATURE0.2 OLLAMA_MODELllama3.2:latest OLLAMA_BASE_URLhttp://127.0.0.1:11434 PY_PROJECT_DIR/root/do_langmcp/ # TaoToken 云端接入可选二选一 TAOTOKEN_API_KEYsk-你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELgpt-4o-mini注意PY_PROJECT_DIR结尾要带斜杠后面拼路径的时候会用到。这个变量指向你放 MCP Server 脚本的目录。Python 环境建议用虚拟环境隔离避免和系统包打架。我用的 Python 3.11依赖装这几个python -m venv langx source langx/bin/activate pip install langgraph langchain-ollama langchain-mcp-adapters mcp python-dotenv这里有个版本坑要提前说langchain-mcp-adapters的 API 在 0.1.x 和 0.2.x 之间有变化load_mcp_tools的导入路径和返回结构不太一样。如果你装完跑起来报ImportError先pip show langchain-mcp-adapters看版本然后按对应版本的文档调整。我下面给的代码是基于较新的版本如果你用的是老版本create_react_agent的参数顺序可能要调。Ollama 那边装好之后拉模型ollama pull llama3.2:latest ollama serveollama serve默认监听127.0.0.1:11434和.env里的OLLAMA_BASE_URL对上就行。如果你用 TaoToken 的云端模型Ollama 这步可以跳过但 LangGraph 的 LLM 初始化要换成ChatOpenAI并指向 TaoToken 的 Base URL。环境搭好之后目录结构大概是这样do_langmcp/ ├── .env ├── mcp-interest-server1.py └── mcp-interest-client1.pyServer 和 Client 放在同一目录PY_PROJECT_DIR指向这个目录。这样 Client 拉起 Server 子进程时路径拼接不会出错。3. 可复制的 MCP Server 与 LangGraph Client 配置这一节是核心两个文件都要能直接跑。先写 MCP Server用 FastMCP 把两个利息函数暴露成工具。3.1 MCP Servermcp-interest-server1.pyfrom mcp.server.fastmcp import FastMCP import logging logging.basicConfig( format%(levelname)s %(asctime)s - %(message)s, levellogging.INFO ) logger logging.getLogger(interest_mcp_server) mcp FastMCP(InterestCalculator) mcp.tool() def yearly_simple_interest(principal: float, rate: float) - float: Tool to compute simple interest rate for a year. logger.info(fSimple interest - Principal: {principal}, Rate: {rate}) return principal * rate / 100.00 mcp.tool() def yearly_compound_interest(principal: float, rate: float) - float: Tool to compute compound interest rate for a year. logger.info(fCompound interest - Principal: {principal}, Rate: {rate}) return principal * (1 rate / 100.0) if __name__ __main__: logger.info(Starting the interest MCP server...) mcp.run(transportstdio)关键点在最后一行transportstdio。这告诉 FastMCP 用标准输入输出通信而不是起 HTTP 服务。mcp.tool()装饰器会把函数签名和 docstring 自动转成 MCP 工具描述模型就是靠这个描述来决定调不调的。所以 docstring 要写清楚别偷懒。3.2 LangGraph Clientmcp-interest-client1.pyfrom dotenv import load_dotenv, find_dotenv from langchain_ollama import ChatOllama from langchain_mcp_adapters.tools import load_mcp_tools from langgraph.prebuilt import create_react_agent from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client import asyncio import logging import os logging.basicConfig( format%(levelname)s %(asctime)s - %(message)s, levellogging.INFO ) logger logging.getLogger(interest_mcp_client) load_dotenv(find_dotenv()) llm_temperature float(os.getenv(LLM_TEMPERATURE)) ollama_model os.getenv(OLLAMA_MODEL) ollama_base_url os.getenv(OLLAMA_BASE_URL) py_project_dir os.getenv(PY_PROJECT_DIR) server_params StdioServerParameters( commandpython, args[py_project_dir mcp-interest-server1.py], ) ollama_chat_llm ChatOllama( base_urlollama_base_url, modelollama_model, temperaturellm_temperature, ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await load_mcp_tools(session) logger.info(fLoaded MCP Tools - {tools}) agent create_react_agent(ollama_chat_llm, tools) agent_response_1 await agent.ainvoke( {messages: explain the definition of simple interest ?} ) logger.info(agent_response_1[messages][::-1]) agent_response_2 await agent.ainvoke( {messages: compute the simple interest for a principal of 1000 at rate 3.75 ?} ) logger.info(agent_response_2[messages][::-1]) agent_response_3 await agent.ainvoke( {messages: compute the compound interest for a principal of 1000 at rate 4.25 ?} ) logger.info(agent_response_3[messages][::-1]) if __name__ __main__: asyncio.run(main())这段代码的骨架是stdio_client拉起 Server 子进程 →ClientSession建立会话 →session.initialize()握手 →load_mcp_tools把 MCP 工具转成 LangChain 的 StructuredTool →create_react_agent把 LLM 和工具绑成 Agent。如果你要用 TaoToken 的云端模型替换 Ollama把 LLM 初始化那段换成from langchain_openai import ChatOpenAI llm ChatOpenAI( base_urlos.getenv(TAOTOKEN_BASE_URL), api_keyos.getenv(TAOTOKEN_API_KEY), modelos.getenv(TAOTOKEN_MODEL), temperaturellm_temperature, )然后create_react_agent(llm, tools)就行。注意base_url填https://taotoken.net/api不要带/v1SDK 会自己拼。这个细节踩过坑带错了会 404。3.3 配置对照表配置项本地 OllamaTaoToken 云端Base URLhttp://127.0.0.1:11434https://taotoken.net/apiAPI Key不需要控制台创建Model IDllama3.2:latestgpt-4o-mini等工具调用稳定性一般较好适用场景离线原型正式 Agent三件套Base URL Key Model ID在两种模式下都要对齐缺一个就会在请求阶段报错。特别是 Model ID写错了模型名Ollama 会返回 404云端会返回 model not found。4. 端到端验证从启动 Server 到 Agent 调用工具配置写完跑起来看结果。先单独启动 Server 确认它能跑python mcp-interest-server1.py如果没报错说明 FastMCP 正常加载它会阻塞等待 stdio 输入。这时候 CtrlC 退出因为 stdio 模式下 Server 是被 Client 拉起的单独跑没意义只是验证语法和依赖。然后跑 Clientpython mcp-interest-client1.py正常的话日志会分三段输出。第一段是工具加载INFO - Starting the interest MCP server... INFO - Processing request of type ListToolsRequest INFO - Loaded MCP Tools - [StructuredTool(nameyearly_simple_interest, ...), StructuredTool(nameyearly_compound_interest, ...)]看到Loaded MCP Tools里有这两个工具说明 stdio 链路通了MCP 工具成功转成了 LangChain 工具。第二段是第一个问题「解释单利定义」。这个问题不需要调工具模型直接回答。日志里会看到AIMessage带一段英文解释没有tool_calls。第三段是计算类问题这里才是重点。以「principal 1000, rate 3.75 的单利」为例日志顺序是这样的INFO - Processing request of type CallToolRequest INFO - Simple interest - Principal: 1000.0, Rate: 3.75 INFO - HTTP Request: POST http://127.0.0.1:11434/api/chat 200 OK对应的消息序列倒序打印所以从下往上看HumanMessage(contentcompute the simple interest for a principal of 1000 at rate 3.75 ?) AIMessage(content, tool_calls[{name: yearly_simple_interest, args: {principal: 1000, rate: 3.75}}]) ToolMessage(content37.5, nameyearly_simple_interest) AIMessage(contentThe simple interest for a principal of $1000 at a rate of 3.75% per year is $37.50.)这个序列就是 ReAct 循环的完整轨迹模型先输出一个空的 content 加 tool_callsLangGraph 执行工具拿到37.5把结果作为 ToolMessage 塞回上下文模型再基于这个结果生成最终回答。37.5这个数字是工具算出来的不是模型编的这就是 MCP 的价值。复利那个 case 类似工具返回1040.0模型组织成「compound interest is approximately $40.00」。注意这里模型说的是「approximately」因为 1040 是本金加利息利息部分是 40模型自己做了减法。这个细节说明模型在理解工具返回值的语义而不是简单复读。验证成功的标志有三个Loaded MCP Tools里有工具、日志出现CallToolRequest、最终 AIMessage 里的数字和工具返回值对得上。三个都满足链路就是通的。如果你想更直观地看工具调用可以在load_mcp_tools之后打印每个工具的name和args_schema确认参数类型是 number这样模型传参时不会因为类型不匹配失败。5. 本篇常见错误排查401、local proxy failed、reading choices跑不通的时候报错信息往往指向几个固定位置。我按实际遇到的频率排一下。5.1 401 Unauthorized如果你用 TaoToken 云端模型报 401 基本是 Key 的问题。检查三处.env里TAOTOKEN_API_KEY有没有引号包裹导致读进来带了空格ChatOpenAI初始化时api_key参数有没有传对Key 是不是在控制台被删了或者过期。用os.getenv读出来之后print(repr(key))看一眼前后有没有多余字符。本地 Ollama 不会报 401但会报连接拒绝那是OLLAMA_BASE_URL写错或者ollama serve没起。5.2 local proxy failed / Connection refused这个报错通常出现在stdio_client拉起 Server 的时候。原因有几个commandpython在当前环境里找不到换成绝对路径sys.executable更稳args里的脚本路径拼错了PY_PROJECT_DIR结尾没斜杠导致do_langmcp和文件名粘在一起Server 脚本本身有语法错误子进程启动就崩了。排查方法把args里的路径打印出来手动python 那个路径跑一下能跑通再回到 Client。另外 stdio 模式下 Server 的 stdout 被协议占用你如果在 Server 里print调试信息会污染协议流导致解析失败。调试信息一律走logging输出到 stderr。5.3 reading choices / KeyError choices这个报错一般出在模型返回格式不符合预期的时候。如果你用ChatOpenAI接 TaoToken但 Base URL 写成了https://taotoken.net/api/v1请求路径会变成/api/v1/v1/chat/completions返回 404 的 HTMLSDK 解析时找不到choices字段就报这个。正确写法是https://taotoken.net/api让 SDK 自己拼/v1/chat/completions。还有一种情况是模型不支持 function calling返回的响应里没有tool_calls字段LangGraph 的 ReAct 逻辑拿不到工具调用信息后续解析也会出问题。换一个支持工具调用的模型就行。5.4 OAuth / 认证相关报错MCP 的 stdio 模式本身不走 OAuth但如果你之前配过 SSE 模式的 MCP Server环境变量里可能残留了SSE_BASE_URL之类的配置Client 代码里如果误读了这些变量去连 SSE 端点就会报认证失败。检查.env里有没有多余的 SSE 配置stdio 模式下只需要PY_PROJECT_DIR和模型相关变量。5.5 工具加载为空Loaded MCP Tools - []说明session.initialize()成功了但没拿到工具。检查 Server 里mcp.tool()装饰器有没有漏函数有没有被if __name__ __main__之外的代码提前执行。FastMCP 是在mcp.run()时才注册工具的如果 Server 脚本在导入阶段就退出了工具列表就是空的。5.6 排障速查表报错大概率原因处理401Key 错误/缺失检查.env和初始化参数local proxy failed路径错/子进程崩打印路径手动跑 Serverreading choicesBase URL 带/v1改成https://taotoken.net/api工具列表为空装饰器漏/提前退出检查mcp.tool()和mcp.run()模型不调工具模型不支持 function calling换模型排障的时候日志级别开到 INFO 就够DEBUG 会刷太多 MCP 协议细节反而看不清。重点看CallToolRequest有没有出现出现了说明工具被调了没出现就是模型没决定调往模型能力或 prompt 方向查。6. 把这条链路用到真实 Agent 里跑通利息计算器只是起点真正有价值的是把这套模式套到你自己的工具上。几个实践建议。工具粒度别太细。一个工具做一件事但别把「读文件」和「解析 JSON」拆成两个工具模型会在多步调用里迷路。利息例子里单利和复利是两个独立工具因为它们语义清晰、参数简单。如果你的工具需要三个以上参数考虑封装成一个带默认值的函数减少模型传参出错的概率。docstring 就是 prompt。模型选工具全靠 docstring写「计算单利」比写「Tool to compute simple interest rate for a year」在中文场景下更准。如果你的 Agent 主要处理中文请求docstring 用中文写模型匹配度会高一些。stdio 模式适合本地开发和单机部署如果你要把 Agent 部署到服务器给多人用SSE 模式更合适但那是另一套配置。stdio 的优势是零网络开销、零端口管理缺点是 Server 生命周期绑在 Client 上Client 挂了 Server 也停。模型选择上工具调用密集的场景优先选 function calling 支持好的模型。本地 llama3.2 在简单场景够用但参数一多就容易漏调或传错。云端模型在这块稳定得多TaoToken 的接口兼容 OpenAI 格式切换成本很低改 Base URL 和 Model ID 就行。最后说一个调试技巧把agent.ainvoke返回的messages完整打印出来倒序看能清楚看到 Human → AI(tool_calls) → Tool → AI(final) 的完整链路。哪一步断了一眼就能定位。这个习惯比看零散日志高效得多。如果你想把这条链路接到更复杂的 Agent 工作流里比如多工具协作、条件分支LangGraph 的 StateGraph 可以自定义节点把 MCP 工具调用嵌进任意节点。利息例子用的是 prebuilt 的create_react_agent够简单但扩展性有限。等你需要控制每一步的流转逻辑时再往 StateGraph 迁移。需要创建 API Key 或者看接入文档的话可以从这里进API Keys 页面在控制台里接入文档有各语言的示例。模型对话入口适合先验证模型连通性Coding Plan 适合长期跑编码类 Agent 的场景。