
做Agent这一年多我最大的感受是Agent值不值钱往往不取决于模型本身而取决于它手里能调哪些工具。以前接工具基本靠写死在项目里——在LangChain里用tool定义一个函数绑给Agent换一个项目就得复制粘贴一遍换一个框架更是从头再来。MCPModel Context Protocol就是来解决这个问题的它把工具从项目里解耦出来变成可以独立部署、统一协议调用的服务而LangChain也提供了官方适配层让Agent可以直接接上这些MCP Server。这篇文章我会从零跑通一条完整的LangChain MCP链路。内容包括MCP到底解决了什么问题、怎么用FastMCP快速写一个MCP Server、LangChain侧怎么通过langchain-mcp-adapters加载工具、两个独立MCP Server同时挂到一个Agent上的实战演示以及我在真实项目里踩过的几个坑和排查思路。适合已经玩过LangChain Agent基础、想解决工具复用和跨项目共享问题的开发者。1. 让工具标准化MCP到底动了哪块奶酪1.1 先回顾一下工具锁死的尴尬以前给Agent接一个工具常规操作大概是三步写一个普通函数用tool装饰器包成LangChain的Tool对象然后塞进Agent的tools列表。看起来不复杂但问题在于这套东西和项目是焊死的。举一个非常常见的场景你给电商项目写了一个订单查询工具另一个内容推荐项目也需要查用户信息。想复用只能把代码拷过去稍微改改再包一层。更麻烦的是工具一旦要跨客户端使用就彻底无能为力了——你辛辛苦苦写的LangChain Tool只能在LangChain里用换到Claude Desktop或者自研推理框架里要么重写要么被框架生态绑死。这种工具即项目内部对象的写法在单项目、单框架时代没什么问题。但到了现在Agent应用形态五花八门工具复用的诉求越来越强缺的就是一个标准接口。MCP就是在这个背景下出现的。1.2 MCP的架构拆解三个角色一套协议MCP的全称是Model Context Protocol官方定位是给AI应用提供标准化的上下文和数据访问方式。它做的核心事情是定义了一套Client和Server之间的通信规范让谁提供工具和谁使用工具彻底解耦。一个标准的MCP架构里有三个角色MCP Host承载Agent的应用进程比如你的LangChain应用、Claude Desktop、Cline都属于Host。MCP ClientHost内部负责和Server建立连接的协议客户端负责JSON-RPC消息的发送、接收和会话维护。MCP Server工具、数据资源、提示词模板的实际提供方可以是一个本地进程也可以是一个远程HTTP服务。打个不太严谨但很形象的比方MCP就像AI世界的USB-C接口。以前每个工具是一种专属充电头换个设备就充不进去现在大家统一用USB-C同一个充电头到哪都能用。MCP Server不关心你的Agent是用LangChain写的还是用别的框架写的只要双方遵循同一套协议就能通信。这套协议底层走的是JSON-RPC 2.0传输层目前主流的两种stdio用于本地进程通信Streamable HTTP用于远程服务早期版本叫SSE现在已经演进出更通用的streamable_http。在LangChain集成里这两种方式都会被用到后面实战部分我会分别展示。1.3 Tools、Resources、Prompts先分清三种原语MCP协议里定义了三种原语很多人上手时容易混。Tools是可执行动作模型主动发起调用用来做事。比如查订单、发邮件、调用内部API有入参、有返回值属于双向交互。Resources是只读数据由应用读取后注入给模型用来喂资料。比如项目规范文档、数据库schema、运营报表这类信息不需要模型去操作只要在对话时能引用到就行。你可以把它理解成RAG场景里的外部知识源。Prompts是服务端预定义好的提示词模板用来复用一套固定的引导话术比如销售周报生成模板代码评审标准流程。在LangChain适配层里load_mcp_tools对应Toolsload_mcp_resources对应Resources。绝大多数场景下你只需要和Tools打交道这也是本文的重点。2. 用FastMCP十分钟写一个MCP Server2.1 环境准备我推荐直接用Python的mcp官方SDK它自带FastMCP这个高层封装写Server的成本极低。新建一个项目目录用虚拟环境隔离依赖python -m venv .venv source .venv/bin/activate pip install mcp[cli] langchain-mcp-adapters langchain langchain-openai langgraph几个包的分工说一下mcp是协议核心SDK里面包含FastMCP和客户端langchain-mcp-adapters是LangChain官方出的适配层负责把MCP工具转成LangChain的Tool对象langchain-openai是模型接入langgraph是LangChain新版Agent底层依赖。2.2 定义一个带类型的工具FastMCP最舒服的地方在于定义一个MCP工具跟写普通Python函数差不多协议细节全部被框架吞掉了。下面我用一个订单查询场景做示例# order_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(order-tool) mcp.tool() def get_order_status(order_id: str) - dict: 查询订单当前状态返回订单编号、状态、更新时间与物流公司。 Args: order_id: 订单编号格式如 A1001 fake_db { A1001: {status: 已发货, updated_at: 2025-06-20 10:32, carrier: 顺丰}, A1002: {status: 待付款, updated_at: 2025-06-20 09:15, carrier: }, } order fake_db.get(order_id) if order is None: return {order_id: order_id, status: 未找到, updated_at: , carrier: } return {order_id: order_id, **order}这里有两个细节值得注意。第一函数的docstring和类型注解会被自动转换成JSON Schema作为工具描述提供给模型所以不要写仅供人看的废话要写清楚这个工具是干什么的、每个参数什么意思这直接决定模型会不会在正确场景调用它。第二真实项目里函数体内部直接查数据库或者调内部系统即可MCP只负责暴露业务逻辑放外面还是放里面都行。运行这个Server只需要一行命令python order_server.py启动后进程会卡在那里这是正常的说明它正在等待客户端通过stdin/stdout建立连接。2.3 用MCP Inspector做独立调试这里分享一个能帮你少掉一半头发的小技巧先用MCP Inspector调试Server再写Agent。npx modelcontextprotocol/inspector python order_server.py命令执行后会在浏览器打开一个调试面板你可以在里面直接查看Server暴露了哪些工具、模拟调用工具、检查返回结果整个流程不依赖任何LangChain代码。我在实践中已经养成了一个习惯凡是新增MCP工具第一步永远是开Inspector验证工具返回有问题就到Server端修千万不要等Agent跑起来再排错否则问题来源根本分不清是Server的bug还是Agent的调用姿势不对。3. LangChain适配层load_mcp_tools怎么用3.1 两种连接方式stdio还是HTTPLangChain接入MCP本质工作是启动一个MCP客户端、建立会话、把工具列表抓取过来转成LangChain的Tool。在langchain-mcp-adapters里核心函数是load_mcp_tools。先看stdio连接方式。它适合Server和Agent跑在同一台机器的情况例如本地脚本、内部工具服务import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from langchain_mcp_adapters.tools import load_mcp_tools server_params StdioServerParameters( commandpython, args[order_server.py], ) 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) print(tools) asyncio.run(main())注意这段代码里的async with嵌套不是随便写的它对应着MCP连接的生命周期stdio_client负责拉起子进程并维护管道ClientSession负责JSON-RPC消息的收发两层都必须被正确关闭否则会留下僵尸进程这个坑我在后面会专门展开。如果MCP Server部署在远端走HTTP连接代码非常接近只需要换成streamablehttp_client并指定URLfrom mcp.client.streamable_http import streamablehttp_client async with streamablehttp_client(http://your-server:8000/mcp) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await load_mcp_tools(session)远距离接入的好处是Server可以独立部署、独立扩容一个工具服务能被多个Agent共享真正做到一次发布到处调用。3.2 把工具挂进ReAct Agent拿到MCP工具列表之后就回到了LangChain的日常。我常用的是create_react_agent因为ReAct是通用推理框架理论上任何对话模型都能跑不依赖模型显式的function calling能力兼容性好。from langchain.agents import create_react_agent from langchain_openai import ChatOpenAI from langchain_core.prompts import PromptTemplate model ChatOpenAI(modelgpt-4o-mini, temperature0) prompt PromptTemplate.from_template( You are a helpful assistant. Use the following tools to answer questions. Tools: {tools} Use this format: Question: the input question Thought: you should always think about what to do Action: the tool name to use, must be one of [{tool_names}] Action Input: the JSON object as input to the tool Observation: the tool response ... (repeat Thought/Action/Observation until you have an answer) Final Answer: the final answer to the original input {agent_scratchpad} ) agent create_react_agent(model, tools, prompt) result await agent.ainvoke({messages: [{role: user, content: 查询订单A1001的状态}]}) print(result[messages][-1].content)这里需要额外说明的是tools参数。load_mcp_tools返回的是一个list[BaseTool]LangChain的Agent要求传入可迭代的BaseTool对象适配层已经帮你转好了直接传就行。3.3 MultiServerMCPClient一个Agent挂多个Server现实中的Agent不太可能只用一个工具。langchain-mcp-adapters提供了一个MultiServerMCPClient用配置文件的方式统一管理多个MCP Serverfrom langchain_mcp_adapters.client import MultiServerMCPClient client MultiServerMCPClient( { order: { command: python, args: [order_server.py], transport: stdio, }, weather: { url: http://localhost:8000/mcp, transport: streamable_http, }, } ) tools_dict await client.get_tools() # tools_dict 是一个 dictkey 是 server 名value 是对应的工具列表 all_tools [] for server_tools in tools_dict.values(): all_tools.extend(server_tools)用get_tools()拿到的是按Server分组的字典这样你可以在接入Agent之前对每个Server的工具做定制处理比如加前缀、过滤、改名比直接拼接列表灵活得多。这个架构意识很重要每个MCP Server应该是一个独立的工具域而Agent是跨域调度的编排者。4. 完整实战两个MCP Server同时接入一个Agent4.1 准备两个独立Server这一节把上面的代码拼起来做一个能跑通的完整示例。假设现在有两个独立的工具域一个是订单服务另一个是天气服务。订单Server就是上一节写的order_server.py。天气Server我再补一个故意用MCP的Resources原语来做这样能同时展示工具和知识源两种接法# weather_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(weather-tool) mcp.tool() def get_weather(city: str) - dict: 查询指定城市当前天气返回温度、天气现象和风力等级。 Args: city: 城市名如 杭州、上海 fake_weather { 杭州: {temp: 28, condition: 多云, wind: 东南风3级}, 上海: {temp: 26, condition: 小雨, wind: 东风2级}, } weather fake_weather.get(city, {temp: 0, condition: 未知, wind: }) return {city: city, **weather} mcp.resource(docs://shipping-policy) def get_shipping_policy() - str: 快递收寄政策说明供模型回答物流问题时参考。 return 雨天可能影响配送时效高温天气轻拿轻放生鲜商品优先派送。这就是MCP有意思的地方weather_server.py不只暴露了可调用的天气工具还能通过Resource暴露一段快递政策说明。后续Agent回答今天适不适合收快递这类问题时可以把这段政策作为背景知识读进来。4.2 用一个Agent调度跨域任务现在这个场景是用户问订单A1001现在什么状态杭州今天这天气适合收这个快递吗这个问题天然需要两个域的信息——订单状态从订单Server拿天气和物流政策从天气Server拿。接入代码import asyncio from langchain.agents import create_react_agent from langchain_openai import ChatOpenAI from langchain_core.prompts import PromptTemplate from langchain_mcp_adapters.client import MultiServerMCPClient async def main(): async with MultiServerMCPClient( { order: { command: python, args: [order_server.py], transport: stdio, }, weather: { command: python, args: [weather_server.py], transport: stdio, }, } ) as client: tools_dict await client.get_tools() # 给不同域的工具加上前缀避免跟其他工具重名 tools [] for server_name, server_tools in tools_dict.items(): for tool in server_tools: renamed tool.model_copy(update{name: f{server_name}__{tool.name}}) tools.append(renamed) model ChatOpenAI(modelgpt-4o-mini, temperature0) prompt PromptTemplate.from_template( ...同上模板... ) agent create_react_agent(model, tools, prompt) result await agent.ainvoke({ messages: [{role: user, content: 订单A1001现在什么状态杭州今天这天气适合收这个快递吗}] }) print(result[messages][-1].content) asyncio.run(main())4.3 观察Agent的工具调用链路实际跑起来你会看到类似这样的推理轨迹Thought: 用户想知道订单A1001的状态还要结合杭州天气判断适不适合收快递。我需要先查订单状态再查杭州天气最后综合回答。 Action: order__get_order_status Action Input: {order_id: A1001} Observation: {order_id: A1001, status: 已发货, updated_at: 2025-06-20 10:32, carrier: 顺丰} Thought: 订单已经发货了。接下来需要查杭州天气。 Action: weather__get_weather Action Input: {city: 杭州} Observation: {city: 杭州, temp: 28, condition: 多云, wind: 东南风3级} Thought: 订单已发货杭州多云无雨温度28度适合正常收快递。 Final Answer: 订单A1001已经发货物流公司是顺丰。杭州今天多云、28度没有降雨适合正常收取快递。注意我在这里给工具加了order__和weather__前缀这样做绝不只是为了演示好看。真实项目中多个Server之间工具重名的概率非常高比如查询用户信息这种工具几乎每个业务域都会有不加前缀的话Agent调用时极可能张冠李戴。如果你想在Agent回答时让物流政策也作为参考可以再用load_mcp_resources把Resource读成上下文文档拼进System Prompt这样适合不适合收快递的判断会更有依据。这一步在代码上只是多几行资源加载但对回答质量的影响很直接。5. 实战里踩过的坑以及完整排查思路5.1 stdio Server变成孤儿进程应用退出卡死这是我最早遇到、也最隐蔽的一个问题。现象是脚本明明执行完了但进程就是不退出在CI里表现为整个任务挂住直到超时。排查思路分三步走第一步确认是不是Agent的问题把Agent逻辑注释掉只保留MCP客户端连接和load_mcp_tools发现依旧挂住基本锁定是连接没有正常关闭。第二步检查async with结构如果用了stdio_client生成管道后没有用ClientSession包裹或者自己手写了session ClientSession(...)却忘了await session.__aenter__()很容易出现连接泄露。第三步用超时兜底在应用退出前显式关闭session或者给整个流程包一个asyncio.wait_for做最大运行时间限制。我在生产环境里的经验是MCP连接的声明周期必须和Agent进程的生命周期严格绑定。如果是FastAPI应用就在Lifespan事件里初始化连接、在shutdown时关闭如果是普通脚本务必用上下文管理器包住而不是创建一个全局session然后自生自灭。5.2 工具重名导致Agent调错工具这节前面提过但值得单独强调。有一次我把订单Server和用户Server挂到同一个Agent两个Server里都有一个叫get_info的工具结果Agent在需要查订单的时候调到了用户域的get_info返回了一堆用户画像字段最后答案完全跑偏。我当时花了不少时间才定位到根因后来沉淀下来的排查方法很简单把tools_dict的每个域工具名打印出来肉眼扫一遍有没有重复。用model_copy(update{name: ...})给工具统一加前缀不要手动改源码。在Prompt模板里把tool_names展示出来人工核对有没有歧义。这个问题本质上不是MCP的问题而是多工具集成的架构命名问题。只要你有多个Server就应该在接入层就定好命名规范不要指望模型在重名工具中做出正确选择。5.3 异步边界同步环境里调用MCP工具老报错MCP Python SDK的客户端是纯异步实现的load_mcp_tools是async函数返回的LangChain Tool底层也是对MCP Server的异步调用。如果你在Django或者Flask的同步视图里直接写tools load_mcp_tools(...)八成会碰到event loop相关报错。我的建议是不要跟异步较劲直接做好边界封装def get_tools_sync(): return asyncio.run(_load_tools()) async def _load_tools(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() return await load_mcp_tools(session)注意这种asyncio.run的用法适合一次性脚本或者请求量很低的内部工具。如果是高并发的Web服务更好的选择是让整个请求链路保持async或者在进程启动时预先建好session池。把MCP连接当成类似数据库连接池的资源来管理这个心智模型比每次调用现场创建要靠谱得多。5.4 工具返回内容太大模型看花眼MCP工具返回给模型的是序列化后的文本结构。如果Server端工具直接返回一个巨大的JSON比如订单的几十条物流轨迹、一个复杂的业务对象模型在推理时很容易被长文本干扰甚至出现幻觉、提取错字段。遇到这个问题我采取的是工具返回要收敛策略工具只返回当前任务需要的最小字段集不要贪多。如果确实需要明细数据可以设计两个工具一个查询摘要一个查询明细让Agent按需调用。在docstring里写清楚返回结构比如返回格式: {status: 枚举值, updated_at: 时间字符串}帮助模型解析。这里其实反映了一个原则工具返回给模型的不是给人看的而是给模型做推理用的。宁可多设计几个细分工具也别让一个巨型工具把模型搞懵。5.5 远程HTTP连接频繁断开把MCP Server部署到远程之后用HTTP方式接入有时会遇到Agent调用工具时连接突然关闭、或者长时间没有响应。这种问题十有八九出在网关配置上。如果是Nginx反代SSE/streamable_http这类长连接最怕两个配置proxy_buffering on会把响应缓冲住导致消息不能及时送达proxy_read_timeout默认60秒Agent一次复杂推理超过这个时间连接就被掐断。解决方法是location /mcp { proxy_buffering off; proxy_read_timeout 3600s; proxy_set_header Connection ; proxy_http_version 1.1; }当然排查时要先做隔离验证用curl或者MCP Inspector直接连远程地址看连接是否稳定。如果Inspector通了而Agent断再去查Agent侧的超时设置如果Inspector也不稳基本就是网关或者Server本身的问题别急着在Agent代码里找原因。6. 什么时候该上MCP什么时候不该上6.1 和LangChain原生Tool做个对比很多朋友看完会问既然LangChain本身就能定义Tool为什么要绕一圈走MCP我整理了一张对比表直接用事实说话对比维度LangChain原生ToolMCP Server 适配层定义成本低一个tool搞定略高需要写Server理解协议跨项目复用差基本靠复制粘贴好独立部署、独立版本管理跨框架兼容只能LangChain用协议标准Claude Desktop、Cline、自研框架都能用远程部署要自己写API gateway天然支持Streamable HTTP独立调试弱得跑Agent才能验强MCP Inspector可直接验证性能开销低函数直接调用略高多一层协议序列化和进程/网络通信这个表基本能覆盖大多数场景的选型判断。结论很清晰如果只是单项目里一两个一次性工具用原生Tool最省事如果工具要被多个项目、多个框架共享或者有远程集中管控需求上MCP是更优解。6.2 我建议上MCP的场景从实践角度看有这么几类场景非常适合引入MCP一是工具中台化。团队里有多个后端系统每个系统有稳定的业务能力比如订单、用户、商品、风控希望把这些能力以统一方式开放给所有AI应用。这种情况把每个系统做成独立MCP Server比给每个项目单独写Agent工具要可持续得多。二是跨客户端复用。同一个工具集今天在LangChain里用明天可能给Claude Desktop用后天还要挂在自研的Agent框架里。只要工具实现一次、MCP包装一次后面所有客户端都能接不用被任何框架绑定。三是工具逻辑复杂、需要独立迭代。工具内部判断逻辑多、依赖特殊运行环境那它就应该作为一个独立Server存在有自己的发布计划和监控体系而不是随着一个Agent项目发版。6.3 不建议的场景与替代方案反过来也有明显不适合上MCP的场景。比如工具只是一个简单的计算函数只在某个脚本里用一次那直接tool完事没必要多跑一个进程、多维护一套协议。再比如对延迟极度敏感的场景。MCP每调用一次工具就要走一轮协议序列化、进程间通信或者HTTP往返相比进程内函数调用有可见的额外延迟。如果Agent的核心链路要求工具调用在毫秒级完成MCP这种重方案不一定合适更合理的设计是把热路径上的简单计算留在进程内只把跨系统的复杂操作通过MCP暴露出去。还要考虑到运维成本。MCP Server不是写完就完了它需要部署、监控、升级、权限控制。小团队如果没有人能维护这套基础设施硬上MCP反而会拖慢开发速度。我的建议是先从一个真正有复用价值的工具开始试点跑顺了再逐步扩大范围不要一上来就搞一刀切。就我个人目前的工程习惯来说我倾向于把MCP当成组织级工具治理的抓手凡是需要跨团队、跨应用共享的工具一律封装成MCP Server凡是一次性脚本里的小工具保持轻量原生。这种混合模式在实践中踩得最稳既享受了协议带来的复用价值又避免了为简单场景背负过重的架构包袱。最后再分享一个经验总结MCP的生态还在快速演进协议版本、SDK API都可能有变动但工具标准化这个方向基本不会再回头。我建议你现在就可以做一件事——把项目里那几个到处被复制粘贴的工具选出来用FastMCP包成Server再用LangChain适配层接一次。走完一遍你就能理解工具不再锁死在项目里对Agent开发的效率提升有多大。