最新MCP规范解读,看这篇就够了!

一、MCP是什么? 为什么需要它?

想象一下,你正在开发一个 AI 编程助手,它需要:

  • 读取和修改项目文件
  • 查询数据库Schema
  • 搜索代码仓库
  • 执行Git操作

传统做法是为每个数据源写一套专用代码,不同团队重复造轮子。Model Context Protocol(MCP) 就是为了解决这个问题而生的开放标准协议。

通俗理解: MCP就像是「AI应用的USB接口标准」。就像USB让不同设备都能接入电脑一样,MCP让不同的数据源和工具都能以统一方式接入AI应用。

实际案例: 在Claude Desktop中,你可以配置多个官方MCP服务器:

  • Filesystem服务器: 安全地读写本地文件,有权限控制
  • SQLite服务器: 查询和分析SQLite数据库,自动生成SQL
  • GitHub服务器: 搜索仓库、创建Issue、管理PR

你的AI应用只需实现一个MCP客户端,就能连接所有服务器,无需为每个服务器写专用代码。

二、架构设计: 三个角色的分工

MCP采用宿主-客户端-服务器三层架构,就像一家公司的组织结构:

宿主(Host) = 总经理

  • 管理所有客户端
  • 控制安全策略和权限
  • 负责AI模型的调用

客户端(Client) = 部门经理

  • 客户端负责连接服务器
  • 负责双方的沟通协调
  • 转发消息和通知

服务器(Server) = 业务专员

  • 提供具体功能(资源、工具、提示模板)
  • 可以是本地程序或远程服务
  • 不知道其他服务器的存在

三、协议约定:统一规范与个性化扩展

每个MCP服务器提供的工具、资源都不一样,但它们都遵循相同的MCP协议规范。

3.1 协议的分层设计

MCP采用 基础协议 + 功能扩展 的设计,就像HTTP协议一样:

核心层(所有实现必须支持) :

  • JSON-RPC 2.0消息格式
  • 初始化握手流程(initialize/initialized)
  • 基本错误处理

功能层(按需选择) :

  • Resources、Prompts、Tools(服务器端)
  • Roots、Sampling、Elicitation(客户端)

这样设计的好处:

markdown 复制代码
统一的基础协议 → 保证互操作性
     +
灵活的功能选择 → 满足不同场景需求
     ↓
既标准化又可扩展

3.2 协议约定的过程

步骤1: 基础协议是固定的

所有MCP服务器和客户端都遵循相同的JSON-RPC 2.0格式:

json 复制代码
// 请求格式(固定)
{
  "jsonrpc": "2.0",      // 必须是2.0
  "id": 1,                // 唯一标识
  "method": "方法名",     // 要调用的方法
  "params": {...}         // 参数对象
}

// 响应格式(固定)
{
  "jsonrpc": "2.0",
  "id": 1,                // 对应请求的ID
  "result": {...}         // 成功结果
  // 或 "error": {...}    // 错误信息
}

步骤2: 能力在初始化时协商

json 复制代码
// 客户端发起初始化
{
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "sampling": {},       // 我支持LLM采样
      "roots": {}           // 我支持根目录
    },
    "clientInfo": {"name": "MyClient", "version": "1.0"}
  }
}

// 服务器响应
{
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},          // 我提供工具
      "resources": {}       // 我提供资源
    },
    "serverInfo": {"name": "SQLiteServer", "version": "2.0"}
  }
}

协商完成后,双方都知道对方支持什么功能,只使用交集部分

步骤3: 方法名称是标准化的

MCP规范定义了标准方法名:

功能 方法名 说明
列出资源 resources/list 固定方法名
读取资源 resources/read 固定方法名
列出工具 tools/list 固定方法名
调用工具 tools/call 固定方法名
列出提示 prompts/list 固定方法名
获取提示 prompts/get 固定方法名

步骤4: 具体内容是个性化的

虽然方法名固定,但每个服务器返回的具体数据不同:

json 复制代码
// SQLite服务器的工具
{
  "tools": [
    {"name": "query", "description": "执行SQL查询"},
    {"name": "list_tables", "description": "列出所有表"}
  ]
}

// Filesystem服务器的工具
{
  "tools": [
    {"name": "read_file", "description": "读取文件"},
    {"name": "write_file", "description": "写入文件"},
    {"name": "search_files", "description": "搜索文件"}
  ]
}

3.3 协议发现机制

客户端如何知道服务器有哪些工具?

第一步:列举

css 复制代码
客户端 → 服务器: {"method": "tools/list"}
服务器 → 客户端: {
  "tools": [
    {
      "name": "query",
      "description": "执行SQL查询",
      "inputSchema": {           // JSON Schema定义输入格式
        "type": "object",
        "properties": {
          "sql": {"type": "string"}
        }
      }
    }
  ]
}

第二步:调用

json 复制代码
客户端 → 服务器: {
  "method": "tools/call",
  "params": {
    "name": "query",           // 使用第一步获得的工具名
    "arguments": {"sql": "SELECT * FROM users"}
  }
}

关键点:通过JSON Schema,客户端知道如何正确调用工具,无需硬编码。

四、协议基础:如何通信?

MCP基于JSON-RPC 2.0构建,这是一个成熟的远程过程调用协议。理解这一层对掌握MCP至关重要。

4.1 JSON-RPC 2.0基础

消息类型

MCP中有三种基本消息类型。
1. 请求(Request) - 期待响应

