Model Context Protocol (MCP) 是一个开放协议,旨在使 AI 应用与外部数据源和工具进行标准化、安全的交互。它类似于 AI 应用的 USB-C 接口,提供了一种统一方式让 AI 模型与各种数据源和工具连接。本章将详细解析 MCP 的架构、通信流程及安全模型,帮助读者全面理解这一协议的设计与实现。
2.1 MCP的核心架构
MCP 采用客户端-服务器架构,通过标准化的协议层和灵活的传输层,实现 AI 模型与外部资源的无缝连接。下面将详细介绍 MCP 架构的核心组件。
2.1.1 MCP Host与Client
MCP Host 是整个 MCP 架构的核心容器,它是运行 AI 应用的环境,如 Claude Desktop、集成开发环境 (IDE) 或其他通过 MCP 协议访问资源的 AI 工具。MCP Host 负责管理 MCP 客户端与服务器之间的连接,并提供一个安全的运行环境。
MCP Host 的主要特点与职责:
环境提供:Host 为 MCP 客户端和服务器提供运行环境,负责初始化和配置。例如,Claude Desktop 应用程序就是一个典型的 MCP Host,它通过配置文件
claude_desktop_config.json
来管理和启动各种 MCP 服务器。资源管理:Host 负责分配和管理系统资源,确保客户端和服务器能够高效运行。它监控资源使用情况,并在必要时进行调整。
安全边界:Host 提供安全边界,控制服务器对系统资源的访问权限。例如,Claude Desktop 会以用户账户权限运行 MCP 服务器,同时限制它们只能访问配置中指定的目录。
用户界面:许多 Host 还提供用户界面,让用户能够查看可用的工具和资源,以及授权或拒绝特定操作。例如,Claude Desktop 在命令窗口右下角提供一个小锤子图标,用户可以通过它查看所有可用的 MCP 工具。
MCP Client 的角色与功能:
MCP Client 是 MCP 协议的客户端实现,它与 MCP 服务器建立连接,并负责管理请求和响应。在 MCP 架构中,每个客户端通常与一个服务器维持一对一的连接。
连接管理:客户端负责建立、维护和关闭与服务器的连接。它处理连接生命周期中的各种事件,如初始化、心跳检测和异常处理。
请求协调:客户端将来自 AI 模型或应用程序的请求转发到适当的服务器,并将服务器的响应返回给请求方。
功能发现:客户端可以查询服务器提供的功能和能力,包括可用的资源、工具和提示模板。
错误处理:客户端负责处理连接和通信过程中可能出现的各种错误,并向上层应用提供适当的错误信息。
类型安全:高质量的客户端实现通常提供类型安全的 API,使开发者能够以类型安全的方式与服务器交互。
在 MCP 架构中,Host 和 Client 紧密协作,共同为 AI 应用提供与外部资源交互的能力。Host 提供整体环境和安全边界,而 Client 则负责具体的协议实现和通信管理。这种分层设计使 MCP 既灵活又安全,能够适应各种 AI 应用场景。
2.1.2 MCP Server
MCP Server 是 MCP 架构中的核心组件,它通过标准化的模型上下文协议为客户端提供特定的功能和服务。MCP Server 负责向 AI 模型提供数据访问、功能执行和上下文管理的能力,使 AI 模型能够与外部系统进行交互。
MCP Server 的基本概念:
MCP Server 可以类比为 ChatGPT 的 GPTs 或工具(Tools),它们提供的能力(Capabilities)相当于 GPTs 的 Action 或工具调用(Tool Calling)。每个 MCP Server 专注于特定的功能领域,如文件系统操作、数据库访问、API 集成等,使 AI 模型能够安全地访问这些功能。
MCP Server 的核心组件:
资源(Resources):
- 服务器可以提供文件内容、数据库记录、API 响应等各种数据资源
- 资源通过唯一的 URI 标识,可以包含文本或二进制数据
- 客户端可以通过
resources/list
和resources/read
端点来发现和访问资源 - 资源还可以通过 URI 模板定义动态资源,使客户端能够构造有效的资源 URI
工具(Tools):
- 工具是服务器暴露的可执行函数,允许 AI 模型执行外部操作
- 每个工具都有唯一的名称、描述和基于 JSON Schema 的输入参数定义
- 客户端通过
tools/list
和tools/call
端点来发现和调用工具 - 工具可以执行从简单计算到复杂 API 交互的各种操作
提示(Prompts):
- 提示是服务器定义的可重用模板,帮助 AI 模型生成特定类型的响应
- 提示可以接受动态参数,并包含来自资源的上下文
- 客户端通过
prompts/list
和prompts/get
端点来发现和使用提示 - 提示可以设计成多步骤的工作流,引导用户完成复杂的任务
MCP Server 的类型与实现:
MCP Server 根据实现语言和功能可以分为多种类型:
官方参考实现:
- 文件系统服务器:提供文件操作能力,包括读写文件、目录管理等
- 数据库服务器:如 PostgreSQL、SQLite 服务器,提供数据库访问能力
- Web 服务器:如 Brave Search、Fetch 服务器,提供 Web 内容获取和处理能力
- 开发工具:如 Git、GitHub、GitLab 服务器,提供版本控制和代码管理能力
- 生产力工具:如 Slack、Google Maps、Memory 服务器,提供通信和信息管理能力
- AI 工具:如 EverArt、Sequential Thinking 服务器,提供 AI 增强能力
官方集成:
- 各公司为其平台提供的 MCP 服务器,如 Axiom、Cloudflare、Stripe 等
- 这些服务器通常提供对特定平台或服务的优化访问
社区实现:
- 由社区开发者创建的 MCP 服务器,扩展了 MCP 的生态系统
- 包括 Docker、Kubernetes、Spotify 等各种服务的集成
MCP Server 的实现与运行:
MCP Server 的实现通常基于官方提供的 SDK,主要有两种类型:
TypeScript 实现:
- 使用
npx
命令直接运行,例如:npx -y @modelcontextprotocol/server-memory
- 通常适用于 Web 和 JavaScript 生态系统的集成
- 使用
Python 实现:
- 使用
uvx
或pip
安装和运行,例如:uvx mcp-server-git
- 适用于数据科学、机器学习和后端系统的集成
- 使用
MCP Server 的配置通常在 Claude Desktop 的配置文件中定义,例如:
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
}
}
}
这种配置指定了服务器的名称、启动命令和参数,使 Host 能够正确启动和管理服务器。
2.1.3 MCP协议层
MCP 协议层是 Model Context Protocol 的核心,它定义了客户端和服务器之间的通信规则、消息格式和交互模式。协议层的设计使得不同的 AI 应用和服务能够以标准化方式进行交互,促进了生态系统的发展和互操作性。
协议层的基本结构:
MCP 协议层基于会话(Session)概念构建,每个会话管理客户端和服务器之间的请求和响应。核心类和接口包括:
class Session(BaseSession[RequestT, NotificationT, ResultT]):
async def send_request(
self,
request: RequestT,
result_type: type[Result]
) -> Result:
"""
发送请求并等待响应。如果响应包含错误,则抛出 McpError。
"""
# 请求处理实现
async def send_notification(
self,
notification: NotificationT
) -> None:
"""发送不期望响应的单向通知。"""
# 通知处理实现
async def _received_request(
self,
responder: RequestResponder[ReceiveRequestT, ResultT]
) -> None:
"""处理从另一方接收的请求。"""
# 请求处理实现
async def _received_notification(
self,
notification: ReceiveNotificationT
) -> None:
"""处理从另一方接收的通知。"""
# 通知处理实现
协议层的关键组件包括:
- Protocol:定义基本的协议规范和版本兼容性检查
- Client:实现客户端功能,包括请求发送和响应处理
- Server:实现服务器功能,包括请求接收和处理
能力协商(Capability Negotiation):
MCP 协议的一个重要特性是能力协商机制,它允许客户端和服务器在连接建立时交换各自支持的功能。这使得协议能够灵活适应不同的实现和需求:
- 客户端能力:客户端在初始化时声明它支持的功能,如资源访问、工具调用、提示模板等
- 服务器能力:服务器响应自己支持的功能和版本信息
- 动态适应:客户端可以根据服务器支持的能力调整交互方式
这种机制确保了即使在不同版本或实现之间,协议也能够保持兼容性和可扩展性。
请求-响应模式:
MCP 协议采用请求-响应模式进行通信,这是一种常见的分布式系统通信模式:
- 请求:客户端向服务器发送请求,包含方法名和参数
- 处理:服务器接收请求,执行相应操作
- 响应:服务器将结果或错误返回给客户端
- 回调:客户端处理响应或错误
此外,协议还支持通知(Notification)机制,允许单向消息传递,不需要响应。
错误处理机制:
MCP 协议定义了标准的错误处理机制,使客户端能够优雅地处理各种异常情况:
enum ErrorCode {
// 标准 JSON-RPC 错误代码
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603
}
错误通过以下方式传播:
- 请求的错误响应
- 传输层的错误事件
- 协议级错误处理器
这种结构化的错误处理确保了通信的健壮性和可靠性。
协议扩展性:
MCP 协议设计具有良好的扩展性,允许在不破坏现有功能的情况下添加新功能:
- 版本兼容性:协议支持版本控制,确保新旧版本的兼容性
- 可选功能:通过能力协商机制,功能可以是可选的
- 扩展点:协议预留了扩展点,允许添加自定义功能
- 命名空间:使用命名空间避免冲突,允许多个组织扩展协议
这种扩展性使 MCP 协议能够适应不断变化的 AI 和应用需求,保持长期的发展活力。
2.1.4 传输层与消息类型
MCP 的传输层负责处理客户端和服务器之间的实际通信,它定义了消息的传输方式和格式。MCP 支持多种传输机制,以适应不同的部署场景和需求。
传输层机制:
MCP 支持两种主要的传输机制:
标准输入/输出(Stdio)传输:
- 使用标准输入/输出流进行通信
- 适用于本地进程间通信
- 简单且高效,特别适合同一机器上的通信
- 实现示例:
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {}
});
const transport = new StdioServerTransport();
await server.connect(transport);
HTTP 与服务器发送事件(SSE)传输:
- 使用 Server-Sent Events 实现服务器到客户端的消息传递
- 使用 HTTP POST 实现客户端到服务器的消息传递
- 适用于远程通信和跨网络场景
- 实现示例:
import express from "express";
const app = express();
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {}
});
let transport: SSEServerTransport | null = null;
app.get("/sse", (req, res) => {
transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
app.post("/messages", (req, res) => {
if (transport) {
transport.handlePostMessage(req, res);
}
});
app.listen(3000);
所有传输机制都使用 JSON-RPC 2.0 来交换消息,这是一种轻量级的远程过程调用协议,使用 JSON 作为数据格式。JSON-RPC 提供了一种标准化的方式来表示请求、响应和错误。
消息类型:
MCP 定义了四种主要的消息类型,用于客户端和服务器之间的通信:
请求(Request):
- 期望对方回复的消息
- 结构:
interface Request {
method: string;
params?: { ... };
}
- 示例:
{
"method": "resources/list",
"params": { "type": "text" }
}
结果(Result):
- 请求的成功响应
- 结构: ```typescript interface Result {
[key: string]: unknown;
}
```
- 示例:
{
"resources": [
{ "uri": "file:///logs/app.log", "name": "Application Logs" }
]
}
错误(Error):
- 表示请求失败的响应
- 结构:
interface Error {
code: number;
message: string;
data?: unknown;
}
- 示例:
{
"code": -32601,
"message": "Method not found",
"data": { "method": "unknown_method" }
}
通知(Notification):
- 不期望响应的单向消息
- 结构:
interface Notification {
method: string;
params?: { ... };
}
- 示例:
{
"method": "resources/updated",
"params": { "uri": "file:///logs/app.log" }
}
传输安全性考虑:
在实现 MCP 传输时,需要考虑以下安全因素:
本地传输安全:
- Stdio 传输通常限制在本地机器上,安全风险较低
- 但仍需注意进程权限和资源访问控制
远程传输安全:
- 使用 TLS 加密连接
- 实现适当的身份验证和授权机制
- 验证连接来源和请求合法性
消息验证:
- 验证所有传入消息的格式和内容
- 实现消息大小限制,防止资源耗尽攻击
- 确保 JSON-RPC 格式正确
错误处理:
- 不在错误响应中泄露敏感信息
- 适当记录安全相关错误
- 实现请求超时和重试机制
自定义传输:
MCP 允许实现自定义传输,以满足特定需求。自定义传输只需遵循 Transport 接口:
interface Transport {
// 开始处理消息
start(): Promise<void>;
// 发送 JSON-RPC 消息
send(message: JSONRPCMessage): Promise<void>;
// 关闭连接
close(): Promise<void>;
// 回调
onclose?: () => void;
onerror?: (error: Error) => void;
onmessage?: (message: JSONRPCMessage) => void;
}
自定义传输可以实现特定网络协议、专用通信通道或针对性能进行优化的传输机制。
2.2 MCP的通信流程
MCP 的通信流程定义了客户端和服务器之间的交互方式,包括连接的建立、消息的交换和连接的终止。理解这些流程对于开发 MCP 应用至关重要。
2.2.1 连接初始化
MCP 连接初始化是建立客户端和服务器之间通信的第一个阶段,它负责协商协议版本、交换能力信息并建立通信会话。连接初始化在 MCP 通信流程中至关重要,因为它为后续的所有交互设置了基础。
初始化过程的步骤:
客户端发送初始化请求:
- 客户端向服务器发送
initialize
请求,包含以下信息:- 协议版本:客户端支持的协议版本范围
- 客户端能力:客户端支持的功能列表
- 客户端标识:客户端的名称、版本等信息
- 请求示例:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "0.3.0",
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"capabilities": {
"resources": {},
"tools": {},
"prompts": {}
}
}
}
- 客户端向服务器发送
服务器验证请求:
- 服务器检查请求的有效性,包括协议版本兼容性
- 如果版本不兼容或请求无效,服务器会返回错误响应
服务器返回初始化响应:
- 服务器返回一个包含以下信息的响应:
- 协议版本:服务器实际使用的协议版本
- 服务器能力:服务器支持的功能列表
- 服务器信息:服务器的名称、版本等信息
- 响应示例:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "0.3.0",
"serverInfo": {
"name": "example-server",
"version": "1.0.0"
},
"capabilities": {
"resources": {
"list": {},
"read": {}
},
"tools": {
"list": {},
"call": {}
}
}
}
}
- 服务器返回一个包含以下信息的响应:
客户端确认:
- 客户端向服务器发送
initialized
通知,表示初始化完成 - 通知示例:
{
"jsonrpc": "2.0",
"method": "initialized",
"params": {}
}
- 客户端向服务器发送
建立会话:
- 初始化完成后,客户端和服务器之间建立了一个会话
- 会话维护连接状态、请求上下文和其他元数据
连接参数协商:
在初始化过程中,客户端和服务器需要协商几个关键参数:
协议版本兼容性:
- 客户端提供它支持的协议版本范围
- 服务器选择一个兼容的版本,或者如果不兼容则返回错误
- 这种机制确保不同版本的客户端和服务器能够互操作
能力协商:
- 客户端和服务器通过能力对象交换各自支持的功能
- 每个功能域(资源、工具、提示等)可以有子能力
- 客户端可以根据服务器的能力调整其行为,例如是否显示特定的用户界面元素
连接参数:
- 可以协商其他连接参数,如超时设置、缓冲区大小等
- 这些参数影响后续的通信行为和性能
错误处理:
初始化过程中可能出现的错误包括:
协议版本不兼容:
- 如果客户端和服务器不能就协议版本达成一致,连接将失败
- 错误代码:
-32600
(无效请求) - 服务器应当返回明确的错误信息,指出支持的版本范围
能力不兼容:
- 如果客户端依赖服务器不支持的能力,客户端可能决定终止连接
- 这不是协议级错误,而是客户端决策
连接超时:
- 如果初始化请求或响应超时,连接应当被终止
- 客户端应当实现适当的重试策略
传输错误:
- 底层传输可能遇到问题,如连接断开
- 处理程序应当捕获和记录这些错误,并尝试恢复或重新连接
正确实现连接初始化的重要性:
连接初始化是 MCP 通信的基础,正确实现它对于稳定的通信至关重要:
- 版本兼容性:确保不同版本的客户端和服务器能够互操作
- 功能发现:使客户端能够发现服务器的能力,并适应它们
- 安全连接:建立安全参数,如身份验证和授权信息
- 高效通信:设置最佳的通信参数,如批处理和缓冲策略
- 错误恢复:为后续通信设置错误处理策略
通过标准化的连接初始化过程,MCP 确保了客户端和服务器之间的高效、可靠和安全的通信。
2.2.2 消息交换
连接初始化完成后,MCP 客户端和服务器进入消息交换阶段,这是通信流程的核心部分。在这个阶段,双方可以交换请求、响应和通知,实现各种功能交互。
消息交换模式:
MCP 支持三种主要的消息交换模式:
请求-响应模式:
- 客户端发送请求,服务器处理后返回响应
- 每个请求都有一个唯一的 ID,响应中包含相同的 ID
- 适用于需要返回结果的操作,如获取资源、调用工具等
- 示例流程:
客户端 --> 服务器:请求工具列表 (ID: 42)
服务器 --> 客户端:返回工具列表 (ID: 42)
通知模式:
- 单向消息,发送方不期望接收响应
- 没有 ID,接收方不返回任何消息
- 适用于状态更新、事件通知等场景
- 示例流程:
服务器 --> 客户端:资源已更新通知(没有 ID)
进度报告模式:
- 在长时间运行的操作中,发送方可以报告进度
- 通过特殊的进度通知消息实现
- 接收方可以更新 UI 或执行其他操作
- 示例流程:
服务器 --> 客户端:工具执行进度 50%(进度 token: xyz)
常见的请求类型:
MCP 定义了几种标准请求类型,用于实现不同的功能:
资源相关请求:
resources/list
:列出可用的资源resources/read
:读取资源内容resources/subscribe
:订阅资源更新resources/unsubscribe
:取消订阅资源更新
工具相关请求:
tools/list
:列出可用的工具tools/call
:调用工具并执行操作
提示相关请求:
prompts/list
:列出可用的提示模板prompts/get
:获取提示模板的内容
采样相关请求(高级功能):
sampling/createMessage
:请求 LLM 生成内容
请求处理流程:
以工具调用为例,一个典型的请求处理流程如下:
客户端发送请求:
{
"jsonrpc": "2.0",
"id": 123,
"method": "tools/call",
"params": {
"name": "calculate_sum",
"arguments": {
"a": 5,
"b": 7
}
}
}
服务器接收并处理请求:
- 验证请求格式和参数
- 查找名为 “calculate_sum” 的工具
- 使用提供的参数执行工具
- 可能报告进度(对于长时间运行的操作)
服务器返回响应:
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"content": [
{
"type": "text",
"text": "12"
}
]
}
}
通知处理流程:
以资源更新通知为例:
服务器发送通知:
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"uri": "file:///logs/app.log"
}
}
客户端接收并处理通知:
- 验证通知格式和参数
- 更新相关的 UI 或状态
- 可能请求更新的资源内容
并发和批处理:
MCP 支持并发请求和批处理操作,提高通信效率:
并发请求:
- 客户端可以发送多个请求而不等待前一个请求的响应
- 每个请求都有唯一的 ID,服务器可以以任何顺序响应
- 客户端使用 ID 将响应与请求关联起来
批处理请求(高级功能):
- 多个请求可以在一个消息中发送
- 减少网络往返次数,提高效率
- 特别适合高延迟环境
错误处理与恢复:
在消息交换过程中,可能发生各种错误,MCP 提供了结构化的错误处理机制:
请求错误:
- 如果请求处理失败,服务器返回错误响应
- 错误包含代码、消息和可选的数据
- 示例:
{
"jsonrpc": "2.0",
"id": 123,
"error": {
"code": -32602,
"message": "Invalid parameters",
"data": {
"missing": ["a"]
}
}
}
连接错误:
- 如果连接中断,传输层报告错误
- 客户端可以尝试重新连接并重新发送未完成的请求
- 服务器应当能够处理重复的请求
超时处理:
- 客户端应当为请求设置超时时间
- 如果在指定时间内没有收到响应,客户端可以取消请求或重试
消息验证与安全:
在消息交换阶段,安全性至关重要:
输入验证:
- 服务器必须验证所有请求参数
- 对于工具调用,应当根据工具定义的 JSON Schema 验证参数
- 应当拒绝格式错误或参数无效的请求
身份验证和授权:
- 服务器应当验证客户端的身份和权限
- 对于敏感操作,可能需要用户确认
- 应当记录所有关键操作,以便审计
通过精心设计的消息交换机制,MCP 实现了灵活、高效和安全的通信,为 AI 应用与外部系统的交互提供了坚实的基础。
2.2.3 终止流程
MCP 连接的终止流程是通信生命周期的最后阶段,它确保客户端和服务器能够以有序和安全的方式关闭连接,释放资源,并维持系统的稳定性。良好的终止流程对于防止资源泄漏、数据丢失和意外行为至关重要。
终止流程的类型:
MCP 支持两种主要的终止流程类型:
优雅关闭(Graceful Shutdown):
- 由客户端或服务器主动发起的有序关闭
- 双方有机会完成处理中的请求并清理资源
- 通过明确的协议消息发起和确认
- 是推荐的终止方式,确保系统稳定性
强制终止(Forced Termination):
- 由于错误、超时或其他异常情况导致的意外关闭
- 可能没有机会完成处理中的请求或清理资源
- 通常是由传输层断开连接引起的
- 双方应当有处理此类情况的机制,以防止不良后果
优雅关闭的步骤:
发起关闭请求:
- 关闭发起方(客户端或服务器)发送
shutdown
请求 - 请求参数可以包含关闭原因和其他元数据
- 例如,客户端发送:
{
"jsonrpc": "2.0",
"id": 999,
"method": "shutdown",
"params": {
"reason": "user_request"
}
}
- 关闭发起方(客户端或服务器)发送
接收方处理关闭请求:
- 接收方完成所有处理中的请求
- 保存必要的状态信息
- 准备关闭内部资源
- 返回成功响应:
{
"jsonrpc": "2.0",
"id": 999,
"result": {}
}
发送最终通知:
- 发起方发送
exit
通知,表示即将关闭连接 - 例如,客户端发送:
{
"jsonrpc": "2.0",
"method": "exit",
"params": {}
}
- 发起方发送
关闭连接:
- 发起方关闭传输连接
- 接收方检测到连接关闭,也终止自己的连接
强制终止的处理:
在强制终止情况下,没有正式的关闭流程,但双方应当实现以下机制:
连接断开检测:
- 通过传输层事件或心跳超时检测连接断开
- 例如,使用 WebSocket 的
onclose
事件或 SSE 连接断开事件
资源清理:
- 连接断开后,自动清理与连接相关的资源
- 服务器应当取消所有正在进行的操作
- 客户端应当更新 UI 状态,提示用户连接已断开
状态保存:
- 如有必要,保存当前状态,以便在重新连接时恢复
- 服务器可能将未完成的工作保存到持久存储中
重连策略:
- 客户端可以实现自动重连机制,使用指数退避策略
- 连接恢复后,可以恢复未完成的操作
资源释放与清理:
终止流程中的资源清理是确保系统稳定性的关键环节:
文件句柄:
- 关闭所有打开的文件
- 确保临时文件被删除
数据库连接:
- 关闭数据库连接,释放连接池资源
- 完成未提交的事务或回滚
系统资源:
- 释放锁和信号量
- 终止子进程和线程
- 释放内存和其他系统资源
缓存和队列:
- 刷新缓存,确保重要数据被持久化
- 处理未完成的队列项目或将其保存起来
错误处理:
终止流程中可能出现的错误及其处理策略:
关闭请求超时:
- 如果对方没有在合理时间内响应关闭请求,发起方可以强制关闭连接
- 应当记录此类情况,以便后续分析
清理失败:
- 如果资源清理失败,应当记录错误并尽可能继续清理其他资源
- 在下次启动时,可能需要检测和恢复这些失败的清理操作
异常终止:
- 在程序崩溃或强制终止的情况下,可能无法执行正常的关闭流程
- 系统设计应当考虑到这一点,确保即使在这种情况下也能恢复正常状态
终止流程的最佳实践:
完整实现终止流程:
- 客户端和服务器都应当完整实现优雅关闭流程
- 支持处理强制终止情况
超时设置:
- 为终止流程的各个步骤设置合理的超时时间
- 如果超时,应当继续执行下一步或强制关闭
日志记录:
- 记录终止流程的关键步骤和任何异常情况
- 这有助于故障排除和系统改进
状态持久化:
- 在关闭前保存关键状态
- 确保在重新启动后能够恢复正常操作
资源监控:
- 监控终止过程中的资源使用情况
- 确保没有资源泄漏
通过实现健壮的终止流程,MCP 应用能够确保系统的稳定性和可靠性,防止资源泄漏和数据丢失,并提供更好的用户体验。
2.3 MCP的安全模型
MCP 安全模型是整个 Model Context Protocol 的重要组成部分,它确保 AI 应用能够以安全、可控的方式访问外部数据和功能。安全模型涵盖了传输安全、消息验证、资源保护和错误处理等多个层面,为 MCP 的可靠运行提供了保障。
2.3.1 传输安全
传输安全是 MCP 安全模型的第一道防线,负责确保客户端和服务器之间传输的数据的机密性、完整性和真实性。传输安全对于防止数据泄露、中间人攻击和其他网络威胁至关重要,尤其是在处理敏感数据和操作时。
传输加密:
为确保数据传输的安全性,MCP 实现以下加密机制:
TLS/SSL 加密:
- 对于基于 HTTP 的传输(如 SSE),应当使用 TLS(传输层安全协议)或 SSL(安全套接字层)加密
- 推荐使用 TLS 1.3 或更高版本,提供更好的安全性和性能
- 证书应当由可信的证书颁发机构(CA)签发,或者在开发环境中适当处理自签名证书
- 定期更新和轮换加密密钥和证书
本地传输安全:
- 对于 Stdio 传输等本地通信机制,应当依赖操作系统提供的进程隔离和权限控制
- 确保只有授权进程能够访问通信流
- 考虑使用本地套接字或命名管道等更安全的本地通信机制
数据加密:
- 对于特别敏感的数据,可以在应用层实现额外的端到端加密
- 使用强加密算法和适当的密钥管理实践
- 考虑实现消息级加密,确保即使中间层被破坏也能保护数据
连接验证:
除了加密,连接验证是确保通信安全的关键环节:
证书验证:
- 客户端应当验证服务器的 TLS 证书,检查其有效性、颁发者和域名
- 服务器也可以要求客户端证书(双向 TLS),确保只有授权客户端能够连接
- 实现证书撤销检查(CRL 或 OCSP),防止使用已撤销的证书
连接源验证:
- 服务器应当验证连接的来源 IP 地址或其他标识符
- 实现 IP 白名单或其他访问控制机制
- 考虑使用 HTTP 头(如 Referer 或 Origin)进行额外的验证
中间人攻击防护:
- 实现严格的证书链验证,防止使用假证书
- 考虑使用证书固定(Certificate Pinning)技术,将预期的证书或公钥固定在客户端代码中
- 检测和防止 SSL 剥离攻击,确保始终使用加密连接
身份认证:
身份认证确保通信双方的身份真实可信:
基于令牌的认证:
- 使用 JWT(JSON Web Token)或类似机制进行身份验证
- 令牌应当包含足够的信息,如身份标识、权限和过期时间
- 实现令牌刷新机制,避免长时间使用同一令牌
API 密钥认证:
- 对于服务器到服务器的通信,可以使用 API 密钥进行认证
- 密钥应当足够复杂且定期轮换
- 实现适当的密钥管理和存储机制
OAuth 和 OpenID Connect:
- 对于涉及用户身份的场景,考虑实现 OAuth 2.0 或 OpenID Connect
- 支持多种授权模式,如授权码、客户端凭证等
- 实现适当的权限范围控制和令牌验证
传输安全的最佳实践:
实现 MCP 传输安全时,应当遵循以下最佳实践:
安全配置:
- 禁用不安全的加密算法和协议版本
- 配置适当的密码套件,优先考虑安全性而非兼容性
- 实现 HSTS(HTTP 严格传输安全)和安全 Cookie
安全监控:
- 记录和监控所有安全相关事件,如认证失败、异常连接等
- 实现入侵检测和防御机制
- 定期审查日志和安全事件
安全更新:
- 保持所有传输相关库和组件的最新版本
- 跟踪相关的安全公告和漏洞信息
- 制定和测试安全事件响应计划
传输防护:
- 实现速率限制,防止暴力攻击
- 使用防火墙规则控制连接源和目标
- 考虑使用内容分发网络(CDN)或 API 网关提供额外的保护
本地传输安全:
- 确保本地传输(如 Stdio)遵循最小权限原则
- 适当设置文件和进程权限
- 考虑使用操作系统级安全机制,如沙箱和容器
通过全面实现传输安全措施,MCP 能够确保数据在客户端和服务器之间安全传输,防止未授权访问和数据泄露,为整个系统提供坚实的安全基础。
2.3.2 消息验证
消息验证是 MCP 安全模型的第二道防线,它确保所有传输的消息都是合法的、符合预期格式的,并且不包含恶意内容。消息验证包括输入验证、参数检查和格式验证等方面,有效防止了多种常见的安全威胁,如注入攻击和参数篡改。
输入验证:
输入验证是确保所有接收到的数据安全且有效的基础:
类型验证:
- 验证所有输入参数的数据类型是否符合预期
- 确保数字、字符串、布尔值等基本类型的正确性
- 验证复杂类型的结构和内容
使用示例:
// 使用 JSON Schema 进行类型验证
const schema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number", minimum: 0 },
email: { type: "string", format: "email" }
},
required: ["name", "email"]
};
// 验证输入数据
const validateInput = (data) => {
const validator = new JSONSchemaValidator(schema);
return validator.validate(data);
};
范围验证:
- 检查数值参数是否在合理范围内
- 验证字符串长度不超过限制
- 确保日期和时间值在有效范围内
- 对于集合类型,验证元素数量不超过系统处理能力
格式验证:
- 使用正则表达式或专用库验证特定格式的数据,如电子邮件、URL、电话号码等
- 对于特殊格式的数据(如 JSON、XML 等),验证其结构的合法性
- 遵循”接受已知好的数据”而非”拒绝已知坏的数据”的原则
跨字段验证:
- 检查相关字段之间的一致性和逻辑关系
- 验证依赖关系,确保条件字段的存在和有效性
- 实现复杂的业务规则验证
参数检查:
参数检查专注于验证请求中的参数是否安全和合法:
必需参数检查:
- 确保所有必需参数都存在且非空
- 对于可选参数,验证其符合预期的条件
- 检查参数的数量是否符合接口定义
参数过滤和清洁:
- 移除不必要的空白字符和特殊字符
- 转义或编码特殊字符,防止注入攻击
- 规范化参数值,确保一致的处理
参数白名单:
- 实现参数白名单,只接受预定义的参数名称
- 拒绝任何未定义或未预期的参数
- 对于工具调用,严格遵循工具定义的参数列表
深度参数验证:
- 对于嵌套的复杂参数结构,实现递归验证
- 验证数组或列表中的每个元素
- 确保对象属性的正确性和完整性
格式验证:
格式验证确保消息的整体结构和格式符合 MCP 协议规范:
JSON-RPC 格式验证:
- 验证消息是否符合 JSON-RPC 2.0 规范
- 检查必需字段(如
jsonrpc
、method
、id
)的存在和正确性 - 验证请求 ID 的唯一性和有效性
方法验证:
- 验证请求的方法名称是否存在且合法
- 确保方法名称符合 MCP 协议定义的命名规则
- 检查方法是否符合当前上下文和权限
消息大小限制:
- 实现消息大小限制,防止资源耗尽攻击
- 拒绝超过预定义大小的消息
- 考虑实现分块处理机制,处理大型消息
消息序列验证:
- 验证消息的顺序是否符合预期,特别是在需要特定顺序的操作中
- 检测重放攻击,确保消息的唯一性
- 实现会话状态跟踪,确保消息的上下文一致性
防止注入攻击:
注入攻击是最常见的安全威胁之一,消息验证应当专门防范各种类型的注入:
命令注入防护:
- 对于涉及系统命令执行的工具,严格验证和过滤输入
- 使用参数化执行而非字符串拼接
- 实现命令白名单,限制可执行的命令
使用示例:
// 错误示例:直接拼接命令
const runCommand = (fileName) => {
exec(`cat ${fileName}`); // 危险:可能导致命令注入
};
// 正确示例:使用参数化执行
const runCommand = (fileName) => {
exec('cat', [fileName]); // 安全:参数不会被解释为命令的一部分
};
SQL 注入防护:
- 对于数据库操作,使用参数化查询或预编译语句
- 避免直接拼接 SQL 语句
- 实现适当的数据库权限控制,使用最小权限原则
路径遍历防护:
- 规范化和验证所有文件路径
- 防止
../
等路径遍历攻击 - 限制文件操作在预定义的安全目录中
XSS 和 CSRF 防护:
- 对于可能在 Web 环境中使用的 MCP 服务器,实现 XSS 和 CSRF 防护
- 适当编码输出内容,防止脚本注入
- 实现同源策略和 CSRF 令牌验证
验证的最佳实践:
实现消息验证时,应当遵循以下最佳实践:
统一验证框架:
- 实现集中式的验证框架,确保一致的验证规则
- 使用声明性验证规则,提高可维护性
- 考虑使用成熟的验证库,如 JSON Schema、Joi 或 Yup
层次化验证:
- 实现多层验证策略,从传输层到应用层
- 在每一层应用适当的验证规则
- 确保验证覆盖所有可能的输入路径
错误反馈:
- 提供明确且有用的验证错误消息
- 不泄露敏感信息在错误消息中
- 实现适当的错误日志记录和分析
性能考虑:
- 平衡安全性和性能,避免过度验证导致的性能问题
- 实现高效的验证算法和数据结构
- 考虑缓存常用验证结果
通过严格的消息验证,MCP 能够有效防止各种注入攻击和参数篡改,确保系统只处理合法和预期的请求,从而保障整个系统的安全性和可靠性。
2.3.3 资源保护
资源保护是 MCP 安全模型的第三道防线,它确保系统资源(如文件、数据库、API 等)只能被授权用户以授权方式访问和使用。资源保护涵盖了访问控制、权限验证和路径安全等多个方面,是保障用户数据安全和系统完整性的关键环节。
访问控制:
访问控制机制确定谁可以访问什么资源:
身份认证与授权:
- 实现多因素身份认证,验证用户身份
- 基于用户角色和权限控制资源访问
- 使用最小权限原则,只授予必要的访问权限
实现示例:
# 基于角色的访问控制实现
def check_resource_access(user_id, resource_uri, operation):
user_roles = get_user_roles(user_id)
required_roles = get_required_roles(resource_uri, operation)
# 检查用户是否拥有所需角色
return any(role in user_roles for role in required_roles)
资源分类与标记:
- 对资源进行分类,如公共、内部、机密、受限等
- 基于资源敏感度和重要性实施不同级别的保护
- 使用元数据标记资源,便于访问控制和审计
访问控制列表(ACL):
- 为每个资源维护访问控制列表,详细指定谁可以执行什么操作
- 支持细粒度的访问控制,如读取、写入、执行等权限
- 定期审查和更新 ACL,确保其准确性和时效性
用户同意机制:
- 对于敏感操作,实现用户确认机制
- 提供清晰的权限请求和操作说明
- 记录用户同意历史,以便审计和问题排查
- MCP 中的用户同意机制示例:在 Claude Desktop 中,工具调用前会提示用户确认,确保用户知情并同意操作
权限验证:
权限验证确保所有资源访问请求都经过适当的验证:
多层权限检查:
- 在多个层次实施权限检查,如传输层、协议层和应用层
- 使用不同的验证机制,提高安全性
- 确保权限检查的一致性和完整性
上下文感知权限:
- 基于请求上下文(如时间、位置、设备等)调整权限
- 实现动态权限控制,适应不同的使用场景
- 考虑实现风险评估机制,自动调整权限级别
权限委托与传递:
- 支持临时权限委托,允许一个服务代表用户访问资源
- 实现安全的权限传递机制,确保权限不被滥用
- 限制委托权限的范围和有效期
权限审计与监控:
- 记录所有权限验证活动,包括成功和失败的尝试
- 实时监控异常的权限请求模式,检测潜在的安全威胁
- 定期审查权限使用情况,优化权限分配
路径安全:
路径安全专注于防止未授权访问和路径操作:
路径规范化与验证:
- 规范化所有资源路径,确保一致的处理
- 验证路径的有效性和合法性
- 防止路径遍历攻击(如
../
操作) 实现示例:
import os
from pathlib import Path
def validate_and_normalize_path(base_dir, requested_path):
# 规范化路径
base_path = Path(base_dir).resolve()
full_path = Path(os.path.join(base_dir, requested_path)).resolve()
# 验证路径是否在允许的基础目录内
if not str(full_path).startswith(str(base_path)):
raise SecurityError("Path traversal attempt detected")
return full_path
资源隔离:
- 隔离不同用户和应用的资源,防止未授权访问
- 使用沙箱技术限制资源访问范围
- 实现资源命名空间,避免资源冲突
限制资源操作:
- 限制允许的资源操作类型,如只读、读写等
- 对于危险操作(如删除),实现额外的验证步骤
- 设置资源操作限制,如大小限制、速率限制等
资源引用验证:
- 验证所有资源引用(如 URI)的有效性和合法性
- 实现资源引用的白名单机制
- 检测和阻止未授权的资源引用
敏感数据保护:
敏感数据需要特殊的保护措施:
数据分类与标记:
- 识别和分类敏感数据,如个人身份信息、财务数据、健康信息等
- 为敏感数据添加适当的标记,指导保护措施
- 实施不同级别的保护措施,根据数据敏感度
数据加密:
- 对敏感数据实施加密存储
- 使用强加密算法和适当的密钥管理
- 考虑实现字段级加密,只加密敏感字段
数据最小化:
- 只收集和存储必要的数据
- 限制敏感数据的传输和共享
- 实现数据保留策略,定期删除不再需要的数据
数据泄露防护:
- 实现数据泄露检测机制
- 对输出数据进行过滤,防止意外泄露敏感信息
- 制定数据泄露响应计划,及时处理潜在事件
资源保护的最佳实践:
实现资源保护时,应当遵循以下最佳实践:
深度防御策略:
- 实施多层次的保护措施,不依赖单一防线
- 组合使用不同的保护技术,提高整体安全性
- 定期评估保护措施的有效性,并进行必要的调整
最小权限原则:
- 默认拒绝所有访问,除非明确授权
- 只授予完成任务所需的最小权限
- 定期审查和更新权限分配,移除不必要的权限
完整性检查:
- 实施资源完整性检查,防止未授权修改
- 使用加密哈希、数字签名等技术确保数据完整性
- 定期验证关键资源的完整性
安全默认配置:
- 提供安全的默认配置,减少安全风险
- 避免过于宽松的默认权限设置
- 明确记录和说明安全配置选项
通过全面实现资源保护措施,MCP 能够确保系统资源只能被授权用户以授权方式访问和使用,有效防止未授权访问、数据泄露和资源滥用,为用户数据安全和系统完整性提供坚实保障。
2.3.4 错误处理
错误处理是 MCP 安全模型的最后一道防线,它确保系统能够妥善处理各种错误情况,防止安全漏洞和信息泄露,并为问题诊断和解决提供必要的信息。良好的错误处理机制是构建健壮和安全系统的关键环节。
错误检测:
错误检测是识别和捕获系统中各种异常情况的过程:
全面的错误捕获:
- 实现全面的错误捕获机制,覆盖所有可能的错误场景
- 使用 try-catch 块或类似结构捕获异常
- 处理不同类型的错误,包括语法错误、逻辑错误、运行时错误等
- 实现示例:
try {
// 执行可能产生错误的操作
const result = await callTool(name, args);
return result;
} catch (error) {
if (error instanceof ValidationError) {
// 处理验证错误
return handleValidationError(error);
} else if (error instanceof AuthorizationError) {
// 处理授权错误
return handleAuthorizationError(error);
} else {
// 处理其他类型的错误
return handleGenericError(error);
}
}
错误分类:
- 将错误分类为不同的类型,如验证错误、授权错误、资源错误等
- 为每种错误类型定义明确的错误代码和消息模板
- 维护错误代码和类型的一致性,便于追踪和分析
- MCP 定义的标准错误代码示例:
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603
超时检测:
- 为所有操作设置适当的超时时间
- 实现超时检测和处理机制,避免操作挂起
- 对于长时间运行的操作,考虑实现进度报告和取消机制
资源约束监控:
- 监控资源使用情况,如内存、CPU、磁盘空间等
- 在资源即将耗尽时检测并生成错误
- 实现资源使用限制,防止资源滥用和拒绝服务攻击
错误报告:
错误报告确保适当的错误信息被传递给相关方,同时不泄露敏感信息:
标准化错误响应:
- 使用标准的 JSON-RPC 错误格式:
{
"jsonrpc": "2.0",
"id": 123,
"error": {
"code": -32602,
"message": "Invalid parameters",
"data": {
"details": "Parameter 'filename' cannot contain path traversal sequences"
}
}
}
- 对于工具错误,使用工具结果的
isError
字段标记错误 - 确保错误消息清晰、具体且有用
- 使用标准的 JSON-RPC 错误格式:
安全的错误信息:
- 避免在错误消息中包含敏感信息,如内部路径、密码、API 密钥等
- 提供足够详细的错误信息以便排查问题,但不泄露系统细节
- 考虑为不同的受众(如用户、开发者、管理员)提供不同级别的错误详情
错误日志记录:
- 记录所有错误,包括详细的上下文信息和堆栈跟踪
- 确保日志安全存储,防止未授权访问
- 实现结构化日志记录,便于自动化分析
- 对于安全相关错误,确保日志包含足够的信息以便事后分析
错误聚合与分析:
- 实现错误聚合机制,识别模式和趋势
- 使用日志分析工具监控错误率和类型
- 设置错误阈值和告警机制,及时响应异常情况
安全响应:
安全响应确保系统在错误情况下仍能保持安全状态:
故障安全设计:
- 实现故障安全默认值和行为
- 确保在错误情况下系统回到安全状态
- 防止部分失败导致系统处于不一致或不安全的状态
资源清理:
- 确保在错误情况下所有资源被适当释放
- 使用 try-finally 块或类似机制保证资源清理
- 实现事务性操作,支持在错误情况下回滚
实现示例:
async def handle_request(session, request):
resources = []
try:
# 分配资源
file_handle = await open_file(request.path)
resources.append(file_handle)
# 执行操作
result = await process_file(file_handle)
return result
except Exception as e:
# 处理错误
log_error(e)
return error_response(e)
finally:
# 确保资源被释放
for resource in resources:
try:
await resource.close()
except Exception as close_error:
log_error(close_error)
优雅降级:
- 实现服务优雅降级机制,当关键组件失败时仍能提供基本功能
- 设计可部分运行的系统,避免单点故障
- 考虑实现备用操作模式或替代服务
重试和恢复策略:
- 对于可恢复的错误,实现适当的重试策略
- 使用指数退避算法避免重试风暴
- 实现重试限制,防止无限重试导致资源耗尽
示例重试策略:
async function callWithRetry(fn, maxRetries = 3, initialDelay = 1000) {
let retries = 0;
while (true) {
try {
return await fn();
} catch (error) {
if (!isRetryableError(error) || retries >= maxRetries) {
throw error;
}
retries++;
const delay = initialDelay * Math.pow(2, retries - 1);
await sleep(delay);
}
}
}
错误处理的最佳实践:
实现 MCP 错误处理时,应当遵循以下最佳实践:
预期异常设计:
- 预期并明确处理可能出现的异常情况
- 使用自定义错误类型表达特定的错误条件
- 区分预期错误和程序缺陷,适当处理两种情况
一致的错误处理:
- 在整个系统中保持一致的错误处理模式
- 使用集中式的错误处理机制,避免重复代码
- 确保所有错误都被适当捕获和处理,没有遗漏
错误处理分层:
- 实现分层的错误处理策略,不同层负责不同类型的错误
- 在适当的层次处理错误,避免错误传播过远
- 考虑实现错误转换机制,将低级错误转换为高级错误
错误处理测试:
- 针对错误处理编写全面的测试
- 测试各种错误场景,包括边缘情况
- 使用故障注入技术测试系统的错误响应能力
用户友好的错误处理:
- 提供用户友好的错误消息,避免技术术语
- 在可能的情况下,提供问题解决建议
- 考虑实现错误恢复机制,帮助用户恢复正常操作
通过全面实现错误处理机制,MCP 能够在各种异常情况下保持系统的安全性和可靠性,防止安全漏洞和信息泄露,并为问题诊断和解决提供必要的信息,从而提高整个系统的安全性和用户体验。
总结
本章详细介绍了 Model Context Protocol (MCP) 的架构、通信流程和安全模型。MCP 采用客户端-服务器架构,通过标准化的协议层和灵活的传输层,实现 AI 模型与外部资源的安全、高效交互。
在核心架构方面,我们了解了 MCP Host 和 Client 的角色与职责,MCP Server 的功能与类型,协议层的设计与能力协商机制,以及传输层的实现和消息类型。
在通信流程方面,我们详细分析了连接初始化、消息交换和终止流程的每个步骤,包括请求-响应模式、通知机制和错误处理等关键环节。
在安全模型方面,我们深入探讨了传输安全、消息验证、资源保护和错误处理四个安全层次,介绍了各种安全威胁及其防护措施,以及实现安全 MCP 应用的最佳实践。
通过理解和实现这些架构设计和安全机制,开发者可以构建安全、可靠、高效的 MCP 应用,为 AI 模型提供丰富的上下文和功能,从而实现更智能、更实用的 AI 系统。
MCP 作为一个开放协议,为 AI 应用与外部世界的交互提供了标准化的接口,就像 USB-C 为设备连接提供标准接口一样。它不仅简化了开发流程,还促进了生态系统的发展,使 AI 模型能够以安全、一致的方式访问外部资源和功能。随着 MCP 的不断发展和完善,我们可以期待看到更多创新的 AI 应用和集成方案。