第三十三章:MCP模型上下文协议

Model Context Protocol —— 让AI与外部世界对话的标准化接口

学习目标

  • 理解MCP协议的设计动机与核心价值
  • 掌握MCP的三层架构:Host、Client、Server
  • 理解Resources、Tools、Prompts、Sampling四大原语
  • 掌握基于JSON-RPC 2.0的协议规范
  • 能够使用Python和TypeScript开发MCP Server
  • 了解MCP的安全机制与最佳实践

前置知识

  • 了解大语言模型的基本工作原理(第1-15章)
  • 熟悉REST API与JSON-RPC协议基础
  • 具备Python或TypeScript编程基础
  • 了解AI Agent的概念(第二十九章)
MCP模型上下文协议架构图
图33-1 MCP协议架构:Host、Client、Server三层结构与四大核心原语

一、MCP简介:为什么需要模型上下文协议

在大语言模型快速发展的今天,每个AI应用都需要与外部数据源、工具和服务进行交互。然而,如果没有统一的标准,每个集成都需要从头开发,导致了大量重复劳动和碎片化问题。MCP(Model Context Protocol)正是为了解决这个问题而诞生的开放标准协议。

MCP是什么

MCP是由Anthropic于2024年底发布的开源协议,全称为Model Context Protocol(模型上下文协议)。它定义了一种标准化的方式,让AI模型能够安全、高效地与外部世界进行交互——包括读取数据、执行操作、访问工具等。

  • 标准化接口:就像USB-C统一了设备连接方式,MCP统一了AI与外部服务的连接方式
  • 可复用组件:一个MCP Server可以被任何支持MCP的AI应用复用
  • 安全隔离:通过明确定义的权限边界,确保数据访问的安全性
  • 跨平台兼容:不依赖特定的LLM提供商或应用框架
MCP的核心价值

在MCP出现之前,如果你想让AI助手访问数据库、文件系统或API,通常需要为每个集成编写定制代码。MCP让这变成"插拔式"的:编写一次MCP Server,所有支持MCP的AI应用都能使用。

MCP与函数调用的区别

很多开发者会混淆MCP与LLM的Function Calling。实际上,Function Calling是LLM内部的能力,而MCP是外部的标准化通信协议。两者是互补的关系,而非替代关系。

维度 Function Calling MCP
性质 LLM内部能力 外部标准化协议
定义位置 应用代码中 独立的MCP Server
复用性 每个应用独立实现 跨应用复用
协议规范 各厂商各自定义 统一标准(JSON-RPC 2.0)
安全边界 由应用控制 协议层内置权限控制
类比理解: Function Calling 像是"车里的方向盘",MCP 像是"道路标准"——方向盘让你能操控车,而道路标准让不同品牌的车都能在同一条路上行驶。

二、架构设计:Host、Client、Server

MCP采用分层架构,包含三个核心角色:Host(宿主)、Client(客户端)和Server(服务端)。这种分层设计使得协议既灵活又安全。

三层架构概览

  • Host(宿主):用户直接交互的AI应用,如Claude Desktop、IDE插件或自定义聊天应用。Host负责管理整体的用户体验和会话状态。
  • Client(客户端):嵌入在Host中的MCP客户端组件,负责与MCP Server建立连接、协商能力并处理消息。每个Client维护与一个Server的1:1连接。
  • Server(服务端):独立运行的服务,通过MCP协议暴露Resources、Tools、Prompts等能力。Server可以是本地进程或远程服务。

一个Host可以同时连接多个Client,每个Client对应一个Server。这种设计使得Host能够聚合来自不同Server的能力,同时保持清晰的权限边界。

MCP协议架构:模型上下文协议的三层结构

连接生命周期

MCP Client与Server之间的连接遵循标准的生命周期管理流程:

连接三阶段

  • 1. 初始化(Initialize):Client发送initialize请求,包含自身支持的协议版本和能力。Server返回自己的协议版本和能力。双方据此协商出共同支持的功能子集。
  • 2. 运行(Operate):连接建立后,Client和Server可以自由交换请求和通知。Server可以提供Resources、Tools和Prompts,Client可以调用这些能力。
  • 3. 关闭(Shutdown):任一方可以优雅地关闭连接。Client在关闭前应确保所有进行中的操作已完成。

这种设计确保了连接的可靠性和可预测性,使得错误处理和资源清理变得更加容易。

消息流动模式

MCP中的消息流动遵循严格的请求-响应和通知两种模式。理解这些模式对于正确实现协议至关重要。

请求-响应模式

Client发送一个请求(带有id字段),Server处理后返回对应的响应(带有相同id)。这是MCP中最常见的通信模式。例如:Client调用Tool、读取Resource等。

  • 同步语义:虽然底层传输可能是异步的,但协议层面保证每个请求最多对应一个响应
  • 超时处理:Client应为每个请求设置合理的超时时间,超时后应终止等待并清理资源
  • 错误响应:Server可以返回错误响应(error字段),Client应根据错误码决定重试或报错

通知模式

通知是没有id字段的消息,发送方不期望收到响应。通知常用于进度更新、状态变化等不需要回复的场景。

  • 进度更新:Server在处理耗时操作时发送进度通知
  • 资源变更:Server通知Client某个资源已更新
  • 日志消息:Server发送调试或信息日志
JSON - 通知示例
// Server → Client: 进度通知
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "abc-123",
    "progress": 50,
    "total": 100,
    "message": "正在处理第50条记录..."
  }
}

// Server → Client: 资源变更通知
{
  "jsonrpc": "2.0",
  "method": "notifications/resources/updated",
  "params": {
    "uri": "file:///data/report.csv"
  }
}
通知不可靠性

通知是"发后即忘"的,不保证送达。如果Client需要可靠的进度更新,应使用带有进度token的请求,通过progressNotification跟踪进度。对于关键状态变更,应使用请求-响应模式。

多Server聚合模式

在实际应用中,一个Host可能需要连接多个Server来聚合不同的能力。MCP的架构设计天然支持这种多Server聚合模式。

聚合架构

  • 扁平聚合:Host直接管理多个Client,每个Client对应一个Server。适用于Server数量较少的场景。
  • 层级聚合:引入Aggregator Server,它本身也是一个MCP Server,同时作为Client连接多个下游Server。适用于需要统一入口的场景。
  • 混合模式:结合扁平和层级模式,关键Server直接连接,辅助Server通过Aggregator聚合。
Python - 多Server管理
"""
多Server管理示例
展示如何在Host中管理多个MCP Server
"""
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

class MultiServerHost:
    def __init__(self):
        self.sessions: dict[str, ClientSession] = {}

    async def add_server(self, name: str, params: StdioServerParameters):
        """添加并连接一个新的MCP Server"""
        read, write = await stdio_client(params).__aenter__()
        session = ClientSession(read, write)
        await session.__aenter__()
        await session.initialize()

        self.sessions[name] = session
        print(f"已连接到Server: {name}")

        # 列出该Server提供的能力
        tools = await session.list_tools()
        print(f"  工具: {[t.name for t in tools.tools]}")

        resources = await session.list_resources()
        print(f"  资源: {[r.uri for r in resources.resources]}")

    async def call_tool(self, server_name: str, tool_name: str, args: dict):
        """调用指定Server上的工具"""
        if server_name not in self.sessions:
            raise ValueError(f"Server未连接: {server_name}")

        session = self.sessions[server_name]
        result = await session.call_tool(tool_name, args)
        return result.content[0].text

    async def search_tools(self, query: str):
        """在所有Server中搜索工具"""
        results = []
        for name, session in self.sessions.items():
            tools = await session.list_tools()
            for tool in tools.tools:
                if query.lower() in tool.name.lower() or \
                   query.lower() in tool.description.lower():
                    results.append({
                        "server": name,
                        "tool": tool.name,
                        "description": tool.description
                    })
        return results

    async def disconnect_all(self):
        """断开所有Server连接"""
        for name, session in self.sessions.items():
            await session.close()
            print(f"已断开Server: {name}")
        self.sessions.clear()

