MCP协议 进阶 MCP 能力协商 初始化 协议规范

MCP能力协商机制:协议初始化与特性发现

AIEng Hub
阅读约 16 分钟

引言

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 ...     │
  │                                   │

握手步骤说明

步骤方向消息类型说明
1Client → ServerRequest发送客户端能力声明和协议版本
2Server → ClientResponse返回服务端能力声明和协议版本
3Client → ServerNotification通知初始化完成
4双向各种方法开始正常通信

Capabilities 声明格式

Client Capabilities

Client在初始化请求中声明自己支持的功能:

{
  "capabilities": {
    "roots": {
      "listChanged": true
    },
    "sampling": {},
    "experimental": {
      "customFeature": {}
    }
  }
}
能力字段含义说明
roots根目录支持Client可以向Server提供文件系统根目录列表
roots.listChanged根目录变更通知支持根目录变更时主动通知Server
samplingLLM采样支持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-052024-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。