
先问一个问题你看到的 AI Agent 大多数还停留在“能聊天、能写诗”的阶段而真正有价值的是让大模型去执行任务。可是大模型本身不会操作你的 API、不会查你的数据库、不会调用你项目里的工具它只会“说话”。为了让模型安全、统一、低成本地使用真实工具2026 年前后的 Agent 工程化路径基本收敛到了 LangChain MCP Agent 这套组合上。这篇文章面向有 Python 基础、想搞懂 Agent 工具调用全流程的开发者也适合后端同学快速了解 Java 侧怎么接入 MCP。我会先解释 MCP 和 FastMCP 到底是什么再带你把一个 FastMCP Server 写出来然后交给 LangChain Agent 调用最后补上工具拦截器、Java 集成和常见报错排查。整个流程走完你不仅能跑通 Demo还能理解为什么生产环境需要设计拦截层。1. MCP 不是又一个 AI 框架而是模型与工具之间的“标准化插座”1.1 MCP 解决什么问题在没有 MCP 之前大模型要调用一个工具通常需要开发者写大量胶水代码。每个模型有各自的 Function Calling 格式每个工具又有自己的鉴权方式、参数结构、返回格式结果就是模型、工具、应用三者之间互相绑定换一个模型就要重写一遍工具层。MCPModel Context Protocol的出现相当于在模型和工具之间定义了一套统一协议。它可以理解成一个“标准化插座”工具只需实现一套 MCP 服务端协议任何支持 MCP 的客户端LangChain、Claude、各类 Agent 框架都可以直接对接。开发者不必再为每个模型单独适配工具。1.2 LangChain、LangGraph、Agent 三者关系很多新手容易把 LangChain 理解成一个“大而全的 AI 应用框架”但从 2024 年到 2026 年的演进来看官方已经逐渐把复杂工作流能力交给了 LangGraphLangChain 则更多负责模型接入、工具管理、链式调用、检索增强等基础编排。LangChain面向大模型应用的开发工具集提供模型的统一抽象、 Prompt 模板、输出解析、工具调用等。LangGraph在 LangChain 之上构建有状态、可编排的 Agent 工作流适合复杂多步任务、人机协同、循环控制等场景。Agent基于大模型决策自动选择并调用工具完成任务。做简单问答可以只用 LangChain但要做真正的 Agent需要在“模型判断、工具选择、结果观察”之间循环这正是 LangGraph 的强项。不过在实际中单 Agent 快速实现也可以只用 LangChain 自带的 AgentExecutor。1.3 FastMCP 在生态中的位置FastMCP 是 MCP Server 的一种轻量级快速开发方式它降低了一步步手写 JSON-RPC 和 schema 定义的成本。调用者甚至不需要完整理解 MCP 底层消息格式就可以把普通 Python 函数暴露成 MCP 工具。FastMCP 底层实际上是包装了 MCP Python SDK因此不同写法之间经常容易混淆我会在后面单独讲兼容问题。2. 环境准备与版本说明2.1 准备 Python 环境建议使用 Python 3.10 及以上版本虚拟环境隔离依赖。Windows、macOS、Linux 都可以命令行操作略有差异。Linux 服务器部署时可使用python3 -m venv创建虚拟环境本文以 Python 3.10 为例。python3 -m venv venv source venv/bin/activateWindows 下激活命令是venv\Scripts\activate2.2 安装基础依赖需要说明的是2026 年这些库迭代速度很快版本不能写死。实际安装时建议锁定一份能跑通的版本组合尤其是 FastMCP 相关的底层 SDK。pip install -U langchain langchain-openai langchain-mcp-adapters fastmcp mcp这里按模块拆解一下langchainLangChain 核心库。langchain-openai用于接入兼容 OpenAI 协议的大模型也包括主流国内模型的 OpenAI 兼容接口。langchain-mcp-adaptersLangChain 官方提供的 MCP 适配器能把 MCP 工具转换成 LangChain Tool。fastmcp用于快速构建 MCP Server。mcpMCP Python SDK某些旧版 FastMCP 会依赖它。如果你的项目里只需要调用已存在的 MCP Server可以不装fastmcp只装mcp和适配器。2.3 建议的工程目录为了让 Demo 清晰建议按下面结构组织代码langchain-mcp-agent/ ├── .env ├── requirements.txt ├── server.py # FastMCP Server ├── agent_client.py # LangChain Agent 调用端 └── interceptor_demo.py # 工具拦截器示例代码尽量按文件拆分不要全堆到一个脚本里。3. FastMCP 核心原理解析3.1 MCP 的工作流程先看一个简化的调用链路大模型Agent ↓ 决定调用工具 LangChain 工具层 ↓ 通过 MCP Client MCP 协议stdio 或 HTTP/SSE ↓ MCP Server工具执行 ↓ 返回结构化结果也就是说MCP 客户端和 MCP Server 之间通过统一协议通信。stdio 模式适合本地进程远程场景会用 HTTP 或 SSE 传输。对开发者来说日常最关心两件事一是写 Server 时怎么把函数暴露成工具二是在 Agent 端怎么发现并调用这些工具。3.2 一个最简单的 FastMCP Server创建一个server.py内容如下from fastmcp import FastMCP # 创建 MCP Server 实例 mcp FastMCP(demo-server) # 通过装饰器把普通函数暴露为 MCP 工具 mcp.tool() def add(a: int, b: int) - int: 计算两个整数之和 return a b mcp.tool() def get_weather(city: str) - str: 模拟查询城市天气返回简单文本结果 # 真实项目中这里可以调用外部 API return f{city} 的天气多云24℃ if __name__ __main__: # 以 stdio 方式运行供 MCP 客户端连接 mcp.run(transportstdio)这段代码包含两个工具add整数加法用于演示参数传递。get_weather模拟第三方服务演示返回外部数据。mcp.tool()装饰器会根据函数签名、类型注解和 docstring自动生成 MCP 协议要求的工具描述这就是 FastMCP 简化开发的核心能力。Docstring 会被大模型当作工具说明务必写清楚。3.3 FastMCP 常见写法差异不同版本的 FastMCP 暴露出的导入方式不完全一致。常见的写法有# 新版写法 from fastmcp import FastMCP # 也常见于基于 mcp SDK 的写法 from mcp.server.fastmcp import FastMCP如果你遇到这样的报错ImportError: cannot import name fastmcp from fastmcp (unknown location)原因通常是本地存在一个名为fastmcp.py的文件或fastmcp/目录屏蔽了真正安装的包。另一种情况是包损坏或版本不兼容。排查时先看报错里fastmcp指向的路径如果指向的是你的项目目录说明命名冲突需要把本地文件改名。3.4 MCP Server 的四种核心能力FastMCP 底层把 MCP Server 能力划分为Tool可被模型直接调用的函数。Resource暴露数据资源通常走文件式或资源式读取。Prompt预置提示词模板。Sampling让服务端反向请求模型补全。工具类开发最常用 Tool。如果你的场景是让 Agent 获取私有数据而不是执行操作可以考虑 Resource它更强调“上下文读取”。4. LangChain Agent 集成 MCP 实战4.1 在 Python 端通过适配器连接 MCP Server要实现 Agent 调用 FastMCP 暴露的工具需要启动一个 MCP Client。下面给出一个完整可运行示例。创建.env文件写入大模型相关配置OPENAI_API_KEYyour-api-key OPENAI_API_BASEhttps://api.openai.com/v1 MODEL_NAMEgpt-4o-mini如果使用的是国内兼容 OpenAI 协议的大模型把OPENAI_API_BASE替换成对应服务商地址即可。创建agent_client.pyimport asyncio import os from dotenv import load_dotenv from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_mcp_adapters.client import MultiServerMCPClient load_dotenv() async def main(): # 启动一个 MCP Server 客户端 async with MultiServerMCPClient( { demo: { command: python, args: [server.py], transport: stdio, } } ) as client: # 获取 MCP 暴露的工具转换为 LangChain Tool tools client.get_tools() model ChatOpenAI( modelos.getenv(MODEL_NAME, gpt-4o-mini), api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_API_BASE), temperature0 ) prompt ChatPromptTemplate.from_messages( [ (system, 你是一个智能助手可以调用外部工具完成任务。), (human, {input}), (placeholder, {agent_scratchpad}), ] ) agent create_openai_tools_agent(model, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue) result executor.invoke( {input: 请帮我计算 123456 和 789012 两个数的和 然后查询城市【杭州】的天气。} ) print(result[output]) if __name__ __main__: asyncio.run(main())这段代码的思路是MultiServerMCPClient以 stdio 方式拉起server.py。client.get_tools()拿到所有 MCP 工具。将 MCP 工具作为 LangChain Agent 的可用工具集合。大模型根据用户问题自动决定是否调用工具。AgentExecutor 负责执行 Agent 循环。运行命令python agent_client.py预期会看到模型内部多次调用add和get_weather的日志最终输出类似这样这两个数的和是 912468杭州的天气为多云24℃。4.2 create_openai_tools_agent 与 create_react_agent 怎么选很多人在网上看到两种 Agent 写法会疑惑有什么区别。create_openai_tools_agent面向支持 Function/Tool Calling 的模型要求模型能够返回结构化工具调用指令适合 GPT、Claude、通义、智谱等新模型。create_react_agent使用 ReAct 提示词风格让模型以文本方式输出思考和动作兼容性更广但稳定性相对不如 Tool Calling。新项目优先选择create_openai_tools_agent或者升级到 LangGraph 的create_react_agent风格。如果你只需要快速验证 MCP 工具是否连通上面基于 AgentExecutor 的写法已经足够。4.3 复杂流程时切换到 LangGraph当任务状态多、需要分支判断、需要人工确认或循环重试时直接使用 AgentExecutor 会显得线程混乱。LangGraph 提供更明确的状态机写法同样可以把 MCP 工具接入。以下是一个简要示例只展示 LangGraph 端到端的 Agent 用法from langchain_openai import ChatOpenAI from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent async def run_graph(): async with MultiServerMCPClient( { demo: { command: python, args: [server.py], transport: stdio, } } ) as client: tools client.get_tools() model ChatOpenAI(modelgpt-4o-mini, temperature0) agent create_react_agent(model, tools) result await agent.ainvoke({messages: [(human, 帮我计算 1 2)]}) return result import asyncio print(asyncio.run(run_graph()))对比 LangChain 旧版 AgentExecutorLangGraph 更强调messages状态流转底层会维护消息历史在复杂的多轮对话、条件分支场景中更有优势。5. 工具拦截器为什么 Agent 需要一层“安检”5.1 拦截器解决的问题MCP 工具一旦被 Agent 使用模型就可能根据用户输入调用你的真实服务。如果完全不设防会带来三个直接风险敏感参数越权模型本来只应该查询某用户自己的数据却被诱导传入其他用户 ID。高风险操作不可控删除、覆盖、转账、发消息等操作如果没有二次确认后果严重。审计缺失模型调用了哪些工具、参数是什么、结果如何全部没有日志。工具拦截器的本质是在用户输入进入模型之前、以及模型返回工具调用之后增加两层约束。常见做法是把“调用意图校验”和“参数校验”抽成装饰器或代理层。5.2 Python 侧实现一个简单工具拦截器我们可以在 FastMCP Server 外层包一层参数校验逻辑也可以在 LangChain 工具层封装。这里给出一个通用的装饰器方案import functools import logging logger logging.getLogger(tool-interceptor) ALLOWED_CITIES {杭州, 上海, 北京} def tool_interceptor(func): 工具拦截器统一校验和审计 functools.wraps(func) def wrapper(*args, **kwargs): # 模拟从上下文获取当前用户 user_id kwargs.pop(_user_id, anonymous) # 1. 参数校验 city kwargs.get(city, ) if city and city not in ALLOWED_CITIES: logger.warning(user%s 尝试访问未授权城市: %s, user_id, city) raise ValueError(f城市 {city} 不在授权列表内) # 2. 操作审计 logger.info(user%s call func%s args%s, user_id, func.__name__, kwargs) # 3. 执行原函数 return func(*args, **kwargs) return wrapper mcp.tool() tool_interceptor def get_weather(city: str) - str: 查询指定城市天气仅限授权城市 return f{city} 的天气多云24℃这里有一个关键点装饰器顺序。mcp.tool()在最外层用来把函数注册为 MCP 工具tool_interceptor在内层用来包装真正的业务函数。这样 MCP 层对外暴露的是被拦截后的函数而不是原始逻辑。5.3 Java 和 Harness 场景下的“拦截”对应到哪一层搜索中经常出现Harness在 Agent 工程化里它不是某个具体 MCP 框架而更偏向“处理链路中的执行环境、权限上下文、策略控制”。例如 Java 侧接入时你可以在 MCP Client 与业务服务之间添加一个 Filter 层类似 Spring MVC 拦截器统一做身份解析、参数校验、调用审计。如果团队已经有 Policy Engine 或网关可以让 MCP 工具调用走统一鉴权网关而不是在各工具内部重复写鉴权。拦截器不应该只做日志它更是“安全边界”。6. Java 侧集成 MCP 的思路6.1 Java 生态里的 MCP 客户端MCP 官方提供了 Java SDK基于 Java 17 以上版本开发。常见路线是启动一个 MCP Server 地址然后通过客户端注册 ToolSpecification把远程工具适配成 Spring AI 的 Tool。需要说明的是Java SDK 版本迭代比较快下面的代码只演示思路不保证在你的版本上零修改运行。// 使用 MCP Java SDK 建立连接并获取工具列表的核心思路 McpTransport transport new StdioMcpTransport.Builder() .command(python) .args(List.of(server.py)) .build(); McpClient client McpClient.using(transport) .sync(); InitializeRequest initRequest new InitializeRequest(); client.initialize(initRequest); ListToolsResult tools client.listTools(null); for (Tool tool : tools.tools()) { System.out.println(Tool name: tool.name()); System.out.println(Tool schema: tool.inputSchema()); }如果你的服务端通过 HTTP 暴露 MCPJava Client 需要换成 Http 或 SSE 传输方式原理类似。6.2 Java Agent 与工具调用闭环实际 Java Agent 项目通常顺序是Spring AI / LangChain4j 负责大模型对话和输出解析。MCP Java SDK 负责发现工具并把工具转换成函数调用格式。业务服务通过 Spring Bean 暴露给工具层。拦截器或 AOP 切面负责鉴权、限流、审计。相比 PythonJava 对类型规范和异常处理更严格MCP 的 JSON-Schema 到 Java 类型的映射容易出问题尤其嵌套复杂对象时。建议保持 MCP 工具参数为简单类型例如String、int、简单 List避免对象嵌套导致序列化问题。7. LangChain 与 LangGraph 如何配合使用对于真正要上生产的项目2026 年更合理的分工是MCP Server 负责把存量系统能力变成标准工具由业务团队独立维护。LangGraph 负责 Agent 状态机、任务编排、循环控制和人工审核节点。LangChain 作为基础库提供模型接入、Prompt、输出解析能力。一个比较常见的 Agent 设计是先由 Graph 判断用户意图然后按需调用 MCP 工具如果工具结果置信度不够再走人工确认分支。这样做的好处是MCP 工具复用性高LangGraph 编排灵活两者通过 Tool 接口天然打通。如果你只是做 AI 助手 Demo直接 LangChain 足矣。如果你要做“能自动执行多步骤任务”的 Agent建议优先学习 LangGraph它的可控性会更好。8. 常见问题与排查下面整理 MCP LangChain Agent 抛锚的高频场景。问题现象常见原因解决思路ImportError: cannot import name fastmcp from fastmcp本地文件覆盖了包命名或版本混乱查看报错路径删除本地fastmcp.py重装依赖MCP Server 启动但 Agent 找不到工具Server 进程未正常运行或 transport 配置错误单独运行python server.py验证检查 stdio 命令Agent 报The agent execution provider did not respond in time...模型响应慢、超时配置过短、网络异常增加 timeout检查模型网络降低单次请求 token工具调用返回乱码或中文异常编码不一致确保 MCP Server stdout 不含额外日志print 调试会破坏协议Java 侧 ListTools 为空服务端工具注册失败检查服务端是否有Tool注解或mcp.tool()注册FastMCP 和 mcp SDK 版本冲突两个包的内部依赖交叉在虚拟环境中重装fastmcp mcp langchain-mcp-adapters8.1 为什么不能在 MCP Server 里随便 print这点特别容易踩坑当你使用 stdio transport 时MCP Server 是通过标准输入输出与客户端通信的。如果你在 Server 代码里加一句print(debug log)这段字符串会直接污染协议通道轻则解析失败重则 Client 一直等待。调试时请使用logging模块并且让日志输出到 stderrimport sys print(debug, filesys.stderr)8.2 Agent 执行时间过长导致超时热词中有一条比较典型的英文报错The agent execution provider did not respond in time. This may indicate the model...它的含义是模型在执行链中没有在限定时间内返回响应。常见原因有三个模型服务网络延迟高。Agent 进入了多轮工具循环总耗时长。请求上下文中塞入了过多工具描述导致首 token 生成变慢。排查时可以开启 verbose 模式观察具体在哪一步耗时过高再调整timeout、模型max_tokens或减少不必要的工具数量。8.3 工具注册不上特别是搜索里提到 Figma MCP 在 Codex 中注册不上。这类问题的共同点往往是MCP Client 使用 OAuth 或 Token但 Server 侧鉴权失败。服务端工具列表与客户端 schema 不匹配。远程 MCP URL 不稳定客户端握手时拉取不到工具列表。解决方式是先用官方 MCP Inspector 或ListTools测试工具枚举是否正常不要直接冲进 Agent 排错。9. 工程落地建议9.1 工具边界要小权限要收敛一个 MCP Server 不要塞进几十个工具建议按“领域”划分。比如订单域一个 Server库存域一个 Server。一来工具说明会更聚焦模型选择准确率更高二来权限隔离也更清晰。工具必须遵循最小必要权限比如查询工具只给只读数据库账号。拦截器中即使校验通过底层数据源也应限制行级权限不能只依赖模型自觉。9.2 工具描述就是“模型的操作说明书”模型是通过 docstring/description 来决定要不要调用工具的。比如# 描述不清晰 def add(a, b): ... # 描述清晰 def add(a: int, b: int) - int: 计算两个整数之和参数 a 和 b 必须是整数第二种才适合给 Agent 使用。描述里需要明确用途、参数含义、单位、边界条件、可能的异常。建议把调用失败的常见原因写进说明减少模型误判。9.3 统一异常格式让 Agent 能“看懂错误”工具报错时不要直接抛一堆堆栈Agent 拿到之后很难处理。生产环境更推荐返回结构化错误{ error_code: CITY_NOT_ALLOWED, message: 城市不在授权列表内, suggestion: 请询问用户是否切换为允许的城市 }这样模型可以根据suggestion生成补救回答而不是对堆栈束手无策。9.4 设计审计与监控每个工具调用都打印到日志可不够建议记录下来用户 ID、会话 ID。模型本次选择的工具名称和参数。返回摘要和耗时。结果是否命中成功。如果做 Agent 平台审计日志还需要支持回放方便定位模型是否被 Prompt 注入诱导执行了风险动作。9.5 关于安全边界的额外提醒大模型调用工具的核心风险在于“不可预测性”。因此要求默认拒绝高风险操作也就是白名单机制而不是黑名单。涉及真实验收、删除、发消息、付款必须接入人工确认节点。不要在 MCP Server 里硬编码生产环境密钥使用环境变量或配置中心。对用户输入和工具返回内容都要做长度限制防止超长内容耗尽上下文或形成提示注入。10. 下一步可以做什么跑通上面的 Demo 之后建议你继续做三件事第一把 MCP Server 从 stdio 改成远程 HTTP 版让它作为一个独立服务运行同时用 FastMCP 的远程配置方式连接体验真正的服务化。第二把一个旧项目里的工具方法比如查询订单、计算价格、生成周报手动封装成 MCP 工具接入 LangGraph观察 Agent 在真实任务里的表现。第三写一个工具拦截器的中间件把参数白名单、用户上下文、审计日志一起接进去然后再让外部使用者通过 MCP 调用一次你的工具。你会发现没有拦截层的 Demo 和带拦截层的工程系统安全体验完全不一样。如果这篇文章对你有帮助可以先收藏备用。后续我会继续更新远程 MCP、工具注册中心、LangGraph 任务编排、Java Spring AI MCP 实战等内容。遇到环境安装或者代码报错欢迎在评论区说明异常栈和版本我尽量帮你一起排查。