perl 复制代码
{
  "jsonrpc": "2.0",           // 协议版本,必须是"2.0"
  "id": 1,                     // 请求唯一标识(字符串或数字)
  "method": "tools/list",     // 要调用的方法名
  "params": {                  // 可选的参数对象
    "cursor": "page2"
  }
}

2. 响应(Response) - 对请求的回复

json 复制代码
// 成功响应
{
  "jsonrpc": "2.0",
  "id": 1,                     // 必须与请求的id相同
  "result": {                  // 成功结果
    "tools": [
      {"name": "query", "description": "执行查询"}
    ]
  }
}

// 错误响应
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {                   // 错误对象
    "code": -32602,            // 错误码(整数)
    "message": "参数无效",      // 错误描述
    "data": {                  // 可选的额外信息
      "field": "cursor",
      "reason": "格式错误"
    }
  }
}

3. 通知(Notification) - 单向消息,无需响应

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",  // 通知方法名
  "params": {                                   // 通知参数
    "uri": "file:///project/data.json"
  }
  // 注意:没有id字段
}

标准错误码

MCP使用JSON-RPC 2.0的标准错误码:

错误码 含义 说明
-32700 Parse error JSON解析错误
-32600 Invalid Request 无效的请求格式
-32601 Method not found 方法不存在
-32602 Invalid params 参数无效
-32603 Internal error 服务器内部错误
-32002 Resource not found 资源未找到(MCP扩展)

4.2 能力协商详解

能力协商是MCP连接建立的第一步,决定了整个会话中可用的功能。

初始化流程详解

阶段1: 客户端发起初始化

json 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",  // 客户端支持的协议版本
    "capabilities": {                  // 客户端能力声明
      "roots": {                       // 支持根目录
        "listChanged": true            // 支持根目录变更通知
      },
      "sampling": {},                  // 支持LLM采样
      "elicitation": {},               // 支持用户询问
      "experimental": {                // 实验性功能
        "customFeature": {}            // 自定义功能
      }
    },
    "clientInfo": {                    // 客户端信息
      "name": "MyAIApp",               // 程序名(必填)
      "version": "1.2.0",              // 版本号(必填)
      "title": "我的AI应用"             // 显示名称(可选)
    }
  }
}

阶段2: 服务器响应能力

json 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",  // 服务器选择的协议版本
    "capabilities": {                  // 服务器能力声明
      "resources": {                   // 支持资源
        "subscribe": true,             // 支持资源订阅
        "listChanged": true            // 支持资源列表变更通知
      },
      "tools": {                       // 支持工具
        "listChanged": true
      },
      "prompts": {                     // 支持提示模板
        "listChanged": false           // 不支持列表变更通知
      },
      "logging": {}                    // 支持日志输出
    },
    "serverInfo": {                    // 服务器信息
      "name": "sqlite-mcp-server",
      "version": "2.1.0",
      "title": "SQLite MCP服务器"
    },
    "instructions": "此服务器提供SQLite数据库访问能力"  // 可选的使用说明
  }
}

阶段3: 客户端确认就绪

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"  // 无id,这是通知
}

协议版本协商规则

arduino 复制代码
客户端请求版本: "2024-11-05"
         ↓
    服务器支持?
    ↙        ↘
  支持        不支持
   ↓            ↓
返回相同版本  返回服务器支持的最新版本
   ↓            ↓
协商成功    客户端检查是否支持
              ↙        ↘
           支持        不支持
            ↓            ↓
         协商成功     断开连接

实际示例:

json 复制代码
// 场景1: 版本匹配
客户端: "protocolVersion": "2024-11-05"
服务器: "protocolVersion": "2024-11-05"  ✅ 成功

// 场景2: 服务器版本更新
客户端: "protocolVersion": "2024-06-01"
服务器: "protocolVersion": "2024-11-05"  
→ 客户端检查是否支持2024-11-05 → 如果不支持则断开

// 场景3: 客户端版本更新
客户端: "protocolVersion": "2025-01-01"
服务器: "protocolVersion": "2024-11-05"  
→ 客户端检查是否支持2024-11-05 → 如果支持则降级使用

能力交集计算

初始化后,双方只能使用共同支持的能力:

css 复制代码
客户端能力: {roots, sampling, elicitation}
服务器能力: {resources, tools, prompts}
         ↓
   可用功能集合
   ├─ 客户端 → 服务器: resources, tools, prompts
   └─ 服务器 → 客户端: roots, sampling, elicitation

示例:

csharp 复制代码
# 客户端代码示例
if server_capabilities.get("tools"):
    # 服务器支持工具,可以调用
    tools = await session.list_tools()
else:
    # 服务器不支持工具,跳过
    print("服务器不提供工具功能")

if client_capabilities.get("sampling"):
    # 客户端支持采样,服务器可以请求
    # (服务器端会检查这个能力)
    pass

4.3 连接生命周期深入

完整的消息时序图