async def main():
    host = MultiServerHost()

    # 连接文件系统Server
    await host.add_server("filesystem", StdioServerParameters(
        command="python",
        args=["filesystem_server.py"]
    ))

    # 连接数据库Server
    await host.add_server("database", StdioServerParameters(
        command="python",
        args=["database_server.py"]
    ))

    # 搜索所有包含"read"的工具
    read_tools = await host.search_tools("read")
    print(f"\n包含'read'的工具:")
    for t in read_tools:
        print(f"  [{t['server']}] {t['tool']}: {t['description']}")

    # 调用文件系统Server的read_file工具
    content = await host.call_tool(
        "filesystem", "read_file",
        {"path": "config.json"}
    )
    print(f"\n文件内容:\n{content}")

    await host.disconnect_all()

if __name__ == "__main__":
    asyncio.run(main())
聚合模式选择

对于桌面应用(如Claude Desktop),通常使用扁平聚合,直接管理所有Server连接。对于Web应用或微服务架构,层级聚合更合适,通过一个网关Server统一对外暴露接口。选择哪种模式取决于应用的部署架构和安全需求。

三、核心概念:四大原语

MCP定义了四种核心原语(Primitives),它们是Server向Client暴露能力的基本方式。理解这四种原语是掌握MCP的关键。

3.1 Resources:数据资源

Resources代表Server可以提供的数据内容,类似于REST API中的GET端点。Resources是只读的,由应用程序或用户控制是否包含在LLM上下文中。

Resource的类型

  • 文件内容:本地文件、配置文件、文档等
  • 数据库记录:特定表、查询结果或记录
  • API响应:外部API返回的数据
  • 实时信息:系统状态、监控数据等
Python
from mcp.server import Server
from mcp.types import Resource, ResourceContents

server = Server("my-server")

# 定义一个文件资源
@server.resource("file:///logs/{date}")
async def get_log_file(date: str) -> ResourceContents:
    """读取指定日期的日志文件"""
    file_path = f"/var/log/app/{date}.log"
    content = await read_file(file_path)
    return ResourceContents(
        uri=f"file:///logs/{date}",
        mimeType="text/plain",
        text=content
    )

# 定义一个动态资源
@server.resource("db://users/{user_id}")
async def get_user(user_id: str) -> ResourceContents:
    """获取用户信息"""
    user = await db.get_user(user_id)
    return ResourceContents(
        uri=f"db://users/{user_id}",
        mimeType="application/json",
        text=json.dumps(user)
    )

3.2 Tools:可执行工具

Tools代表Server可以执行的操作,由模型控制(LLM决定何时调用)。Tools类似于函数,接受输入参数并返回执行结果。与Resources不同,Tools是有副作用的。

Tool的特征

  • 模型控制:由LLM根据上下文决定是否调用
  • 有副作用:可能修改外部状态(如发送邮件、更新数据库)
  • 参数化:通过JSON Schema定义输入参数
  • 结果返回:执行后返回文本或结构化数据
Python
from mcp.server import Server
from mcp.types import Tool, ToolResult
from pydantic import BaseModel

server = Server("my-server")

# 定义工具的输入参数
class SendEmailArgs(BaseModel):
    to: str
    subject: str
    body: str

# 注册邮件发送工具
@server.tool()
async def send_email(args: SendEmailArgs) -> ToolResult:
    """发送邮件到指定地址"""
    await email_service.send(
        to=args.to,
        subject=args.subject,
        body=args.body
    )
    return ToolResult(
        content=f"邮件已发送至 {args.to}",
        isError=False
    )

# 注册数据库查询工具
@server.tool()
async def query_database(sql: str) -> ToolResult:
    """执行SQL查询"""
    try:
        results = await db.execute(sql)
        return ToolResult(
            content=json.dumps(results),
            isError=False
        )
    except Exception as e:
        return ToolResult(
            content=f"查询失败: {str(e)}",
            isError=True
        )

3.3 Prompts:提示模板

Prompts是Server提供的预定义提示模板,由用户控制(用户选择是否使用)。Prompts可以将上下文和指令封装为可复用的模板,简化复杂的交互流程。

Python
from mcp.server import Server
from mcp.types import Prompt, PromptMessage, Role

server = Server("my-server")

@server.prompt()
async def code_review_prompt(
    repository: str,
    pull_request: int
) -> list[PromptMessage]:
    """代码审查提示模板"""
    # 获取PR信息
    pr_info = await github.get_pr(repository, pull_request)
    diff = await github.get_diff(repository, pull_request)

    return [
        PromptMessage(
            role=Role.USER,
            content=f"请审查以下Pull Request:\n\n"
                    f"标题: {pr_info['title']}\n"
                    f"描述: {pr_info['body']}\n\n"
                    f"代码变更:\n{diff}"
        )
    ]

3.4 Sampling:模型采样

Sampling是MCP最独特的原语,它允许Server请求LLM进行文本生成。这使得Server可以利用AI能力完成复杂任务,同时Host保持对采样过程的完全控制。

Sampling工作流

  • 1. Server向Client发送sampling/createMessage请求
  • 2. Client(通常由Host)可以审查和修改请求
  • 3. 请求被发送到LLM进行推理
  • 4. 响应返回给Server
Sampling安全原则

Sampling赋予了Server调用LLM的能力,因此必须严格控制。Host应该:(1) 对所有采样请求进行人类审批;(2) 限制采样可以使用的模型范围;(3) 记录所有采样活动以供审计。

四、协议规范:JSON-RPC 2.0

MCP基于JSON-RPC 2.0构建,这是一种轻量级的远程过程调用协议。JSON-RPC使用JSON作为数据格式,支持请求-响应和通知两种消息模式。

消息类型

  • Request(请求):带有id字段,期望收到Response。例如:tools/call、resources/read
  • Response(响应):包含result或error字段,与请求的id对应
  • Notification(通知):没有id字段,不期望响应。例如:进度更新、日志消息

初始化握手

连接建立时,Client和Server通过初始化消息交换能力信息:

JSON
// Client → Server: 初始化请求
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {}
    },
    "clientInfo": {
      "name": "MyAIApp",
      "version": "1.0.0"
    }
  }
}

// Server → Client: 初始化响应
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "resources": { "subscribe": true },
      "tools": { "listChanged": true },
      "prompts": {}
    },
    "serverInfo": {
      "name": "DatabaseServer",
      "version": "1.0.0"
    }
  }
}

能力发现

初始化完成后,Client可以通过列表请求发现Server提供的所有能力:

JSON
// Client → Server: 列出所有工具
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

