Gradio全解13——MCP详解(2)——MCP能力协商与通信机制

Gradio全解13——MCP详解(2)——MCP能力协商与通信机制

  • 第13章 MCP详解
    • 13.2 MCP能力协商与通信机制
      • 13.2.1 能力协商机制与消息规范
        • 1. 能力协商机制
        • 2. 消息规范及错误码
      • 13.2.2 MCP通信机制
        • 1. 协议层四种方法
        • 2. 传输层机制:Stdio与Streamable HTTP
        • 3. Stdio与Streamable HTTP实战
    • 参考文献

本章目录如下:

  1. 《Gradio全解13——MCP详解(1)——MCP协议介绍与架构组件》
  2. 《Gradio全解13——MCP详解(2)——MCP能力协商与通信机制》
  3. 《Gradio全解13——MCP详解(3)——TypeScript介绍》
  4. 《Gradio全解13——MCP详解(4)——TypeScript包命令:npm与npx》
  5. 《Gradio全解13——MCP详解(5)——Python包命令:uv与uvx实战》
  6. 《Gradio全解13——MCP详解(6)——MCP服务器》
  7. 《Gradio全解13——MCP详解(7)——MCP客户端》
  8. 《Gradio全解13——MCP详解(8)——MCP六大​功能特性》
  9. 《Gradio全解13——MCP详解(9)——MCP Inspector》

第13章 MCP详解

MCP是当前人工智能领域最热门技术之一,是实现大模型快速应用的捷径。本章将基于MCP最新方案修订版:2025-06-18,详细讲解MCP协议细节,并实战如何通过Gradio构建MCP客户端与服务器。

13.2 MCP能力协商与通信机制

在了解MCP协议的组成和架构后,本节学习MCP的各部分之间如何通信,包括能力协商机制、消息规范及错误码、通信机制的协议层和传输层等。

13.2.1 能力协商机制与消息规范

在连接初始化时,客户端和服务器通过能力协商机制明确能力范围,而消息规范则规定了传输消息的格式。

1. 能力协商机制

MCP采用基于能力(Capability)的协商系统,即客户端与服务器在初始化时需显式声明其支持的能力特性。能力声明决定会话期间可用的协议特性及原语操作,例如,服务器能力声明:如订阅资源、支持工具、提示模板等,客户端能力声明:如支持采样、通知处理等。双方必须全程遵循能力声明的范围,新增能力也可通过协商扩展协议实现。能力协商流程如图13-3:
Gradio全解13——MCP详解(2)——MCP能力协商与通信机制_第1张图片

图13-3

协商流程包括五个阶段:初始化会话、客户端资源与工具请求、服务器采样请求、通知消息和结束会话。会话期间,每项能力对应解锁特定协议特性,例如:

  • 已实现的服务器特性必须在服务器能力声明中公示。
  • 发送资源订阅通知要求服务器声明订阅支持。
  • 工具调用要求服务器声明工具的能力。
  • 采样操作要求客户端声明采样支持。

该能力协商机制确保双方对支持功能有明确认知,同时保持协议的可扩展性。

2. 消息规范及错误码

MCP客户端与服务器之间的所有消息必须遵循JSON-RPC 2.0规范。在MCP中,通过TypeScript模式的schema.ts,定义了官方权威协议要求。

本协议定义了以下消息类型:

  1. 请求 (Request)。请求可由客户端发往服务端,亦可由服务端发往客户端,用于发起操作,需要接收方返回响应,接口类定义如下:
interface Request {
  method: string;
  params?: { ... };
}

传输中接口的实例如下:

{
  jsonrpc: "2.0";
  id: string | number;
  method: string;
  params?: {    # ?:可选参数
    [key: string]: unknown;
  };
}

请求Request中必须包含字符串或整型ID,与基础JSON-RPC不同,ID不得为null。请求方在同一会话中不得重复使用已用过的请求ID。

  1. 结果 (Result)。Result是对请求的成功响应,有时也写作Response。响应消息用于回复请求,包含操作结果或错误信息。接口类定义如下:
interface Result {
  [key: string]: unknown;
}

传输中实例如下:

{
  jsonrpc: "2.0";
  id: string | number;
  result?: {
    [key: string]: unknown;
  }
  error?: {
    code: number;
    message: string;
    data?: unknown;
  }
}

响应必须包含与对应请求相同的ID。响应进一步细分为成功结果或错误类型,必须设置结果或错误之一,但不得同时设置。结果可采用任意JSON对象结构,而错误必须至少包含错误代码和消息,错误代码必须为整数。

  1. 错误 (Error)。出现Error时,表示请求处理失败,包含在结果的错误字段,其接口类定义如下:
