引言
MCP协议的核心设计理念之一是能力协商(Capability Negotiation)。在建立连接时,Client和Server通过一套标准的握手流程,明确对方支持的特性和协议版本,从而实现灵活兼容。
本文将深入解析:
- 初始化握手流程
- Capabilities声明格式
- 版本协商策略
- 特性降级与兼容性处理
- 实际开发中的最佳实践
初始化握手流程
完整握手过程
Client Server
│ │
│──── 1. initialize request ──────► │
│ { │
│ protocolVersion: "2024-11-05"│
│ capabilities: { │
│ roots: { listChanged: true}│
│ sampling: {} │
│ } │
│ clientInfo: { │
│ name: "my-app", │
│ version: "1.0.0" │
│ } │
│ } │
│ │
│◄─── 2. initialize response ──────│
│ { │
│ protocolVersion: "2024-11-05"│
│ capabilities: { │
│ tools: {} │
│ resources: { │
│ subscribe: true │
│ } │
│ prompts: {} │
│ } │
│ serverInfo: { │
│ name: "my-server", │
│ version: "1.2.0" │
│ } │
│ } │
│ │
│──── 3. initialized notification ►│
│ (无响应) │
│ │
│──── 4. 正常通信 ──────────────► │
│ tools/list, tools/call ... │
│ │
握手步骤说明
| 步骤 | 方向 | 消息类型 | 说明 |
|---|---|---|---|
| 1 | Client → Server | Request | 发送客户端能力声明和协议版本 |
| 2 | Server → Client | Response | 返回服务端能力声明和协议版本 |
| 3 | Client → Server | Notification | 通知初始化完成 |
| 4 | 双向 | 各种方法 | 开始正常通信 |
Capabilities 声明格式
Client Capabilities
Client在初始化请求中声明自己支持的功能:
{
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {},
"experimental": {
"customFeature": {}
}
}
}
| 能力字段 | 含义 | 说明 |
|---|---|---|
roots | 根目录支持 | Client可以向Server提供文件系统根目录列表 |
roots.listChanged | 根目录变更通知 | 支持根目录变更时主动通知Server |
sampling | LLM采样支持 | Client支持Server通过采样协议请求LLM调用 |
Server Capabilities
Server在初始化响应中声明自己支持的功能:
{
"capabilities": {
"tools": {},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {},
"logging": {},
"experimental": {
"customServerFeature": {}
}
}
}
| 能力字段 | 含义 | 支持子特性 |
|---|---|---|
tools | 工具支持 | listChanged(工具列表变更通知) |
resources | 资源支持 | subscribe(资源订阅)、listChanged(资源列表变更通知) |
prompts | 提示模板支持 | listChanged(提示列表变更通知) |
logging | 日志支持 | 无子特性 |
能力声明最佳实践
// TypeScript: 构建Client Capabilities
function buildClientCapabilities(): ClientCapabilities {
const caps: ClientCapabilities = {};
// 声明根目录支持
caps.roots = {
listChanged: true, // 支持根目录变更通知
};
// 声明LLM采样支持
caps.sampling = {};
return caps;
}
// TypeScript: 处理Server Capabilities
function handleServerCapabilities(caps: ServerCapabilities) {
if (caps.tools) {
console.log("Server支持工具调用");
enableToolFeatures();
}
if (caps.resources) {
console.log("Server支持资源访问");
if (caps.resources.subscribe) {
console.log(" - 支持资源订阅");
}
if (caps.resources.listChanged) {
console.log(" - 支持资源变更通知");
}
}
if (caps.prompts) {
console.log("Server支持提示模板");
}
}
版本协商策略
协议版本格式
MCP使用日期格式的版本标识符:
2024-11-05
| 版本 | 发布日期 | 主要特性 |
|---|---|---|
2024-11-05 | 2024-11-05 | 初始发布版本,核心规范定型 |
| (未来版本) | 向后兼容的增量更新 |
版本协商逻辑
function negotiateVersion(
clientVersion: string,
serverVersion: string
): string | null {
// 简单的版本校验
if (clientVersion === serverVersion) {
return clientVersion; // 完全匹配
}
// 处理版本差异
const clientDate = new Date(clientVersion);
const serverDate = new Date(serverVersion);
if (clientDate >= serverDate) {
// Client版本 >= Server版本,使用Server版本
return serverVersion;
} else {
// Server版本 > Client版本,仍使用Server版本
// Server负责保持向后兼容
return serverVersion;
}
}
版本不兼容处理
def negotiate_version(
client_version: str,
server_version: str
) -> str:
"""协商双方都兼容的协议版本"""
from datetime import datetime
try:
client_date = datetime.strptime(client_version, "%Y-%m-%d")
server_date = datetime.strptime(server_version, "%Y-%m-%d")
except ValueError:
raise ValueError(f"无效的版本格式: {client_version} / {server_version}")
# 总是使用较新的版本
# Server负责保持向后兼容
agreed = max(client_date, server_date).strftime("%Y-%m-%d")
return agreed
特性降级策略
降级示例
当Client或Server不支持对方声明的某些能力时,需要优雅降级:
// Client端:根据Server能力调整行为
class MCPClient {
private caps: ServerCapabilities | null = null;
async initialize(serverUrl: string) {
const response = await this.sendRequest("initialize", {
protocolVersion: "2024-11-05",
capabilities: this.buildCapabilities(),
clientInfo: {
name: "my-app",
version: "1.0.0"
}
});
this.caps = response.capabilities;
this.adapterBehavior();
}
private adapterBehavior() {
if (!this.caps) return;
// Server不支持资源订阅→不使用订阅功能
if (!this.caps.resources?.subscribe) {
this.usePollingInstead(); // 回退到轮询
}
// Server不支持工具列表变更通知
if (!this.caps.tools?.listChanged) {
this.cacheToolsIndefinitely(); // 工具列表缓存永不过期
}
// Server不支持提示模板
if (!this.caps.prompts) {
this.disablePromptFeatures(); // 禁用提示相关UI
}
}
}
降级策略汇总表
| 缺少能力 | 降级行为 | 对用户的影响 |
|---|---|---|
tools | 不允许工具调用 | 仅使用提示和资源功能 |
resources | 不允许资源访问 | 仅使用工具和提示功能 |
resources.subscribe | 轮询检查资源变更 | 变更响应有延迟 |
prompts | 不显示提示模板 | 功能减少,不影响核心 |
logging | 不显示日志 | 影响调试体验 |
动态能力发现
除了初始化时的能力声明,MCP还支持运行时动态发现:
按需能力发现
async def discover_capabilities_dynamically(session):
"""在运行时动态发现Server的能力"""
capabilities = {}
# 尝试列出工具
try:
tools_result = await session.list_tools()
capabilities["has_tools"] = len(tools_result.tools) > 0
capabilities["tools"] = tools_result.tools
except Exception:
capabilities["has_tools"] = False
# 尝试列出资源
try:
resources_result = await session.list_resources()
capabilities["has_resources"] = len(resources_result.resources) > 0
capabilities["resources"] = resources_result.resources
except Exception:
capabilities["has_resources"] = False
# 尝试列出提示
try:
prompts_result = await session.list_prompts()
capabilities["has_prompts"] = len(prompts_result.prompts) > 0
capabilities["prompts"] = prompts_result.prompts
except Exception:
capabilities["has_prompts"] = False
return capabilities
最佳实践
1. 声明保守,使用进取
// ✅ 正确:声明的能力都要有实现
const capabilities = {
roots: { listChanged: true },
// 实际实现了listChanged通知
};
// ❌ 错误:声明但未实现
const capabilities = {
sampling: {},
// 实际没有实现LLM采样
};
2. 依赖检测
// ✅ 正确:使用前检查能力
if (serverCaps.resources?.subscribe) {
await session.subscribeResource(uri);
}
// ❌ 错误:假设能力存在
await session.subscribeResource(uri);
3. 超时与重试
async function initializeWithRetry(
session: ClientSession,
maxRetries = 3
): Promise<InitializeResult> {
for (let i = 0; i < maxRetries; i++) {
try {
const result = await session.initialize();
return result;
} catch (error) {
if (i === maxRetries - 1) throw error;
await delay(Math.pow(2, i) * 1000); // 指数退避
}
}
throw new Error("初始化失败");
}
总结
MCP的能力协商机制是其灵活兼容的关键设计:
| 设计点 | 实现方式 | 优势 |
|---|---|---|
| 初始化握手 | 请求-响应-通知三步 | 确保双方状态同步 |
| 能力声明 | JSON对象描述 | 灵活可扩展 |
| 版本协商 | 日期格式版本号 | 简单清晰的版本管理 |
| 降级策略 | 按能力调整行为 | 渐进增强的用户体验 |
下一步学习建议:
本文最后更新于 2024-07-01。