// Server → Client: 返回工具列表
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "query_database",
        "description": "执行SQL查询",
        "inputSchema": {
          "type": "object",
          "properties": {
            "sql": {
              "type": "string",
              "description": "SQL查询语句"
            }
          },
          "required": ["sql"]
        }
      },
      {
        "name": "send_email",
        "description": "发送邮件",
        "inputSchema": {
          "type": "object",
          "properties": {
            "to": { "type": "string" },
            "subject": { "type": "string" },
            "body": { "type": "string" }
          },
          "required": ["to", "subject", "body"]
        }
      }
    ]
  }
}
能力协商机制

MCP的初始化阶段使用协商机制:Client和Server各自声明自己支持的能力版本,最终以双方都支持的最高版本为准。这确保了协议的向后兼容性,使得不同版本的Client和Server可以互相通信。

错误处理机制

MCP定义了一套标准的错误码体系,用于处理各种异常情况。正确的错误处理对于构建健壮的MCP应用至关重要。

标准错误码

  • -32700 Parse Error:JSON解析失败,Server收到的不是有效的JSON
  • -32600 Invalid Request:请求格式正确但语义无效
  • -32601 Method Not Found:请求的方法不存在或未实现
  • -32602 Invalid Params:参数验证失败,缺少必填参数或参数类型错误
  • -32603 Internal Error:Server内部错误,如未捕获的异常
JSON - 错误响应示例
// 参数验证错误
{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -32602,
    "message": "Invalid parameters",
    "data": {
      "field": "path",
      "reason": "Path must be a non-empty string"
    }
  }
}

// 工具执行错误
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [{
      "type": "text",
      "text": "文件不存在: /nonexistent/file.txt"
    }],
    "isError": true
  }
}
错误处理最佳实践

Server应尽量返回有意义的错误信息,而不是抛出原始异常。在ToolResult中,使用isError=True标记错误结果,并在content中包含用户友好的错误描述。对于可重试的错误(如网络超时),Client可以自动重试;对于永久性错误(如文件不存在),应向用户报告。

取消与超时

在实际使用中,某些操作可能耗时较长或被用户主动取消。MCP提供了标准化的取消机制来处理这些场景。

取消机制

  • 请求取消:Client可以发送notifications/cancelled通知来取消一个进行中的请求
  • 进度token:请求可以包含progressToken,用于跟踪和取消特定操作
  • 优雅降级:Server收到取消通知后应尽快停止处理,并返回部分结果(如果可能)
JSON - 取消操作
// Client → Server: 取消进行中的请求
{
  "jsonrpc": "2.0",
  "method": "notifications/cancelled",
  "params": {
    "requestId": 5,
    "reason": "用户取消操作"
  }
}

超时是另一个重要的考虑因素。Client应该为每个请求设置合理的超时时间,避免无限等待。MCP规范建议使用30秒作为默认超时时间,对于长时间运行的操作(如文件上传),可以通过进度通知来延长超时。

五、传输机制

MCP的传输层负责消息的序列化和通信。协议本身与传输方式解耦,当前支持两种主要的传输机制:标准输入/输出(stdio)和HTTP Server-Sent Events(SSE)。

5.1 标准输入/输出(stdio)

stdio是最简单的传输方式,适用于本地进程间通信。Server作为子进程启动,通过stdin/stdout交换JSON消息。这种方式无需网络配置,安全性由操作系统进程隔离保证。

stdio适用场景

  • 本地工具集成(文件系统、Git、数据库等)
  • CLI工具封装为MCP Server
  • 开发和测试环境
  • 单用户场景
Python - stdio传输
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server

server = Server("local-tools")

@server.tool()
async def read_file(path: str) -> str:
    """读取本地文件"""
    with open(path, 'r') as f:
        return f.read()

async def main():
    # 启动stdio传输
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )

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

5.2 HTTP SSE(Server-Sent Events)

SSE传输适用于远程Server部署场景。Server作为HTTP服务运行,Client通过POST请求发送消息,Server通过SSE流返回响应和通知。这种方式支持多Client并发连接,适合生产环境部署。

SSE传输特点

  • HTTP兼容:使用标准HTTP,便于通过负载均衡器和代理
  • 流式响应:通过SSE实现服务端到客户端的消息推送
  • 多客户端支持:一个Server可同时服务多个Client
  • 远程部署:Server可以部署在云端,Client通过网络访问
Python - SSE传输
import uvicorn
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route, Mount

server = Server("remote-server")

@server.resource("config://app")
async def get_config() -> str:
    """获取应用配置"""
    return json.dumps({"version": "1.0", "debug": False})

# SSE传输实例
sse = SseServerTransport("/messages/")

async def handle_sse(request):
    """处理SSE连接"""
    async with sse.connect_sse(
        request.scope, request.receive, request._send
    ) as streams:
        await server.run(
            streams[0], streams[1],
            server.create_initialization_options()
        )

# 创建Starlette应用
app = Starlette(
    routes=[
        Route("/sse", endpoint=handle_sse),
        Mount("/messages/", app=sse.handle_post_message),
    ]
)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
传输方式 适用场景 连接方式 部署方式
stdio 本地集成、CLI工具 进程stdin/stdout 作为子进程启动
SSE 远程服务、生产环境 HTTP POST + SSE 独立HTTP服务

六、服务端开发实战

本节通过完整示例展示如何开发一个实用的MCP Server。我们将构建一个文件系统MCP Server,它能够读写文件、列出目录并执行基本的文件操作。

6.1 Python实现

以下是使用Python MCP SDK实现的文件系统Server完整代码:

Python - 文件系统MCP Server
"""
文件系统MCP Server
提供文件读写、目录列表等功能
"""
import os
import json
import asyncio
from pathlib import Path
from typing import Optional
from mcp.server import Server
from mcp.types import (
    Tool, Resource, ResourceContents,
    ToolResult, Prompt, PromptMessage, Role
)
from pydantic import BaseModel

# 创建Server实例
server = Server("filesystem-server")

# 工具参数模型
class ReadFileArgs(BaseModel):
    path: str

class WriteFileArgs(BaseModel):
    path: str
    content: str

class ListDirArgs(BaseModel):
    path: str = "."

class DeleteFileArgs(BaseModel):
    path: str

# ==================== Tools ====================

@server.tool()
async def read_file(args: ReadFileArgs) -> ToolResult:
    """读取文件内容"""
    try:
        file_path = Path(args.path).resolve()
        if not file_path.exists():
            return ToolResult(
                content=f"文件不存在: {args.path}",
                isError=True
            )
        content = file_path.read_text(encoding='utf-8')
        return ToolResult(content=content)
    except Exception as e:
        return ToolResult(
            content=f"读取失败: {str(e)}",
            isError=True
        )

@server.tool()
async def write_file(args: WriteFileArgs) -> ToolResult:
    """写入文件内容"""
    try:
        file_path = Path(args.path).resolve()
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.write_text(args.content, encoding='utf-8')
        return ToolResult(
            content=f"文件已写入: {args.path} ({len(args.content)} 字节)"
        )
    except Exception as e:
        return ToolResult(
            content=f"写入失败: {str(e)}",
            isError=True
        )

@server.tool()
async def list_directory(args: ListDirArgs) -> ToolResult:
    """列出目录内容"""
    try:
        dir_path = Path(args.path).resolve()
        if not dir_path.is_dir():
            return ToolResult(
                content=f"目录不存在: {args.path}",
                isError=True
            )
        items = []
        for item in sorted(dir_path.iterdir()):
            item_type = "📁" if item.is_dir() else "📄"
            size = item.stat().st_size if item.is_file() else 0
            items.append(f"{item_type} {item.name} ({size} bytes)")

        result = f"目录: {dir_path}\n\n" + "\n".join(items)
        return ToolResult(content=result)
    except Exception as e:
        return ToolResult(
            content=f"列表失败: {str(e)}",
            isError=True
        )

