总结之LangChain Agent与MCP工具协议

LangChain Agent 与 MCP 工具协议学习笔记

一、本章概览

本模块包含三个核心文件,覆盖 LangChain Agent 开发和 MCP 工具协议两大主题:

文件 内容
langchain_test.py LangChain Agent 完整开发流程:工具定义 → 模型初始化 → Agent 创建 → 调用
mcp_server.py MCP Server 实现:注册数学工具,支持 stdio / SSE / Streamable HTTP 三种传输
mcp_test.py MCP Client 测试:对接 MCP Server 的三种通信模式

#mermaid-svg-fZci3C9kr3IpsMRj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fZci3C9kr3IpsMRj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fZci3C9kr3IpsMRj .error-icon{fill:#552222;}#mermaid-svg-fZci3C9kr3IpsMRj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fZci3C9kr3IpsMRj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fZci3C9kr3IpsMRj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fZci3C9kr3IpsMRj .marker.cross{stroke:#333333;}#mermaid-svg-fZci3C9kr3IpsMRj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fZci3C9kr3IpsMRj p{margin:0;}#mermaid-svg-fZci3C9kr3IpsMRj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fZci3C9kr3IpsMRj .cluster-label text{fill:#333;}#mermaid-svg-fZci3C9kr3IpsMRj .cluster-label span{color:#333;}#mermaid-svg-fZci3C9kr3IpsMRj .cluster-label span p{background-color:transparent;}#mermaid-svg-fZci3C9kr3IpsMRj .label text,#mermaid-svg-fZci3C9kr3IpsMRj span{fill:#333;color:#333;}#mermaid-svg-fZci3C9kr3IpsMRj .node rect,#mermaid-svg-fZci3C9kr3IpsMRj .node circle,#mermaid-svg-fZci3C9kr3IpsMRj .node ellipse,#mermaid-svg-fZci3C9kr3IpsMRj .node polygon,#mermaid-svg-fZci3C9kr3IpsMRj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fZci3C9kr3IpsMRj .rough-node .label text,#mermaid-svg-fZci3C9kr3IpsMRj .node .label text,#mermaid-svg-fZci3C9kr3IpsMRj .image-shape .label,#mermaid-svg-fZci3C9kr3IpsMRj .icon-shape .label{text-anchor:middle;}#mermaid-svg-fZci3C9kr3IpsMRj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fZci3C9kr3IpsMRj .rough-node .label,#mermaid-svg-fZci3C9kr3IpsMRj .node .label,#mermaid-svg-fZci3C9kr3IpsMRj .image-shape .label,#mermaid-svg-fZci3C9kr3IpsMRj .icon-shape .label{text-align:center;}#mermaid-svg-fZci3C9kr3IpsMRj .node.clickable{cursor:pointer;}#mermaid-svg-fZci3C9kr3IpsMRj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fZci3C9kr3IpsMRj .arrowheadPath{fill:#333333;}#mermaid-svg-fZci3C9kr3IpsMRj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fZci3C9kr3IpsMRj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fZci3C9kr3IpsMRj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fZci3C9kr3IpsMRj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fZci3C9kr3IpsMRj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fZci3C9kr3IpsMRj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fZci3C9kr3IpsMRj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fZci3C9kr3IpsMRj .cluster text{fill:#333;}#mermaid-svg-fZci3C9kr3IpsMRj .cluster span{color:#333;}#mermaid-svg-fZci3C9kr3IpsMRj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fZci3C9kr3IpsMRj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fZci3C9kr3IpsMRj rect.text{fill:none;stroke-width:0;}#mermaid-svg-fZci3C9kr3IpsMRj .icon-shape,#mermaid-svg-fZci3C9kr3IpsMRj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fZci3C9kr3IpsMRj .icon-shape p,#mermaid-svg-fZci3C9kr3IpsMRj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fZci3C9kr3IpsMRj .icon-shape .label rect,#mermaid-svg-fZci3C9kr3IpsMRj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fZci3C9kr3IpsMRj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fZci3C9kr3IpsMRj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fZci3C9kr3IpsMRj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MCP 工具协议
stdio
SSE
Streamable HTTP
MCP Server
Client
Agent
Agent 开发
定义工具
初始化模型
创建 Agent
调用 Agent


二、LangChain Agent 开发四步流程

2.1 第一步:定义工具(Tools)

工具是 Agent 的能力扩展。使用 @tool 装饰器定义:

python 复制代码
from langchain_core.tools import tool
from pydantic import BaseModel, Field

