MCP(Model Context Protocol)详解

MCP(Model Context Protocol)是 Anthropic 于 2024 年 11 月推出的开放协议,旨在标准化 LLM 与外部工具、数据源之间的连接方式。经过一年多的发展,MCP 已成为 Agent 工具接口的事实标准,被 Claude Desktop、Cursor、Windsurf、OpenAI Agents SDK 等主流产品广泛支持。

MCP 的核心思想

在 MCP 之前,每个 Agent 框架都有自己的工具接口:

# 不同框架的工具定义(不兼容!)

# OpenAI Function Calling
openai_tool = {
    "type": "function",
    "function": {"name": "search", "parameters": {...}}
}

# LangChain Tool
from langchain_core.tools import tool
@tool
def search(query: str) -> str: ...

# Anthropic Tool
anthropic_tool = {
    "name": "search",
    "description": "...",
    "input_schema": {...}
}

MCP 提供统一标准,让工具可以在不同框架间复用——你可以把它理解为 AI 世界的"USB-C 接口":

MCP 架构示意图

MCP 架构:

[LLM 应用/Host]  ←→  [MCP Client]  ←→  [MCP Server]
                        (标准协议)         (工具提供者)

MCP Server 可以是:
- 本地工具(文件系统、代码执行)
- 外部服务(数据库、API)
- 知识库(文档、向量存储)

MCP 服务器实现

# pip install mcp

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
    Tool, TextContent, CallToolResult,
    ListToolsResult
)

# 创建 MCP Server
server = Server("my-tools-server")

# 声明可用工具
@server.list_tools()
async def list_tools() -> ListToolsResult:
    return ListToolsResult(tools=[
        Tool(
            name="calculate",
            description="计算数学表达式",
            inputSchema={
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "要计算的数学表达式"
                    }
                },
                "required": ["expression"]
            }
        ),
        Tool(
            name="read_file",
            description="读取文件内容",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "文件路径"
                    }
                },
                "required": ["path"]
            }
        )
    ])

# 实现工具逻辑
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> CallToolResult:
    if name == "calculate":
        import math
        try:
            expression = arguments["expression"]
            safe_env = {k: getattr(math, k) for k in dir(math) if not k.startswith('_')}
            # ⚠️ 安全警告:eval() 在生产环境中有安全风险
            # 建议使用 simpleeval 库替代:pip install simpleeval
            result = eval(expression, {"__builtins__": {}}, safe_env)
            return CallToolResult(
                content=[TextContent(type="text", text=f"{expression} = {result}")]
            )
        except Exception as e:
            return CallToolResult(
                content=[TextContent(type="text", text=f"错误:{e}")],
                isError=True
            )
    
    elif name == "read_file":
        path = arguments["path"]
        try:
            with open(path, 'r', encoding='utf-8') as f:
                content = f.read()
            return CallToolResult(
                content=[TextContent(type="text", text=content)]
            )
        except Exception as e:
            return CallToolResult(
                content=[TextContent(type="text", text=f"读取失败:{e}")],
                isError=True
            )
    
    else:
        return CallToolResult(
            content=[TextContent(type="text", text=f"未知工具:{name}")],
            isError=True
        )

# 以 stdio 模式运行(供 Claude Desktop 等客户端连接)
async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream)

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

在 Agent 中使用 MCP 工具

# pip install mcp anthropic

import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio

async def use_mcp_tools(query: str):
    """使用 MCP 工具的 Agent"""
    
    # 连接到 MCP Server
    server_params = StdioServerParameters(
        command="python",
        args=["my_mcp_server.py"],  # 上面定义的服务器文件
    )
    
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            
            # 初始化连接
            await session.initialize()
            
            # 获取可用工具
            tools_result = await session.list_tools()
            
            # 将 MCP 工具转换为 Anthropic 格式
            anthropic_tools = [
                {
                    "name": tool.name,
                    "description": tool.description,
                    "input_schema": tool.inputSchema
                }
                for tool in tools_result.tools
            ]
            
            client = anthropic.Anthropic()
            
            # 调用 Claude 并允许使用工具
            response = client.messages.create(
                model="claude-4-sonnet-20250514",
                max_tokens=1024,
                tools=anthropic_tools,
                messages=[{"role": "user", "content": query}]
            )
            
            # 处理工具调用
            while response.stop_reason == "tool_use":
                tool_results = []
                
                for content_block in response.content:
                    if content_block.type == "tool_use":
                        # 通过 MCP 执行工具
                        tool_result = await session.call_tool(
                            content_block.name,
                            content_block.input
                        )
                        
                        tool_results.append({
                            "type": "tool_result",
                            "tool_use_id": content_block.id,
                            "content": tool_result.content[0].text
                        })
                
                # 继续对话
                response = client.messages.create(
                    model="claude-4-sonnet-20250514",
                    max_tokens=1024,
                    tools=anthropic_tools,
                    messages=[
                        {"role": "user", "content": query},
                        {"role": "assistant", "content": response.content},
                        {"role": "user", "content": tool_results}
                    ]
                )
            
            # 返回最终文本回复
            for block in response.content:
                if hasattr(block, "text"):
                    return block.text

# 测试
asyncio.run(use_mcp_tools("计算 sqrt(144) + pi * 2"))

MCP 的主要资源类型

# MCP 除了工具(Tools),还支持:

# 1. 资源(Resources):静态数据源
@server.list_resources()
async def list_resources():
    return [
        Resource(
            uri="file:///docs/readme.md",
            name="项目文档",
            mimeType="text/markdown"
        )
    ]