@server.tool()
async def delete_file(args: DeleteFileArgs) -> ToolResult:
    """删除指定文件"""
    try:
        file_path = Path(args.path).resolve()
        if not file_path.exists():
            return ToolResult(
                content=f"文件不存在: {args.path}",
                isError=True
            )
        if file_path.is_dir():
            return ToolResult(
                content="不能删除目录,请使用rm -r",
                isError=True
            )
        file_path.unlink()
        return ToolResult(content=f"文件已删除: {args.path}")
    except Exception as e:
        return ToolResult(
            content=f"删除失败: {str(e)}",
            isError=True
        )

# ==================== Resources ====================

@server.resource("file://{path}")
async def get_file_resource(path: str) -> ResourceContents:
    """获取文件资源"""
    file_path = Path(path).resolve()
    content = file_path.read_text(encoding='utf-8')
    mime_type = "text/plain"
    if path.endswith('.json'):
        mime_type = "application/json"
    elif path.endswith('.md'):
        mime_type = "text/markdown"

    return ResourceContents(
        uri=f"file://{path}",
        mimeType=mime_type,
        text=content
    )

# ==================== Prompts ====================

@server.prompt()
async def summarize_file_prompt(path: str) -> list[PromptMessage]:
    """生成文件摘要的提示模板"""
    file_content = await read_file(ReadFileArgs(path=path))
    return [
        PromptMessage(
            role=Role.USER,
            content=f"请为以下文件内容生成简洁的摘要:\n\n```\n{file_content.content}\n```"
        )
    ]

# ==================== 启动 ====================

async def main():
    from mcp.server.stdio import stdio_server
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream, write_stream,
            server.create_initialization_options()
        )

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

6.2 TypeScript实现

TypeScript版本的实现同样简洁,使用官方@modelcontextprotocol/sdk包:

TypeScript - 文件系统MCP Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";

// 创建MCP Server
const server = new McpServer({
  name: "filesystem-server",
  version: "1.0.0",
});

// 注册读取文件工具
server.tool(
  "read_file",
  "读取文件内容",
  { path: z.string().describe("文件路径") },
  async ({ path: filePath }) => {
    try {
      const content = await fs.readFile(filePath, "utf-8");
      return {
        content: [{ type: "text", text: content }],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `读取失败: ${error.message}` }],
        isError: true,
      };
    }
  }
);

// 注册写入文件工具
server.tool(
  "write_file",
  "写入文件内容",
  {
    path: z.string().describe("文件路径"),
    content: z.string().describe("文件内容"),
  },
  async ({ path: filePath, content }) => {
    try {
      await fs.mkdir(path.dirname(filePath), { recursive: true });
      await fs.writeFile(filePath, content, "utf-8");
      return {
        content: [{ type: "text", text: `文件已写入: ${filePath}` }],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `写入失败: ${error.message}` }],
        isError: true,
      };
    }
  }
);

// 注册列出目录工具
server.tool(
  "list_directory",
  "列出目录内容",
  { path: z.string().default(".").describe("目录路径") },
  async ({ path: dirPath }) => {
    try {
      const items = await fs.readdir(dirPath, { withFileTypes: true });
      const result = items
        .map((item) => {
          const icon = item.isDirectory() ? "📁" : "📄";
          return `${icon} ${item.name}`;
        })
        .join("\n");
      return {
        content: [{ type: "text", text: `目录: ${dirPath}\n\n${result}` }],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `列表失败: ${error.message}` }],
        isError: true,
      };
    }
  }
);

// 注册资源
server.resource("config", "config://app", async (uri) => ({
  contents: [
    {
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify({ version: "1.0.0", debug: false }),
    },
  ],
}));

// 启动stdio传输
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Filesystem MCP Server 运行中...");
}

main().catch(console.error);
Python vs TypeScript选择

Python SDK更适合数据科学和后端集成场景,TypeScript SDK更适合Web应用和前端工具链集成。两者都完全支持MCP协议的所有功能。对于新手,建议从Python SDK开始,因为它的API更直观。

七、客户端集成

MCP客户端负责连接Server并将其能力集成到AI应用中。本节展示如何在Python应用中集成MCP客户端。

Python - MCP客户端
"""
MCP客户端示例
连接MCP Server并调用其工具和资源
"""
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    # 配置Server连接参数
    server_params = StdioServerParameters(
        command="python",
        args=["filesystem_server.py"],
        env=None  # 可选环境变量
    )

    # 建立连接
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            # 初始化连接
            await session.initialize()

            print("已连接到MCP Server!")

            # 列出所有可用工具
            tools = await session.list_tools()
            print(f"\n可用工具 ({len(tools.tools)}):")
            for tool in tools.tools:
                print(f"  - {tool.name}: {tool.description}")

            # 调用工具
            result = await session.call_tool(
                "list_directory",
                arguments={"path": "."}
            )
            print(f"\n目录内容:\n{result.content[0].text}")

            # 读取资源
            resource = await session.read_resource("config://app")
            print(f"\n配置信息:\n{resource.contents[0].text}")

            # 使用提示模板
            prompts = await session.list_prompts()
            print(f"\n可用提示模板 ({len(prompts.prompts)}):")
            for prompt in prompts.prompts:
                print(f"  - {prompt.name}: {prompt.description}")

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

集成到AI应用的模式

  • 直接集成:在AI应用代码中直接使用MCP Client
  • 中间件模式:通过MCP代理服务器聚合多个Server
  • 插件模式:将MCP Client封装为可插拔的插件

在实际应用中,通常需要将MCP能力与LLM的Function Calling结合使用。以下是将MCP工具转换为LLM工具定义的示例:

Python - MCP工具转换为LLM工具
async def convert_mcp_tools_to_llm(session):
    """将MCP工具定义转换为LLM可使用的格式"""
    tools = await session.list_tools()

    llm_tools = []
    for tool in tools.tools:
        llm_tools.append({
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema
            }
        })

    return llm_tools

async def handle_llm_tool_call(session, tool_name, arguments):
    """处理LLM的工具调用请求"""
    result = await session.call_tool(tool_name, arguments)
    return result.content[0].text
现有MCP客户端支持

目前已有多款AI应用原生支持MCP协议,包括:Claude Desktop、Cursor、Windsurf、Cline等IDE插件、Continue.dev等。这些应用可以无缝连接任何符合MCP规范的Server。

八、安全机制与最佳实践

安全是MCP协议设计的核心考量。由于MCP Server可以访问文件系统、执行代码、连接数据库等敏感操作,必须采取严格的安全措施来保护用户数据和系统安全。

8.1 安全架构原则

安全三层模型

  • 传输层安全:本地stdio传输依赖操作系统进程隔离;远程SSE传输应使用HTTPS/TLS加密
  • 协议层安全:通过能力协商确保双方支持的安全特性;使用JSON Schema验证所有输入
  • 应用层安全:Host实现人类审批机制;限制Tool的权限范围;记录所有操作日志
Python - 安全Server配置
from mcp.server import Server
from mcp.types import ToolResult
import logging

server = Server("secure-server")

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-server")

