目录

Claude API基础专题(五):MCP协议深度解析

Claude API基础专题(五):MCP协议深度解析 ⭐⭐⭐⭐

目标读者:希望扩展Claude能力边界的开发者 前置知识:已完成第一篇《API基础》、第二篇《提示词工程》、第三篇《工具调用》、第四篇《RAG系统》 学习提醒:MCP是Claude能力扩展的核心协议,建议先理解其设计思想再动手实现


章节导航

小节主题重要程度
5.1MCP概述:为什么需要MCP⭐⭐⭐⭐⭐
5.2MCP架构详解⭐⭐⭐⭐⭐
5.3MCP协议工作流程⭐⭐⭐⭐⭐
5.4构建MCP服务器⭐⭐⭐⭐⭐
5.5MCP客户端开发⭐⭐⭐⭐
5.6MCP生态与实践⭐⭐⭐⭐
5.7MCP与工具调用的区别⭐⭐⭐⭐

5.1 MCP概述:为什么需要MCP

问题的起源

要理解MCP(Model Context Protocol,模型上下文协议),我们需要先理解一个根本问题:为什么LLM需要额外的协议来与外部工具交互?

让我们回顾一下工具调用的发展历程:

阶段一:硬编码工具调用(2019-2022)

在LLM发展的早期阶段,每个LLM提供者(如OpenAI、Google)都定义了各自专有的工具调用格式:

# OpenAI的格式
{
    "name": "get_weather",
    "description": "获取天气",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string"}
        }
    }
}

# Google的格式
{
    "function_declarations": [{
        "name": "get_weather",
        "description": "获取天气",
        "parameters": {...}
    }]
}

# Anthropic的格式
{
    "name": "get_weather",
    "description": "获取天气",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名称"}
        }
    }
}

为什么会有这种碎片化?

原因很简单:每个LLM提供商都想建立自己的生态锁定(vendor lock-in)。当你用OpenAI的格式写了一套工具,你就很难迁移到Google的模型,因为需要重写所有工具定义。

这就带来了几个严重问题:

问题影响
供应商锁定开发者被绑定在某一LLM提供商
重复开发同一个工具需要为不同提供商编写不同版本
生态割裂工具开发者只愿意为流行平台开发
创新受阻新入局的LLM难以快速建立工具生态

MCP的诞生

MCP是由Anthropic牵头,与多个行业合作伙伴共同制定的一个开放标准。它的核心思想是:

为LLM工具调用建立一个"USB接口"标准——一次开发,到处可用。

为什么叫"模型上下文协议"?

这个名字实际上揭示了MCP的本质:

  • 协议(Protocol):一套标准化的通信规则
  • 上下文(Context):MCP不仅仅是调用工具,它还能为LLM提供持久的上下文状态
  • 模型无关(Model-agnostic):理论上任何LLM都可以实现MCP

MCP vs 传统工具调用

特性传统工具调用MCP
标准化程度各大厂商各自定义统一开放标准
上下文持久化无,每次请求独立有,支持多轮对话状态
双向通信单向(LLM调用工具)支持服务端主动推送
工具发现手动配置支持自动发现
类型安全弱(JSON Schema)强(JSON-RPC + 类型系统)
供应商锁定严重无(一次开发,多端使用)

MCP的设计原则

MCP的设计遵循了几个核心原则:

1. 开放性(Openness)

MCP是一个开放标准,任何人都可以免费使用和实现。这与当年USB取代各种专用接口的道理一样——标准化带来生态繁荣

2. 简单性(Simplicity)

MCP的协议设计尽量简单,降低实现门槛。一个MCP服务器可以用任何语言实现,只需要支持JSON-RPC 2.0。

3. 可组合性(Composability)

MCP支持模块化设计。多个MCP服务器可以并行工作,共同为LLM提供能力。这就像搭积木——你可以根据需要组合不同的服务器。

4. 安全性(Security)

MCP强调在安全沙箱环境中运行工具,避免LLM直接访问敏感资源。这是MCP与其他方案的显著区别。


5.2 MCP架构详解

整体架构

MCP采用客户端-服务器架构,包含三个核心组件:

┌─────────────────────────────────────────────────────────────┐
│                      LLM 应用层                             │
│                   (Claude Desktop / IDE)                  │
└─────────────────────────┬───────────────────────────────────┘
                          │ MCP协议
┌─────────────────────────▼───────────────────────────────────┐
│                     MCP 客户端                              │
│              (负责与服务器通信,管理连接)                   │
└─────────────────────────┬───────────────────────────────────┘
                          │ 
┌─────────────────────────▼───────────────────────────────────┐
│                    MCP 服务器                               │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
│  │ 文件系统  │  │  数据库  │  │  Web API │  │ GitHub   │   │
│  │  服务器  │  │  服务器  │  │  服务器  │  │  服务器  │   │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘   │
└─────────────────────────────────────────────────────────────┘

为什么这样设计?

这种架构的好处是关注点分离(Separation of Concerns)

  • LLM应用层只需要理解如何与MCP客户端交互,不需要关心具体工具的实现
  • MCP客户端负责连接管理、协议解析、请求路由等通用功能
  • MCP服务器专注于提供特定能力(如文件系统、数据库等)

MCP服务器类型

MCP服务器主要分为两类:

1. 本地服务器(Local Servers)

本地服务器直接运行在用户的机器上,访问本地资源:

服务器用途权限
filesystem读写本地文件需要用户授权
memory跨会话持久化记忆自动获得
sequential-thinking复杂推理辅助自动获得
slackerSlack消息操作需要OAuth授权

2. 远程服务器(Remote Servers)

远程服务器部署在云端,通过网络访问:

服务器用途连接方式
githubGitHub操作API Token
google-maps地图服务API Key
puppeteer网页抓取无头浏览器

资源与工具的区别

MCP中有一个重要概念:资源(Resources)vs 工具(Tools)

# 资源:是LLM可以读取的数据
# 类似于"文件",LLM主动请求读取
{
    "type": "resource",
    "name": "user_profile",
    "uri": "file://./user_profile.json",
    "mimeType": "application/json"
}

# 工具:是LLM可以调用的函数
# 类似于"程序",LLM调用执行
{
    "type": "tool",
    "name": "send_email",
    "description": "发送邮件",
    "inputSchema": {...}
}

为什么要区分?

区别在于谁来发起操作

类型发起方例子权限要求
资源LLM主动读取查看文件内容读权限
工具LLM调用执行删除文件、发送邮件写权限

这种区分让权限控制更精细:读取文件只需要读权限,删除文件需要写权限。


5.3 MCP协议工作流程

JSON-RPC基础

MCP底层使用JSON-RPC 2.0协议进行通信。JSON-RPC是一种轻量级的远程过程调用协议。

为什么选择JSON-RPC?

这是MCP设计中的又一个"为什么":

  1. 简单易实现:JSON-RPC只定义了少数几种消息类型,任何语言都能轻松实现
  2. 无状态友好:适合HTTP等无状态协议
  3. 广泛支持:几乎所有编程语言都有成熟的JSON-RPC库
  4. 调试友好:基于JSON,人类可读,方便调试

核心消息类型

MCP定义了以下核心消息类型:

# 1. 初始化请求(客户端 → 服务器)
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "roots": {"list": True},
            "sampling": {}
        },
        "clientInfo": {
            "name": "claude-desktop",
            "version": "1.0.0"
        }
    }
}

# 2. 初始化响应(服务器 → 客户端)
{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "tools": {"listChanged": True},
            "resources": {"subscribe": True, "listChanged": True}
        },
        "serverInfo": {
            "name": "filesystem-server",
            "version": "1.0.0"
        }
    }
}

完整交互流程

时间轴          客户端                          服务器
   │              │                               │
   │ ─── handshake request ──────────────────►  │
   │              │                               │
   │              │  ◄────────────────── handshake response ──
   │              │                               │
   │ ─── tools/list ─────────────────────────►  │
   │              │                               │
   │              │  ◄──────────────────── tools/list result ──
   │              │                               │
   │ ─── tools/call ─────────────────────────►  │
   │              │                               │
   │              │  ◄─────────────────────── result ──
   │              │                               │
   │ ─── resources/list ────────────────────►  │
   │              │                               │
   │              │  ◄────────────────── resources/list result ──

工具调用详解

当LLM决定调用一个工具时,客户端会发送:

# 工具调用请求
{
    "jsonrpc": "2.0",
    "id": 42,
    "method": "tools/call",
    "params": {
        "name": "read_file",
        "arguments": {
            "path": "/Users/demo/readme.md"
        }
    }
}

服务器处理后会返回:

# 成功响应
{
    "jsonrpc": "2.0",
    "id": 42,
    "result": {
        "content": [
            {
                "type": "text",
                "text": "# Hello World\n\nThis is a sample file."
            }
        ],
        "isError": False
    }
}

# 错误响应
{
    "jsonrpc": "2.0",
    "id": 42,
    "error": {
        "code": -32603,
        "message": "File not found: /Users/demo/readme.md"
    }
}

5.4 构建MCP服务器

项目结构

一个标准的MCP服务器项目结构:

my-mcp-server/
├── src/
│   ├── __init__.py
│   ├── server.py          # 主服务器代码
│   ├── tools.py           # 工具定义
│   └── resources.py       # 资源定义
├── pyproject.toml
└── README.md

基础服务器实现

# src/server.py
from typing import Any
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

# 创建服务器实例
server = Server("my-filesystem-server")

@server.list_tools()
async def list_tools() -> list[Tool]:
    """
    列出服务器提供的所有工具
    
    为什么需要这个函数?
    当客户端连接时,它需要知道服务器能做什么。
    这个函数就像服务器的"产品目录",告诉客户端有哪些工具可用。
    """
    return [
        Tool(
            name="read_file",
            description="读取文件内容。适用于需要查看文本文件内容的场景。",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "要读取的文件路径"
                    }
                },
                "required": ["path"]
            }
        ),
        Tool(
            name="write_file",
            description="写入内容到文件。如果文件存在,会覆盖原有内容。",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "要写入的文件路径"
                    },
                    "content": {
                        "type": "string",
                        "description": "要写入的内容"
                    }
                },
                "required": ["path", "content"]
            }
        ),
        Tool(
            name="list_directory",
            description="列出目录中的文件和文件夹",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "要列出的目录路径",
                        "default": "."
                    }
                }
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: Any) -> list[TextContent]:
    """
    执行工具调用
    
    为什么分离list_tools和call_tool?
    - list_tools:定义"有什么工具",在连接时调用一次
    - call_tool:执行"具体某个工具",每次调用工具时都触发
    
    这种分离的好处:
    1. 工具定义和执行逻辑分离,代码更清晰
    2. 客户端可以缓存工具列表,不需要每次调用都查询
    3. 支持工具的动态注册和注销
    """
    if name == "read_file":
        return await _read_file(arguments)
    elif name == "write_file":
        return await _write_file(arguments)
    elif name == "list_directory":
        return await _list_directory(arguments)
    else:
        raise ValueError(f"Unknown tool: {name}")

async def _read_file(arguments: dict) -> list[TextContent]:
    """读取文件的实现"""
    import aiofiles
    
    path = arguments["path"]
    
    try:
        async with aiofiles.open(path, "r", encoding="utf-8") as f:
            content = await f.read()
        return [TextContent(type="text", text=content)]
    except FileNotFoundError:
        return [TextContent(type="text", text=f"Error: File not found: {path}")]
    except PermissionError:
        return [TextContent(type="text", text=f"Error: Permission denied: {path}")]

async def _write_file(arguments: dict) -> list[TextContent]:
    """写入文件的实现"""
    import aiofiles
    import os
    
    path = arguments["path"]
    content = arguments["content"]
    
    # 确保目录存在
    directory = os.path.dirname(path)
    if directory and not os.path.exists(directory):
        os.makedirs(directory, exist_ok=True)
    
    async with aiofiles.open(path, "w", encoding="utf-8") as f:
        await f.write(content)
    
    return [TextContent(type="text", text=f"Successfully wrote to {path}")]

async def _list_directory(arguments: dict) -> list[TextContent]:
    """列出目录的实现"""
    import os
    
    path = arguments.get("path", ".")
    
    try:
        entries = os.listdir(path)
        formatted = "\n".join(f"- {entry}" for entry in sorted(entries))
        return [TextContent(type="text", text=formatted)]
    except FileNotFoundError:
        return [TextContent(type="text", text=f"Directory not found: {path}")]
    except PermissionError:
        return [TextContent(type="text", text=f"Permission denied: {path}")]

async def main():
    """服务器入口点"""
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )

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

使用FastMCP简化开发

FastMCP是一个高级封装库,可以大幅简化MCP服务器开发:

# 使用FastMCP的简化版本
from mcp.server.fastmcp import FastMCP

# 创建FastMCP实例
mcp = FastMCP("my-demo-server")

@mcp.tool()
def calculate(operation: str, a: float, b: float) -> dict:
    """
    执行数学计算
    
    为什么使用装饰器而不是显式注册?
    装饰器是一种"声明式"编程风格:
    - 优点:代码简洁,意图清晰
    - 缺点:执行顺序依赖装饰器顺序
    
    适用于:简单工具定义
    """
    operations = {
        "add": lambda x, y: x + y,
        "subtract": lambda x, y: x - y,
        "multiply": lambda x, y: x * y,
        "divide": lambda x, y: x / y if y != 0 else "Error: Division by zero"
    }
    
    if operation not in operations:
        return {"error": f"Unknown operation: {operation}"}
    
    result = operations[operation](a, b)
    return {"operation": operation, "a": a, "b": b, "result": result}

@mcp.resource("config://app")
def get_config() -> str:
    """提供应用配置资源"""
    return '{"version": "1.0.0", "theme": "dark"}'

# 运行服务器
if __name__ == "__main__":
    mcp.run()

5.5 MCP客户端开发

连接管理

# 客户端连接示例
import asyncio
from mcp.client import ClientSession
from mcp.client.stdio import stdio_client

async def main():
    async with stdio_client() as (read, write):
        async with ClientSession(read, write) as session:
            # 1. 初始化连接
            # 握手过程让客户端和服务器交换能力信息
            await session.initialize()
            
            # 2. 列出可用工具
            tools = await session.list_tools()
            print("可用工具:", [t.name for t in tools])
            
            # 3. 调用工具
            result = await session.call_tool(
                "read_file",
                arguments={"path": "/tmp/demo.txt"}
            )
            print("文件内容:", result[0].text)

asyncio.run(main())

错误处理

from mcp.exceptions import MCPError

async def safe_call_tool(session, tool_name: str, arguments: dict):
    """
    带错误处理的工具调用
    
    MCP定义了标准化的错误格式,客户端应该理解这些错误
    """
    try:
        result = await session.call_tool(tool_name, arguments)
        return {"success": True, "result": result}
    except MCPError as e:
        return {
            "success": False,
            "error": "protocol_error",
            "message": str(e)
        }
    except Exception as e:
        return {
            "success": False,
            "error": "internal_error",
            "message": str(e)
        }

5.6 MCP生态与实践

官方MCP服务器

Anthropic提供了多个官方MCP服务器:

服务器用途位置
filesystem本地文件操作官方维护
memory持久化记忆官方维护
sequential-thinking推理辅助官方维护
slackerSlack集成社区维护

在Claude Desktop中配置MCP

// ~/.claude-desktop/mcp.json
{
    "mcpServers": {
        "filesystem": {
            "command": "npx",
            "args": ["-y", "@anthropic/mcp-server-filesystem"],
            "args": ["/Users/username/projects", "/Users/username/documents"]
        },
        "github": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {
                "GITHUB_TOKEN": "your-github-pat"
            }
        }
    }
}

5.7 MCP与工具调用的区别

这是很多开发者困惑的问题:MCP和普通的工具调用有什么区别?

本质区别

维度普通工具调用MCP
架构LLM → 直接调用函数LLM → MCP客户端 → MCP服务器
协议各厂商私有格式统一的开放协议
上下文每次请求独立支持跨请求状态
工具发现手动配置支持自动发现
实现语言必须与LLM同语言任何语言

什么时候用哪个?

使用普通工具调用

  • 简单场景:只需要偶尔调用一两个工具
  • 单次任务:不需要跨会话保持状态
  • 快速原型:想快速验证想法

使用MCP

  • 复杂系统:需要多个工具协同工作
  • 长期助手:需要在会话之间保持记忆
  • 生态建设:希望工具能被多个应用复用
  • 企业应用:对标准化和安全性有要求

本章总结

核心知识点

知识点掌握程度关键点
MCP概述⭐⭐⭐⭐⭐为什么需要MCP、解决什么问题
MCP架构⭐⭐⭐⭐⭐客户端-服务器、资源vs工具
协议流程⭐⭐⭐⭐⭐JSON-RPC、初始化、调用流程
服务器开发⭐⭐⭐⭐⭐定义工具、处理调用、返回结果
客户端开发⭐⭐⭐⭐连接管理、错误处理
MCP生态⭐⭐⭐⭐官方服务器、配置方法
选型决策⭐⭐⭐⭐MCP vs 普通工具调用

关键设计思想

设计思想为什么重要
开放标准打破供应商锁定
关注点分离架构更清晰
资源与工具分离权限控制更精细
错误结果化简化LLM处理逻辑
装饰器模式开发更便捷

下一步

  • 继续阅读:Claude Code与Computer Use专题(六)- 了解Claude的计算机操控能力
  • 实践项目:用MCP构建一个个人知识助手
  • 参考资料:MCP官方文档

文档元信息 难度:⭐⭐⭐⭐ | 类型:专家设计 | 更新日期:2026-03-25 | 预计阅读时间:50分钟 | 字数:约6500字