本章目录如下:
MCP是当前人工智能领域最热门技术之一,是实现大模型快速应用的捷径。本章将基于MCP最新方案修订版:2025-06-18,详细讲解MCP协议细节,并实战如何通过Gradio构建MCP客户端与服务器。
在了解MCP协议的组成和架构后,本节学习MCP的各部分之间如何通信,包括能力协商机制、消息规范及错误码、通信机制的协议层和传输层等。
在连接初始化时,客户端和服务器通过能力协商机制明确能力范围,而消息规范则规定了传输消息的格式。
MCP采用基于能力(Capability)的协商系统,即客户端与服务器在初始化时需显式声明其支持的能力特性。能力声明决定会话期间可用的协议特性及原语操作,例如,服务器能力声明:如订阅资源、支持工具、提示模板等,客户端能力声明:如支持采样、通知处理等。双方必须全程遵循能力声明的范围,新增能力也可通过协商扩展协议实现。能力协商流程如图13-3:
协商流程包括五个阶段:初始化会话、客户端资源与工具请求、服务器采样请求、通知消息和结束会话。会话期间,每项能力对应解锁特定协议特性,例如:
该能力协商机制确保双方对支持功能有明确认知,同时保持协议的可扩展性。
MCP客户端与服务器之间的所有消息必须遵循JSON-RPC 2.0规范。在MCP中,通过TypeScript模式的schema.ts,定义了官方权威协议要求。
本协议定义了以下消息类型:
interface Request {
method: string;
params?: { ... };
}
传输中接口的实例如下:
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: { # ?:可选参数
[key: string]: unknown;
};
}
请求Request中必须包含字符串或整型ID,与基础JSON-RPC不同,ID不得为null。请求方在同一会话中不得重复使用已用过的请求ID。
interface Result {
[key: string]: unknown;
}
传输中实例如下:
{
jsonrpc: "2.0";
id: string | number;
result?: {
[key: string]: unknown;
}
error?: {
code: number;
message: string;
data?: unknown;
}
}
响应必须包含与对应请求相同的ID。响应进一步细分为成功结果或错误类型,必须设置结果或错误之一,但不得同时设置。结果可采用任意JSON对象结构,而错误必须至少包含错误代码和消息,错误代码必须为整数。
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
以上范围自定义错误码。另外,错误传播途径包括:
interface Notification {
method: string;
params?: { ... };
}
传输中实例如下:
{
jsonrpc: "2.0";
method: string;
params?: {
[key: string]: unknown;
};
}
通知中不得包含ID。
MCP架构实现了大语言模型应用与集成间的无缝通信,本节将介绍MCP通信机制中的协议层和传输层,让读者理解MCP如何连接客户端、服务器和大语言模型应用。
协议层负责消息帧处理、请求/响应关联以及高层通信模式管理。协议类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为例,分别说明如下:
利用协议层,连接生命周期及周期各阶段信息交互如下:
传输层负责处理客户端与服务器之间的实际通信。MCP使用JSON-RPC 2.0进行消息交换,且JSON-RPC 2.0消息必须采用UTF-8编码。MCP支持多种传输机制:
这两种传输机制如何选择呢?方案如下:①本地通信:对本地进程使用stdio传输,尤其同机进程间可高效通信,还可简化进程管理。②远程通信:在需要HTTP兼容性的场景中使用Streamable HTTP,另外需考虑安全影响,应包括身份验证和授权。关于transport的更多信息请参阅:Base Protocol - Transports。
对于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官网。