# 工具权限控制
TOOL_PERMISSIONS = {
    "read_file": {"requires_approval": False},
    "write_file": {"requires_approval": True},
    "delete_file": {"requires_approval": True},
    "execute_query": {"requires_approval": True},
}

@server.tool()
async def secure_tool_wrapper(tool_name: str, **kwargs) -> ToolResult:
    """安全包装器:验证权限并记录操作"""
    permission = TOOL_PERMISSIONS.get(tool_name)

    if permission is None:
        return ToolResult(
            content=f"未知工具: {tool_name}",
            isError=True
        )

    # 记录操作日志
    logger.info(
        f"工具调用: {tool_name}, "
        f"参数: {kwargs}, "
        f"需要审批: {permission['requires_approval']}"
    )

    # 执行实际工具(此处应调用对应的工具函数)
    # result = await execute_tool(tool_name, **kwargs)

    return ToolResult(content="操作完成")

8.2 常见安全威胁

必须防范的安全威胁
  • 提示注入:恶意用户可能通过工具返回的内容操纵LLM行为。Server应对所有输出进行消毒处理。
  • 权限提升:确保Server只能访问明确授权的资源。使用最小权限原则。
  • 数据泄露:敏感数据不应出现在Tool返回给LLM的内容中。实现数据分类和过滤。
  • 拒绝服务:对工具调用设置超时和资源限制,防止Server被恶意请求压垮。
Python - 输入验证与消毒
from pydantic import BaseModel, validator
import re

class SafeQueryArgs(BaseModel):
    """安全的SQL查询参数"""
    query: str
    max_rows: int = 100

    @validator('query')
    def validate_query(cls, v):
        """验证SQL查询安全性"""
        # 禁止危险操作
        dangerous_keywords = [
            'DROP', 'DELETE', 'TRUNCATE', 'ALTER',
            'INSERT', 'UPDATE', 'EXEC', 'EXECUTE'
        ]
        upper_query = v.upper()
        for keyword in dangerous_keywords:
            if keyword in upper_query:
                raise ValueError(f"禁止执行危险操作: {keyword}")

        # 长度限制
        if len(v) > 1000:
            raise ValueError("查询长度超过限制")

        return v

    @validator('max_rows')
    def validate_max_rows(cls, v):
        """验证返回行数限制"""
        if v < 1 or v > 1000:
            raise ValueError("返回行数必须在1-1000之间")
        return v

8.3 日志与审计

完善的日志记录和审计机制是MCP Server安全运行的重要保障。通过记录所有关键操作,可以快速定位问题并满足合规要求。

日志记录要点

  • 请求日志:记录所有Tool调用请求,包括参数、来源、时间戳
  • 响应日志:记录Tool执行结果,包括成功/失败状态、耗时
  • 错误日志:记录所有错误详情,包括堆栈跟踪、上下文信息
  • 安全事件:记录权限拒绝、认证失败等安全相关事件
  • 性能指标:记录Tool执行时间,用于性能监控和优化
Python - 审计日志实现
"""
MCP Server审计日志实现
记录所有工具调用和安全事件
"""
import logging
import json
from datetime import datetime
from typing import Any
from dataclasses import dataclass, asdict

# 配置审计日志
audit_logger = logging.getLogger("mcp.audit")
audit_logger.setLevel(logging.INFO)

# 创建JSON格式的审计日志处理器
class AuditFormatter(logging.Formatter):
    def format(self, record):
        audit_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "level": record.levelname,
            "message": record.getMessage(),
            "extra": getattr(record, 'extra', {})
        }
        return json.dumps(audit_entry, ensure_ascii=False)

handler = logging.StreamHandler()
handler.setFormatter(AuditFormatter())
audit_logger.addHandler(handler)

@dataclass
class AuditEvent:
    """审计事件数据结构"""
    event_type: str  # tool_call, auth_failure, permission_denied
    tool_name: str
    arguments: dict
    result: str
    duration_ms: float
    user_id: str = None
    error: str = None

def log_tool_call(event: AuditEvent):
    """记录工具调用审计日志"""
    audit_logger.info(
        f"Tool调用: {event.tool_name}",
        extra={"extra": asdict(event)}
    )

def log_security_event(event_type: str, details: dict):
    """记录安全事件审计日志"""
    audit_logger.warning(
        f"安全事件: {event_type}",
        extra={"extra": details}
    )

# 使用示例
async def audited_tool_call(tool_name: str, args: dict) -> Any:
    """带审计的工具调用包装器"""
    start_time = datetime.now()

    try:
        # 执行工具
        result = await execute_tool(tool_name, args)
        duration = (datetime.now() - start_time).total_seconds() * 1000

        # 记录成功调用
        event = AuditEvent(
            event_type="tool_call",
            tool_name=tool_name,
            arguments=args,
            result="success",
            duration_ms=duration
        )
        log_tool_call(event)

        return result

    except PermissionError as e:
        duration = (datetime.now() - start_time).total_seconds() * 1000
        # 记录权限拒绝
        event = AuditEvent(
            event_type="permission_denied",
            tool_name=tool_name,
            arguments=args,
            result="denied",
            duration_ms=duration,
            error=str(e)
        )
        log_tool_call(event)
        raise

    except Exception as e:
        duration = (datetime.now() - start_time).total_seconds() * 1000
        # 记录执行错误
        event = AuditEvent(
            event_type="tool_error",
            tool_name=tool_name,
            arguments=args,
            result="error",
            duration_ms=duration,
            error=str(e)
        )
        log_tool_call(event)
        raise
审计日志最佳实践
  • 使用结构化日志(JSON格式),便于后续分析和查询
  • 将审计日志与应用日志分离,便于独立管理
  • 设置日志保留策略,定期归档旧日志
  • 敏感信息(如密码、Token)应在日志中脱敏
  • 考虑使用集中式日志服务(如ELK Stack)进行日志聚合
if keyword in upper_query: raise ValueError(f"禁止执行: {keyword}") # 只允许SELECT查询 if not upper_query.strip().startswith('SELECT'): raise ValueError("只允许SELECT查询") return v @validator('max_rows') def validate_max_rows(cls, v): """限制返回行数""" if v > 1000: return 1000 if v < 1: return 1 return v @server.tool() async def safe_query(args: SafeQueryArgs) -> ToolResult: """安全的数据库查询工具""" try: # 使用参数化查询防止SQL注入 results = await db.execute( args.query + f" LIMIT {args.max_rows}" ) return ToolResult(content=json.dumps(results)) except ValueError as e: return ToolResult( content=f"安全验证失败: {str(e)}", isError=True )

8.3 人类审批机制

对于高风险操作,MCP推荐实现人类审批(Human-in-the-Loop)机制。Host应该在执行敏感Tool前征求用户确认。

审批流程设计

  • 1. LLM决定调用某个Tool
  • 2. Host检查该Tool是否需要审批
  • 3. 如果需要,弹出确认对话框,显示Tool名称、参数和预期效果
  • 4. 用户确认后执行,拒绝则返回错误
  • 5. 记录用户的选择和操作结果
Python - 人类审批实现
"""
人类审批机制示例
在执行敏感操作前征求用户确认
"""
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Callable
import asyncio

class ApprovalLevel(Enum):
    NONE = "none"           # 无需审批
    NOTIFICATION = "notification"  # 仅通知
    CONFIRMATION = "confirmation"  # 需要确认
    DIALOG = "dialog"       # 需要详细对话框

@dataclass
class ApprovalRequest:
    tool_name: str
    arguments: dict
    description: str
    risk_level: ApprovalLevel

class ApprovalManager:
    def __init__(self):
        self.approval_configs: dict[str, ApprovalLevel] = {}
        self.approval_history: list[dict] = []

    def configure_tool(self, tool_name: str, level: ApprovalLevel):
        """配置工具的审批级别"""
        self.approval_configs[tool_name] = level

    async def request_approval(
        self, request: ApprovalRequest
    ) -> bool:
        """请求用户审批"""
        level = self.approval_configs.get(
            request.tool_name,
            ApprovalLevel.CONFIRMATION
        )

        if level == ApprovalLevel.NONE:
            return True

        if level == ApprovalLevel.NOTIFICATION:
            print(f"[通知] 即将执行: {request.tool_name}")
            return True

        # 需要用户确认的操作
        print(f"\n{'='*50}")
        print(f"工具调用审批请求")
        print(f"{'='*50}")
        print(f"工具: {request.tool_name}")
        print(f"描述: {request.description}")
        print(f"参数: {request.arguments}")
        print(f"风险等级: {level.value}")
        print(f"{'='*50}")

        response = input("是否允许执行?(y/n): ").strip().lower()

        approved = response == 'y'

        # 记录审批历史
        self.approval_history.append({
            "tool": request.tool_name,
            "approved": approved,
            "arguments": request.arguments
        })

        return approved

# 使用示例
async def main():
    manager = ApprovalManager()

    # 配置高风险工具需要确认
    manager.configure_tool("delete_file", ApprovalLevel.CONFIRMATION)
    manager.configure_tool("execute_query", ApprovalLevel.DIALOG)
    manager.configure_tool("read_file", ApprovalLevel.NONE)

    # 请求删除文件审批
    request = ApprovalRequest(
        tool_name="delete_file",
        arguments={"path": "important.txt"},
        description="删除文件 important.txt",
        risk_level=ApprovalLevel.CONFIRMATION
    )

    approved = await manager.request_approval(request)
    if approved:
        print("用户已批准,执行删除操作")
    else:
        print("用户拒绝,操作已取消")

8.4 认证与授权

对于远程MCP Server(SSE传输),认证和授权是必不可少的安全机制。MCP协议本身定义了认证的框架,具体实现由Server和Host协商决定。

认证方式

  • API Key:最简单的认证方式,适合内部服务和开发环境。Client在连接时提供API Key,Server验证后允许访问。
  • OAuth 2.0:标准的授权框架,适合需要细粒度权限控制的场景。支持多种授权流程(授权码、客户端凭证等)。
  • mTLS:双向TLS认证,适合高安全性要求的生产环境。Client和Server都需要提供证书。
Python - OAuth 2.0集成示例
"""
MCP Server OAuth 2.0认证示例
展示如何在MCP Server中集成OAuth 2.0认证
"""
from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from authlib.integrations.starlette_client import OAuth
from starlette.config import Config
import jwt

app = FastAPI()
security = HTTPBearer()

# OAuth配置
oauth = OAuth()
oauth.register(
    name='mcp_provider',
    client_id='your-client-id',
    client_secret='your-client-secret',
    server_metadata_url='https://auth.example.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid email profile mcp:tools'}
)

# JWT验证
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
    try:
        payload = jwt.decode(
            credentials.credentials,
            'your-secret-key',
            algorithms=['HS256']
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token已过期")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="无效的Token")

# 需要认证的MCP端点
@app.get("/mcp/tools")
async def list_tools(user=Depends(verify_token)):
    """列出可用工具(需要认证)"""
    # 根据用户权限返回工具列表
    tools = get_tools_for_user(user['sub'])
    return {"tools": tools}

@app.post("/mcp/tools/{tool_name}/call")
async def call_tool(
    tool_name: str,
    arguments: dict,
    user=Depends(verify_token)
):
    """调用工具(需要认证+授权)"""
    # 检查用户是否有权调用该工具
    if not has_permission(user['sub'], tool_name):
        raise HTTPException(status_code=403, detail="权限不足")

    result = await execute_tool(tool_name, arguments)
    return result

# 登录端点
@app.get("/login")
async def login(request):
    """重定向到OAuth提供商"""
    redirect_uri = request.url_for('auth_callback')
    return await oauth.mcp_provider.authorize_redirect(request, redirect_uri)

@app.get("/auth/callback")
async def auth_callback(request):
    """OAuth回调处理"""
    token = await oauth.mcp_provider.authorize_access_token(request)
    # 生成JWT并返回给客户端
    jwt_token = create_jwt(token)
    return {"access_token": jwt_token, "token_type": "bearer"}
认证配置注意事项
  • 永远不要在代码中硬编码密钥和凭证,使用环境变量或密钥管理服务
  • 为不同环境(开发、测试、生产)使用不同的密钥和配置
  • 定期轮换密钥,设置合理的过期时间
  • 记录所有认证事件,便于安全审计
安全最佳实践清单
  • 始终使用最小权限原则设计Server
  • 对所有输入进行严格的验证和消毒
  • 实施速率限制防止滥用
  • 记录所有工具调用和资源访问日志
  • 定期进行安全审计和漏洞扫描
  • 为敏感操作实现人类审批机制
  • 使用HTTPS/TLS加密远程通信
  • 实施数据分类和访问控制

九、实战:构建一个完整的MCP Server

本节将综合前面所学,从零开始构建一个实用的天气查询MCP Server。该Server提供天气查询工具、城市资源和天气预报提示模板。

Python - 天气查询MCP Server(完整版)
"""
天气查询MCP Server
提供天气查询工具、城市资源和天气预报提示模板
"""
import json
import asyncio
import httpx
from datetime import datetime
from mcp.server import Server
from mcp.types import (
    Tool, Resource, ResourceContents,
    ToolResult, Prompt, PromptMessage, Role
)
from pydantic import BaseModel

server = Server("weather-server")

# 模拟天气数据(实际应用中应调用真实API)
WEATHER_DATA = {
    "北京": {"temp": 25, "condition": "晴", "humidity": 45, "wind": "北风3级"},
    "上海": {"temp": 28, "condition": "多云", "humidity": 70, "wind": "东南风2级"},
    "广州": {"temp": 32, "condition": "雷阵雨", "humidity": 85, "wind": "南风2级"},
    "深圳": {"temp": 31, "condition": "多云", "humidity": 80, "wind": "西南风3级"},
    "杭州": {"temp": 26, "condition": "晴转多云", "humidity": 60, "wind": "东风2级"},
    "成都": {"temp": 24, "condition": "阴", "humidity": 75, "wind": "微风"},
    "武汉": {"temp": 29, "condition": "晴", "humidity": 55, "wind": "北风2级"},
    "西安": {"temp": 27, "condition": "晴", "humidity": 40, "wind": "西北风3级"},
}

# ==================== 工具参数 ====================

class WeatherQueryArgs(BaseModel):
    city: str
    unit: str = "celsius"

class ForecastArgs(BaseModel):
    city: str
    days: int = 3

class CompareArgs(BaseModel):
    cities: list[str]

# ==================== Tools ====================

@server.tool()
async def query_weather(args: WeatherQueryArgs) -> ToolResult:
    """查询指定城市的当前天气"""
    city = args.city

    if city not in WEATHER_DATA:
        available = ", ".join(WEATHER_DATA.keys())
        return ToolResult(
            content=f"未找到 {city} 的天气数据。支持的城市: {available}",
            isError=True
        )

    data = WEATHER_DATA[city]
    temp = data["temp"]
    if args.unit == "fahrenheit":
        temp = temp * 9/5 + 32

    result = {
        "city": city,
        "temperature": f"{temp}°{'F' if args.unit == 'fahrenheit' else 'C'}",
        "condition": data["condition"],
        "humidity": f"{data['humidity']}%",
        "wind": data["wind"],
        "update_time": datetime.now().isoformat()
    }

    return ToolResult(content=json.dumps(result, ensure_ascii=False))

@server.tool()
async def get_forecast(args: ForecastArgs) -> ToolResult:
    """获取未来几天的天气预报"""
    if args.city not in WEATHER_DATA:
        return ToolResult(
            content=f"未找到 {args.city} 的天气数据",
            isError=True
        )

    base = WEATHER_DATA[args.city]
    forecasts = []
    for i in range(args.days):
        forecast = {
            "date": f"第{i+1}天",
            "city": args.city,
            "temp_high": base["temp"] + i,
            "temp_low": base["temp"] - 2 + i,
            "condition": base["condition"],
            "humidity": f"{base['humidity']}%"
        }
        forecasts.append(forecast)

    return ToolResult(
        content=json.dumps(forecasts, ensure_ascii=False, indent=2)
    )

@server.tool()
async def compare_cities(args: CompareArgs) -> ToolResult:
    """比较多个城市的天气"""
    results = []
    for city in args.cities:
        if city in WEATHER_DATA:
            data = WEATHER_DATA[city]
            results.append({
                "city": city,
                "temp": f"{data['temp']}°C",
                "condition": data["condition"]
            })
        else:
            results.append({
                "city": city,
                "error": "数据不可用"
            })

    return ToolResult(
        content=json.dumps(results, ensure_ascii=False, indent=2)
    )

# ==================== Resources ====================

@server.resource("weather://cities")
async def get_supported_cities() -> ResourceContents:
    """获取支持的城市列表"""
    cities = list(WEATHER_DATA.keys())
    return ResourceContents(
        uri="weather://cities",
        mimeType="application/json",
        text=json.dumps(cities, ensure_ascii=False)
    )

@server.resource("weather://{city}/current")
async def get_city_weather_resource(city: str) -> ResourceContents:
    """获取城市当前天气资源"""
    if city not in WEATHER_DATA:
        raise ValueError(f"城市不存在: {city}")

    data = WEATHER_DATA[city]
    return ResourceContents(
        uri=f"weather://{city}/current",
        mimeType="application/json",
        text=json.dumps({
            "city": city,
            "data": data,
            "timestamp": datetime.now().isoformat()
        }, ensure_ascii=False)
    )

# ==================== Prompts ====================

@server.prompt()
async def weather_advisor_prompt(
    city: str,
    activity: str
) -> list[PromptMessage]:
    """天气出行建议提示模板"""
    weather_data = WEATHER_DATA.get(city, {})
    prompt_text = f"""请根据以下天气信息,为用户的{activity}活动提供出行建议:

城市: {city}
温度: {weather_data.get('temp', '未知')}°C
天气: {weather_data.get('condition', '未知')}
湿度: {weather_data.get('humidity', '未知')}%
风力: {weather_data.get('wind', '未知')}

请给出:
1. 是否适合进行该活动
2. 穿衣建议
3. 注意事项
4. 替代方案(如果不适合)"""

    return [PromptMessage(role=Role.USER, content=prompt_text)]

# ==================== 启动 ====================

async def main():
    from mcp.server.stdio import stdio_server
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream, write_stream,
            server.create_initialization_options()
        )

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

TypeScript MCP Server示例

TypeScript是MCP的另一种主流开发语言。以下是使用官方TypeScript SDK构建MCP Server的完整示例:

TypeScript - MCP Server基础示例
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建MCP Server实例
const server = new McpServer({
  name: "weather-server",
  version: "1.0.0",
});

// 定义工具 - 使用zod进行参数验证
server.tool(
  "query_weather",
  "查询指定城市的当前天气",
  {
    city: z.string().describe("城市名称"),
    unit: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("温度单位"),
  },
  async ({ city, unit }) => {
    // 模拟天气数据
    const weatherData: Record<string, any> = {
      "北京": { temp: 25, condition: "晴", humidity: 45 },
      "上海": { temp: 28, condition: "多云", humidity: 70 },
      "广州": { temp: 32, condition: "雷阵雨", humidity: 85 },
    };

    const data = weatherData[city];
    if (!data) {
      return {
        content: [{ type: "text", text: `未找到 ${city} 的天气数据` }],
        isError: true,
      };
    }

    let temp = data.temp;
    if (unit === "fahrenheit") {
      temp = temp * 9/5 + 32;
    }

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          city,
          temperature: `${temp}°${unit === "fahrenheit" ? "F" : "C"}`,
          condition: data.condition,
          humidity: `${data.humidity}%`,
        }),
      }],
    };
  }
);

// 定义资源
server.resource(
  "config",
  "config://app",
  async (uri) => ({
    contents: [{
      uri: uri.href,
      mimeType: "application/json",
      text: JSON.stringify({
        name: "天气查询服务",
        version: "1.0.0",
        supportedCities: ["北京", "上海", "广州"],
      }),
    }],
  })
);

// 启动Server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server已启动");
}

main().catch(console.error);

Python vs TypeScript对比

特性 Python SDK TypeScript SDK
参数验证 Pydantic BaseModel zod schema
异步支持 asyncio 原生async/await
类型安全 运行时检查 编译时+运行时
包管理 pip / poetry npm / pnpm
适用场景 数据处理、ML集成 Web服务、全栈应用

错误处理与重试模式

健壮的MCP Server需要完善的错误处理机制。以下是常见的错误处理模式和最佳实践:

Python - MCP错误处理模式
import asyncio
import logging
from functools import wraps
from typing import Callable, Any

logger = logging.getLogger("mcp-server")

# ==================== 重试装饰器 ====================

def with_retry(
    max_retries: int = 3,
    delay: float = 1.0,
    backoff: float = 2.0,
    exceptions: tuple = (Exception,)
):
    """带指数退避的重试装饰器"""
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        async def wrapper(*args, **kwargs) -> Any:
            last_exception = None
            current_delay = delay

            for attempt in range(max_retries + 1):
                try:
                    return await func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt < max_retries:
                        logger.warning(
                            f"尝试 {attempt + 1}/{max_retries} 失败: {e}, "
                            f"{current_delay}秒后重试..."
                        )
                        await asyncio.sleep(current_delay)
                        current_delay *= backoff
                    else:
                        logger.error(f"所有重试均失败: {e}")

            raise last_exception
        return wrapper
    return decorator

# ==================== 工具错误处理 ====================

class MCPErrorHandler:
    """MCP错误处理器"""

    @staticmethod
    def create_error_result(message: str, error_code: str = "UNKNOWN_ERROR"):
        """创建标准错误结果"""
        return {
            "content": [{
                "type": "text",
                "text": f"[{error_code}] {message}"
            }],
            "isError": True,
            "_meta": {"errorCode": error_code}
        }

    @staticmethod
    def handle_validation_error(e: Exception):
        """处理参数验证错误"""
        return MCPErrorHandler.create_error_result(
            f"参数验证失败: {str(e)}",
            error_code="VALIDATION_ERROR"
        )

    @staticmethod
    def handle_not_found(resource: str, identifier: str):
        """处理资源未找到"""
        return MCPErrorHandler.create_error_result(
            f"未找到{resource}: {identifier}",
            error_code="NOT_FOUND"
        )

    @staticmethod
    def handle_permission_denied(operation: str):
        """处理权限不足"""
        return MCPErrorHandler.create_error_result(
            f"权限不足,无法执行操作: {operation}",
            error_code="PERMISSION_DENIED"
        )

    @staticmethod
    def handle_rate_limit(retry_after: int = 60):
        """处理速率限制"""
        return {
            "content": [{
                "type": "text",
                "text": f"请求过于频繁,请在{retry_after}秒后重试"
            }],
            "isError": True,
            "_meta": {
                "errorCode": "RATE_LIMITED",
                "retryAfter": retry_after
            }
        }

# ==================== 使用示例 ====================

@server.tool()
@with_retry(max_retries=3, delay=1.0, exceptions=(ConnectionError,))
async def query_weather_with_retry(args: WeatherQueryArgs) -> ToolResult:
    """带重试机制的天气查询工具"""
    try:
        # 模拟可能失败的API调用
        data = await fetch_weather_api(args.city)
        return ToolResult(content=json.dumps(data))
    except ConnectionError as e:
        logger.error(f"天气API连接失败: {e}")
        raise  # 交给重试装饰器处理
    except TimeoutError as e:
        return MCPErrorHandler.create_error_result(
            "天气API请求超时",
            error_code="TIMEOUT"
        )
    except Exception as e:
        logger.exception("未知错误")
        return MCPErrorHandler.create_error_result(
            f"查询失败: {str(e)}",
            error_code="INTERNAL_ERROR"
        )
Server测试

可以使用MCP Inspector工具来测试Server。运行 npx @modelcontextprotocol/inspector python weather_server.py 即可在浏览器中调试所有工具、资源和提示模板。

十、MCP生态系统与未来展望

MCP协议自发布以来,已经形成了一个活跃的生态系统。从官方Server到社区贡献,从工具库到最佳实践,MCP正在成为AI工具集成的事实标准。

官方MCP Server

  • 文件系统:安全的本地文件访问,支持读写、目录列表、文件搜索
  • GitHub:仓库管理、Issue、PR操作、代码搜索
  • PostgreSQL:数据库查询和管理,支持Schema探索
  • Slack:消息发送、频道管理、用户搜索
  • Google Drive:文件访问、搜索、权限管理
  • Puppeteer:浏览器自动化、截图、页面交互
  • Brave Search:网页搜索、新闻搜索
  • Memory:持久化记忆存储和检索

社区贡献

除了官方Server,MCP社区也贡献了大量高质量的Server和工具库。这些社区项目覆盖了更多使用场景,推动了MCP生态的繁荣。

社区热门项目

  • Notion MCP:Notion API集成,支持页面、数据库、块操作
  • Airtable MCP:Airtable数据查询和管理
  • Linear MCP:Linear项目管理工具集成
  • Figma MCP:Figma设计文件访问和操作
  • Docker MCP:Docker容器管理和部署
  • Kubernetes MCP:K8s集群管理
  • AWS MCP:AWS服务集成(S3、Lambda、DynamoDB等)
  • Cloudflare MCP:Cloudflare Workers和R2集成

开发工具

  • MCP Inspector:官方调试工具,可视化测试Server能力
  • MCP CLI:命令行工具,快速创建和测试Server
  • MCP Dev:开发服务器,支持热重载和实时调试
  • MCP Proxy:代理工具,用于测试和监控MCP通信

未来发展方向

协议演进路线

  • 认证标准化:引入OAuth 2.0等标准认证机制,支持企业级SSO
  • 远程发现:通过DNS/HTTP发现远程MCP Server,支持动态服务发现
  • 权限模型:更细粒度的权限控制框架,支持RBAC和ABAC
  • 性能优化:减少协议开销,提升大数据传输效率,支持流式处理
  • 多模态支持:支持图像、音频、视频等非文本数据的传输和处理
  • 批量操作:支持批量Tool调用,减少网络往返次数
  • 事务支持:引入事务机制,确保多个操作的原子性
加入MCP生态

如果你正在开发AI应用,强烈建议采用MCP协议来处理工具集成。这不仅可以减少开发工作量,还能让你的应用立即兼容现有的MCP Server生态。访问 modelcontextprotocol.io 获取最新文档和SDK。你也可以通过GitHub贡献自己的MCP Server,帮助生态繁荣发展。

练习题

练习1: MCP Server开发

开发一个TODO列表MCP Server,支持创建、完成、删除和列出待办事项。使用Python或TypeScript实现,并编写测试代码验证所有功能。

提示:考虑使用内存存储或JSON文件持久化数据。为每个TODO项添加ID、标题、描述、完成状态和创建时间字段。

练习2: 多Server聚合

设计一个MCP代理(Aggregator),能够同时连接文件系统Server和数据库Server,并提供统一的查询接口。编写客户端代码测试聚合功能。

提示:参考本章"多Server聚合模式"一节,使用MultiServerHost类管理多个连接。考虑如何处理不同Server返回的结果格式差异。

练习3: 安全审计

分析本章提供的文件系统Server代码,识别至少3个潜在的安全风险,并提出具体的修复方案。编写安全测试用例验证修复效果。

提示:关注路径遍历、命令注入、权限绕过等常见漏洞。参考OWASP Top 10和MCP安全最佳实践。

练习4: 生态贡献

选择一个你常用的API(如天气、新闻、翻译等),将其封装为MCP Server并开源。编写完整的README文档和使用示例。

提示:参考官方MCP Server仓库的结构,包含pyproject.toml、README.md、LICENSE等文件。添加详细的API文档和配置说明。

练习5: Prompt模板设计

为一个代码审查工具设计MCP Prompt模板,支持多种审查场景(安全审查、性能优化、代码风格等)。模板应能根据输入的代码片段和审查类型生成合适的提示。

提示:使用动态Prompt模板,根据参数生成不同的审查指令。考虑如何平衡提示的详细程度和LLM的上下文限制。

章节小结

  • MCP是标准化AI与外部服务交互的开放协议,解决了工具集成的碎片化问题
  • 三层架构(Host、Client、Server)提供了清晰的职责分离和安全边界
  • 四大原语(Resources、Tools、Prompts、Sampling)覆盖了AI应用的主要集成场景
  • JSON-RPC 2.0提供了轻量级、可扩展的协议基础
  • stdio和SSE两种传输机制分别适用于本地和远程部署场景
  • 安全机制贯穿协议设计,包括输入验证、权限控制和人类审批
  • MCP生态正在快速发展,越来越多的AI应用和Server正在涌现

核心要点回顾:MCP的设计哲学是"一次集成,处处可用"。通过标准化协议,开发者只需实现一次MCP Server,即可被所有支持MCP的Host调用。这种解耦模式不仅降低了集成成本,还促进了AI工具生态的繁荣。

下一步学习建议:建议读者亲自尝试编写一个简单的MCP Server(如练习1),加深对协议的理解。然后可以阅读官方文档和SDK源码,了解更高级的特性。最后,考虑为开源社区贡献自己的MCP Server,将所学知识付诸实践。