LangChain Agent 接入 MCP:FastMCP 与工具拦截器实践
发布时间:2026/9/4 3:24:13 作者:尧图编辑部 阅读量:1,286

在 LangChain 工程里Agent 真正难的点往往不是“调用一次大模型”而是让模型稳定、合规地去调用外部工具。MCPModel Context Protocol模型上下文协议出现之前每个工具接入都需要自定义入参格式、鉴权方式和返回结构引入 MCP 之后LangChain Agent 可以把库存系统、数据库、企业内部服务统一当作“MCP Server 暴露出来的工具”来调度。文章会围绕一条最小可复现链路展开用 FastMCP 写一个库存查询和扣减工具作为 MCP Server 运行在 LangChain 中通过 MCP 适配层把工具注册给大模型再在 Agent 调用循环里加入一层工具拦截器Harness统一处理权限、日志、Mock 和异常返回。Java 侧如何对接、LangChain 与 LangGraph 怎么选型也会放到后面说明。这套技术组合的适用人群不是只看概念的新手而是已经能跑通基本 LangChain 项目但被“工具越来越多、模型调用越来越乱、生产环境没人敢放 Agent 上线”这类问题卡住的开发者。学完之后你应该能在自己的机器上跑通一个最小 Agent 项目并且能说清楚哪一层负责协议、哪一层负责模型决策、哪一层负责规则约束。1. LangChain Agent 接入 MCP 前先把三层职责分清1.1 Agent 不是万能盒子而是“模型决定调什么工具”的执行循环很多刚接触 Agent 的开发者容易把 Agent 理解成一个完整系统输入问题输出结果中间过程全自动。实际站在 LangChain 的内部看Agent 的主干是一个循环把用户消息和系统提示词交给大模型。大模型判断当前问题是否需要调用工具如果需要就返回一个包含工具名、参数、调用 ID 的结构化结果。LangChain 侧读取这个结构化结果找到真实工具并执行。把工具返回值作为 ToolMessage 追加进消息历史。再把历史消息交给大模型让它继续推理。大模型本身并不直接操作你想调用的数据库或 HTTP 接口。它只会输出“我想调用deduct_stock参数是{product_id: A001, quantity: 2}”这样的指令。真正执行指令的是 LangChain 或 LangGraph 运行时。这个区分很重要因为隔离了“模型意图”和“工具副作用”你可以在工具执行层加权限控制也可以在返回给模型之前过滤敏感字段而不是把希望寄托在模型不会乱调工具上。1.2 MCP 是连接模型和外部系统的工具协议不是另一个 AgentMCP 解决的核心问题是工具接入方式不统一。没有 MCP 时A 系统暴露一个 OpenAPI 接口B 系统暴露一个自定义 SDKC 系统可能只能查数据库。LangChain 要把它们都变成模型能使用的工具就得分别为它们写转换类、错误处理和参数映射。MCP 定义了三类角色角色说明对应本文示例Host承载模型和用户交互的宿主程序LangChain AgentClientHost 内部连接 MCP Server 的客户端LangChain MCP 适配层Server对外暴露工具、资源、提示词的进程FastMCP 启动的库存服务MCP Server 通过 JSON-RPC 2.0 格式提供出tools/list、tools/call这类能力。LangChain 侧并不需要关心你的库存服务用的是 Python 还是 Java只要它实现了 MCP 协议LangChain 就能把它注册成标准工具列表。需要特别澄清MCP 不是 Agent也不是模型推理框架。它只负责“工具怎么暴露、客户端怎么发现和调用”。模型调用工具的决策能力仍然来自 LLM 自身的 function calling 能力MCP 只是把外部工具变成一种标准格式让 LangChain 这类 Host 更容易接入。1.3 FastMCP 和“工具拦截器/Harness”在链路中的位置FastMCP 是 Python 生态里一种快速实现 MCP Server 的写法设计目标是让开发者少写协议层代码。你只要定义普通函数FastMCP 会负责把函数签名转换成工具参数 Schema把返回值序列化回 Client。工具拦截器在本文里指的是 Agent 循环中“模型产生调用意图之后、真实工具执行之前”的一段治理代码英文语境中常叫 Harness、Tool Gateway 或 Control Layer。它解决四个问题统一审计每次工具名称、入参、耗时、trace_id 都能落到日志。权限约束满足条件的工具才允许执行不满足时直接给模型返回可解释的错误。测试 Mock拦截器可以根据环境开关返回 fixture不让模型在测试阶段修改真实库存。异常兜底真实工具抛出的异常不直接打崩流程而是变成可读 ToolMessage让模型重新决策或向用户解释。这里有一个容易混淆的点MCP Server 内部当然也可以做校验但企业落地时治理逻辑往往集中在 Host 侧拦截器里。原因是同一个 MCP Server 可能会被多个 Agent Host 使用Server 层厚了每个 Client 的策略就难以独立变化。2. 环境准备和目录结构先定版本再写集成代码2.1 运行环境与依赖准备在开始写代码前先确认三部分环境Python 运行时、LangChain 生态、大模型接口。本文示例默认使用 Python。较老版本的 Python 可能不被新版本 FastMCP 或 LangChain 支持落地前建议先确认依赖要求不要直接照抄一个从未验证过的版本组合。组件说明建议PythonFastMCP 和 LangChain 运行基础优先使用 3.10 以上版本大模型服务提供函数调用能力可选择 OpenAI 兼容接口但必须确认该服务完整支持 function callingFastMCP编写 MCP Server使用 pip 安装版本以 PyPI 最新稳定版为准LangChain组装 Agent 循环使用当前仍受维护的版本锁定精确版本号langchain-mcp-adaptersLangChain 与 MCP 的适配层以该包 README 为准不同版本方法名可能有变化安装依赖的命令如下pip install fastmcp langchain langchain-openai langchain-mcp-adapters mcp如果网络环境或者公司内部源不一致建议把精确版本写入requirements.txt例如fastmcpx.y.z。不要用pip install后不做版本记录否则排错时无法复现“上次能跑这次不能跑”的问题。2.2 项目目录结构为了演示把链路拆成三个文件好处是每一层都能独立验证避免出现“所有代码都跑在主流程里报错都不知道是哪一层”的问题。lm-mcp-demo/ ├── inventory_server.py # FastMCP 编写的 MCP Server ├── langchain_agent.py # LangChain Agent 主流程 ├── tool_policy.py # 工具拦截器相关校验与日志逻辑 ├── requirements.txt在实际项目中tool_policy.py可以换成配置中心、权限中心或网关接口。MCP Server 拆到独立进程还有一个好处LangChain 主进程重启时MCP Server 的连接和状态可以被统一管理而不是被硬编码在主程序里。2.3 为什么需要单独设计一条拦截链很多教程只写到“把工具 bind_tools 给模型然后 Executor 自动跑”就结束了。一旦到了生产环境你会发现缺少一个关键控制面模型发出了真实的扣库存调用但没有任何代码在它执行前要求账号身份、校验白名单、输出审计日志、控制 Mock 开关。在 Agent 链路里工具调用是有副作用的。被调用的工具可能扣减库存、发送订单、更新数据库。把这类调用直接暴露给模型而没有任何中间层相当于把生产库的操作权限交给了一个概率输出。工具拦截器的本质是在“模型意图”和“物理副作用”之间加一道闸门这道闸门不能依赖模型自律必须由工程代码保证。拦截层可以设计成同步的规则检查也可以异步上报到监控系统。最简版本至少包含开始时间、工具名、参数。是否命中 Mock 开关。是否命中禁止调用名单。执行结果或异常。耗时和 trace_id。这些字段会贯穿后面的示例代码。3. 先用 FastMCP 写一个 Inventory MCP Server并让工具返回业务化错误3.1 定义内存库存业务示例选择库存业务主要是因为它有明确的副作用查询没有风险扣减会改变状态适合用来演示拦截器的必要性和 Agent 的多步决策。下面代码用一个内存字典保存商品数据生产环境请替换为数据库、微服务或内部 RPC。from fastmcp import FastMCP INVENTORY { A001: {name: 高性能计算服务器, stock: 8, unit_price: 128000}, B002: {name: 智能门锁, stock: 20, unit_price: 1599}, C003: {name: USB 采集卡, stock: 0, unit_price: 899}, } mcp FastMCP( nameinventory-server, instructions这是一个库存查询与扣减工具服务。商品编号例如 A001、B002、C003。, )FastMCP 的instructions只是附加说明真正决定模型会不会用某个工具的信息来源是下面每个函数 docstring 和参数类型。工具描述写得越具体模型越不会自己编参数。3.2 用 FastMCP 暴露查询与扣减工具查询工具负责获取库存信息扣减工具负责预占库存。这里故意保留扣减工具让 Agent 有条件地调用后面才能演示拦截器为什么比“永远允许”安全。mcp.tool() def get_product_stock(product_id: str) - dict: 查询某个商品的库存。 商品 ID 只能使用 A001、B002、C003。 返回字段name 商品名、stock 当前库存、unit_price 单价、product_id 商品 ID。 参数示例 - product_id: A001 if product_id not in INVENTORY: return { code: PRODUCT_NOT_F