MCP 协议完全指南:从原理到 LangGraph 集成,打造即插即用的 AI Agent 工具生态

文章目录

    • [一、引言:为什么需要 MCP?](#一、引言:为什么需要 MCP?)
    • [二、MCP 架构:三个核心角色](#二、MCP 架构:三个核心角色)
      • [2.1 角色定义](#2.1 角色定义)
      • [2.2 关键设计原则](#2.2 关键设计原则)
    • [三、MCP 的两层协议架构](#三、MCP 的两层协议架构)
      • [3.1 数据层](#3.1 数据层)
      • [3.2 传输层](#3.2 传输层)
    • [四、代码实战:从零构建 MCP Server](#四、代码实战:从零构建 MCP Server)
      • [4.1 安装依赖](#4.1 安装依赖)
      • [4.2 实现 MCP Server(math_server.py)](#4.2 实现 MCP Server(math_server.py))
    • [五、LangGraph 集成 MCP:两种方式](#五、LangGraph 集成 MCP:两种方式)
      • [5.1 方式一:使用 MultiServerMCPClient(推荐)](#5.1 方式一:使用 MultiServerMCPClient(推荐))
      • [5.2 方式二:手动管理 Session(更精细控制)](#5.2 方式二:手动管理 Session(更精细控制))
    • 六、完整项目结构
    • 七、执行流程详解:从用户提问到最终回答
    • [八、MCP Client 与 MCP Server 的通信细节](#八、MCP Client 与 MCP Server 的通信细节)
      • [8.1 STDIO 传输的工作流程](#8.1 STDIO 传输的工作流程)
      • [8.2 HTTP + SSE 传输的工作流程](#8.2 HTTP + SSE 传输的工作流程)
    • [九、与纯 LangGraph 方案的对比](#九、与纯 LangGraph 方案的对比)
    • [十、常见问题 Q&A](#十、常见问题 Q&A)
      • [Q1:MCP Client 和 MCP Server 必须在同一台机器上吗?](#Q1:MCP Client 和 MCP Server 必须在同一台机器上吗?)
      • [Q2:`create_agent` 和手动构建 `StateGraph` 有什么区别?](#Q2:create_agent 和手动构建 StateGraph 有什么区别?)
      • [Q3:MCP 的 `messages` 和 LangGraph 的 `messages` 是同一个东西吗?](#Q3:MCP 的 messages 和 LangGraph 的 messages 是同一个东西吗?)
    • 十一、总结

一、引言:为什么需要 MCP?

在构建 AI Agent 应用时,我们常常面临一个尴尬的局面:工具与模型之间的"N×M 集成问题"。每个 AI 应用需要为每一个外部工具(数据库、API、文件系统)编写独立的连接代码,导致代码臃肿、难以维护。

MCP(Model Context Protocol,模型上下文协议) 应运而生。它是由 Anthropic 于 2024 年发布的开放标准协议,旨在标准化 AI 应用与外部工具、数据源之间的连接方式

官方定义 :MCP 是一个开放协议,它标准化了应用程序如何向 LLM 提供上下文。你可以把它想象成 AI 应用的"USB-C 接口" ------ 任何遵循 MCP 标准的工具都能即插即用地接入任何支持 MCP 的 AI 应用。


二、MCP 架构:三个核心角色

MCP 采用 Client-Host-Server 架构。以下是三个核心角色的详细拆解:

2.1 角色定义

角色 职责 通俗类比
MCP Host(主机) AI 应用程序(如你的 LangGraph Agent),负责创建和管理多个 MCP Client 实例。 总司令部 ------ 负责整体决策和调度。
MCP Client(客户端) Host 为每个 MCP Server 创建的通信组件,维护与 Server 的专用连接。 通信兵 ------ 负责收发指令和结果。
MCP Server(服务端) 提供特定能力(工具、资源、提示词)的独立程序。 前线武器库 ------ 负责执行具体任务。

2.2 关键设计原则

MCP 有几个重要的设计理念,直接决定了它的使用方式:

  1. Server 极易构建:开发者只需关注具体功能实现,复杂编排由 Host 负责。
  2. Server 专注于明确定义的能力:每个 Server 只做一件事,做精做好。
  3. Server 高度可组合:多个 Server 可以无缝组合使用。
  4. 安全隔离:Server 无法读取完整对话历史或"窥视"其他 Server。每个 Server 只接收必要的上下文信息。

三、MCP 的两层协议架构

MCP 由数据层(Data Layer)传输层(Transport Layer) 构成:

3.1 数据层

数据层定义了基于 JSON-RPC 2.0 的通信协议,包括:

  • 生命周期管理:Client 与 Server 的握手、初始化、关闭流程。
  • 核心原语:工具(Tools)、资源(Resources)、提示词(Prompts)。
  • 通知机制:Server 可主动向 Client 推送更新。

3.2 传输层

传输层定义了 Client 和 Server 之间的通信通道。MCP 目前定义了两种标准传输机制:

传输方式 工作原理 适用场景
STDIO Client 将 MCP Server 作为子进程启动,通过标准输入(stdin)和标准输出(stdout)交换 JSON-RPC 消息。 本地通信,适合 CLI 工具、桌面应用。
HTTP + SSE Server 作为独立进程运行,提供 SSE 端点和 HTTP POST 端点。Client 通过 SSE 接收 Server 消息,通过 POST 发送消息。 远程通信,适合 Web 部署、云服务。

注意 :SSE 传输已在 2025 年 3 月被标记为弃用,新项目推荐使用 Streamable HTTP


四、代码实战:从零构建 MCP Server

我们使用 FastMCP 库来创建一个简单的数学计算 MCP Server。

4.1 安装依赖

bash 复制代码
pip install fastmcp langchain-mcp-adapters langgraph langchain-openai

4.2 实现 MCP Server(math_server.py)

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

# 创建一个 MCP Server 实例,命名为 "Math"
mcp = FastMCP("Math")

# 用 @mcp.tool() 装饰器将普通函数变成 MCP 工具
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""  # 这个文档字符串会被 AI 读取
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """Multiply two numbers"""
    return a * b

@mcp.tool()
def subtract(a: int, b: int) -> int:
    """Subtract two numbers"""
    return a - b

if __name__ == "__main__":
    # 启动 Server,使用 stdio 传输(本地子进程通信)
    mcp.run(transport="stdio")

代码解析

  • FastMCP("Math"):创建一个 MCP Server 实例。
  • @mcp.tool():装饰器将函数暴露为可供 AI 调用的工具。
  • FastMCP 会自动利用 Python 的类型提示和文档字符串生成 JSON Schema 描述,供 AI 理解工具的用途和参数。
  • mcp.run(transport="stdio"):启动 Server,监听标准输入输出。

这个 Server 是一个独立的进程,它不知道自己会被谁调用,只负责接收指令、执行计算、返回结果。


五、LangGraph 集成 MCP:两种方式

LangChain 官方提供了 langchain-mcp-adapters 库,用于将 MCP 工具无缝接入 LangGraph Agent。

5.1 方式一:使用 MultiServerMCPClient(推荐)

这是最简洁的方式,适合连接一个或多个 MCP Server。

python 复制代码
# agent.py
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

async def main():
    # 1. 创建 MCP Client,配置要连接的 Server
    client = MultiServerMCPClient({
        "math": {  # 给这个 Server 起个名字
            "transport": "stdio",  # 本地子进程通信
            "command": "python",
            "args": ["/path/to/math_server.py"],  # 指向你的 Server 文件
        },
        # 可以继续添加更多 Server
        # "weather": {
        #     "transport": "http",
        #     "url": "http://localhost:8000/mcp",
        # }
    })

    # 2. 从所有配置的 Server 中加载工具
    #    MultiServerMCPClient 会自动与每个 Server 通信,获取工具列表
    tools = await client.get_tools()

    # 3. 创建 LangGraph Agent(使用 create_agent 快捷方式)
    model = ChatOpenAI(model="gpt-4o")
    agent = create_agent(model, tools)

    # 4. 运行 Agent
    response = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
    )
    print(response)

if __name__ == "__main__":
    asyncio.run(main())

代码解析

  • MultiServerMCPClient({...}):创建客户端,配置多个 MCP Server 的连接信息。
  • client.get_tools()核心方法 。Client 会与所有配置的 Server 建立连接,获取每个 Server 暴露的工具列表,并将它们自动转换成 LangChain 兼容的 Tool 对象
  • create_agent(model, tools):使用高层封装创建 ReAct Agent(内部自动构建了包含 messages 状态的标准图)。
  • Agent 在调用工具时,完全感知不到背后是 MCP ------ 它就像使用本地函数一样自然。

5.2 方式二:手动管理 Session(更精细控制)

如果需要更细粒度的控制(如自定义认证、会话管理),可以手动管理 MCP Session。

python 复制代码
# agent_manual.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

async def main():
    # 1. 配置 Server 参数
    server_params = StdioServerParameters(
        command="python",
        args=["/path/to/math_server.py"],
    )

    # 2. 建立 stdio 连接
    async with stdio_client(server_params) as (read, write):
        # 3. 创建 Client Session
        async with ClientSession(read, write) as session:
            # 4. 初始化连接(握手)
            await session.initialize()
            
            # 5. 从 Session 加载工具
            tools = await load_mcp_tools(session)
            
            # 6. 创建并运行 Agent
            model = ChatOpenAI(model="gpt-4o")
            agent = create_agent(model, tools)
            response = await agent.ainvoke(
                {"messages": [{"role": "user", "content": "what's 10 - 3?"}]}
            )
            print(response)

if __name__ == "__main__":
    asyncio.run(main())

代码解析

  • stdio_client(server_params):建立 STDIO 传输层的连接。
  • ClientSession(read, write):创建 MCP 会话。
  • await session.initialize():MCP 协议要求的初始化握手,Client 和 Server 在此交换能力和协议版本信息。
  • load_mcp_tools(session):从已建立的 Session 中加载工具。

六、完整项目结构

一个集成了 MCP 的 LangGraph 项目通常长这样:

复制代码
my-mcp-agent-project/
├── .env                     # 环境变量(OpenAI API Key 等)
├── math_server.py           # MCP Server:数学计算
├── weather_server.py        # MCP Server:天气查询(可选)
└── agent.py                 # 主程序:LangGraph Agent + MCP Client

七、执行流程详解:从用户提问到最终回答

结合上面的代码,我们来看一个完整的执行流程(以 (3+5)×12 为例):

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 1:用户输入                                                            │
│ "what's (3 + 5) x 12?"                                                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 2:LangGraph Agent(Host)接收消息                                     │
│ - Agent 内部状态(messages 列表)追加了 HumanMessage                        │
│ - 流程进入 chatbot 节点(LLM 调用)                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 3:LLM 决策(第一次思考)                                              │
│ - LLM 看到可用工具列表:[add, multiply, subtract]                          │
│ - LLM 推理:需要先算 3+5,再乘 12                                         │
│ - LLM 生成 tool_calls:                                                    │
│   [{"name": "add", "args": {"a": 3, "b": 5}},                             │
│    {"name": "multiply", "args": {"a": 8, "b": 12}}]                       │
│ - 追加 AIMessage(带 tool_calls)到 messages 列表                         │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 4:路由判断(条件边)                                                  │
│ - route_tools 检查最后一条消息(AIMessage)是否有 tool_calls               │
│ - 有 → 返回 "tools",跳转到工具节点                                        │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 5:ToolNode 执行(MCP Client 介入)                                   │
│ - ToolNode 遍历 tool_calls 列表                                            │
│ - 对于每个 tool_call:                                                     │
│   a. 识别工具名(如 "add")                                                │
│   b. 调用 MCP Client 的 call_tool 方法                                     │
│   c. MCP Client 通过 STDIO/HTTP 向 MCP Server 发送 JSON-RPC 请求          │
│   d. MCP Server 执行对应函数,返回结果                                     │
│   e. MCP Client 将结果返回给 ToolNode                                     │
│ - ToolNode 将每个结果包装成 ToolMessage,追加到 messages 列表              │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 6:固定边 tools → chatbot(回到 LLM)                                 │
│ - 流程强制回到 chatbot 节点                                                │
│ - LLM 看到 ToolMessage 中的结果(add=8, multiply=96)                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 7:LLM 决策(第二次思考)                                              │
│ - LLM 有了工具结果,不再需要调用更多工具                                   │
│ - 生成纯文本 AIMessage(无 tool_calls):"The result is 96"               │
│ - 追加到 messages 列表                                                     │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 8:路由判断(再次)                                                    │
│ - route_tools 检查最后一条消息(纯文本 AIMessage)                         │
│ - 无 tool_calls → 返回 END,流程结束                                       │
└─────────────────────────────────────────────────────────────────────────────┘
                                    ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ 步骤 9:返回最终结果                                                        │
│ - Agent 将 messages 列表中的最后一条 AIMessage.content 返回给用户          │
│ - 用户看到:"The result is 96"                                             │
└─────────────────────────────────────────────────────────────────────────────┘

八、MCP Client 与 MCP Server 的通信细节

8.1 STDIO 传输的工作流程

当使用 transport="stdio" 时:

复制代码
┌─────────────┐                    ┌─────────────┐
│  MCP Client │                    │ MCP Server  │
│ (LangGraph) │                    │(子进程)     │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       │  1. 启动子进程                    │
       │─────────────────────────────────>│
       │                                  │
       │  2. 发送 JSON-RPC 请求 (stdin)   │
       │─────────────────────────────────>│
       │                                  │
       │  3. 执行函数逻辑                  │
       │                                  │
       │  4. 返回 JSON-RPC 响应 (stdout)  │
       │<─────────────────────────────────│
       │                                  │
       │  5. 关闭 stdin,终止子进程        │
       │─────────────────────────────────>│

关键点

  • Client 将 Server 作为子进程启动。
  • 消息通过 标准输入(stdin)和标准输出(stdout) 传输。
  • 消息以换行符分隔,每条消息必须是有效的 JSON-RPC 格式。
  • Server 可以将日志写入 stderr,Client 可捕获或忽略。

8.2 HTTP + SSE 传输的工作流程

当使用 transport="http" 时:

复制代码
┌─────────────┐                    ┌─────────────┐
│  MCP Client │                    │ MCP Server  │
│ (LangGraph) │                    │(独立进程)   │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       │  1. 建立 SSE 连接                 │
       │─────────────────────────────────>│
       │                                  │
       │  2. 返回 endpoint 事件 (含 POST URI)
       │<─────────────────────────────────│
       │                                  │
       │  3. 发送 JSON-RPC 请求 (HTTP POST)
       │─────────────────────────────────>│
       │                                  │
       │  4. 执行函数逻辑                  │
       │                                  │
       │  5. 返回结果 (SSE message 事件)   │
       │<─────────────────────────────────│
       │                                  │
       │  6. 关闭 SSE 连接                 │
       │─────────────────────────────────>│

关键点

  • Server 作为独立进程 运行,可处理多个客户端连接
  • Server 必须提供两个端点:
    • SSE 端点:Client 建立连接并接收 Server 消息。
    • HTTP POST 端点:Client 发送消息给 Server。
  • Client 连接时,Server 发送 endpoint 事件,告知 Client 用于发送消息的 URI。

九、与纯 LangGraph 方案的对比

特性 纯 LangGraph(@tool 函数) LangGraph + MCP
工具定义位置 与 Agent 代码在同一文件/进程 工具在独立的 MCP Server 进程
编程语言 仅限 Python(与 Agent 相同) 任意语言(Server 可用 Go、Java、Node.js 等)
工具更新 需要重启整个 Agent 服务 只需重启对应的 MCP Server,Agent 无感知
安全隔离 工具代码与主程序混在一起,风险较高 Server 独立运行,权限隔离,Server 无法读取完整对话
可组合性 工具在代码中硬编码 多个 Server 可无缝组合,即插即用
适用场景 小型项目、快速原型 大型企业级应用、多团队协作、跨语言需求

十、常见问题 Q&A

Q1:MCP Client 和 MCP Server 必须在同一台机器上吗?

不一定。

  • STDIO 传输 :Client 和 Server 必须在同一台机器上,因为 Client 需要将 Server 作为子进程启动。
  • HTTP 传输 :Client 和 Server 可以分布在不同机器上,Server 作为远程服务运行。

Q2:create_agent 和手动构建 StateGraph 有什么区别?

create_agent 是 LangChain 提供的高层封装,内部自动完成了:

  • 定义包含 messages 列表的状态(使用 add_messages 归约器保证追加而非覆盖)
  • 创建 chatbot 节点和 tools 节点
  • 配置条件边(路由函数)
  • 添加 tools → chatbot 的循环边

并没有改变 LangGraph 的运行本质 ,只是提供了开箱即用的标准 ReAct 模板。需要精细控制时,仍可手动构建 StateGraph

Q3:MCP 的 messages 和 LangGraph 的 messages 是同一个东西吗?

是的,但使用方式不同。

在 LangGraph 中,messages 是 State 中的一个列表,存储 HumanMessageAIMessageToolMessage 等。当 MCP 工具被调用时:

  1. LangGraph 的 ToolNode 将工具调用请求发送给 MCP Client。
  2. MCP Client 通过 STDIO/HTTP 将请求转发给 MCP Server。
  3. MCP Server 执行后返回结果。
  4. 结果回到 ToolNode,被包装成 ToolMessage 追加到 LangGraph 的 messages 列表中。

MCP Server 本身不维护对话历史,它只接收当前请求的参数,返回执行结果。完整对话历史始终由 Host(LangGraph Agent)管理。


十一、总结

MCP 通过标准化协议将 AI 应用的"大脑"(LangGraph Agent)和"手脚"(工具)解耦:

层级 组件 职责
Host LangGraph Agent 决策、编排、状态管理
Client MultiServerMCPClient 发现工具、发起调用、协议适配
Transport STDIO / HTTP 消息传输(进程内/跨网络)
Server FastMCP Server 执行具体功能(计算、查文件、调 API)

核心优势

  1. 解耦:工具独立于 Agent 开发和部署。
  2. 跨语言:Server 可用任何语言实现。
  3. 可组合:多个 Server 即插即用。
  4. 安全:Server 隔离,无法访问完整对话。

一句话总结

MCP 让 LangGraph Agent 的工具系统从"内置函数"变成了"即插即用的 USB 外设" ------ 你的 Agent 只需关注"做什么",而"谁来做、怎么做"交给 MCP 生态去解决。


参考资料

相关推荐
HIT_Weston3 小时前
153、【Agent】【OpenCode】启动分析(completion)
人工智能·agent·opencode
武子康4 小时前
1.2GB 离线语音 Agent 真正值得复用的不是 908ms:四阶段职责 + 状态感知 Tool Schema + 可观测时间锚点
人工智能·后端·agent
csdn_aspnet5 小时前
如何将 Cursor MCP 与 VS Code 连接
vscode·cursor·mcp·composio
后端小肥肠6 小时前
Doubao-Seed-Evolving实测:我做了个情绪价值拉满的星座记账 Skill
人工智能·aigc·agent
李剑一6 小时前
看完DeepSeek梁文锋内部交流会发言实录,我或许明白了为什么一直坚持不融资的DeepSeek开启了融资之路,也明白了中国人做AI一定是能够成功的
aigc·agent
怕浪猫6 小时前
第6章 检索增强生成:打造知识库驱动型Agent
openai·agent·ai编程
牧子川7 小时前
何时拒绝使用工具:Agent 不是万能钥匙
人工智能·大模型·agent·tools·functioncalling
MicrosoftReactor7 小时前
技术速递|智能体测试智能体:基于 Foundry Hosted Agents 构建云原生 Skill-Eval Harness
ai·云原生·agent·ai-agent·skill