scss 复制代码
客户端                                            服务器
  │                                              │
  │  1. initialize (请求)                         │
  ├──────────────────────────────────────>│
  │     {protocolVersion, capabilities}          │
  │                                              │
  │  2. initialize (响应)                         │
  │<──────────────────────────────────────┤
  │     {protocolVersion, capabilities}          │
  │                                              │
  │  3. initialized (通知)                        │ 
  ├──────────────────────────────────────>│
  │                                              │
  │═══════════ 正常操作阶段 ════════════        │
  │                                              │
  │  4. tools/list (请求)                         │
  ├──────────────────────────────────────>│
  │                                              │
  │  5. tools/list (响应)                         │
  │<──────────────────────────────────────┤
  │     {tools: [...]}                           │
  │                                              │
  │  6. tools/call (请求)                         │
  ├──────────────────────────────────────>│
  │     {name: "query", arguments: {...}}        │
  │                                              │
  │  7. notifications/progress (通知)             │
  │<──────────────────────────────────────┤
  │     {progress: 50, total: 100}               │
  │                                              │
  │  8. tools/call (响应)                         │
  │<──────────────────────────────────────┤
  │     {content: [...]}                         │
  │                                              │
  │  9. notifications/resources/updated          │
  │<──────────────────────────────────────┤
  │     {uri: "file://..."}                      │
  │                                              │
  │═══════════ 关闭阶段 ═══════════           │
  │                                              │
  │  10. 关闭stdin                               │
  ├─────────────X                             │
  │                                             │
  │                                          服务器退出

初始化前的限制

initialized通知发送前:

客户端只能发送:

  • initialize请求
  • ping请求(用于保活)
  • ❌ 其他任何请求

服务器只能发送:

  • initialize响应
  • ping请求
  • logging通知(日志)
  • ❌ 其他任何消息

违反限制的后果:

css 复制代码
// 客户端在初始化前调用tools/list
请求: {"method": "tools/list"}
响应: {
  "error": {
    "code": -32600,
    "message": "会话未初始化"
  }
}

超时和重试机制

请求超时:

python 复制代码
import asyncio

# 设置30秒超时
try:
    result = await asyncio.wait_for(
        session.call_tool("slow_operation", {}),
        timeout=30.0
    )
except asyncio.TimeoutError:
    # 发送取消通知
    await session.send_notification(
        "notifications/cancelled",
        {"requestId": "123", "reason": "超时"}
    )

进度通知重置超时:

ini 复制代码
# 当收到进度通知时,可以重置超时计时器
timeout = 30  # 基础超时
max_timeout = 300  # 最大超时(5分钟)

while True:
    try:
        msg = await wait_for_message(timeout)
        if msg.method == "notifications/progress":
            # 收到进度,重置超时
            timeout = 30
    except TimeoutError:
        # 超时处理
        break

4.4 传输方式对比

stdio传输详解

优点:

  • ✅ 简单直接,适合本地开发
  • ✅ 进程隔离,安全性好
  • ✅ 自动管理生命周期
  • ✅ 无需网络配置

缺点:

  • ❌ 只能本地使用
  • ❌ 不支持多客户端
  • ❌ 调试相对困难

消息格式:

复制代码
消息1\n
消息2\n
消息3\n

每个JSON对象占一行,以\n分隔。

HTTP传输详解

架构:

bash 复制代码
┌─────────┐         HTTP POST         ┌─────────┐
│         ├──────────────────────────>│         │
│ 客户端  │  请求/通知/响应(JSON-RPC) │ 服务器  │
│         │<──────────────────────────┤         │
└─────────┘     HTTP 响应/SSE流       └─────────┘
             (application/json 或
              text/event-stream)

发送消息(POST) :

bash 复制代码
POST /mcp HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Accept: application/json, text/event-stream
Mcp-Session-Id: abc123

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

立即响应(JSON) :

css 复制代码
HTTP/1.1 200 OK
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}

流式响应(SSE) :

vbnet 复制代码
HTTP/1.1 200 OK
Content-Type: text/event-stream
Mcp-Session-Id: abc123

id: 1
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":25}}

id: 2  
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progress":50}}

id: 3
data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}

接收服务器消息(GET) :

vbnet 复制代码
GET /mcp HTTP/1.1
Host: localhost:8080
Accept: text/event-stream
Mcp-Session-Id: abc123
Last-Event-ID: 42

会话管理:

python 复制代码
# 服务器端设置会话ID
@app.post("/mcp")
async def handle_mcp(request):
    if request.method == "initialize":
        session_id = generate_session_id()
        return Response(
            content=json.dumps(result),
            headers={"Mcp-Session-Id": session_id}
        )

# 客户端后续请求携带会话ID
@client.request
async def send_request(method, params):
    headers = {}
    if self.session_id:
        headers["Mcp-Session-Id"] = self.session_id
    
    return await http.post(
        "/mcp",
        json={"jsonrpc": "2.0", "method": method, "params": params},
        headers=headers
    )

断线重连:

python 复制代码
async def connect_sse(last_event_id=None):
    headers = {"Accept": "text/event-stream"}
    if last_event_id:
        headers["Last-Event-ID"] = last_event_id
    
    async with httpx.stream("GET", "/mcp", headers=headers) as stream:
        async for line in stream.aiter_lines():
            if line.startswith("id:"):
                last_event_id = line[3:].strip()
            elif line.startswith("data:"):
                data = json.loads(line[5:])
                yield data, last_event_id

4.5 实际通信示例

让我们看一个完整的SQLite查询场景:

css 复制代码
// 1. 列出工具
客户端 → 服务器:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

服务器 → 客户端:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "query",
        "description": "执行SQL查询",
        "inputSchema": {
          "type": "object",
          "properties": {
            "sql": {"type": "string"}
          },
          "required": ["sql"]
        }
      }
    ]
  }
}

