文章目录
-
- [一、引言:为什么需要 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 有几个重要的设计理念,直接决定了它的使用方式:
- Server 极易构建:开发者只需关注具体功能实现,复杂编排由 Host 负责。
- Server 专注于明确定义的能力:每个 Server 只做一件事,做精做好。
- Server 高度可组合:多个 Server 可以无缝组合使用。
- 安全隔离: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 中的一个列表,存储 HumanMessage、AIMessage、ToolMessage 等。当 MCP 工具被调用时:
- LangGraph 的
ToolNode将工具调用请求发送给 MCP Client。 - MCP Client 通过 STDIO/HTTP 将请求转发给 MCP Server。
- MCP Server 执行后返回结果。
- 结果回到
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) |
核心优势:
- 解耦:工具独立于 Agent 开发和部署。
- 跨语言:Server 可用任何语言实现。
- 可组合:多个 Server 即插即用。
- 安全:Server 隔离,无法访问完整对话。
一句话总结:
MCP 让 LangGraph Agent 的工具系统从"内置函数"变成了"即插即用的 USB 外设" ------ 你的 Agent 只需关注"做什么",而"谁来做、怎么做"交给 MCP 生态去解决。
参考资料: