第三十三章: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简介:为什么需要模型上下文协议
在大语言模型快速发展的今天,每个AI应用都需要与外部数据源、工具和服务进行交互。然而,如果没有统一的标准,每个集成都需要从头开发,导致了大量重复劳动和碎片化问题。MCP(Model Context Protocol)正是为了解决这个问题而诞生的开放标准协议。
MCP是什么
MCP是由Anthropic于2024年底发布的开源协议,全称为Model Context Protocol(模型上下文协议)。它定义了一种标准化的方式,让AI模型能够安全、高效地与外部世界进行交互——包括读取数据、执行操作、访问工具等。
- 标准化接口:就像USB-C统一了设备连接方式,MCP统一了AI与外部服务的连接方式
- 可复用组件:一个MCP Server可以被任何支持MCP的AI应用复用
- 安全隔离:通过明确定义的权限边界,确保数据访问的安全性
- 跨平台兼容:不依赖特定的LLM提供商或应用框架
在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) |
| 安全边界 | 由应用控制 | 协议层内置权限控制 |
二、架构设计: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 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发送调试或信息日志
// 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聚合。
"""
多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返回的数据
- 实时信息:系统状态、监控数据等
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定义输入参数
- 结果返回:执行后返回文本或结构化数据
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可以将上下文和指令封装为可复用的模板,简化复杂的交互流程。
Pythonfrom 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赋予了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内部错误,如未捕获的异常
// 参数验证错误
{
"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收到取消通知后应尽快停止处理,并返回部分结果(如果可能)
// 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
- 开发和测试环境
- 单用户场景
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通过网络访问
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 Serverimport { 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 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
目前已有多款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的权限范围;记录所有操作日志
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被恶意请求压垮。
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执行时间,用于性能监控和优化
"""
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)进行日志聚合
8.3 人类审批机制
对于高风险操作,MCP推荐实现人类审批(Human-in-the-Loop)机制。Host应该在执行敏感Tool前征求用户确认。
审批流程设计
- 1. LLM决定调用某个Tool
- 2. Host检查该Tool是否需要审批
- 3. 如果需要,弹出确认对话框,显示Tool名称、参数和预期效果
- 4. 用户确认后执行,拒绝则返回错误
- 5. 记录用户的选择和操作结果
"""
人类审批机制示例
在执行敏感操作前征求用户确认
"""
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都需要提供证书。
"""
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"
)
可以使用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调用,减少网络往返次数
- 事务支持:引入事务机制,确保多个操作的原子性
如果你正在开发AI应用,强烈建议采用MCP协议来处理工具集成。这不仅可以减少开发工作量,还能让你的应用立即兼容现有的MCP Server生态。访问 modelcontextprotocol.io 获取最新文档和SDK。你也可以通过GitHub贡献自己的MCP Server,帮助生态繁荣发展。
练习题
开发一个TODO列表MCP Server,支持创建、完成、删除和列出待办事项。使用Python或TypeScript实现,并编写测试代码验证所有功能。
提示:考虑使用内存存储或JSON文件持久化数据。为每个TODO项添加ID、标题、描述、完成状态和创建时间字段。
设计一个MCP代理(Aggregator),能够同时连接文件系统Server和数据库Server,并提供统一的查询接口。编写客户端代码测试聚合功能。
提示:参考本章"多Server聚合模式"一节,使用MultiServerHost类管理多个连接。考虑如何处理不同Server返回的结果格式差异。
分析本章提供的文件系统Server代码,识别至少3个潜在的安全风险,并提出具体的修复方案。编写安全测试用例验证修复效果。
提示:关注路径遍历、命令注入、权限绕过等常见漏洞。参考OWASP Top 10和MCP安全最佳实践。
选择一个你常用的API(如天气、新闻、翻译等),将其封装为MCP Server并开源。编写完整的README文档和使用示例。
提示:参考官方MCP Server仓库的结构,包含pyproject.toml、README.md、LICENSE等文件。添加详细的API文档和配置说明。
为一个代码审查工具设计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,将所学知识付诸实践。