interface Error {
  code: number;
  message: string;
  data?: unknown;
}

MCP制定了错误处理规范,定义了以下标准错误码:

enum ErrorCode {
  // Standard JSON-RPC error codes
  ParseError = -32700,
  InvalidRequest = -32600,
  MethodNotFound = -32601,
  InvalidParams = -32602,
  InternalError = -32603,
}

SDK和应用程序可在-32000以上范围自定义错误码。另外,错误传播途径包括:

  • 请求错误响应,对请求返回的错误应答。
  • 传输层错误事件,传输通道触发的错误事件。
  • 协议级错误处理器,协议层面的错误处理机制。
  1. 通知 (Notification)。通知作为单向消息在客户端与服务器之间传输,接收方不得发送响应。接口类定义如下:
interface Notification {
  method: string;
  params?: { ... };
}

传输中实例如下:

{
  jsonrpc: "2.0";
  method: string;
  params?: {
    [key: string]: unknown;
  };
}

通知中不得包含ID。

13.2.2 MCP通信机制

MCP架构实现了大语言模型应用与集成间的无缝通信,本节将介绍MCP通信机制中的协议层和传输层,让读者理解MCP如何连接客户端、服务器和大语言模型应用。

1. 协议层四种方法

协议层负责消息帧处理、请求/响应关联以及高层通信模式管理。协议类TypeScript代码如下:

class Protocol<Request, Notification, Result> {
    // Send requests and await responses
    request<T>(request: Request, schema: T, options?: RequestOptions): Promise<T>
    // Send one-way notifications
    notification(notification: Notification): Promise<void>
}
    // Handle incoming requests
    setRequestHandler<T>(schema: T, handler: (request: T, extra: RequestHandlerExtra) => Promise<Result>): void
    // Handle incoming notifications
    setNotificationHandler<T>(schema: T, handler: (notification: T) => Promise<void>): void

这里列出协议类对应Python代码,方便读者对比:

class Session(BaseSession[RequestT, NotificationT, ResultT]):
    async def send_request(self, request: RequestT,
        result_type: type[Result]
    ) -> Result:
        """Send request and wait for response. Raises McpError if response contains error."""
    async def send_notification(self,
        notification: NotificationT
    ) -> None:
        """Send one-way notification that doesn't expect response."""
    async def _received_request(self,
        responder: RequestResponder[ReceiveRequestT, ResultT]
    ) -> None:
        """Handle incoming request from other side."""
    async def _received_notification(self,
        notification: ReceiveNotificationT
    ) -> None:
        """Handle incoming notification from other side."""

可以看到,TypeScript代码更简洁。协议层共包含四种方法,以TypeScript为例,分别说明如下:

  • request:负责发送请求并等待响应,当相应包含错误时引发McpError。
  • notification:发送单向消息,无需响应。
  • setRequestHandler:处理收到的相应。
  • setNotificationHandler:处理收到的通知。

利用协议层,连接生命周期及周期各阶段信息交互如下:

  1. 初始化阶段。交互参数并建立连接:
  • 客户端发送初始化请求,包含协议版本与能力参数。
  • 服务端响应自身协议版本与能力参数。
  • 客户端发送初始化完成通知作为确认。
  • 开始正常消息交互。
  1. 消息交互阶段。初始化完成后支持以下模式:
  • 请求-响应模式:客户端或服务端发送请求,另一方返回响应。
  • 通知模式:任一方均可发送单向消息。
  1. 终止阶段。连接可通过以下方式终止:
  • 通过close()方法正常关闭。
  • 传输层断开连接。
  • 发生错误情况。
2. 传输层机制:Stdio与Streamable HTTP

传输层负责处理客户端与服务器之间的实际通信。MCP使用JSON-RPC 2.0进行消息交换,且JSON-RPC 2.0消息必须采用UTF-8编码。MCP支持多种传输机制:

  1. Stdio传输。在标准输入输出传输模式下:
  • 客户端将MCP服务器作为子进程启动。
  • 服务器从其标准输入(stdin)读取JSON-RPC消息,并向标准输出(stdout)发送消息。
  • 消息应为独立JSON-RPC 2.0格式的请求、通知或响应。
  • 消息以换行符分隔,且不得包含嵌入式换行符。
  • 服务器为记录日志,可向标准错误(stderr)输出UTF-8字符串,客户端可选择捕获、转发或忽略该日志。
  • 服务器不得向stdout输出任何非合规MCP消息的内容。
  • 客户端不得向服务器stdin写入任何非合规MCP消息的内容。
  1. Streamable HTTP传输。在流式HTTP传输模式下:
  • 服务器作为一个独立的进程运行,可以处理多个客户端连接。
  • 服务器采用HTTP协议,使用HTTP POST和GET请求。因此服务器必须提供支持POST和GET方法的单个HTTP端点路径,一般称为MCP端点。例如,类似URL为:https://example.com/mcp。
  • Streamable HTTP取代了2024-11-05协议版本中的HTTP+SSE传输,兼容性操作请参阅:Streamable HTTP - Backwards Compatibility。
  • 服务器可选SSE(Server-Sent Events)实现流式传输多个消息。这允许使用基本的MCP服务器以及功能更丰富的服务器,比如支持流式传输、server-to-client的通知和请求。

