MCP 入门(二):从握手到执行,一文拆解架构设计
核心概念
三个角色,一套架构
MCP 采用客户端-服务器(client-server)架构。AI 应用作为主机(Host),负责建立与一个或多个 MCP 服务器的连接。具体怎么连?主机会为每个 MCP 服务器创建一个专属的客户端(Client),每个客户端与其对应的服务器之间维持一条独立的连接。
一句话概括:Host 管全局,Client 管连接,Server 管能力。
根据运行位置不同,MCP 服务器分为两种:
- 本地服务器:使用 STDIO 传输,服务单个客户端
- 远程服务器:使用 Streamable HTTP 传输,可同时服务多个客户端
MCP 架构的三个核心角色:
- MCP 主机(Host):负责协调和管理一个或多个 MCP 客户端的 AI 应用
- MCP 客户端(Client):维持与 MCP 服务器连接的组件,从服务器获取上下文信息,供主机使用
- MCP 服务器(Server):为 MCP 客户端提供上下文的程序
举个例子:VSCode 就是一个 MCP 主机。当 VSCode 需要连接某个 MCP 服务器时,它会实例化一个 MCP 客户端对象来维持这条连接。如果再连另一个 MCP 服务器,VSCode 就会再创建一个新的客户端对象。一个主机,多个客户端,各管各的连接,互不干扰。
两层架构:数据层 + 传输层
MCP 在设计上分为两层:
- 数据层(Data Layer):定义基于 JSON-RPC 的客户端-服务器通信协议,包含生命周期管理和核心元素(工具、资源、提示词、通知等)
- 传输层(Transport Layer):定义客户端与服务器之间交换数据的通信机制和通道,包含连接建立、消息帧和授权机制
可以这样理解:数据层管"说什么",传输层管"怎么送达"。
数据层
数据层基于 JSON-RPC 2.0 实现消息交换协议,定义了消息的结构和语义。它包括:
- 生命周期管理:处理连接初始化、能力协商和连接终止
- 服务器功能:让服务器提供核心能力——供 AI 调用的工具、提供上下文数据的资源、用于构建交互的模板提示词
- 客户端功能:让服务器能够请求客户端从主机的 LLM 中采样、向用户征询输入、将消息记录到客户端
- 实用功能:支持实时通知和长时间运行操作的进度跟踪等辅助能力
传输层
传输层负责管理客户端与服务器之间的通信通道和身份验证,处理连接建立、消息帧结构以及安全通信。
两种传输机制:
- 标准传输(STDIO):通过标准输入输出流进行本地通信,零网络开销,性能最高
- Streamable HTTP 传输:用于远程通信,兼容 HTTP 身份验证方法(bearer 令牌、API 密钥、自定义请求头等)。MCP 建议使用 OAuth 获取身份令牌
传输层的一个精妙之处在于:它将通信细节完全抽象出来。不管底层用的是 STDIO 还是 HTTP,上层的 JSON-RPC 2.0 消息格式完全一致。
数据层协议:MCP 最有趣的部分
MCP 的核心在于定义客户端和服务器之间的数据模式和语义。其中,一组叫做"原语(Primitives)"的基础构建块是整个协议最有意思的部分——它们就是将上下文从服务器传送到客户端的具体方式。
MCP 使用 JSON-RPC 2.0 作为底层 RPC 协议。客户端和服务器互相发送请求并作出响应;如果不需要回复,也可以使用通知机制。
生命周期管理
MCP 是有状态协议,需要生命周期管理。它的目的很明确:在连接建立时,协商客户端和服务器双方各自支持什么功能。
原语(Primitives):MCP 的核心积木
**原语是 MCP 最重要的概念。**它们定义了客户端和服务器之间能相互提供的核心功能,明确了哪些上下文信息可以共享给 LLM,以及可以执行哪些操作。
服务器可以暴露三个核心原语:
- 工具(Tools):AI 应用可调用的可执行函数(文件操作、API 调用、数据库查询等)
- 资源(Resources):为 AI 应用提供上下文信息的数据源(文件内容、数据库记录、API 响应等)
- 提示词(Prompts):可重用的模板,用于构建与 LLM 的交互(系统提示词、少样本示例等)
每个原语都关联了发现(*/list)、检索(*/get)以及部分场景下的执行(tools/call)方法。
工作流程是这样的:客户端先通过 */list 发现可用原语,再列出所有可用工具,最后按需执行调用。这个设计让工具列表可以动态更新,而不是写死的。
举个实际的例子:一个提供数据库上下文的 MCP 服务器,可以同时提供查询数据库的工具、包含数据库模式(Schema)的资源,以及包含使用示例的提示词。三种原语各司其职,配合使用。
除了服务器端和客户端的原语,MCP 还提供了横切性的实用原语,用来增强请求的执行方式:
- 任务(Tasks,实验性):持久化执行封装器,为 MCP 请求提供延迟结果获取与状态追踪功能(适用于高开销计算、工作流自动化、批量处理、多步骤操作等场景)
通知:让连接"活"起来
MCP 支持实时通知,让服务器能主动告知客户端发生了变化。比如,当服务器的可用工具发生变动时,它可以通过通知将变更推送给客户端。
通知以 JSON-RPC 2.0 通知消息的形式发送,不需要等待响应。这意味着 MCP 连接不是"一问一答"的死板模式,而是具备实时推送能力的活连接。
例子:数据层实战
1. 初始化(生命周期管理)
MCP 的起点是通过能力协商握手来完成生命周期管理。客户端发送一个 initialize 请求,建立连接并协商双方支持的功能特性。
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"elicitation": {}
},
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {}
},
"serverInfo": {
"name": "example-server",
"version": "1.0.0"
}
}
}
1.1. 握手到底在做什么?
初始化是 MCP 生命周期管理的关键环节,它承担三个核心职能:
- 协议版本协商:
protocolVersion字段确保客户端和服务器使用兼容的协议版本。版本不匹配?直接终止连接,绝不含糊。 - 能力发现:
capabilities对象让双方亮出各自的"底牌"——支持哪些原语(工具、资源、提示词),是否支持通知功能。提前摸清对方的能力边界,避免后续出现无效调用。 - 身份交换:
clientInfo和serverInfo对象提供身份和版本信息,方便调试和兼容性判断。
具体来看这个例子中的能力协商:
客户端能力
"elicitation": {}:客户端声明自己支持用户交互请求(可接收elicitation/create方法调用)
服务器能力
"tools": {"listChanged": true}:服务器支持工具原语,并且当工具列表发生变动时,会主动发送tools/list_changed通知"resources": {}:服务器也支持资源原语(可以处理resources/list和resources/read方法)
初始化成功后,客户端发送一个通知,表明已准备就绪:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
1.2. AI 应用中是怎么跑起来的?
初始化阶段,AI 应用的 MCP 客户端管理器会与已配置的服务器逐一建立连接,并把各服务器的能力信息存下来,方便后续使用。应用据此判断哪些服务器能提供工具、哪些能提供资源,以及谁支持实时更新。
# 伪代码
# 启动服务器进程并建立通信:使用 stdio(标准输入输出)传输方式。
# 根据 server_config 运行服务器,返回读写两个流对象。
# async with 确保任务结束时自动关闭进程。
async with stdio_client(server_config) as (read, write):
# 在传输层之上构建协议层:ClientSession 将原始读写流封装成
# 符合 MCP 协议规范的会话,让我们能调用 initialize() 这样的
# 高层级方法,而不用手动拼 JSON 字符串。
async with ClientSession(read, write) as session:
# 执行握手初始化:客户端向服务器"打招呼"——
# "你好,你能做什么?"
# 服务器返回版本号、身份信息以及它支持的功能列表。
init_response = await session.initialize()
# 按需注册能力:检查服务器是否支持工具。如果支持,
# 就在应用中注册它为"具备工具能力"的服务器。
# 之后 AI 就可以通过这个会话调用服务器提供的函数了。
if init_response.capabilities.tools:
app.register_mcp_server(session, supports_tools=True)
# 标记就绪:一切就绪,通知应用该服务器可以正式接活了。
app.set_server_ready(session)
2. 工具发现(原语)
连接建立之后,客户端可以通过发送 tools/list 请求来发现可用工具。这是 MCP 工具发现机制的核心——先摸清家底,再干活。
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "calculator_arithmetic",
"title": "Calculator",
"description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
}
},
"required": ["expression"]
}
},
{
"name": "weather_current",
"title": "Weather Information",
"description": "Get current weather information for any location worldwide",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, address, or coordinates (latitude,longitude)"
},
"units": {
"type": "string",
"enum": ["metric", "imperial", "kelvin"],
"description": "Temperature units to use in response",
"default": "metric"
}
},
"required": ["location"]
}
}
]
}
}
2.1. 工具发现请求
tools/list 请求非常简洁,不需要任何参数。
2.2. 工具发现响应
响应中的 tools 数组提供了每个可用工具的完整元数据。这种基于数组的结构让服务器可以同时暴露多个工具,同时在不同功能之间保持清晰的边界。
每个工具对象包含几个关键字段:
name:工具在服务器命名空间中的唯一标识符。它是工具执行的"主键",命名应该清晰明确(比如用calculator_arithmetic而不是简单的calculate)title:人类可读的显示名称,客户端可以用它向用户展示description:工具用途的详细说明,包括它能做什么、适合什么场景inputSchema:JSON Schema 格式的输入参数定义,提供类型验证,并清晰标注哪些参数必填、哪些可选
2.3. AI 应用中是怎么跑起来的?
AI 应用会从所有已连接的 MCP 服务器获取可用工具,汇总成一个统一的工具注册表供 LLM 使用。这样 LLM 就知道自己能调用哪些操作,并在对话中自动生成相应的工具调用指令。
# 伪代码:使用 MCP Python SDK 模式
available_tools = []
# 遍历所有已建立连接的会话
for session in app.mcp_server_sessions():
# 获取工具列表(Discovery):返回该服务器的工具列表
tools_response = await session.list_tools()
# 工具聚合:将不同服务器的工具汇总到统一数组中
available_tools.extend(tools_response.tools)
# 注册到对话上下文:将汇总后的工具列表交给对话管理系统
conversation.register_available_tools(available_tools)
3. 工具执行(原语)
客户端可以通过 tools/call 方法执行工具。这就是 MCP 原语在实际中的运作方式:先发现,再调用。
3.1. 工具执行请求
tools/call 请求采用结构化格式,确保客户端和服务器之间的类型安全和通信清晰。注意,这里使用的是工具发现响应中的完整名称 weather_current,而不是简化命名:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": {
"location": "San Francisco",
"units": "imperial"
}
}
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
}
]
}
}
3.2. 请求结构解析
请求中有几个关键部分:
name:必须与发现响应中的工具名精确匹配(weather_current),确保服务器能准确定位到要执行的工具arguments:包含工具inputSchema中定义的输入参数:location:“San Francisco”,必填参数units:“imperial”,可选参数(默认 metric)
- JSON-RPC 结构:使用标准 JSON-RPC 2.0 格式,通过唯一的
id实现请求与响应的精确关联
3.3. 响应结构解析
响应体现了 MCP 灵活的内容系统:
content数组:工具响应返回内容对象数组,支持多种响应格式(文本、图片、资源等)- 内容类型:每个内容对象都有一个
type字段。"type": "text"表示纯文本,但 MCP 还支持多种内容类型以适配不同场景 - 结构化输出:响应提供可操作的信息,作为 AI 应用与 LLM 交互的上下文
这种执行模式让 AI 应用可以动态调用服务器功能,并接收结构化的响应结果,无缝整合到对话流中。
3.4. AI 应用中是怎么跑起来的?
当 LLM 在对话中决定使用某个工具时,AI 应用会拦截这个工具调用请求,将它路由到对应的 MCP 服务器执行,然后把结果作为对话流的一部分返回给 LLM。这样,LLM 就能访问实时数据并与外部世界交互了。
# 伪代码:AI 应用工具执行
# 触发执行:LLM 分析用户请求后,如果需要外部数据,
# 会生成一个结构化输出,指明要用的 tool_name 和 arguments。
async def handle_tool_call(conversation, tool_name, arguments):
# 路由分发:查找"路由表"(工具聚合阶段建立的),
# 找到拥有这个工具的 session
session = app.find_mcp_session_for_tool(tool_name)
# 远程调用:
# 1. 客户端将工具名和参数打包成 MCP 协议格式
# 2. 发送给对应的 MCP Server
# 3. Server 执行实际操作(访问 API、读文件等)
# 4. 等待 Server 返回执行结果
result = await session.call_tool(tool_name, arguments)
# 闭环反馈:将工具返回的数据追加到对话历史中,
# 让 LLM 能基于这些信息继续推理
conversation.add_tool_result(result.content)
4. 实时更新(通知)
MCP 通过实时通知机制,让服务器能主动告知客户端工具发生了变化,而无需客户端反复轮询。这是保持 MCP 连接同步和快速响应的关键特性。
4.1. 工具列表变动通知
当服务器的可用工具发生变动——新工具上线、现有工具被修改、或工具暂时不可用——服务器会主动推送通知:
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
4.2. MCP 通知的核心特点
- 无需响应:通知中没有
id字段,遵循 JSON-RPC 2.0 的通知语义——发了就完事,不用等回复 - 基于能力声明:只有在初始化阶段声明了
"listChanged": true的服务器才会发送这类通知(见第 1 步) - 事件驱动:服务器根据内部状态变化决定何时推送,让 MCP 连接具备动态性和实时性
4.3. 客户端如何响应?
收到通知后,客户端通常会立即请求更新后的工具列表。这就形成了一个"通知 -> 刷新"的闭环,确保客户端掌握的工具信息始终是最新的:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/list"
}
4.4. 为什么通知机制如此重要?
- 动态环境:工具可能随服务状态、外部依赖或用户权限的变化而增减
- 高效通信:客户端无需轮询,有变化时自然会被通知
- 状态一致:确保客户端对服务器可用工具的认知始终准确
- 实时协作:让 AI 应用能够即时适应变化的上下文环境
通知模式不仅限于工具,它延伸到了所有 MCP 原语,实现客户端和服务器之间全方位的实时同步。
4.5. AI 应用中是怎么跑起来的?
当 AI 应用收到工具变更通知时,它会立刻刷新工具注册表并更新 LLM 的可用能力。如果当前有对话正在进行,还会实时注入一条系统消息告诉 LLM:“你的工具箱刚更新了。”
# 伪代码:AI 应用中的通知处理
# 监听回调:当收到 notifications/tools/list_changed 消息时,
# 自动触发这个异步函数
async def handle_tools_changed_notification(session):
# 主动拉取:不猜测变化内容,直接请求最新的完整工具列表
tools_response = await session.list_tools()
# 更新注册表:将新工具列表同步到应用的内部存储中
app.update_available_tools(session, tools_response.tools)
# 实时上下文注入:如果对话正在进行,
# 向 LLM 的上下文窗口插入一条系统消息,
# 告知可用工具已更新
if app.conversation.is_active():
app.conversation.notify_llm_of_new_capabilities()
更多推荐

所有评论(0)