// 2. 调用查询工具
客户端 → 服务器:
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "query",
    "arguments": {
      "sql": "SELECT COUNT(*) FROM users WHERE active = 1"
    },
    "_meta": {
      "progressToken": "query-123"  // 请求进度通知
    }
  }
}

// 3. 服务器发送进度(异步通知)
服务器 → 客户端:
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "query-123",
    "progress": 50,
    "total": 100,
    "message": "正在扫描users表..."
  }
}

// 4. 返回查询结果
服务器 → 客户端:
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "查询结果: 1,234个活跃用户"
      }
    ],
    "isError": false
  }
}

// 5. 如果查询出错
服务器 → 客户端(错误情况):
{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32603,
    "message": "SQL语法错误",
    "data": {
      "sql": "SELECT COUNT(*) FROM users WHERE active = 1",
      "error": "near "WHERE": syntax error",
      "position": 35
    }
  }
}

这就是MCP通信的完整过程!通过JSON-RPC 2.0,客户端和服务器可以进行结构化、类型安全的通信。

五、服务器能力:三种核心功能

MCP服务器可以提供三种功能。

5.1 Resources(资源):应用决定用什么

资源就是数据,比如文件内容、数据库记录、API响应。

谁控制: 应用程序决定把哪些资源提供给AI

如何使用:

json 复制代码
// 列出所有可用资源
{"method": "resources/list"}

// 读取某个资源
{
  "method": "resources/read",
  "params": {"uri": "file:///project/main.py"}
}

资源URI示例:

  • file:///project/src/main.py - 文件
  • db://schema/users - 数据库表结构
  • git://commits/main - Git提交历史
  • https://api.example.com/data - Web API

订阅变更: 可以订阅资源,当它变化时自动收到通知。

实际案例: Filesystem服务器暴露资源

swift 复制代码
{
  "uri": "file:///Users/alice/project/src/main.py",  // Python源文件
  "name": "main.py",                                  // 文件名
  "mimeType": "text/x-python",                        // 文件类型
  "text": "import os\ndef main()..."                  // 文件内容
}

客户端AI可以读取这个资源,理解代码结构后提供重构建议或生成测试。

5.2 Prompts(提示模板):用户选择用什么

什么是Prompt?

Prompt就像是「对话模板」或「快捷指令」,把常用的复杂指令预设好,用户一键调用。用生活中的例子类比,就像微信的「快捷回复」或IDE中的「代码片段(Snippet)」。

为什么需要Prompt?

场景1:没有Prompt时

markdown 复制代码
用户每次都要输入:
"请分析这个Git仓库最近一周的提交,统计:
1. 总提交次数
2. 每个作者的贡献
3. 修改的主要文件
4. 是否有破坏性变更
请用表格格式输出"

场景2:有Prompt后

makefile 复制代码
用户只需:
1. 点击 "/analyze-commits" 命令
2. 选择分支 "main"
3. AI自动执行完整分析

Prompt的数据结构

定义一个Prompt:

json 复制代码
{
  "name": "analyze_commits",              // Prompt的唯一标识
  "title": "提交历史分析",                  // 用户界面显示的名称
  "description": "分析Git提交并生成报告",    // 功能说明
  "arguments": [                          // 需要的参数列表
    {
      "name": "branch",                   // 参数名
      "description": "要分析的分支名",      // 参数说明
      "required": true                    // 是否必填
    },
    {
      "name": "since",                    // 时间范围
      "description": "起始日期(如:7 days ago)",
      "required": false                   // 可选参数
    },
    {
      "name": "author",                   // 作者过滤
      "description": "只看某个作者的提交",
      "required": false
    }
  ]
}

实际使用示例

步骤1: 列出所有可用的Prompt

json 复制代码
// 客户端请求
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "prompts/list"
}

// 服务器响应
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "prompts": [
      {
        "name": "analyze_commits",
        "title": "📊 提交历史分析",
        "description": "分析指定分支的提交历史,生成统计报告",
        "arguments": [
          {"name": "branch", "required": true},
          {"name": "since", "required": false}
        ]
      },
      {
        "name": "review_code",
        "title": "🔍 代码审查",
        "description": "对代码进行质量审查和改进建议",
        "arguments": [
          {"name": "file_path", "required": true},
          {"name": "focus", "required": false}
        ]
      },
      {
        "name": "explain_error",
        "title": "🐛 错误诊断",
        "description": "解释错误信息并提供修复建议",
        "arguments": [
          {"name": "error_message", "required": true}
        ]
      }
    ]
  }
}

步骤2: 用户在界面上看到这些选项

bash 复制代码
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  可用命令:
  
  📊 /analyze-commits
     分析指定分支的提交历史
     
  🔍 /review-code  
     对代码进行质量审查
     
  🐛 /explain-error
     解释错误信息并修复
━━━━━━━━━━━━━━━━━━━━━━━━━━━━

步骤3: 用户选择并填写参数

ini 复制代码
用户输入: /analyze-commits

系统弹窗:
┌─────────────────────────┐
│ 提交历史分析            │
├─────────────────────────┤
│ 分支名 *: [main      ] │
│ 时间范围: [7 days ago] │
│ 作者:     [          ] │
│                         │
│      [取消]  [确定]     │
└─────────────────────────┘

步骤4: 获取完整的Prompt内容

swift 复制代码
// 客户端请求
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "prompts/get",
  "params": {
    "name": "analyze_commits",       // 使用哪个模板
    "arguments": {                    // 用户填写的参数
      "branch": "main",
      "since": "7 days ago"
    }
  }
}

