A06: MCP 协议详解
模型上下文协议(Model Context Protocol, MCP)是一个开放标准,定义了 AI Agent 如何与外部工具、数据源和服务通信。第 7 章涵盖了 MCP 在 Claude Code 中的实际使用。本附录更深入地介绍协议本身:规范、传输机制、消息生命周期,以及在构建或评估 MCP 服务器时需要理解的设计决策。
MCP 规范概述
目的与设计目标
MCP 的创建是为了解决碎片化问题。在 MCP 之前,每个 AI 工具集成都是定制的:每个 IDE 插件、CLI 工具或 Agent 框架都定义了自己与外部服务连接的方式。MCP 提供了 AI 客户端与工具提供者之间的通用接口,类似于 USB 标准化了外设连接。
核心设计目标:
- 简洁 --- 基于 JSON-RPC 2.0,使用广泛理解的传输协议
- 可组合 --- 多个服务器可以同时连接
- 可发现 --- 服务器在启动时声明其功能
- 安全 --- 客户端控制 AI 模型能看到什么和做什么
架构
MCP 遵循客户端-服务器架构,分为三层:
┌─────────────────────────────────────────┐
│ AI Application │
│ (Claude Code, IDE plugin, Agent SDK) │
│ │
│ ┌───────────────────────────────────┐ │
│ │ MCP Client │ │
│ │ - Manages server connections │ │
│ │ - Routes tool calls │ │
│ │ - Enforces security policies │ │
│ └──────────┬────────────────────────┘ │
└─────────────┼───────────────────────────┘
│ JSON-RPC 2.0
│ (stdio / SSE / streamable HTTP)
┌─────────────┼───────────────────────────┐
│ ┌──────────┴────────────────────────┐ │
│ │ MCP Server │ │
│ │ - Declares tools, resources, │ │
│ │ prompts at startup │ │
│ │ - Executes tool calls │ │
│ │ - Returns structured results │ │
│ └───────────────────────────────────┘ │
│ Tool Provider │
│ (Database, API, filesystem, etc.) │
└─────────────────────────────────────────┘一个客户端可以同时连接多个服务器。Claude Code 日常就是这样做的 --- 你可能在一个会话中同时启用数据库服务器、文档服务器和部署服务器。
消息格式(JSON-RPC 2.0)
所有 MCP 通信使用 JSON-RPC 2.0。有三种消息类型:
请求(客户端到服务器,或服务器到客户端):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "query_database",
"arguments": {
"sql": "SELECT count(*) FROM users WHERE active = true"
}
}
}响应(对请求的回复):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Query returned: 4,271 active users"
}
]
}
}通知(单向,不期望回复):
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "abc123",
"progress": 75,
"total": 100
}
}id 字段区分请求(期望回复)和通知(不期望回复)。
传输类型
MCP 定义了三种传输机制。选择取决于你的部署模型。
stdio(标准 I/O)
客户端将服务器作为子进程启动,通过 stdin/stdout 通信。每条 JSON-RPC 消息是以 \n 结尾的一行。
Client Server (child process)
│ │
│──── spawn process ────────────>│
│ │
│── {"jsonrpc":"2.0",...}\n ──-->│ (stdin)
│<── {"jsonrpc":"2.0",...}\n ────│ (stdout)
│ │
│──── close stdin ──────────────>│ (shutdown signal)适用场景:
- 本地开发工具
- 单用户场景
- Claude Code 本地 MCP 服务器的默认模式
- 服务器需要访问本地文件系统时
优势:无需网络设置、无端口、无需认证。操作系统进程模型提供自然隔离。
限制:无法服务多个客户端。客户端退出时服务器也会退出。
Claude Code 配置(.claude/settings.json):
{
"mcpServers": {
"my-db-tool": {
"command": "node",
"args": ["./mcp-servers/db-tool/index.js"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/mydb"
}
}
}
}SSE(Server-Sent Events)
客户端通过 HTTP 连接服务器。服务器到客户端的消息通过 SSE 流传递;客户端到服务器的消息作为 HTTP POST 请求发送。
适用场景:
- 多客户端访问的共享服务器
- 负载均衡器后的远程服务器
- 服务器生命周期独立于客户端的场景
优势:工作在标准 HTTP 基础设施上。支持多个并发客户端。服务器可以比任何单个客户端存活更久。
限制:需要 HTTP 基础设施。设置和安全配置更复杂。在较新的实现中正被 streamable HTTP 取代。
Streamable HTTP
最新的传输方式,设计为结合 stdio 的简洁性和 HTTP 的灵活性。每个请求-响应对是一次 HTTP 往返,对于长时间运行的操作可选通过 SSE 流式传输。
Client Server
│ │
│── POST /mcp (JSON-RPC) ──────>│
│<── 200 OK (JSON-RPC) ─────────│ (simple case)
│ │
│── POST /mcp (JSON-RPC) ──────>│
│<── 200 OK (SSE stream) ───────│ (streaming case)
│ data: {"jsonrpc":"2.0",...} │
│ data: {"jsonrpc":"2.0",...} │
│ │适用场景:
- 新的服务器实现(这是推荐的未来传输方式)
- 需要简洁性和远程访问兼顾时
- 生产部署
Claude Code 配置:
{
"mcpServers": {
"remote-tool": {
"type": "url",
"url": "https://mcp.example.com/my-tool"
}
}
}服务器生命周期管理
启动与能力协商
当客户端连接到服务器时,第一次交换是 initialize 握手:
Client ──> initialize request (protocol version, client capabilities)
Server <── initialize response (protocol version, server capabilities)
Client ──> initialized notification (handshake complete)服务器的 initialize 响应声明其支持的功能:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": true }
},
"serverInfo": {
"name": "my-database-server",
"version": "1.2.0"
}
}
}listChanged 能力告诉客户端服务器可能在会话期间动态添加或移除工具/资源。
健康检查
对于长时间运行的服务器(SSE 和 streamable HTTP),客户端可以发送定期的 ping 请求。如果服务器在超时内未响应,客户端认为连接已断并可能尝试重连。
{ "jsonrpc": "2.0", "id": 99, "method": "ping" }关闭
对于 stdio 服务器,关闭 stdin 信号服务器关闭。对于基于 HTTP 的服务器,客户端简单地停止发送请求。行为良好的服务器应该优雅地处理 SIGTERM,在退出前完成进行中的操作。
资源和提示原语
MCP 定义了三个核心原语。大多数开发者知道工具(Tools),但资源(Resources)和提示(Prompts)同样重要。
工具
工具是 AI 可以调用的可执行函数。它们是最常见的原语。
{
"name": "query_database",
"description": "Run a read-only SQL query against the application database",
"inputSchema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL query to execute (SELECT only)"
}
},
"required": ["sql"]
}
}资源
资源是 AI 可以读取的数据。与工具不同,资源不执行代码 --- 它们提供内容。可以把它们想象为服务器暴露的文件或文档。
{
"uri": "db://schema/users",
"name": "Users table schema",
"mimeType": "application/json",
"description": "The current schema of the users table"
}资源支持订阅:客户端可以请求在资源变化时收到通知。这对实时仪表板、日志追踪或监控很有用。
提示
提示是服务器提供的可重用模板。AI(或用户)可以调用它们来获得预结构化的交互。
{
"name": "explain_query",
"description": "Explain a SQL query execution plan in plain language",
"arguments": [
{
"name": "sql",
"description": "The SQL query to explain",
"required": true
}
]
}提示对于标准化常见交互特别有用。用户不需要每次写"explain this query",服务器提供一个结构化的提示模板产生一致的结果。
构建生产级 MCP 服务器
错误处理
MCP 使用 JSON-RPC 错误码。你的服务器应该返回结构化错误,而不是崩溃:
// Good: structured error
return {
error: {
code: -32602, // Invalid params
message: "Table 'nonexistent' does not exist",
data: {
availableTables: ["users", "orders", "products"]
}
}
};
// Bad: unhandled exception crashes the server标准 JSON-RPC 错误码:
-32700解析错误-32600无效请求-32601方法未找到-32602无效参数-32603内部错误
使用自定义码(正数)表示领域特定的错误。
日志记录
MCP 提供 notifications/message 方法让服务器向客户端发送日志消息:
{
"jsonrpc": "2.0",
"method": "notifications/message",
"params": {
"level": "warning",
"logger": "db-server",
"data": "Slow query detected: 3.2s for SELECT * FROM orders"
}
}日志级别遵循标准:debug、info、notice、warning、error、critical、alert、emergency。
对于 stdio 服务器,绝不要向 stdout 写日志 --- stdout 是协议通道。使用 stderr 或 MCP 通知机制。
速率限制
如果你的 MCP 服务器包装了有速率限制的外部 API,实现排队:
class RateLimitedServer {
private queue: Array<() => Promise<void>> = [];
private processing = false;
private requestsPerMinute = 60;
private requestCount = 0;
async handleToolCall(params: ToolCallParams): Promise<ToolCallResult> {
if (this.requestCount >= this.requestsPerMinute) {
return {
content: [{
type: "text",
text: "Rate limit reached. Please retry in 60 seconds."
}],
isError: true
};
}
this.requestCount++;
// ... execute the actual tool call
}
}MCP 安全模型
最小权限原则
服务器应暴露所需的最小工具集。用于代码审查的数据库服务器应暴露 query(只读)而不是 execute(写入)。
通过 Claude Code 权限系统沙箱化
Claude Code 将 MCP 工具调用包装在其权限系统中。即使服务器暴露了危险工具,用户也必须批准使用。这提供了一个人类在环(Human-in-the-Loop)的安全层。
在 .claude/settings.json 中,你可以预批准特定 MCP 工具:
{
"permissions": {
"allow": [
"mcp__my-db-tool__query_database"
],
"deny": [
"mcp__my-db-tool__drop_table"
]
}
}输入验证
每个 MCP 服务器都应严格验证输入。AI 模型可能生成意外的参数:
function validateSqlQuery(sql: string): void {
const normalized = sql.trim().toUpperCase();
if (!normalized.startsWith("SELECT")) {
throw new McpError(
-32602,
"Only SELECT queries are allowed"
);
}
// Check for SQL injection patterns
const forbidden = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"];
for (const keyword of forbidden) {
if (normalized.includes(keyword)) {
throw new McpError(
-32602,
`Forbidden SQL keyword: ${keyword}`
);
}
}
}远程服务器的认证
对于暴露在网络上的 streamable HTTP 服务器,使用标准 HTTP 认证。MCP 不定义自己的认证层 --- 它依赖传输层:
{
"mcpServers": {
"remote-tool": {
"type": "url",
"url": "https://mcp.example.com/my-tool",
"headers": {
"Authorization": "Bearer ${MCP_API_TOKEN}"
}
}
}
}示例:最小 MCP 服务器
TypeScript(stdio 传输)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create the server
const server = new McpServer({
name: "word-counter",
version: "1.0.0",
});
// Register a tool
server.tool(
"count_words",
"Count the number of words in a text string",
{
text: z.string().describe("The text to count words in"),
},
async ({ text }) => {
const wordCount = text.trim().split(/\s+/).filter(Boolean).length;
return {
content: [
{
type: "text",
text: `Word count: ${wordCount}`,
},
],
};
}
);
// Register a resource
server.resource(
"server-info",
"info://server",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
name: "word-counter",
uptime: process.uptime(),
nodeVersion: process.version,
}),
},
],
})
);
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);Python(stdio 传输)
from mcp.server.fastmcp import FastMCP
# Create the server
mcp = FastMCP("word-counter")
# Register a tool
@mcp.tool()
def count_words(text: str) -> str:
"""Count the number of words in a text string."""
word_count = len(text.strip().split())
return f"Word count: {word_count}"
# Register a resource
@mcp.resource("info://server")
def server_info() -> str:
"""Return server status information."""
import json, time
return json.dumps({
"name": "word-counter",
"timestamp": time.time(),
})
# Start the server (stdio transport)
if __name__ == "__main__":
mcp.run(transport="stdio")Claude Code 配置(两种服务器皆适用):
{
"mcpServers": {
"word-counter-ts": {
"command": "npx",
"args": ["tsx", "./mcp-servers/word-counter/index.ts"]
},
"word-counter-py": {
"command": "python",
"args": ["./mcp-servers/word-counter/server.py"]
}
}
}MCP 与其他集成方式的对比
| 方式 | 范围 | 发现机制 | 多客户端 | 协议 |
|---|---|---|---|---|
| MCP | 通用标准 | 服务器声明功能 | 是(HTTP 传输) | JSON-RPC 2.0 |
| OpenAI Function Calling | 仅限 OpenAI API | 客户端定义函数 | 不适用(API 级别) | OpenAI API |
| LangChain Tools | 仅限 LangChain 框架 | 框架注册 | 否 | Python 对象 |
| 自定义 REST API | 你的应用 | 手动文档 | 是 | HTTP/REST |
MCP 的优势是一个服务器编写一次就可以与任何 MCP 客户端一起工作 --- Claude Code、Cursor、Windsurf、自定义 Agent,以及任何未来实现该协议的客户端。
核心要点
- MCP 标准化了 AI 与工具的通信,使用 JSON-RPC 2.0 通过三种传输方式:本地工具用 stdio,共享服务器用 SSE,生产部署用 streamable HTTP。
- 启动时的能力协商让服务器准确声明其支持的功能,在功能缺失时实现优雅降级。
- 资源和提示将 MCP 扩展到简单工具调用之外 --- 资源提供只读数据,提示提供可重用的交互模板。
- 生产服务器需要结构化错误处理、正确的日志记录(stdio 服务器绝不写 stdout)、输入验证和速率限制。
- 安全是分层的:服务器验证输入,传输层处理认证,Claude Code 的权限系统提供人类在环审批。
另见:第 7 章 MCP -- 连接 Claude 与一切 了解实际设置,以及 A05 工具调用内部机制 了解 Claude 如何决定调用哪个 MCP 工具。