Python入门指南-AI番外-MCP完整教程:从零开始学会Model Context Protocol
🎯 教程目标
欢迎来到MCP(Model Context Protocol)的世界!本教程将带你从零开始,学会什么是MCP,如何使用它,以及如何用Python创建自己的MCP应用。无论你是编程小白还是有一定基础的开发者,都能轻松掌握!
📚 目录
🤔 MCP是什么?
简单理解
想象一下,你有一个非常聪明的助手(比如通义千问这样的AI),但这个助手被困在一个房间里,无法直接接触外面的世界。MCP就像是为这个助手开了一扇窗户,让它可以:
- 📁 读取和管理你的文件
- 🌐 访问网络获取信息
- 🗄️ 连接数据库查询数据
- 🔧 使用各种工具和服务
技术定义
MCP(Model Context Protocol)是一个开放标准,它定义了AI模型如何与外部系统安全、可靠地交互。它就像是AI和外部世界之间的"翻译官"。
graph TD
A[用户] --> B[AI助手<br/>通义千问]
B --> C[MCP协议]
C --> D[文件系统]
C --> E[网络API]
C --> F[数据库]
C --> G[其他工具]
D --> H[读写文件]
E --> I[获取网络数据]
F --> J[查询数据]
G --> K[执行任务]
style A fill:#e1f5fe
style B fill:#fff3e0
style C fill:#f3e5f5
style D fill:#e8f5e8
style E fill:#e8f5e8
style F fill:#e8f5e8
style G fill:#e8f5e8
🔍 为什么需要MCP?
传统方式的问题
在MCP出现之前,让AI与外部系统交互就像让两个说不同语言的人交流一样困难:
graph LR
A[AI模型] -.-> B[应用1]
A -.-> C[应用2]
A -.-> D[应用3]
B --> E[自定义接口1]
C --> F[自定义接口2]
D --> G[自定义接口3]
style A fill:#ffcdd2
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#ffcdd2
问题:
- 每个应用都需要定制开发
- 安全性难以保证
- 维护成本高
- 无法复用
MCP的优势
MCP就像制定了一套通用的"外交协议":
graph LR
A[AI模型] --> B[MCP标准接口]
B --> C[应用1]
B --> D[应用2]
B --> E[应用3]
style A fill:#c8e6c9
style B fill:#fff9c4
style C fill:#c8e6c9
style D fill:#c8e6c9
style E fill:#c8e6c9
优势:
- 🔒 安全:统一的安全标准
- 🔄 可复用:一次开发,处处使用
- 📈 可扩展:轻松添加新功能
- 🛡️ 稳定:标准化的接口
🏗️ MCP的核心概念
1. 服务器和客户端
sequenceDiagram
participant C as MCP客户端<br/>(AI应用)
participant S as MCP服务器<br/>(工具提供者)
C->>S: 连接请求
S->>C: 连接确认
C->>S: 调用工具
S->>C: 返回结果
C->>S: 断开连接
- MCP客户端:AI应用(如通义千问)
- MCP服务器:提供具体功能的工具
2. 资源(Resources)
资源是MCP服务器可以提供的数据,比如:
- 📄 文件内容
- 🌐 网页数据
- 🗄️ 数据库记录
3. 工具(Tools)
工具是MCP服务器可以执行的操作,比如:
- ✍️ 写文件
- 🔍 搜索
- 📊 数据分析
4. 提示(Prompts)
提示是预定义的对话模板,帮助AI更好地理解任务。
🔧 环境准备
1. 安装Python
首先确保你的电脑上安装了Python 3.8或更高版本。
# 检查Python版本
python --version
# 如果版本过低,请从官网下载最新版本
# https://www.python.org/downloads/
2. 安装必要的包
# 创建虚拟环境(推荐)
python -m venv mcp_env
# 激活虚拟环境
# Windows:
mcp_env\Scripts\activate
# macOS/Linux:
source mcp_env/bin/activate
# 安装MCP相关包
pip install mcp
pip install dashscope # 通义千问的SDK
pip install requests
pip install asyncio
3. 获取通义千问API密钥
- 访问 阿里云控制台
- 创建API Key
- 保存API Key(待会儿会用到)
🚀 第一个MCP应用
让我们创建一个简单的MCP服务器,它可以告诉我们当前的时间。
1. 创建基础MCP服务器
# file: simple_mcp_server.py
import asyncio
import json
from datetime import datetime
from mcp.server import Server
from mcp.types import TextContent, Tool
# 创建MCP服务器实例
server = Server("simple-time-server")
# 定义一个获取当前时间的工具
@server.list_tools()
async def list_tools():
"""列出所有可用的工具"""
return [
Tool(
name="get_current_time",
description="获取当前的日期和时间",
inputSchema={
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "时间格式,默认为'%Y-%m-%d %H:%M:%S'",
"default": "%Y-%m-%d %H:%M:%S"
}
}
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
"""执行工具调用"""
if name == "get_current_time":
# 获取格式参数,如果没有则使用默认格式
time_format = arguments.get("format", "%Y-%m-%d %H:%M:%S")
# 获取当前时间
current_time = datetime.now().strftime(time_format)
return [TextContent(
type="text",
text=f"当前时间是: {current_time}"
)]
raise ValueError(f"未知的工具: {name}")
# 启动服务器的函数
async def main():
"""启动MCP服务器"""
print("🚀 启动简单时间服务器...")
print("服务器正在监听连接...")
# 这里我们使用标准输入输出作为传输方式
# 在实际应用中,你可能会使用WebSocket或HTTP
async with server.run_stdio():
await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())
2. 测试MCP服务器
创建一个简单的测试客户端:
# file: test_mcp_client.py
import asyncio
import json
from mcp.client import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def test_time_server():
"""测试时间服务器"""
# 配置服务器参数
server_params = StdioServerParameters(
command="python",
args=["simple_mcp_server.py"]
)
print("🔗 连接到时间服务器...")
# 创建客户端会话
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化连接
await session.initialize()
print("✅ 连接成功!")
# 获取可用工具列表
tools = await session.list_tools()
print(f"📋 可用工具: {[tool.name for tool in tools]}")
# 调用获取时间的工具
result = await session.call_tool("get_current_time", {})
print(f"⏰ 结果: {result.content[0].text}")
# 使用自定义格式
result = await session.call_tool("get_current_time", {
"format": "%Y年%m月%d日 %H时%M分%S秒"
})
print(f"⏰ 自定义格式: {result.content[0].text}")
if __name__ == "__main__":
asyncio.run(test_time_server())
3. 运行测试
# 在终端中运行测试
python test_mcp_client.py
你应该看到类似的输出:
🔗 连接到时间服务器...
✅ 连接成功!
📋 可用工具: ['get_current_time']
⏰ 结果: 当前时间是: 2024-12-10 14:30:25
⏰ 自定义格式: 当前时间是: 2024年12月10日 14时30分25秒
🤖 与通义千问集成
现在让我们将MCP服务器与通义千问集成,创建一个真正智能的助手。 这里为什么使用通义千问呢?因为通义千问的模型可以处理各种任务,比如文本生成、图像生成、语音识别等,而MCP服务器则可以提供这些任务的接口。 且使我们国内的AI模型,可以更好的使用。
1. 创建通义千问客户端
# file: qianwen_mcp_client.py
import asyncio
import os
from dashscope import Generation
from mcp.client import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
class QianWenMCPClient:
def __init__(self, api_key: str):
"""初始化通义千问MCP客户端"""
self.api_key = api_key
os.environ['DASHSCOPE_API_KEY'] = api_key
self.mcp_sessions = {}
async def connect_mcp_server(self, server_name: str, server_params: StdioServerParameters):
"""连接到MCP服务器"""
print(f"🔗 连接到 {server_name} 服务器...")
try:
# 创建服务器连接
read, write = await stdio_client(server_params).__aenter__()
session = ClientSession(read, write)
await session.initialize()
# 获取可用工具
tools = await session.list_tools()
self.mcp_sessions[server_name] = {
'session': session,
'tools': tools
}
print(f"✅ 成功连接到 {server_name}")
print(f"📋 可用工具: {[tool.name for tool in tools]}")
except Exception as e:
print(f"❌ 连接 {server_name} 失败: {e}")
async def call_mcp_tool(self, server_name: str, tool_name: str, arguments: dict = None):
"""调用MCP工具"""
if server_name not in self.mcp_sessions:
raise ValueError(f"未连接到服务器: {server_name}")
session = self.mcp_sessions[server_name]['session']
try:
result = await session.call_tool(tool_name, arguments or {})
return result.content[0].text if result.content else "无返回内容"
except Exception as e:
return f"调用工具失败: {e}"
def chat_with_tools(self, user_message: str, available_tools: list = None):
"""与通义千问对话,支持工具调用"""
# 构建系统提示
system_prompt = """你是一个智能助手,可以调用外部工具来帮助用户。
可用工具:
- get_current_time: 获取当前时间
当用户询问时间相关信息时,你可以调用相应的工具。
请用自然、友好的语言回答用户的问题。"""
# 调用通义千问
try:
response = Generation.call(
model="qwen-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
],
temperature=0.7
)
if response.status_code == 200:
return response.output.text
else:
return f"调用通义千问失败: {response.message}"
except Exception as e:
return f"发生错误: {e}"
async def intelligent_chat(self, user_message: str):
"""智能对话,自动判断是否需要调用工具"""
# 简单的关键词检测,判断是否需要调用时间工具
time_keywords = ['时间', '现在', '几点', '日期', '今天', '当前时间']
need_time_tool = any(keyword in user_message for keyword in time_keywords)
if need_time_tool and 'simple-time-server' in self.mcp_sessions:
# 先获取时间信息
time_info = await self.call_mcp_tool('simple-time-server', 'get_current_time')
# 将时间信息加入上下文
enhanced_message = f"用户问题: {user_message}\n当前时间信息: {time_info}"
# 调用通义千问生成回答
response = self.chat_with_tools(enhanced_message)
return response
else:
# 直接调用通义千问
return self.chat_with_tools(user_message)
# 使用示例
async def main():
# 请替换为你的API Key
API_KEY = "your_api_key_here"
if API_KEY == "your_api_key_here":
print("❌ 请先设置你的通义千问API Key")
return
# 创建客户端
client = QianWenMCPClient(API_KEY)
# 连接到时间服务器
server_params = StdioServerParameters(
command="python",
args=["simple_mcp_server.py"]
)
await client.connect_mcp_server("simple-time-server", server_params)
# 交互式对话
print("\n🤖 智能助手已启动!输入 'quit' 退出")
print("你可以询问时间相关问题,或者其他任何问题。")
while True:
user_input = input("\n👤 你: ")
if user_input.lower() == 'quit':
break
response = await client.intelligent_chat(user_input)
print(f"🤖 助手: {response}")
if __name__ == "__main__":
asyncio.run(main())
2. 运行智能助手
# 设置API Key(请替换为你的真实API Key)
export DASHSCOPE_API_KEY="your_api_key_here"
# 运行智能助手
python qianwen_mcp_client.py
📁 实战项目:智能文件管理器
让我们创建一个更复杂的MCP应用——智能文件管理器,它可以帮助我们管理文件和文件夹。
1. 文件管理MCP服务器
# file: file_manager_server.py
import asyncio
import os
import json
import shutil
from pathlib import Path
from mcp.server import Server
from mcp.types import TextContent, Tool, Resource
class FileManagerServer:
def __init__(self):
self.server = Server("file-manager-server")
self.setup_handlers()
def setup_handlers(self):
"""设置处理器"""
@self.server.list_tools()
async def list_tools():
"""列出所有可用的工具"""
return [
Tool(
name="list_files",
description="列出指定目录中的文件和文件夹",
inputSchema={
"type": "object",
"properties": {
"directory": {
"type": "string",
"description": "要列出的目录路径,默认为当前目录"
},
"show_hidden": {
"type": "boolean",
"description": "是否显示隐藏文件",
"default": False
}
}
}
),
Tool(
name="create_directory",
description="创建新的目录",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "要创建的目录路径"
}
},
"required": ["path"]
}
),
Tool(
name="read_file",
description="读取文件内容",
inputSchema={
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "要读取的文件路径"
},
"encoding": {
"type": "string",
"description": "文件编码",
"default": "utf-8"
}
},
"required": ["file_path"]
}
),
Tool(
name="write_file",
description="写入文件内容",
inputSchema={
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "要写入的文件路径"
},
"content": {
"type": "string",
"description": "要写入的内容"
},
"encoding": {
"type": "string",
"description": "文件编码",
"default": "utf-8"
}
},
"required": ["file_path", "content"]
}
),
Tool(
name="delete_file",
description="删除文件或目录",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "要删除的文件或目录路径"
}
},
"required": ["path"]
}
),
Tool(
name="get_file_info",
description="获取文件或目录的详细信息",
inputSchema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "文件或目录路径"
}
},
"required": ["path"]
}
),
Tool(
name="search_files",
description="搜索文件",
inputSchema={
"type": "object",
"properties": {
"directory": {
"type": "string",
"description": "搜索目录",
"default": "."
},
"pattern": {
"type": "string",
"description": "搜索模式(支持通配符)"
},
"recursive": {
"type": "boolean",
"description": "是否递归搜索",
"default": True
}
},
"required": ["pattern"]
}
)
]
@self.server.call_tool()
async def call_tool(name: str, arguments: dict):
"""执行工具调用"""
try:
if name == "list_files":
return await self.list_files(arguments)
elif name == "create_directory":
return await self.create_directory(arguments)
elif name == "read_file":
return await self.read_file(arguments)
elif name == "write_file":
return await self.write_file(arguments)
elif name == "delete_file":
return await self.delete_file(arguments)
elif name == "get_file_info":
return await self.get_file_info(arguments)
elif name == "search_files":
return await self.search_files(arguments)
else:
raise ValueError(f"未知的工具: {name}")
except Exception as e:
return [TextContent(
type="text",
text=f"执行工具 {name} 时发生错误: {str(e)}"
)]
async def list_files(self, arguments: dict):
"""列出文件和文件夹"""
directory = arguments.get("directory", ".")
show_hidden = arguments.get("show_hidden", False)
try:
path = Path(directory)
if not path.exists():
return [TextContent(
type="text",
text=f"目录不存在: {directory}"
)]
if not path.is_dir():
return [TextContent(
type="text",
text=f"路径不是目录: {directory}"
)]
items = []
for item in path.iterdir():
if not show_hidden and item.name.startswith('.'):
continue
item_type = "📁" if item.is_dir() else "📄"
size = ""
if item.is_file():
size = f" ({self.format_size(item.stat().st_size)})"
items.append(f"{item_type} {item.name}{size}")
if not items:
result = f"目录 {directory} 为空"
else:
result = f"目录 {directory} 内容:\n" + "\n".join(items)
return [TextContent(type="text", text=result)]
except Exception as e:
return [TextContent(
type="text",
text=f"列出目录失败: {str(e)}"
)]
async def create_directory(self, arguments: dict):
"""创建目录"""
path = arguments["path"]
try:
Path(path).mkdir(parents=True, exist_ok=True)
return [TextContent(
type="text",
text=f"成功创建目录: {path}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"创建目录失败: {str(e)}"
)]
async def read_file(self, arguments: dict):
"""读取文件"""
file_path = arguments["file_path"]
encoding = arguments.get("encoding", "utf-8")
try:
path = Path(file_path)
if not path.exists():
return [TextContent(
type="text",
text=f"文件不存在: {file_path}"
)]
if not path.is_file():
return [TextContent(
type="text",
text=f"路径不是文件: {file_path}"
)]
with open(path, 'r', encoding=encoding) as f:
content = f.read()
return [TextContent(
type="text",
text=f"文件 {file_path} 的内容:\n{content}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"读取文件失败: {str(e)}"
)]
async def write_file(self, arguments: dict):
"""写入文件"""
file_path = arguments["file_path"]
content = arguments["content"]
encoding = arguments.get("encoding", "utf-8")
try:
path = Path(file_path)
# 确保父目录存在
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, 'w', encoding=encoding) as f:
f.write(content)
return [TextContent(
type="text",
text=f"成功写入文件: {file_path}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"写入文件失败: {str(e)}"
)]
async def delete_file(self, arguments: dict):
"""删除文件或目录"""
path = arguments["path"]
try:
path_obj = Path(path)
if not path_obj.exists():
return [TextContent(
type="text",
text=f"路径不存在: {path}"
)]
if path_obj.is_file():
path_obj.unlink()
return [TextContent(
type="text",
text=f"成功删除文件: {path}"
)]
elif path_obj.is_dir():
shutil.rmtree(path_obj)
return [TextContent(
type="text",
text=f"成功删除目录: {path}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"删除失败: {str(e)}"
)]
async def get_file_info(self, arguments: dict):
"""获取文件信息"""
path = arguments["path"]
try:
path_obj = Path(path)
if not path_obj.exists():
return [TextContent(
type="text",
text=f"路径不存在: {path}"
)]
stat = path_obj.stat()
info = [
f"路径: {path}",
f"类型: {'目录' if path_obj.is_dir() else '文件'}",
f"大小: {self.format_size(stat.st_size)}",
f"创建时间: {self.format_time(stat.st_ctime)}",
f"修改时间: {self.format_time(stat.st_mtime)}",
f"访问时间: {self.format_time(stat.st_atime)}"
]
return [TextContent(
type="text",
text="\n".join(info)
)]
except Exception as e:
return [TextContent(
type="text",
text=f"获取文件信息失败: {str(e)}"
)]
async def search_files(self, arguments: dict):
"""搜索文件"""
directory = arguments.get("directory", ".")
pattern = arguments["pattern"]
recursive = arguments.get("recursive", True)
try:
path = Path(directory)
if not path.exists():
return [TextContent(
type="text",
text=f"目录不存在: {directory}"
)]
if recursive:
matches = list(path.rglob(pattern))
else:
matches = list(path.glob(pattern))
if not matches:
return [TextContent(
type="text",
text=f"未找到匹配 '{pattern}' 的文件"
)]
results = []
for match in matches:
match_type = "📁" if match.is_dir() else "📄"
size = ""
if match.is_file():
size = f" ({self.format_size(match.stat().st_size)})"
results.append(f"{match_type} {match}{size}")
return [TextContent(
type="text",
text=f"找到 {len(matches)} 个匹配项:\n" + "\n".join(results)
)]
except Exception as e:
return [TextContent(
type="text",
text=f"搜索失败: {str(e)}"
)]
def format_size(self, size: int) -> str:
"""格式化文件大小"""
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024.0:
return f"{size:.1f} {unit}"
size /= 1024.0
return f"{size:.1f} TB"
def format_time(self, timestamp: float) -> str:
"""格式化时间戳"""
import datetime
return datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
async def run(self):
"""运行服务器"""
print("🚀 启动文件管理服务器...")
print("服务器正在监听连接...")
async with self.server.run_stdio():
await asyncio.Event().wait()
# 启动服务器
if __name__ == "__main__":
server = FileManagerServer()
asyncio.run(server.run())
2. 增强的智能助手
现在让我们创建一个更智能的助手,它可以同时使用时间服务器和文件管理服务器:
# file: enhanced_assistant.py
import asyncio
import os
import re
from dashscope import Generation
from mcp.client import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
class EnhancedAssistant:
def __init__(self, api_key: str):
"""初始化增强助手"""
self.api_key = api_key
os.environ['DASHSCOPE_API_KEY'] = api_key
self.mcp_sessions = {}
self.conversation_history = []
async def connect_servers(self):
"""连接到所有MCP服务器"""
servers = {
"time-server": {
"command": "python",
"args": ["simple_mcp_server.py"]
},
"file-manager": {
"command": "python",
"args": ["file_manager_server.py"]
}
}
for server_name, config in servers.items():
try:
server_params = StdioServerParameters(
command=config["command"],
args=config["args"]
)
print(f"🔗 连接到 {server_name}...")
# 创建连接
read, write = await stdio_client(server_params).__aenter__()
session = ClientSession(read, write)
await session.initialize()
# 获取工具列表
tools = await session.list_tools()
self.mcp_sessions[server_name] = {
'session': session,
'tools': {tool.name: tool for tool in tools}
}
print(f"✅ 成功连接 {server_name}")
print(f"📋 可用工具: {list(self.mcp_sessions[server_name]['tools'].keys())}")
except Exception as e:
print(f"❌ 连接 {server_name} 失败: {e}")
async def call_tool(self, server_name: str, tool_name: str, arguments: dict = None):
"""调用MCP工具"""
if server_name not in self.mcp_sessions:
return f"❌ 未连接到服务器: {server_name}"
session = self.mcp_sessions[server_name]['session']
try:
result = await session.call_tool(tool_name, arguments or {})
return result.content[0].text if result.content else "无返回内容"
except Exception as e:
return f"❌ 调用工具失败: {e}"
def analyze_user_intent(self, message: str):
"""分析用户意图,判断需要调用哪些工具"""
intent = {
'needs_time': False,
'needs_file_ops': False,
'file_operations': []
}
# 时间相关关键词
time_keywords = ['时间', '现在', '几点', '日期', '今天', '当前时间']
if any(keyword in message for keyword in time_keywords):
intent['needs_time'] = True
# 文件操作相关关键词
file_keywords = {
'list': ['列出', '查看', '显示', '目录', '文件夹', '文件'],
'read': ['读取', '打开', '查看内容', '显示内容'],
'write': ['写入', '创建', '保存', '新建'],
'delete': ['删除', '移除'],
'search': ['搜索', '查找', '寻找'],
'info': ['信息', '详情', '属性']
}
for operation, keywords in file_keywords.items():
if any(keyword in message for keyword in keywords):
intent['needs_file_ops'] = True
intent['file_operations'].append(operation)
return intent
def extract_file_params(self, message: str, operation: str):
"""从用户消息中提取文件操作参数"""
params = {}
# 提取文件路径(简单的正则匹配)
path_patterns = [
r'["\']([^"\']+)["\']', # 引号中的路径
r'(\S+\.\w+)', # 带扩展名的文件
r'(/\S+)', # 绝对路径
r'(\.{1,2}/\S+)', # 相对路径
]
for pattern in path_patterns:
matches = re.findall(pattern, message)
if matches:
params['path'] = matches[0]
break
# 根据操作类型提取特定参数
if operation == 'write':
# 尝试提取要写入的内容
content_patterns = [
r'内容[是为]?["\']([^"\']+)["\']',
r'写入["\']([^"\']+)["\']',
r'内容:(.+)',
]
for pattern in content_patterns:
matches = re.findall(pattern, message)
if matches:
params['content'] = matches[0]
break
elif operation == 'search':
# 提取搜索模式
search_patterns = [
r'搜索["\']([^"\']+)["\']',
r'查找["\']([^"\']+)["\']',
r'找["\']([^"\']+)["\']',
]
for pattern in search_patterns:
matches = re.findall(pattern, message)
if matches:
params['pattern'] = matches[0]
break
return params
async def execute_file_operations(self, message: str, operations: list):
"""执行文件操作"""
results = []
for operation in operations:
params = self.extract_file_params(message, operation)
if operation == 'list':
directory = params.get('path', '.')
result = await self.call_tool('file-manager', 'list_files', {'directory': directory})
results.append(f"📁 目录列表:\n{result}")
elif operation == 'read':
if 'path' in params:
result = await self.call_tool('file-manager', 'read_file', {'file_path': params['path']})
results.append(f"📄 文件内容:\n{result}")
else:
results.append("❌ 请指定要读取的文件路径")
elif operation == 'write':
if 'path' in params and 'content' in params:
result = await self.call_tool('file-manager', 'write_file', {
'file_path': params['path'],
'content': params['content']
})
results.append(f"✍️ 写入结果:\n{result}")
else:
results.append("❌ 请指定文件路径和内容")
elif operation == 'delete':
if 'path' in params:
result = await self.call_tool('file-manager', 'delete_file', {'path': params['path']})
results.append(f"🗑️ 删除结果:\n{result}")
else:
results.append("❌ 请指定要删除的文件路径")
elif operation == 'search':
if 'pattern' in params:
result = await self.call_tool('file-manager', 'search_files', {'pattern': params['pattern']})
results.append(f"🔍 搜索结果:\n{result}")
else:
results.append("❌ 请指定搜索模式")
elif operation == 'info':
if 'path' in params:
result = await self.call_tool('file-manager', 'get_file_info', {'path': params['path']})
results.append(f"ℹ️ 文件信息:\n{result}")
else:
results.append("❌ 请指定文件路径")
return "\n\n".join(results)
async def smart_response(self, user_message: str):
"""智能响应用户消息"""
# 分析用户意图
intent = self.analyze_user_intent(user_message)
# 收集上下文信息
context_info = []
# 获取时间信息
if intent['needs_time']:
time_info = await self.call_tool('time-server', 'get_current_time')
context_info.append(f"当前时间: {time_info}")
# 执行文件操作
if intent['needs_file_ops']:
file_results = await self.execute_file_operations(user_message, intent['file_operations'])
context_info.append(f"文件操作结果: {file_results}")
# 构建增强的消息
if context_info:
enhanced_message = f"""用户问题: {user_message}
相关信息:
{chr(10).join(context_info)}
请根据上述信息,用自然、友好的语言回答用户的问题。"""
else:
enhanced_message = user_message
# 调用通义千问生成回答
try:
response = Generation.call(
model="qwen-turbo",
messages=[
{"role": "system", "content": """你是一个智能助手,可以帮助用户管理文件和获取信息。
请用自然、友好的语言回答用户的问题。如果有相关的操作结果,请整合到你的回答中。"""},
{"role": "user", "content": enhanced_message}
],
temperature=0.7
)
if response.status_code == 200:
return response.output.text
else:
return f"❌ 调用通义千问失败: {response.message}"
except Exception as e:
return f"❌ 发生错误: {e}"
async def start_interactive_session(self):
"""启动交互式会话"""
print("\n🤖 增强智能助手已启动!")
print("我可以帮你:")
print(" ⏰ 获取当前时间")
print(" 📁 管理文件和文件夹")
print(" 📄 读写文件内容")
print(" 🔍 搜索文件")
print(" ℹ️ 获取文件信息")
print("\n输入 'quit' 或 'exit' 退出")
print("输入 'help' 获取帮助")
while True:
try:
user_input = input("\n👤 你: ").strip()
if user_input.lower() in ['quit', 'exit']:
print("👋 再见!")
break
if user_input.lower() == 'help':
self.show_help()
continue
if not user_input:
continue
print("🤔 思考中...")
response = await self.smart_response(user_input)
print(f"🤖 助手: {response}")
except KeyboardInterrupt:
print("\n👋 再见!")
break
except Exception as e:
print(f"❌ 发生错误: {e}")
def show_help(self):
"""显示帮助信息"""
help_text = """
📚 使用帮助:
⏰ 时间相关:
- "现在几点了?"
- "今天是什么日期?"
📁 文件操作:
- "列出当前目录的文件"
- "查看 /path/to/directory 目录"
- "读取 example.txt 文件"
- "创建文件 test.txt,内容是 'Hello World'"
- "删除 old_file.txt"
- "搜索 '*.py' 文件"
- "获取 document.pdf 的信息"
💡 提示:
- 文件路径可以用引号括起来
- 支持相对路径和绝对路径
- 可以同时询问多个问题
"""
print(help_text)
# 主程序
async def main():
# 请替换为你的API Key
API_KEY = "your_api_key_here"
if API_KEY == "your_api_key_here":
print("❌ 请先设置你的通义千问API Key")
print("在代码中找到 'your_api_key_here' 并替换为你的真实API Key")
return
# 创建助手
assistant = EnhancedAssistant(API_KEY)
# 连接到MCP服务器
await assistant.connect_servers()
# 启动交互式会话
await assistant.start_interactive_session()
if __name__ == "__main__":
asyncio.run(main())
3. 测试智能文件管理器
让我们创建一个测试脚本来验证我们的文件管理器:
# file: test_file_manager.py
import asyncio
import os
import tempfile
from pathlib import Path
from mcp.client import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def test_file_manager():
"""测试文件管理器的所有功能"""
# 创建临时测试目录
with tempfile.TemporaryDirectory() as temp_dir:
print(f"🧪 在临时目录中测试: {temp_dir}")
# 连接到文件管理服务器
server_params = StdioServerParameters(
command="python",
args=["file_manager_server.py"]
)
print("🔗 连接到文件管理服务器...")
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
print("✅ 连接成功!")
# 测试1: 列出目录
print("\n📋 测试1: 列出目录")
result = await session.call_tool("list_files", {"directory": temp_dir})
print(f"结果: {result.content[0].text}")
# 测试2: 创建目录
print("\n📁 测试2: 创建目录")
test_dir = os.path.join(temp_dir, "test_folder")
result = await session.call_tool("create_directory", {"path": test_dir})
print(f"结果: {result.content[0].text}")
# 测试3: 写入文件
print("\n✍️ 测试3: 写入文件")
test_file = os.path.join(test_dir, "test.txt")
result = await session.call_tool("write_file", {
"file_path": test_file,
"content": "这是一个测试文件!\n包含多行内容。"
})
print(f"结果: {result.content[0].text}")
# 测试4: 读取文件
print("\n📄 测试4: 读取文件")
result = await session.call_tool("read_file", {"file_path": test_file})
print(f"结果: {result.content[0].text}")
# 测试5: 获取文件信息
print("\nℹ️ 测试5: 获取文件信息")
result = await session.call_tool("get_file_info", {"path": test_file})
print(f"结果: {result.content[0].text}")
# 测试6: 创建更多测试文件
print("\n📝 测试6: 创建更多测试文件")
for i in range(3):
file_path = os.path.join(test_dir, f"file_{i}.txt")
await session.call_tool("write_file", {
"file_path": file_path,
"content": f"这是第{i}个测试文件"
})
print(f"创建文件: {file_path}")
# 测试7: 再次列出目录
print("\n📋 测试7: 再次列出目录")
result = await session.call_tool("list_files", {"directory": test_dir})
print(f"结果: {result.content[0].text}")
# 测试8: 搜索文件
print("\n🔍 测试8: 搜索文件")
result = await session.call_tool("search_files", {
"directory": temp_dir,
"pattern": "*.txt"
})
print(f"结果: {result.content[0].text}")
# 测试9: 删除文件
print("\n🗑️ 测试9: 删除文件")
result = await session.call_tool("delete_file", {
"path": os.path.join(test_dir, "file_1.txt")
})
print(f"结果: {result.content[0].text}")
# 测试10: 最终列出目录
print("\n📋 测试10: 最终列出目录")
result = await session.call_tool("list_files", {"directory": test_dir})
print(f"结果: {result.content[0].text}")
print("\n✅ 所有测试完成!")
if __name__ == "__main__":
asyncio.run(test_file_manager())
4. 系统架构图
graph TB
subgraph "用户界面层"
A[用户输入] --> B[增强智能助手]
end
subgraph "AI处理层"
B --> C[意图分析]
C --> D[通义千问LLM]
D --> E[响应生成]
end
subgraph "MCP协议层"
B --> F[MCP客户端]
F --> G[时间服务器]
F --> H[文件管理服务器]
end
subgraph "工具执行层"
G --> I[获取时间]
H --> J[文件操作]
J --> K[读取文件]
J --> L[写入文件]
J --> M[搜索文件]
J --> N[删除文件]
J --> O[获取信息]
end
subgraph "系统资源"
I --> P[系统时间]
K --> Q[文件系统]
L --> Q
M --> Q
N --> Q
O --> Q
end
E --> R[用户反馈]
style A fill:#e3f2fd
style B fill:#fff3e0
style C fill:#f3e5f5
style D fill:#e8f5e8
style F fill:#fff8e1
style G fill:#fce4ec
style H fill:#fce4ec
🎯 高级功能和最佳实践
1. 错误处理和日志记录
# file: advanced_mcp_server.py
import asyncio
import logging
from datetime import datetime
from mcp.server import Server
from mcp.types import TextContent, Tool
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('mcp_server.log'),
logging.StreamHandler()
]
)
class AdvancedMCPServer:
def __init__(self, server_name: str):
self.server = Server(server_name)
self.logger = logging.getLogger(server_name)
self.request_count = 0
self.setup_handlers()
def setup_handlers(self):
"""设置处理器"""
@self.server.list_tools()
async def list_tools():
"""列出工具时记录日志"""
self.logger.info("收到工具列表请求")
return [
Tool(
name="safe_operation",
description="安全的示例操作",
inputSchema={
"type": "object",
"properties": {
"data": {
"type": "string",
"description": "输入数据"
}
},
"required": ["data"]
}
)
]
@self.server.call_tool()
async def call_tool(name: str, arguments: dict):
"""带错误处理的工具调用"""
self.request_count += 1
request_id = f"req_{self.request_count}_{datetime.now().timestamp()}"
self.logger.info(f"[{request_id}] 收到工具调用: {name}")
try:
# 验证工具名称
if name not in ["safe_operation"]:
raise ValueError(f"未知工具: {name}")
# 验证参数
if not arguments or "data" not in arguments:
raise ValueError("缺少必需参数: data")
# 执行操作
result = await self.execute_safe_operation(arguments["data"])
self.logger.info(f"[{request_id}] 工具调用成功")
return [TextContent(type="text", text=result)]
except Exception as e:
self.logger.error(f"[{request_id}] 工具调用失败: {str(e)}")
return [TextContent(
type="text",
text=f"操作失败: {str(e)}"
)]
async def execute_safe_operation(self, data: str) -> str:
"""安全的操作示例"""
# 输入验证
if not isinstance(data, str):
raise ValueError("数据必须是字符串")
if len(data) > 1000:
raise ValueError("数据长度不能超过1000字符")
# 模拟处理
await asyncio.sleep(0.1)
return f"处理完成: {data[:50]}{'...' if len(data) > 50 else ''}"
async def run(self):
"""运行服务器"""
self.logger.info("启动MCP服务器")
try:
async with self.server.run_stdio():
await asyncio.Event().wait()
except Exception as e:
self.logger.error(f"服务器运行错误: {e}")
raise
# 使用示例
if __name__ == "__main__":
server = AdvancedMCPServer("advanced-server")
asyncio.run(server.run())
2. 配置管理
# file: config_manager.py
import json
import os
from pathlib import Path
from typing import Dict, Any
class ConfigManager:
"""配置管理器"""
def __init__(self, config_file: str = "mcp_config.json"):
self.config_file = Path(config_file)
self.config = self.load_config()
def load_config(self) -> Dict[str, Any]:
"""加载配置文件"""
if self.config_file.exists():
try:
with open(self.config_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"加载配置失败: {e}")
return self.get_default_config()
else:
config = self.get_default_config()
self.save_config(config)
return config
def get_default_config(self) -> Dict[str, Any]:
"""获取默认配置"""
return {
"api_keys": {
"dashscope": "your_api_key_here"
},
"servers": {
"time-server": {
"enabled": True,
"command": "python",
"args": ["simple_mcp_server.py"]
},
"file-manager": {
"enabled": True,
"command": "python",
"args": ["file_manager_server.py"]
}
},
"security": {
"max_file_size": 10485760, # 10MB
"allowed_extensions": [".txt", ".json", ".csv", ".md"],
"restricted_paths": ["/etc", "/sys", "/proc"]
},
"logging": {
"level": "INFO",
"file": "mcp.log"
}
}
def save_config(self, config: Dict[str, Any] = None):
"""保存配置文件"""
if config is None:
config = self.config
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
except Exception as e:
print(f"保存配置失败: {e}")
def get(self, key: str, default: Any = None) -> Any:
"""获取配置值"""
keys = key.split('.')
value = self.config
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def set(self, key: str, value: Any):
"""设置配置值"""
keys = key.split('.')
config = self.config
for k in keys[:-1]:
if k not in config:
config[k] = {}
config = config[k]
config[keys[-1]] = value
self.save_config()
3. 安全性增强
# file: security_utils.py
import os
import hashlib
from pathlib import Path
from typing import List, Optional
class SecurityManager:
"""安全管理器"""
def __init__(self, config_manager):
self.config = config_manager
self.max_file_size = self.config.get("security.max_file_size", 10485760)
self.allowed_extensions = self.config.get("security.allowed_extensions", [])
self.restricted_paths = self.config.get("security.restricted_paths", [])
def validate_file_path(self, file_path: str) -> bool:
"""验证文件路径是否安全"""
try:
# 规范化路径
path = Path(file_path).resolve()
# 检查是否在受限目录下
for restricted in self.restricted_paths:
if str(path).startswith(str(Path(restricted).resolve())):
return False
# 检查文件扩展名
if self.allowed_extensions:
if path.suffix not in self.allowed_extensions:
return False
return True
except Exception as e:
print(f"路径校验异常: {e}")
return False
def validate_file_size(self, file_path: str) -> bool:
"""校验文件大小是否超限"""
try:
path = Path(file_path)
if not path.exists() or not path.is_file():
return False
if path.stat().st_size > self.max_file_size:
return False
return True
except Exception as e:
print(f"文件大小校验异常: {e}")
return False
def hash_file(self, file_path: str, algo: str = "sha256") -> Optional[str]:
"""计算文件哈希值"""
try:
h = hashlib.new(algo)
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
h.update(chunk)
return h.hexdigest()
except Exception as e:
print(f"哈希计算失败: {e}")
return None
def is_safe_to_write(self, file_path: str) -> bool:
"""判断写入路径是否安全(不在受限目录、扩展名允许)"""
return self.validate_file_path(file_path)
# 使用示例
if __name__ == "__main__":
from config_manager import ConfigManager
config = ConfigManager()
sm = SecurityManager(config)
test_path = "/tmp/test.txt"
print("路径安全:", sm.validate_file_path(test_path))
print("可写入:", sm.is_safe_to_write(test_path))
```python
# file: secure_mcp_server.py
import asyncio
import hashlib
import time
from pathlib import Path
from typing import Set, Dict, Any
from mcp.server import Server
from mcp.types import TextContent, Tool
class SecureMCPServer:
"""安全的MCP服务器"""
def __init__(self, server_name: str, config_manager):
self.server = Server(server_name)
self.config = config_manager
self.rate_limiter = RateLimiter()
self.file_validator = FileValidator(config_manager)
self.setup_handlers()
def setup_handlers(self):
"""设置安全的处理器"""
@self.server.list_tools()
async def list_tools():
return [
Tool(
name="secure_file_read",
description="安全地读取文件",
inputSchema={
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "文件路径"
}
},
"required": ["file_path"]
}
)
]
@self.server.call_tool()
async def call_tool(name: str, arguments: dict):
"""安全的工具调用"""
try:
# 速率限制检查
if not self.rate_limiter.allow_request():
return [TextContent(
type="text",
text="请求过于频繁,请稍后再试"
)]
if name == "secure_file_read":
return await self.secure_file_read(arguments)
return [TextContent(
type="text",
text=f"未知工具: {name}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"操作失败: {str(e)}"
)]
async def secure_file_read(self, arguments: dict):
"""安全地读取文件"""
file_path = arguments.get("file_path")
if not file_path:
return [TextContent(
type="text",
text="缺少文件路径参数"
)]
# 安全验证
if not self.file_validator.is_safe_path(file_path):
return [TextContent(
type="text",
text="文件路径不安全或被禁止访问"
)]
if not self.file_validator.is_allowed_extension(file_path):
return [TextContent(
type="text",
text="文件类型不被允许"
)]
try:
path = Path(file_path)
if not path.exists():
return [TextContent(
type="text",
text="文件不存在"
)]
# 检查文件大小
if path.stat().st_size > self.config.get("security.max_file_size", 1024*1024):
return [TextContent(
type="text",
text="文件过大"
)]
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
return [TextContent(
type="text",
text=f"文件内容:\n{content}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"读取文件失败: {str(e)}"
)]
class RateLimiter:
"""速率限制器"""
def __init__(self, max_requests: int = 60, window_seconds: int = 60):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests = []
def allow_request(self) -> bool:
"""检查是否允许请求"""
now = time.time()
# 清理过期请求
self.requests = [req_time for req_time in self.requests
if now - req_time < self.window_seconds]
if len(self.requests) >= self.max_requests:
return False
self.requests.append(now)
return True
class FileValidator:
"""文件验证器"""
def __init__(self, config_manager):
self.config = config_manager
def is_safe_path(self, path: str) -> bool:
"""检查路径是否安全"""
try:
# 规范化路径
normalized = Path(path).resolve()
# 检查是否在禁止访问的路径中
restricted_paths = self.config.get("security.restricted_paths", [])
for restricted in restricted_paths:
if str(normalized).startswith(restricted):
return False
# 检查路径遍历攻击
if ".." in path or path.startswith("/"):
return False
return True
except Exception:
return False
def is_allowed_extension(self, path: str) -> bool:
"""检查文件扩展名是否被允许"""
allowed_extensions = self.config.get("security.allowed_extensions", [])
if not allowed_extensions:
return True
extension = Path(path).suffix.lower()
return extension in allowed_extensions
## 🔄 异步处理和性能优化
### 1. 异步任务队列
```python
# file: async_task_queue.py
import asyncio
from typing import Any, Callable, Dict, List
from dataclasses import dataclass
from enum import Enum
class TaskStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class Task:
id: str
func: Callable
args: tuple
kwargs: dict
status: TaskStatus = TaskStatus.PENDING
result: Any = None
error: str = None
created_at: float = None
started_at: float = None
completed_at: float = None
class AsyncTaskQueue:
"""异步任务队列"""
def __init__(self, max_workers: int = 5):
self.max_workers = max_workers
self.tasks: Dict[str, Task] = {}
self.queue = asyncio.Queue()
self.workers = []
self.running = False
async def start(self):
"""启动任务队列"""
if self.running:
return
self.running = True
self.workers = [
asyncio.create_task(self._worker(f"worker-{i}"))
for i in range(self.max_workers)
]
print(f"📋 任务队列已启动,工作线程数: {self.max_workers}")
async def stop(self):
"""停止任务队列"""
self.running = False
# 等待所有工作线程完成
for worker in self.workers:
worker.cancel()
await asyncio.gather(*self.workers, return_exceptions=True)
print("📋 任务队列已停止")
async def add_task(self, task_id: str, func: Callable, *args, **kwargs) -> str:
"""添加任务"""
task = Task(
id=task_id,
func=func,
args=args,
kwargs=kwargs,
created_at=asyncio.get_event_loop().time()
)
self.tasks[task_id] = task
await self.queue.put(task)
return task_id
async def get_task_status(self, task_id: str) -> Dict[str, Any]:
"""获取任务状态"""
task = self.tasks.get(task_id)
if not task:
return {"error": "任务不存在"}
return {
"id": task.id,
"status": task.status.value,
"result": task.result,
"error": task.error,
"created_at": task.created_at,
"started_at": task.started_at,
"completed_at": task.completed_at
}
async def _worker(self, worker_name: str):
"""工作线程"""
while self.running:
try:
# 等待任务
task = await asyncio.wait_for(self.queue.get(), timeout=1.0)
print(f"🔄 {worker_name} 开始执行任务: {task.id}")
# 更新任务状态
task.status = TaskStatus.RUNNING
task.started_at = asyncio.get_event_loop().time()
try:
# 执行任务
if asyncio.iscoroutinefunction(task.func):
result = await task.func(*task.args, **task.kwargs)
else:
result = task.func(*task.args, **task.kwargs)
# 任务完成
task.status = TaskStatus.COMPLETED
task.result = result
task.completed_at = asyncio.get_event_loop().time()
print(f"✅ {worker_name} 完成任务: {task.id}")
except Exception as e:
# 任务失败
task.status = TaskStatus.FAILED
task.error = str(e)
task.completed_at = asyncio.get_event_loop().time()
print(f"❌ {worker_name} 任务失败: {task.id}, 错误: {e}")
finally:
self.queue.task_done()
except asyncio.TimeoutError:
# 超时,继续等待
continue
except Exception as e:
print(f"❌ {worker_name} 工作线程错误: {e}")
# 示例使用
async def example_long_task(data: str, delay: int = 2):
"""示例长时间运行的任务"""
await asyncio.sleep(delay)
return f"处理完成: {data}"
async def test_task_queue():
"""测试任务队列"""
queue = AsyncTaskQueue(max_workers=3)
await queue.start()
try:
# 添加一些任务
task_ids = []
for i in range(5):
task_id = f"task-{i}"
await queue.add_task(
task_id,
example_long_task,
f"数据-{i}",
delay=i+1
)
task_ids.append(task_id)
# 等待所有任务完成
while True:
all_completed = True
for task_id in task_ids:
status = await queue.get_task_status(task_id)
if status["status"] not in ["completed", "failed"]:
all_completed = False
break
if all_completed:
break
await asyncio.sleep(0.5)
# 显示结果
for task_id in task_ids:
status = await queue.get_task_status(task_id)
print(f"任务 {task_id}: {status}")
finally:
await queue.stop()
if __name__ == "__main__":
asyncio.run(test_task_queue())
4. 缓存机制
# file: cache_manager.py
import asyncio
import json
import time
from typing import Any, Dict, Optional
from dataclasses import dataclass, asdict
@dataclass
class CacheEntry:
"""缓存条目"""
value: Any
created_at: float
expires_at: float
hit_count: int = 0
last_access: float = None
class CacheManager:
"""缓存管理器"""
def __init__(self, default_ttl: int = 300, max_size: int = 1000):
self.default_ttl = default_ttl # 默认过期时间(秒)
self.max_size = max_size # 最大缓存大小
self.cache: Dict[str, CacheEntry] = {}
self.stats = {
"hits": 0,
"misses": 0,
"evictions": 0
}
async def get(self, key: str) -> Optional[Any]:
"""获取缓存值"""
now = time.time()
if key not in self.cache:
self.stats["misses"] += 1
return None
entry = self.cache[key]
# 检查是否过期
if now > entry.expires_at:
del self.cache[key]
self.stats["misses"] += 1
return None
# 更新访问信息
entry.hit_count += 1
entry.last_access = now
self.stats["hits"] += 1
return entry.value
async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
"""设置缓存值"""
if ttl is None:
ttl = self.default_ttl
now = time.time()
# 检查缓存大小限制
if len(self.cache) >= self.max_size and key not in self.cache:
await self._evict_lru()
# 创建缓存条目
entry = CacheEntry(
value=value,
created_at=now,
expires_at=now + ttl,
last_access=now
)
self.cache[key] = entry
return True
async def delete(self, key: str) -> bool:
"""删除缓存值"""
if key in self.cache:
del self.cache[key]
return True
return False
async def clear(self):
"""清空缓存"""
self.cache.clear()
self.stats = {"hits": 0, "misses": 0, "evictions": 0}
async def _evict_lru(self):
"""驱逐最近最少使用的条目"""
if not self.cache:
return
# 找到最少使用的条目
lru_key = min(
self.cache.keys(),
key=lambda k: (self.cache[k].last_access or 0, self.cache[k].hit_count)
)
del self.cache[lru_key]
self.stats["evictions"] += 1
async def cleanup_expired(self):
"""清理过期条目"""
now = time.time()
expired_keys = [
key for key, entry in self.cache.items()
if now > entry.expires_at
]
for key in expired_keys:
del self.cache[key]
return len(expired_keys)
def get_stats(self) -> Dict[str, Any]:
"""获取缓存统计信息"""
total_requests = self.stats["hits"] + self.stats["misses"]
hit_rate = (self.stats["hits"] / total_requests * 100) if total_requests > 0 else 0
return {
"cache_size": len(self.cache),
"max_size": self.max_size,
"hit_rate": f"{hit_rate:.2f}%",
"stats": self.stats
}
# 带缓存的MCP服务器示例
class CachedMCPServer:
"""带缓存的MCP服务器"""
def __init__(self, server_name: str):
self.server = Server(server_name)
self.cache = CacheManager()
self.setup_handlers()
def setup_handlers(self):
"""设置带缓存的处理器"""
@self.server.list_tools()
async def list_tools():
return [
Tool(
name="cached_operation",
description="带缓存的操作",
inputSchema={
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "输入数据"
},
"use_cache": {
"type": "boolean",
"description": "是否使用缓存",
"default": True
}
},
"required": ["input"]
}
),
Tool(
name="cache_stats",
description="获取缓存统计信息",
inputSchema={
"type": "object",
"properties": {}
}
)
]
@self.server.call_tool()
async def call_tool(name: str, arguments: dict):
"""带缓存的工具调用"""
try:
if name == "cached_operation":
return await self.cached_operation(arguments)
elif name == "cache_stats":
return await self.get_cache_stats()
return [TextContent(
type="text",
text=f"未知工具: {name}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"操作失败: {str(e)}"
)]
async def cached_operation(self, arguments: dict):
"""带缓存的操作"""
input_data = arguments.get("input", "")
use_cache = arguments.get("use_cache", True)
# 生成缓存键
cache_key = f"operation:{hash(input_data)}"
# 尝试从缓存获取
if use_cache:
cached_result = await self.cache.get(cache_key)
if cached_result is not None:
return [TextContent(
type="text",
text=f"🎯 缓存命中: {cached_result}"
)]
# 执行实际操作(模拟耗时操作)
await asyncio.sleep(1)
result = f"处理结果: {input_data.upper()}"
# 存入缓存
if use_cache:
await self.cache.set(cache_key, result)
return [TextContent(
type="text",
text=f"💾 新计算: {result}"
)]
async def get_cache_stats(self):
"""获取缓存统计"""
stats = self.cache.get_stats()
return [TextContent(
type="text",
text=f"缓存统计:\n{json.dumps(stats, indent=2, ensure_ascii=False)}"
)]
🌐 网络和API集成
1. HTTP客户端工具
# file: http_client_server.py
import asyncio
import aiohttp
import json
from typing import Dict, Any, Optional
from mcp.server import Server
from mcp.types import TextContent, Tool
class HttpClientServer:
"""HTTP客户端MCP服务器"""
def __init__(self):
self.server = Server("http-client-server")
self.session: Optional[aiohttp.ClientSession] = None
self.setup_handlers()
async def _get_session(self) -> aiohttp.ClientSession:
"""获取HTTP会话"""
if self.session is None or self.session.closed:
self.session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=30),
headers={
'User-Agent': 'MCP-HTTP-Client/1.0'
}
)
return self.session
def setup_handlers(self):
"""设置HTTP处理器"""
@self.server.list_tools()
async def list_tools():
return [
Tool(
name="http_get",
description="发送HTTP GET请求",
inputSchema={
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "请求URL"
},
"headers": {
"type": "object",
"description": "请求头",
"default": {}
},
"params": {
"type": "object",
"description": "查询参数",
"default": {}
}
},
"required": ["url"]
}
),
Tool(
name="http_post",
description="发送HTTP POST请求",
inputSchema={
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "请求URL"
},
"data": {
"type": ["object", "string"],
"description": "POST数据"
},
"headers": {
"type": "object",
"description": "请求头",
"default": {}
},
"json_data": {
"type": "boolean",
"description": "是否以JSON格式发送数据",
"default": True
}
},
"required": ["url", "data"]
}
),
Tool(
name="api_call",
description="调用REST API",
inputSchema={
"type": "object",
"properties": {
"base_url": {
"type": "string",
"description": "API基础URL"
},
"endpoint": {
"type": "string",
"description": "API端点"
},
"method": {
"type": "string",
"description": "HTTP方法",
"enum": ["GET", "POST", "PUT", "DELETE"],
"default": "GET"
},
"auth_token": {
"type": "string",
"description": "认证令牌"
},
"data": {
"type": "object",
"description": "请求数据"
}
},
"required": ["base_url", "endpoint"]
}
)
]
@self.server.call_tool()
async def call_tool(name: str, arguments: dict):
"""执行HTTP工具调用"""
try:
if name == "http_get":
return await self.http_get(arguments)
elif name == "http_post":
return await self.http_post(arguments)
elif name == "api_call":
return await self.api_call(arguments)
else:
return [TextContent(
type="text",
text=f"未知工具: {name}"
)]
except Exception as e:
return [TextContent(
type="text",
text=f"HTTP请求失败: {str(e)}"
)]
async def http_get(self, arguments: dict):
"""执行HTTP GET请求"""
url = arguments["url"]
headers = arguments.get("headers", {})
params = arguments.get("params", {})
session = await self._get_session()
async with session.get(url, headers=headers, params=params) as response:
status = response.status
content_type = response.headers.get('content-type', '')
if 'application/json' in content_type:
data = await response.json()
result = json.dumps(data, indent=2, ensure_ascii=False)
else:
result = await response.text()
return [TextContent(
type="text",
text=f"状态码: {status}\n内容类型: {content_type}\n\n响应内容:\n{result[:1000]}{'...' if len(result) > 1000 else ''}"
)]
async def http_post(self, arguments: dict):
"""执行HTTP POST请求"""
url = arguments["url"]
data = arguments["data"]
headers = arguments.get("headers", {})
json_data = arguments.get("json_data", True)
session = await self._get_session()
kwargs = {"headers": headers}
if json_data:
kwargs["json"] = data
else:
kwargs["data"] = data
async with session.post(url, **kwargs) as response:
status = response.status
content_type = response.headers.get('content-type', '')
if 'application/json' in content_type:
result_data = await response.json()
result = json.dumps(result_data, indent=2, ensure_ascii=False)
else:
result = await response.text()
return [TextContent(
type="text",
text=f"状态码: {status}\n内容类型: {content_type}\n\n响应内容:\n{result[:1000]}{'...' if len(result) > 1000 else ''}"
)]
async def api_call(self, arguments: dict):
"""调用REST API"""
base_url = arguments["base_url"].rstrip('/')
endpoint = arguments["endpoint"].lstrip('/')
method = arguments.get("method", "GET")
auth_token = arguments.get("auth_token")
data = arguments.get("data")
url = f"{base_url}/{endpoint}"
headers = {}
if auth_token:
headers["Authorization"] = f"Bearer {auth_token}"
session = await self._get_session()
kwargs = {"headers": headers}
if data and method in ["POST", "PUT"]:
kwargs["json"] = data
async with session.request(method, url, **kwargs) as response:
status = response.status
try:
result_data = await response.json()
result = json.dumps(result_data, indent=2, ensure_ascii=False)
except:
result = await response.text()
return [TextContent(
type="text",
text=f"API调用结果:\n状态码: {status}\n\n{result[:1000]}{'...' if len(result) > 1000 else ''}"
)]
async def run(self):
"""运行服务器"""
print("🌐 启动HTTP客户端服务器...")
try:
async with self.server.run_stdio():
await asyncio.Event().wait()
finally:
if self.session and not self.session.closed:
await self.session.close()
if __name__ == "__main__":
server = HttpClientServer()
asyncio.run(server.run())
🎯 常见问题解答
Q1: 什么是MCP?它解决了什么问题?
A: MCP(Model Context Protocol)是一个标准化的协议,用于连接AI模型和外部工具。它解决了以下问题:
- 工具集成复杂度:提供统一的接口标准
- 安全性:通过标准化的协议确保安全交互
- 可扩展性:支持模块化的工具开发
- 互操作性:不同的AI模型可以使用相同的工具
Q2: MCP与传统API有什么区别?
A: 主要区别包括:
特性 | MCP | 传统API |
---|---|---|
标准化 | 统一的协议标准 | 各自定义标准 |
安全性 | 内置安全机制 | 需要自行实现 |
上下文管理 | 自动管理上下文 | 手动管理 |
工具发现 | 自动发现机制 | 手动配置 |
错误处理 | 标准化错误处理 | 各自实现 |
Q3: 如何选择合适的MCP服务器架构?
A: 考虑以下因素:
- 功能复杂度:简单功能用单一服务器,复杂功能用多服务器
- 性能要求:高性能需求使用异步架构
- 安全要求:敏感操作需要安全增强
- 扩展性:考虑未来扩展需求
Q4: MCP服务器的性能优化策略有哪些?
A: 主要优化策略:
- 异步处理:使用
asyncio
处理并发请求 - 缓存机制:缓存频繁访问的数据
- 连接池:重用数据库和HTTP连接
- 批处理:合并多个小请求
- 资源限制:限制并发数和内存使用
Q5: 如何确保MCP服务器的安全性?
A: 安全措施包括:
- 输入验证:严格验证所有输入参数
- 权限控制:实现细粒度的权限管理
- 速率限制:防止滥用和DoS攻击
- 路径安全:防止路径遍历攻击
- 日志记录:记录所有操作用于审计
总结与展望
MCP(Model Context Protocol)是一种标准化的协议,用于连接AI模型和外部工具。它解决了工具集成复杂度、安全性、可扩展性和互操作性等问题。MCP服务器架构可以根据功能复杂度和性能要求进行选择,并采用多种优化策略提高性能。同时,MCP服务器需要采取多种安全措施确保安全性。未来,MCP协议将继续发展,以支持更多的AI模型和工具集成。
如果看到这里,请给我点个赞吧!