// 服务器响应 - 返回完整的对话消息
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "description": "分析main分支最近7天的提交",
    "messages": [                     // 发送给AI的完整对话
      {
        "role": "user",               // 用户角色
        "content": {
          "type": "text",
          "text": "请分析main分支在过去7天的Git提交历史。\n\n需要统计:\n1. 总提交次数\n2. 每个作者的贡献次数和代码行数\n3. 主要修改的文件列表\n4. 是否包含breaking changes\n5. 提交消息的规范性\n\n请用表格格式输出结果,并在最后给出改进建议。"
        }
      }
    ]
  }
}

步骤5: 客户端将消息发送给AI

bash 复制代码
用户看到AI正在分析...

AI返回:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 main分支提交分析报告(最近7天)

总提交次数: 23

作者贡献统计:
┌────────────┬────────┬──────────┐
│ 作者       │ 提交数 │ 代码行数  │
├────────────┼────────┼──────────┤
│ Alice      │   12   │  +543/-89│
│ Bob        │    8   │  +234/-45│
│ Charlie    │    3   │  +123/-12│
└────────────┴────────┴──────────┘

主要修改文件:
- src/api/users.py (8次修改)
- src/models/user.py (5次修改)
- tests/test_user.py (4次修改)

Breaking Changes: 无

提交规范性: 良好 (91%符合Conventional Commits)

改进建议:
1. 建议增加单元测试覆盖率
2. 部分提交消息过于简短
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Prompt的内容类型

Prompt消息中可以包含多种内容:
1. 纯文本

json 复制代码
{
  "role": "user",
  "content": {
    "type": "text",
    "text": "请审查这段代码..."
  }
}

2. 嵌入图片

json 复制代码
{
  "role": "user",
  "content": {
    "type": "image",
    "data": "base64-encoded-image-data",     // 图片数据
    "mimeType": "image/png"                  // 图片类型
  }
}

3. 嵌入资源(引用MCP资源)

swift 复制代码
{
  "role": "user",
  "content": {
    "type": "resource",
    "resource": {
      "uri": "file:///project/src/user.py",  // 资源URI
      "mimeType": "text/x-python",
      "text": "class User:\n    def __init__..."  // 资源内容
    }
  }
}

4. 多轮对话

json 复制代码
{
  "messages": [
    {
      "role": "user",
      "content": {"type": "text", "text": "我想优化这段代码"}
    },
    {
      "role": "assistant",                    // AI的回复
      "content": {"type": "text", "text": "请提供代码内容"}
    },
    {
      "role": "user",
      "content": {
        "type": "resource",                   // 用户提供代码
        "resource": {...}
      }
    }
  ]
}

Prompt vs Tool vs Resource 对比

scss 复制代码
特性          Prompt              Tool                Resource
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
控制者        用户主动选择         AI自动决定           应用程序控制

触发方式      用户点击命令         AI判断需要调用       应用自动附加
              /analyze                              或用户选择
              
返回内容      对话消息            执行结果             数据内容
              (给AI的指令)        (函数返回值)         (上下文信息)
              
典型用途      工作流模板          执行操作             提供背景信息
              快捷指令            查询数据             文件内容
              
示例          代码审查模板        执行SQL查询          项目README
              错误诊断向导        发送邮件             数据库Schema
              
用户感知      ✅ 明显             ❓ 可能不知道         ❓ 透明的
              (用户点击)          (AI决定)            (自动加载)

Prompt是预设的对话模板,通过参数化实现灵活应用,提升用户体验,并能与MCP其他能力组合形成完整工作流。

代码实现示例

python 复制代码
# 服务器端:注册Prompt
@server.list_prompts()
async def list_prompts():
    return [
        Prompt(
            name="analyze_commits",
            title="📊 提交历史分析",
            description="分析Git提交历史并生成统计报告",
            arguments=[
                {"name": "branch", "description": "分支名", "required": True},
                {"name": "since", "description": "时间范围", "required": False}
            ]
        )
    ]

@server.get_prompt()
async def get_prompt(name: str, arguments: dict):
    if name == "analyze_commits":
        branch = arguments["branch"]
        since = arguments.get("since", "7 days ago")
        
        # 构建完整的提示消息
        prompt_text = f"""
请分析{branch}分支在{since}的Git提交历史。

需要统计:
1. 总提交次数
2. 每个作者的贡献
3. 主要修改的文件
4. 是否有breaking changes

请用表格格式输出。
        """
        
        return {
            "messages": [
                {
                    "role": "user",
                    "content": {"type": "text", "text": prompt_text}
                }
            ]
        }

# 客户端:使用Prompt
async def use_prompt(session, prompt_name, arguments):
    # 获取Prompt内容
    prompt = await session.get_prompt(
        name=prompt_name,
        arguments=arguments
    )
    
    # 将消息发送给AI
    for message in prompt.messages:
        ai_response = await send_to_ai(message)
        print(ai_response)

5.3 Tools(工具):AI 自己决定用什么

Tool就是可执行的函数,比如查询数据库、调用API、写文件。

谁控制:AI模型根据对话内容自己决定调用哪个工具

如何使用:

json 复制代码
// 列出可用工具
{"method": "tools/list"}

// AI调用工具
{
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {"city": "北京"}
  }
}

返回结果:

json 复制代码
{
  "content": [{
    "type": "text",
    "text": "北京天气:晴,温度22°C"
  }],
  "isError": false
}

5.4 其他功能

补全

MCP提供标准化的参数自动补全功能,支持为提示和资源URI提供上下文相关的建议,实现类似IDE的交互体验。服务器通过声明completions能力,支持对ref/promptref/resource两种引用类型的补全,每次最多返回100个按相关性排序的建议值,并可通过completion/complete请求获取补全结果。

日志

MCP提供结构化日志消息传递机制,允许服务器向客户端发送包含严重性级别、可选记录器名称和任意JSON可序列化数据的日志通知。服务器需声明logging能力,支持遵循RFC 5424标准的日志级别(从debug到emergency),客户端可通过logging/setLevel请求配置最低日志级别,服务器通过notifications/message通知发送日志消息。

分页

MCP支持对可能返回大量结果集的列表操作进行分页处理,使用基于不透明游标的分页模型而非数字页码。服务器在响应中包含当前页结果和可选的nextCursor字段(表示更多结果存在),客户端可通过在请求中包含游标继续分页。支持分页的操作包括resources/listresources/templates/listprompts/listtools/list,客户端必须将游标视为不透明令牌。

六、客户端能力:反向请求

客户端不仅接收服务器的数据,也可以提供能力给服务器使用:

6.1 Sampling(采样):服务器请求客户端调用AI

场景: 服务器在处理任务时,需要AI帮忙分析中间结果。

如何使用:

json 复制代码
{
  "method": "sampling/createMessage",
  "params": {
    "messages": [{
      "role": "user",
      "content": {"type": "text", "text": "这个数据正常吗?"}
    }],
    "modelPreferences": {
      "hints": [{"name": "claude-3-sonnet"}],  // 建议用的模型
      "intelligencePriority": 0.8,             // 要求智能程度
      "speedPriority": 0.5                     // 速度要求
    }
  }
}

实际案例:Filesystem服务器在搜索大量文件时,请求AI判断哪些文件最相关。

6.2 Roots(目录):告诉服务器工作范围

场景: 让服务器知道可以访问哪些目录。

如何使用:

json 复制代码
{
  "method": "roots/list"
}

返回:

json 复制代码
{
  "roots": [{
    "uri": "file:///home/user/project",
    "name": "我的项目"
  }]
}

服务器知道只能在这个目录里操作,保护其他文件安全。

6.3 Elicitation(引导):服务器向用户询问信息

场景: 服务器需要用户提供额外信息才能继续。

如何使用:

json 复制代码
{
  "method": "elicitation/create",
  "params": {
    "message": "请提供您的GitHub用户名",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "username": {"type": "string"}
      }
    }
  }
}

用户响应:

perl 复制代码
{
  "action": "accept",  // 或"decline"拒绝, "cancel"取消
  "content": {
    "username": "octocat"
  }
}

实际案例: Git服务器需要知道提交信息格式,弹窗问用户:"请选择提交规范:Conventional Commits/Angular/Custom?"

七、完整实战:从零构建天气查询MCP

下面让我们从头到尾构建一个完整的MCP系统,包含服务器和客户端。

7.1 需求分析

目标: 构建一个天气查询MCP服务器,提供:

  • 资源: 城市列表
  • 工具: 查询天气、获取预报
  • 提示: 天气分析模板

7.2 服务器实现(Python)

第一步: 安装MCP SDK

复制代码
pip install mcp

第二步: 创建服务器 (weather_server.py)

python 复制代码
from mcp.server import Server
from mcp.types import Resource, Tool, Prompt, TextContent
import mcp.server.stdio
import httpx

# 创建MCP服务器实例
server = Server("weather-server")

# 1. 定义资源:支持的城市列表
@server.list_resources()
async def list_resources():
    """返回可用的资源列表"""
    return [
        Resource(
            uri="weather://cities",
            name="支持的城市列表",
            mimeType="application/json",
            description="查询天气支持的所有城市"
        )
    ]

@server.read_resource()
async def read_resource(uri: str):
    """读取具体资源内容"""
    if uri == "weather://cities":
        cities = ["北京", "上海", "广州", "深圳", "杭州"]
        return {
            "contents": [{
                "uri": uri,
                "mimeType": "application/json",
                "text": str(cities)
            }]
        }

# 2. 定义工具:天气查询
@server.list_tools()
async def list_tools():
    """返回可用的工具列表"""
    return [
        Tool(
            name="get_current_weather",
            description="获取指定城市的当前天气",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如'北京'"
                    }
                },
                "required": ["city"]
            }
        ),
        Tool(
            name="get_forecast",
            description="获取未来3天天气预报",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "城市名称"},
                    "days": {"type": "number", "description": "预报天数(1-3)", "default": 3}
                },
                "required": ["city"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    """执行工具调用"""
    if name == "get_current_weather":
        city = arguments["city"]
        # 实际项目中这里调用真实的天气API
        # 示例:使用模拟数据
        weather_data = {
            "city": city,
            "temperature": 22,
            "condition": "晴",
            "humidity": 45
        }
        return {
            "content": [{
                "type": "text",
                "text": f"{city}当前天气:\n温度: {weather_data['temperature']}°C\n天气: {weather_data['condition']}\n湿度: {weather_data['humidity']}%"
            }]
        }
    
    elif name == "get_forecast":
        city = arguments["city"]
        days = arguments.get("days", 3)
        # 模拟预报数据
        forecast = f"{city}未来{days}天预报:\n第1天: 晴,20-25°C\n第2天: 多云,18-23°C\n第3天: 小雨,16-20°C"
        return {
            "content": [{"type": "text", "text": forecast}]
        }

# 3. 定义提示模板:天气分析
@server.list_prompts()
async def list_prompts():
    """返回可用的提示模板"""
    return [
        Prompt(
            name="analyze_weather",
            description="分析天气趋势并给出建议",
            arguments=[
                {"name": "city", "description": "城市名称", "required": True}
            ]
        )
    ]

@server.get_prompt()
async def get_prompt(name: str, arguments: dict):
    """获取提示模板内容"""
    if name == "analyze_weather":
        city = arguments["city"]
        return {
            "messages": [
                {
                    "role": "user",
                    "content": {
                        "type": "text",
                        "text": f"请分析{city}的天气情况,并给出出行建议。包括:\n1. 温度是否适宜\n2. 是否需要带伞\n3. 穿衣建议"
                    }
                }
            ]
        }

# 启动服务器
if __name__ == "__main__":
    # 使用stdio传输(本地)
    mcp.server.stdio.run_stdio_server(server)

7.3 配置服务器(Claude Desktop)

创建配置文件 ~/Library/Application Support/Claude/claude_desktop_config.json:

json 复制代码
{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/path/to/weather_server.py"]
    }
  }
}

7.4 客户端实现(Python)

如果要自己实现客户端:

python 复制代码
from mcp import ClientSession
from mcp.client.stdio import stdio_client
import asyncio

async def main():
    # 连接到服务器
    async with stdio_client(
        command="python",
        args=["/path/to/weather_server.py"]
    ) as (read, write):
        async with ClientSession(read, write) as session:
            
            # 1. 初始化连接
            await session.initialize()
            print("✅ 连接成功!")
            
            # 2. 列出可用资源
            resources = await session.list_resources()
            print(f"\n📁 可用资源: {len(resources.resources)}")
            for r in resources.resources:
                print(f"  - {r.name}: {r.uri}")
            
            # 3. 读取城市列表资源
            cities_resource = await session.read_resource(
                uri="weather://cities"
            )
            print(f"\n🌍 城市列表: {cities_resource.contents[0].text}")
            
            # 4. 列出可用工具
            tools = await session.list_tools()
            print(f"\n🔧 可用工具: {len(tools.tools)}")
            for t in tools.tools:
                print(f"  - {t.name}: {t.description}")
            
            # 5. 调用工具查询天气
            result = await session.call_tool(
                name="get_current_weather",
                arguments={"city": "北京"}
            )
            print(f"\n🌤️  查询结果:\n{result.content[0].text}")
            
            # 6. 获取预报
            forecast = await session.call_tool(
                name="get_forecast",
                arguments={"city": "上海", "days": 3}
            )
            print(f"\n📅 天气预报:\n{forecast.content[0].text}")
            
            # 7. 列出提示模板
            prompts = await session.list_prompts()
            print(f"\n💡 提示模板: {len(prompts.prompts)}")
            for p in prompts.prompts:
                print(f"  - {p.name}: {p.description}")
            
            # 8. 获取提示内容
            prompt = await session.get_prompt(
                name="analyze_weather",
                arguments={"city": "广州"}
            )
            print(f"\n📝 生成的提示:\n{prompt.messages[0]['content']['text']}")

if __name__ == "__main__":
    asyncio.run(main())

7.5 运行效果

makefile 复制代码
$ python weather_client.py

✅ 连接成功!

📁 可用资源: 1
  - 支持的城市列表: weather://cities

🌍 城市列表: ['北京', '上海', '广州', '深圳', '杭州']

🔧 可用工具: 2
  - get_current_weather: 获取指定城市的当前天气
  - get_forecast: 获取未来3天天气预报

🌤️  查询结果:
北京当前天气:
温度: 22°C
天气: 晴
湿度: 45%

📅 天气预报:
上海未来3天预报:
第1天: 晴,20-25°C
第2天: 多云,18-23°C
第3天: 小雨,16-20°C

💡 提示模板: 1
  - analyze_weather: 分析天气趋势并给出建议

📝 生成的提示:
请分析广州的天气情况,并给出出行建议。包括:
1. 温度是否适宜
2. 是否需要带伞
3. 穿衣建议

八、其他部分:MCP基础协议的另一半

8.1 授权(Authorization)

MCP授权规范定义了基于HTTP传输的安全授权机制,使MCP客户端能够代表资源所有者向受限制的MCP服务器发出请求。该规范基于OAuth 2.1及相关标准,实现了授权服务器发现、动态客户端注册和访问令牌管理。例如,客户端通过resource参数明确指定目标MCP服务器(如https://mcp.example.com),服务器则验证令牌是否专门为其颁发,确保令牌不会被误用于其他服务,从而防止"令牌传递"安全漏洞。

8.2 取消(Cancellation)

MCP取消机制允许通过通知消息中止正在进行的请求,任何一方都可以发送notifications/cancelled通知来终止先前发出的请求。例如,当用户取消长时间运行的操作时,客户端可以发送包含请求ID和可选原因的取消通知,接收方应停止处理、释放资源且不发送响应。该机制考虑了网络延迟导致的竞态条件,允许接收方在请求已完成或无法取消时忽略通知,同时建议双方记录取消原因以便调试。

json 复制代码
{
  "method": "notifications/cancelled",
  "params": {
    "requestId": "123",
    "reason": "用户取消"
  }
}

8.3 Ping机制

MCP提供了可选的ping机制,允许任何一方验证对方是否仍然响应且连接存活。该机制通过简单的请求/响应模式实现,例如客户端发送{"jsonrpc":"2.0","id":"123","method":"ping"},服务器必须立即响应{"jsonrpc":"2.0","id":"123","result":{}}。如果在合理超时时间内未收到响应,发送方可以将连接视为陈旧并终止连接或尝试重新连接。实现应定期发送ping以检测连接健康状况,但应避免过度ping以减少网络开销。

8.4 进度跟踪(Progress)

MCP支持通过通知消息对长时间运行的操作进行可选的进度跟踪。请求方可以在请求元数据中包含唯一的progressToken(如字符串"task123")来接收进度更新,接收方则可以发送包含进度值、可选总值和消息的notifications/progress通知。例如,文件上传操作可以发送{"progress":50,"total":100,"message":"正在上传文件..."}来指示完成百分比。进度值必须随每个通知递增,双方应实现速率限制以防止消息泛滥,并在操作完成后停止发送进度通知。

json 复制代码
{
  "method": "notifications/progress",
  "params": {
    "progressToken": "task123",
    "progress": 50,      // 当前进度
    "total": 100,        // 总量
    "message": "正在上传文件..."
  }
}

九、安全实践:必须重视

9.1 核心原则

1. 用户同意优先

  • 所有数据访问必须经用户明确同意
  • 所有工具调用前必须让用户确认

2. 数据隐私保护

  • 服务器只能看到必要的信息
  • 完整对话历史保留在宿主,不发给服务器

3. 工具安全

  • 工具代表代码执行,必须谨慎
  • 显示工具要做什么,让用户批准

4. 输入验证

  • 服务器必须验证所有输入
  • 客户端必须验证工具返回的结果

9.2 实际建议

服务器开发者:

  • 验证所有输入参数
  • 实现访问控制和速率限制
  • 记录操作日志供审计

客户端开发者:

  • 显示清晰的权限请求界面
  • 在调用工具前展示参数
  • 实现工具调用超时机制

十、MCP生态:谁开发客户端?

关键认知 : 在MCP生态中,客户端通常不是由下游开发者开发的 ,而是内置在AI应用平台中

css 复制代码
  开发者开发MCP服务器
       ↓
  配置到AI平台(Claude/Cursor等)
       ↓
  AI平台内置的MCP客户端自动连接

对于软件开发者来说,在MCP生态中的位置如下。

makefile 复制代码
角色定位:
┌─────────────────────────────────────────┐
│ AI平台开发者(Anthropic, Cursor等)       │
│ ────────────────────────────────        │
│ 职责:                                   │
│  ✅ 开发MCP客户端SDK                    │
│  ✅ 集成到自己的AI应用中                │
│  ✅ 提供配置界面                        │
│  ✅ 管理MCP服务器生命周期               │
│  ✅ 处理AI与MCP的交互逻辑               │
└─────────────────────────────────────────┘
                 ↓ 提供平台
┌─────────────────────────────────────────┐
│ MCP服务器开发者(你、我、社区)           │
│ ────────────────────────────────        │
│ 职责:                                   │
│  ✅ 开发MCP服务器                       │
│  ✅ 实现Resources/Tools/Prompts        │
│  ✅ 编写使用文档                        │
│  ✅ 发布到npm/PyPI                      │
│  ❌ 不需要开发客户端                    │
│  ❌ 不需要关心AI如何调用                │
└─────────────────────────────────────────┘
                 ↓ 使用服务
┌─────────────────────────────────────────┐
│ 最终用户(开发者、分析师等)               │
│ ────────────────────────────────        │
│ 职责:                                   │
│  ✅ 安装需要的MCP服务器                 │
│  ✅ 配置到AI平台                        │
│  ✅ 使用AI完成任务                      │
│  ❌ 不需要写代码                        │
└─────────────────────────────────────────┘

后记

MCP 让 AI 应用开发变得更简单、更安全、更强大。它不是银弹,但为构建可靠的AI系统提供了坚实基础。本文全部内容基于提示编写,欢迎交流讨论!

参考文献

  1. MCP官方规范: modelcontextprotocol.io/specificati...
  2. JSON-RPC 2.0: www.jsonrpc.org/
相关推荐
白鲸开源2 小时前
实战干货:Apache DolphinScheduler 参数使用与优化总结
大数据·程序员·开源
IT_陈寒2 小时前
Qoder 降价,立即生效!首购 2 美金/月
程序员
京东云开发者3 小时前
【原理到实战】实验异质性分析
程序员
大模型教程4 小时前
2张4090本地微调万亿参数模型!KTransformers上线模型微调功能,使用指南来了
程序员·llm·agent
大模型教程4 小时前
快速上手Qwen Code:本地部署与环境配置全攻略
程序员·llm·agent
SimonKing6 小时前
SpringBoot邮件发送怎么玩?比官方自带的Mail更好用的三方工具
java·后端·程序员
AI大模型7 小时前
本地部署vLLM+Qwen3:高性能大模型推理引擎,比Ollama强在哪?
程序员·llm·agent
知了一笑8 小时前
个人创业,如何搭产品体系?
程序员·独立开发