# 方式1:简单工具(从类型注解和 docstring 自动推断参数)
@tool
def python_repl(code: str) -> str:
    """执行 Python 代码并返回输出。入参是纯 Python 代码。"""
    import io, contextlib
    output = io.StringIO()
    try:
        with contextlib.redirect_stdout(output):
            exec(code, {"__builtins__": __builtins__})
        return output.getvalue() or "(无输出)"
    except Exception as e:
        return f"执行错误: {e}"

# 方式2:显式定义参数 Schema(适合复杂参数)
class AddInputArgs(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")

@tool(args_schema=AddInputArgs, return_direct=True)
def add(a, b):
    """add two numbers"""
    return a + b

关键参数说明:

参数 作用
args_schema 显式定义工具参数(Pydantic BaseModel),提供精确的类型和描述
return_direct True=工具结果直接返回,不经过 Agent 再处理;False(默认)=Agent 会总结工具结果

注意: @tool 装饰器在新版 langchain 中不支持 description 参数,工具描述从 docstring 的第一行自动提取。

2.2 第二步:初始化大模型

模型统一在 app/common/llm.py 中配置,支持所有 OpenAI API 兼容的服务:

python 复制代码
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    api_key=API_KEY,
    base_url="https://api.deepseek.com/v1",  # 或其他兼容 API
    model="deepseek-chat",
    temperature=0,
)

2.3 第三步:创建 Agent

使用 initialize_agent(来自 langchain_classic.agents)创建 ReAct Agent:

python 复制代码
from langchain_classic.agents import initialize_agent, AgentType

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,                    # 打印思考过程
    return_intermediate_steps=True,  # 返回中间步骤
    max_iterations=10,               # 防止无限循环
    handle_parsing_errors=True,      # 自动处理解析错误
    agent_kwargs={
        "prefix": formatted_prefix,  # 系统提示词
        "suffix": "Question: {input}",
        "input_variables": ["input"],
    },
)

AgentType 类型对比:

类型 特点 适用场景
ZERO_SHOT_REACT_DESCRIPTION 基础 ReAct,纯文本输出 简单任务
STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 结构化 JSON 输出 复杂工具调用(推荐
OPENAI_FUNCTIONS OpenAI 函数调用 仅限 OpenAI 模型
CONVERSATIONAL_REACT_DESCRIPTION 多轮对话 + ReAct 多轮交互任务

2.4 第四步:调用 Agent

python 复制代码
result = agent.invoke({"input": "请计算 123 * 456 + 789 的结果"})

# result 包含:
# - output: 最终答案(字符串)
# - intermediate_steps: 中间步骤列表 [(AgentAction, observation), ...]

print(result["output"])
for action, obs in result["intermediate_steps"]:
    print(f"工具: {action.tool}, 入参: {action.tool_input}, 结果: {obs}")

2.5 ReAct 思考流程

Agent 按照 Thought → Action → Observation 循环执行:
工具 Agent 用户 工具 Agent 用户 #mermaid-svg-L4Z7KGpH1YAtaT2j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-L4Z7KGpH1YAtaT2j .error-icon{fill:#552222;}#mermaid-svg-L4Z7KGpH1YAtaT2j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-L4Z7KGpH1YAtaT2j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-L4Z7KGpH1YAtaT2j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-L4Z7KGpH1YAtaT2j .marker.cross{stroke:#333333;}#mermaid-svg-L4Z7KGpH1YAtaT2j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-L4Z7KGpH1YAtaT2j p{margin:0;}#mermaid-svg-L4Z7KGpH1YAtaT2j .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-L4Z7KGpH1YAtaT2j text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-L4Z7KGpH1YAtaT2j .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-L4Z7KGpH1YAtaT2j .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-L4Z7KGpH1YAtaT2j #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-L4Z7KGpH1YAtaT2j .sequenceNumber{fill:white;}#mermaid-svg-L4Z7KGpH1YAtaT2j #sequencenumber{fill:#333;}#mermaid-svg-L4Z7KGpH1YAtaT2j #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-L4Z7KGpH1YAtaT2j .messageText{fill:#333;stroke:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-L4Z7KGpH1YAtaT2j .labelText,#mermaid-svg-L4Z7KGpH1YAtaT2j .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .loopText,#mermaid-svg-L4Z7KGpH1YAtaT2j .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-L4Z7KGpH1YAtaT2j .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-L4Z7KGpH1YAtaT2j .noteText,#mermaid-svg-L4Z7KGpH1YAtaT2j .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-L4Z7KGpH1YAtaT2j .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-L4Z7KGpH1YAtaT2j .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-L4Z7KGpH1YAtaT2j .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-L4Z7KGpH1YAtaT2j .actorPopupMenu{position:absolute;}#mermaid-svg-L4Z7KGpH1YAtaT2j .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-L4Z7KGpH1YAtaT2j .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-L4Z7KGpH1YAtaT2j .actor-man circle,#mermaid-svg-L4Z7KGpH1YAtaT2j line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-L4Z7KGpH1YAtaT2j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 计算 (123 + 456) * 789Thought: 需要先算加法Action: add(123, 456)Observation: 579Thought: 再做乘法Action: multiply(579, 789)Observation: 456,831Thought: 得到最终答案Final Answer: 456,831


三、ReAct 提示词模板详解

3.1 为什么需要自定义提示词

默认的 ReAct 提示词对 DeepSeek 等非 OpenAI 模型不够友好,经常出现格式解析错误。自定义提示词可以:

  1. 强制 JSON 代码块格式 :让 Agent 的 Action 输出用 ```````json```` 包裹
  2. 明确工具约束:告知 Agent 工具的入参格式限制
  3. 提高解析成功率:减少格式不匹配导致的重试

3.2 提示词结构

复制代码
┌─────────────────────────────────────────┐
│  prefix(系统前缀)                       │
│  - 角色定义                              │
│  - 可用工具列表                           │
│  - 输出格式规范(Thought / Action / Final)│
├─────────────────────────────────────────┤
│  suffix(用户后缀)                       │
│  - Question: {input}                    │
│  - {agent_scratchpad}(思考历史,自动填充)│
└─────────────────────────────────────────┘

3.3 核心提示词模板

python 复制代码
AGENT_PREFIX = """尽你所能回答以下问题,你可以使用以下工具:

{tools}

使用工具时,必须严格按照以下格式(使用 markdown JSON 代码块):

Thought: 思考下一步该做什么
```json
{{"action": "工具名称", "action_input": 工具入参}}

如果已经知道最终答案,使用以下格式:

Thought: 我现在知道最终答案了

Final Answer: 最终答案内容

"""

复制代码
> **关键:** `STRUCTURED_CHAT` 类型的 Agent 要求 Action Input 必须是 ` ```json ` 代码块包裹,否则解析会失败。这是最容易踩坑的地方。

---

## 四、MCP 工具协议

### 4.1 什么是 MCP

**MCP(Model Context Protocol)** 是一个开放标准,用于将 AI 模型与外部工具和数据源连接起来。它定义了统一的通信协议,让 Agent 可以通过标准化接口调用各种工具服务。

```mermaid
graph LR
    Agent[AI Agent] -->|MCP 协议| Server1[MCP Server A 数学工具]
    Agent -->|MCP 协议| Server2[MCP Server B 文件操作]
    Agent -->|MCP 协议| Server3[MCP Server C 数据库查询]

4.2 三种传输模式

模式 通信方式 优点 限制 推荐场景
stdio stdin/stdout 管道 简单、低延迟 仅限本地 开发调试
SSE HTTP GET /sse + POST /message 支持远程调用 已废弃,连接不可恢复 仅了解
Streamable HTTP 统一 /mcp 端点 支持连接恢复、负载均衡 较新 生产环境

#mermaid-svg-nJAv4eBCLV6qoiL9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nJAv4eBCLV6qoiL9 .error-icon{fill:#552222;}#mermaid-svg-nJAv4eBCLV6qoiL9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nJAv4eBCLV6qoiL9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .marker.cross{stroke:#333333;}#mermaid-svg-nJAv4eBCLV6qoiL9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nJAv4eBCLV6qoiL9 p{margin:0;}#mermaid-svg-nJAv4eBCLV6qoiL9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .cluster-label text{fill:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .cluster-label span{color:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .cluster-label span p{background-color:transparent;}#mermaid-svg-nJAv4eBCLV6qoiL9 .label text,#mermaid-svg-nJAv4eBCLV6qoiL9 span{fill:#333;color:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .node rect,#mermaid-svg-nJAv4eBCLV6qoiL9 .node circle,#mermaid-svg-nJAv4eBCLV6qoiL9 .node ellipse,#mermaid-svg-nJAv4eBCLV6qoiL9 .node polygon,#mermaid-svg-nJAv4eBCLV6qoiL9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .rough-node .label text,#mermaid-svg-nJAv4eBCLV6qoiL9 .node .label text,#mermaid-svg-nJAv4eBCLV6qoiL9 .image-shape .label,#mermaid-svg-nJAv4eBCLV6qoiL9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-nJAv4eBCLV6qoiL9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .rough-node .label,#mermaid-svg-nJAv4eBCLV6qoiL9 .node .label,#mermaid-svg-nJAv4eBCLV6qoiL9 .image-shape .label,#mermaid-svg-nJAv4eBCLV6qoiL9 .icon-shape .label{text-align:center;}#mermaid-svg-nJAv4eBCLV6qoiL9 .node.clickable{cursor:pointer;}#mermaid-svg-nJAv4eBCLV6qoiL9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .arrowheadPath{fill:#333333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nJAv4eBCLV6qoiL9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nJAv4eBCLV6qoiL9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nJAv4eBCLV6qoiL9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nJAv4eBCLV6qoiL9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .cluster text{fill:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 .cluster span{color:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nJAv4eBCLV6qoiL9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nJAv4eBCLV6qoiL9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-nJAv4eBCLV6qoiL9 .icon-shape,#mermaid-svg-nJAv4eBCLV6qoiL9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nJAv4eBCLV6qoiL9 .icon-shape p,#mermaid-svg-nJAv4eBCLV6qoiL9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nJAv4eBCLV6qoiL9 .icon-shape .label rect,#mermaid-svg-nJAv4eBCLV6qoiL9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nJAv4eBCLV6qoiL9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nJAv4eBCLV6qoiL9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nJAv4eBCLV6qoiL9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Streamable HTTP 模式
POST /mcp
SSE 流式响应
MCP Client
MCP Server
stdio 模式
子进程启动
stdin/stdout JSON-RPC
MCP Client
MCP Server

4.3 MCP Server 实现

使用 FastMCP 快速创建 MCP Server:

python 复制代码
from mcp.server.fastmcp import FastMCP

# 创建 Server 实例
mcp = FastMCP("math-server")

# 注册工具(@mcp.tool() 装饰器)
@mcp.tool()
def add(a: int, b: int) -> int:
    """两数相加

    Args:
        a: 第一个数
        b: 第二个数

    Returns:
        两数之和
    """
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """两数相乘"""
    return a * b

@mcp.tool()
def divide(a: float, b: float) -> float:
    """两数相除"""
    if b == 0:
        raise ValueError("除数不能为 0")
    return a / b

# 启动(根据参数选择传输模式)
if __name__ == "__main__":
    transport = sys.argv[1] if len(sys.argv) > 1 else "stdio"
    mcp.run(transport=transport)

工具参数描述: MCP 会自动从 Python 类型注解(a: int)和 docstring 的 Args 部分提取参数信息,无需额外定义 Schema。

4.4 MCP Client 对接

使用 langchain-mcp-adapters 将 MCP 工具转换为 LangChain Tool:

stdio 模式(本地)
python 复制代码
from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "math": {
        "command": sys.executable,           # 当前 Python 路径
        "args": ["mcp_server.py", "stdio"],  # 启动命令
        "transport": "stdio",
    }
})

# 获取工具(自动转换为 LangChain Tool)
tools = await client.get_tools()
Streamable HTTP 模式(远程)
python 复制代码
client = MultiServerMCPClient({
    "math": {
        "url": "http://127.0.0.1:8000/mcp",
        "transport": "streamable_http",
    }
})

tools = await client.get_tools()
SSE 模式(已废弃)
python 复制代码
client = MultiServerMCPClient({
    "math": {
        "url": "http://127.0.0.1:8000/sse",
        "transport": "sse",
    }
})

tools = await client.get_tools()

4.5 完整调用链路

MCP Server MCP Client langchain-mcp-adapters LangChain Agent MCP Server MCP Client langchain-mcp-adapters LangChain Agent #mermaid-svg-nrsvazwb6MRt8ZBP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nrsvazwb6MRt8ZBP .error-icon{fill:#552222;}#mermaid-svg-nrsvazwb6MRt8ZBP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nrsvazwb6MRt8ZBP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nrsvazwb6MRt8ZBP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nrsvazwb6MRt8ZBP .marker.cross{stroke:#333333;}#mermaid-svg-nrsvazwb6MRt8ZBP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nrsvazwb6MRt8ZBP p{margin:0;}#mermaid-svg-nrsvazwb6MRt8ZBP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nrsvazwb6MRt8ZBP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nrsvazwb6MRt8ZBP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-nrsvazwb6MRt8ZBP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-nrsvazwb6MRt8ZBP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-nrsvazwb6MRt8ZBP .sequenceNumber{fill:white;}#mermaid-svg-nrsvazwb6MRt8ZBP #sequencenumber{fill:#333;}#mermaid-svg-nrsvazwb6MRt8ZBP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-nrsvazwb6MRt8ZBP .messageText{fill:#333;stroke:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nrsvazwb6MRt8ZBP .labelText,#mermaid-svg-nrsvazwb6MRt8ZBP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .loopText,#mermaid-svg-nrsvazwb6MRt8ZBP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-nrsvazwb6MRt8ZBP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-nrsvazwb6MRt8ZBP .noteText,#mermaid-svg-nrsvazwb6MRt8ZBP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-nrsvazwb6MRt8ZBP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nrsvazwb6MRt8ZBP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nrsvazwb6MRt8ZBP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-nrsvazwb6MRt8ZBP .actorPopupMenu{position:absolute;}#mermaid-svg-nrsvazwb6MRt8ZBP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-nrsvazwb6MRt8ZBP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-nrsvazwb6MRt8ZBP .actor-man circle,#mermaid-svg-nrsvazwb6MRt8ZBP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-nrsvazwb6MRt8ZBP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用 add(123, 456)转换为 MCP JSON-RPC发送 tools/call 请求执行 add 函数返回结果 579转换回 LangChain 格式Observation: 579


五、LangChain Tool vs MCP Tool 对比

维度 LangChain @tool MCP @mcp.tool()
定义方式 Python 函数 + 装饰器 Python 函数 + 装饰器
参数定义 docstring 或 Pydantic Schema 类型注解 + docstring Args
运行位置 与 Agent 同进程 独立进程/远程服务
通信方式 直接函数调用 JSON-RPC 协议
跨语言 否(仅 Python) 是(任何语言实现 MCP Server)
部署灵活性 低(耦合在 Agent 中) 高(独立部署和管理)
适用场景 简单工具、快速开发 生产环境、工具共享、微服务

六、开发踩坑记录

6.1 initialize_agent 迁移到 langchain_classic

问题: 新版 langchain(1.3.9+)中 from langchain.agents import initialize_agent 报错。

原因: LangChain 1.x 进行了大规模 API 重构,initialize_agent 被移到了 langchain_classic 包。

解决方案:

python 复制代码
# 旧写法(已废弃)
from langchain.agents import initialize_agent, AgentType

# 新写法
from langchain_classic.agents import initialize_agent, AgentType

6.2 STRUCTURED_CHAT 必须用 JSON 代码块

问题: 使用 STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 时,Agent 输出的 Action 格式不被解析。

原因: 该 Agent 类型要求 Action Input 必须是 ```````json```` 代码块包裹的 JSON,而非纯文本。

解决方案: 在提示词的 prefix 中明确要求:

复制代码
使用工具时,必须严格按照以下格式(使用 markdown JSON 代码块):

Thought: 思考下一步
```json
{{"action": "工具名称", "action_input": 工具入参}}


### 6.3 `@tool` 不支持 `description` 参数

**问题:** `@tool(description="...")` 报错。

**原因:** 新版 langchain 的 `@tool` 装饰器不再接受 `description` 参数。

**解决方案:** 工具描述从 docstring 的第一行自动提取:
```python
@tool
def add(a: int, b: int) -> int:
    """两数相加"""  # ← 这行就是 description
    return a + b

6.4 agent.invoke() 返回值无需额外解析

问题: 尝试用 JsonOutputParser 解析 Agent 返回结果,导致报错。

原因: agent.invoke() 返回的是 Python dict,已经包含 outputintermediate_steps 字段,无需二次解析。

python 复制代码
result = agent.invoke({"input": "问题"})
# result 已经是 dict:{"output": "...", "intermediate_steps": [...]}
print(result["output"])  # 直接取

6.5 MCP 工具结果全部返回 100

说明: 当前 mcp_server.py 中的 addmultiplydivide 函数返回值都硬编码为 100,这是用于测试 MCP 通信是否正常的 mock 值,实际使用时需要改为真实计算逻辑。


七、依赖安装速查

bash 复制代码
# LangChain Agent 核心依赖
pip install langchain-openai langchain-classic pydantic

# MCP 相关依赖(需 Python 3.10+)
pip install mcp langchain-mcp-adapters

八、关键 API 速查

python 复制代码
# ---- 工具定义 ----
from langchain_core.tools import tool

@tool
def my_tool(param: str) -> str:
    """工具描述"""
    return "结果"

# ---- Agent 创建 ----
from langchain_classic.agents import initialize_agent, AgentType

agent = initialize_agent(
    tools=tools, llm=llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True, max_iterations=10,
    return_intermediate_steps=True, handle_parsing_errors=True,
)

# ---- Agent 调用 ----
result = agent.invoke({"input": "你的问题"})
print(result["output"])

# ---- MCP Server ----
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("server-name")
mcp.run(transport="stdio")

# ---- MCP Client ----
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({"name": {"command": "...", "transport": "stdio"}})
tools = await client.get_tools()