# 2. 提示词模板(Prompts)
@server.list_prompts()
async def list_prompts():
    return [
        Prompt(
            name="code_review",
            description="代码审查模板",
            arguments=[
                PromptArgument(name="code", required=True)
            ]
        )
    ]

MCP 2025 年重要更新

MCP 协议在 2025 年经历了重大演进,主要体现在以下方面:

1. Streamable HTTP 传输(2025 年 3 月)

MCP 引入 Streamable HTTP 替代原来的 HTTP + SSE 传输方式,解决了远程 MCP 服务的多个痛点:

# 新的 Streamable HTTP 传输模式
from mcp.server.fastmcp import FastMCP

# FastMCP 是更简洁的 MCP Server 创建方式
mcp = FastMCP("my-remote-server")

@mcp.tool()
def get_weather(city: str) -> str:
    """查询城市天气"""
    return f"{city}的天气:晴,25°C"

# 以 Streamable HTTP 模式运行(支持远程访问)
# 统一端点,不再需要单独的 /sse 端点
if __name__ == "__main__":
    mcp.run(transport="streamable-http", port=8000)

Streamable HTTP vs 旧版 HTTP + SSE

特性HTTP + SSE(旧)Streamable HTTP(新)
端点需要 /sse + /messages 两个端点统一单一端点
连接恢复连接断开不可恢复支持 Session 恢复
服务端压力长连接持续占用资源按需流式,可选普通 HTTP 响应
部署友好度需要支持 SSE 的基础设施标准 HTTP,兼容任何基础设施

2. 远程 MCP 与认证

2025 年 MCP 规范增加了对远程服务器的完善支持,包括基于 OAuth 2.1 的认证机制:

远程 MCP 架构:

[本地 Agent]  ←→  [MCP Client]  ←→  [互联网]  ←→  [远程 MCP Server]
                                        ↑
                                  OAuth 2.1 认证
                                  + Streamable HTTP

3. MCP 生态系统现状(2025-2026)

截至 2026 年初,MCP 已被广泛采纳:

客户端MCP 支持状态
Claude Desktop✅ 最早支持
Cursor✅ 深度集成
Windsurf✅ 支持
OpenAI Agents SDK✅ 原生支持
VS Code (Copilot)✅ 支持
Dify✅ 插件支持

社区 MCP Server 已超过数千个,覆盖数据库、云服务、开发工具等各类场景。

4. Elicitation:向用户请求信息

2025 年 MCP 规范新增 Elicitation 功能,允许 MCP Server 在执行工具时主动向用户请求额外信息(如确认危险操作、填写缺少的参数),而非直接失败:

# Elicitation 示例:MCP Server 请求用户确认
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> CallToolResult:
    if name == "delete_file":
        path = arguments["path"]
        
        # 通过 Elicitation 向用户确认
        confirmation = await server.elicit(
            message=f"确定要删除文件 {path} 吗?此操作不可恢复。",
            schema={
                "type": "object",
                "properties": {
                    "confirm": {
                        "type": "boolean",
                        "description": "确认删除",
                        "default": False
                    }
                }
            }
        )
        
        if not confirmation.get("confirm"):
            return CallToolResult(
                content=[TextContent(type="text", text="操作已取消")]
            )
        
        # 用户确认后执行删除
        os.remove(path)
        return CallToolResult(
            content=[TextContent(type="text", text=f"已删除 {path}")]
        )

Elicitation 的核心价值

  • 危险操作前获取用户确认(删除文件、发送邮件等)
  • 运行时收集缺失参数(数据库连接信息、API Key 等)
  • 在多步骤工作流中获取用户偏好选择

5. Sampling:MCP Server 调用 LLM

Sampling 功能允许 MCP Server 反过来请求 Host 端的 LLM 能力,实现"工具调用 AI"的闭环:

# Sampling 示例:MCP Server 利用 LLM 能力
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> CallToolResult:
    if name == "smart_summarize":
        file_path = arguments["path"]
        
        with open(file_path, 'r') as f:
            content = f.read()
        
        # MCP Server 请求 Host 端的 LLM 进行摘要
        summary_result = await server.sample(
            messages=[
                {
                    "role": "user",
                    "content": {
                        "type": "text",
                        "text": f"请用 3 句话总结以下文档内容:\n\n{content}"
                    }
                }
            ],
            max_tokens=200,
            model_preferences={
                "hints": [{"name": "claude-4-sonnet"}],
                "costPriority": 0.3,  # 成本优先级(0-1)
                "speedPriority": 0.7,  # 速度优先级
            }
        )
        
        return CallToolResult(
            content=[TextContent(
                type="text",
                text=f"文档摘要:\n{summary_result.content.text}"
            )]
        )

# Sampling 工作流:
# Host(LLM) → 调用 MCP Tool → MCP Server 需要 LLM 能力
#                                → 通过 Sampling 请求 Host 的 LLM
#                                ← 返回 LLM 结果
#             ← 返回工具结果

Sampling 的典型场景

  • 代码分析工具需要 LLM 解释代码意图
  • 数据处理工具需要 LLM 判断异常数据的含义
  • 文件管理工具需要 LLM 自动分类和命名

小结

MCP 的价值:

  • 标准化:统一的工具接口,跨框架复用——"写一次,到处用"
  • 安全性:明确的权限边界,OAuth 2.1 认证
  • 可组合性:多个 MCP Server 组合使用
  • 远程支持:Streamable HTTP 让 MCP 从本地走向云端
  • 生态繁荣:Claude Desktop、Cursor、OpenAI Agents SDK 等已全面支持

下一节:15.2 A2A(Agent-to-Agent)协议