这两种传输机制如何选择呢?方案如下:①本地通信:对本地进程使用stdio传输,尤其同机进程间可高效通信,还可简化进程管理。②远程通信:在需要HTTP兼容性的场景中使用Streamable HTTP,另外需考虑安全影响,应包括身份验证和授权。关于transport的更多信息请参阅:Base Protocol - Transports。

3. Stdio与Streamable HTTP实战

对于Stdio传输,只需创建一个StdioServerTransport接口,用服务器连接即可。或者在服务器启动时,设置transport参数为stdio。下面是一个使用Stdio实现MCP服务器的基本示例:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
  name: "example-server",
  version: "1.0.0"
}, {
  capabilities: {
    resources: {}
  }
});
// Handle requests
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "example://resource",
        name: "Example Resource"
      }
    ]
  };
});
// Connect transport
const transport = new StdioServerTransport();
await server.connect(transport);

对于远程服务器,设置为Streamable HTTP传输,处理客户端请求和服务器到客户端通知。本示例将使用会话管理,在某些情况下,服务器是需要有状态的,可通过会话管理实现:

import express from "express";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"

const app = express();
app.use(express.json());
// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};

// Handle POST requests for client-to-server communication
app.post('/mcp', async (req, res) => {
  // Check for existing session ID
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  let transport: StreamableHTTPServerTransport;
  if (sessionId && transports[sessionId]) {
    // Reuse existing transport
    transport = transports[sessionId];
  } else if (!sessionId && isInitializeRequest(req.body)) {
    // New initialization request
    transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (sessionId) => {
        // Store the transport by session ID
        transports[sessionId] = transport;
      }
    });

    // Clean up transport when closed
    transport.onclose = () => {
      if (transport.sessionId) {
        delete transports[transport.sessionId];
      }
    };
    const server = new McpServer({
      name: "example-server",
      version: "1.0.0"
    });

    // ... set up server resources, tools, and prompts ...
    // Connect to the MCP server
    await server.connect(transport);
  } else {
    // Invalid request
    res.status(400).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: 'Bad Request: No valid session ID provided',
      },
      id: null,
    });
    return;
  }
  // Handle the request
  await transport.handleRequest(req, res, req.body);
});

// Reusable handler for GET and DELETE requests
const handleSessionRequest = async (req: express.Request, res: express.Response) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
  if (!sessionId || !transports[sessionId]) {
    res.status(400).send('Invalid or missing session ID');
    return;
  }
  const transport = transports[sessionId];
  await transport.handleRequest(req, res);
};

// Handle GET requests for server-to-client notifications via SSE
app.get('/mcp', handleSessionRequest);
// Handle DELETE requests for session termination
app.delete('/mcp', handleSessionRequest);
app.listen(3000);

代码中,首先创建StreamableHTTPServerTransport类型的transport,server连接后,就可在访问/mcp端点时,根据消息类型选择Post、Get或Delete。其中Post用于处理客户端向服务器发送的请求,Get用于服务器向客户端发送的相应或通知,Delete用于终止并删除会话。关于会话管理的细节,请参阅:MCP Base Protocol - Transports - Streamable HTTP - Session Management。

在本节中,我们看到MCP的默认编程语言是TypeScript。因此在讲解MCP服务器并实战之前,我们先了解下ypeScript及两个常用命令npx和uvx。为了扩展编程技能,以及考虑到Claude只有Windows版本和MacOS版本,在本章后续编程的平台和语言选择中,将以Windows系统下的TypeScript为主,限于篇幅将不再展示对应Python代码。希望查看Python版的MCP实现代码请参阅第14章——使用Gradio构建MCP服务器和客户端,或移步MCP官网。

参考文献

  1. Python开发人员,请不要低估TypeScript!
  2. TypeScript“杀疯了”!60% 到 70%YC 创企用它构建 AI Agent,超越 Python 有戏了?
  3. NPM vs. NPX,傻傻分不清楚
  4. uv
  5. Python 包管理工具核心指令uvx解析
  6. MCP Introduction

你可能感兴趣的:(Gradio全解13——MCP详解(2)——MCP能力协商与通信机制)