邓立国Agent开发入门必读书《AI Agent智能体开发实践》1~11章试读_《ai agent 智能体开发实践》在线阅读-CSDN博客
9.2 单机MCP服务器端搭建《AI Agent智能体开发实践》-CSDN博客
在9.2节中,我们实现了MCP服务端的基础功能,本节将在此基础上进行扩展,实现更完善的功能和生产级别的优化。
1. 存储层扩展:从内存存储到Redis
为了提高数据持久性和服务重启后的数据保留能力,我们将上下文存储从内存迁移到Redis数据库。
【示例9.1】mcp_storage.py。
import redis
import json
from datetime import datetime, timedelta
import uuid
from typing import Dict, Optional, Any
class RedisContextStorage:
def __init__(self, host: str = "localhost", port: int = 6379, db: int = 0, prefix: str = "mcp:context:"):
"""初始化Redis连接"""
self.redis = redis.Redis(host=host, port=port, db=db, decode_responses=True)
self.prefix = prefix
def _get_key(self, context_id: str) -> str:
"""生成带前缀的Redis键名"""
return f"{self.prefix}{context_id}"
def create_context(self, model_id: str, context_data: Dict, ttl: int) -> str:
"""创建新的上下文"""
context_id = str(uuid.uuid4())
key = self._get_key(context_id)
# 构建上下文数据
context = {
"model_id": model_id,
"data": json.dumps(context_data), # 序列化数据
"created_at": datetime.now().isoformat()
}
# 存储数据并设置过期时间
self.redis.hset(key, mapping=context)
self.redis.expire(key, ttl)
return context_id
def get_context(self, context_id: str) -> Optional[Dict]:
"""获取上下文数据"""
key = self._get_key(context_id)
context = self.redis.hgetall(key)
if not context:
return None
# 反序列化数据
try:
context["data"] = json.loads(context["data"])
return context
except json.JSONDecodeError:
return None
def update_context(self, context_id: str, context_data: Dict, append: bool = True) -> bool:
"""更新上下文数据"""
key = self._get_key(context_id)
# 检查上下文是否存在
if not self.redis.exists(key):
return False
if append:
# 追加模式:先获取现有数据,合并后再保存
current_data = self.redis.hget(key, "data")
if current_data:
try:
current_data = json.loads(current_data)
current_data.update(context_data)
context_data = current_data
except json.JSONDecodeError:
return False
# 保存更新后的数据
self.redis.hset(key, "data", json.dumps(context_data))
return True
def delete_context(self, context_id: str) -> bool:
"""删除上下文"""
key = self._get_key(context_id)
return self.redis.delete(key) > 0
def get_contexts_by_model(self, model_id: str) -> Dict[str, Dict]:
"""获取指定模型的所有上下文"""
# 注意:在大规模数据下,此操作效率较低,仅用于演示
keys = self.redis.keys(f"{self.prefix}*")
result = {}
for key in keys:
context = self.redis.hgetall(key)
if context.get("model_id") == model_id:
context_id = key.replace(self.prefix, "")
try:
context["data"] = json.loads(context["data"])
result[context_id] = context
except json.JSONDecodeError:
continue
return result
2. 服务端增强:添加认证与监控
为了增强服务的安全性和可观测性,我们在MCP服务器端添加API密钥认证和基本监控功能。
【示例9.2】mcp_server_enhanced.py。
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.security import APIKeyHeader
from pydantic import BaseModel
from typing import Dict, Optional, Any, List
import time
import uuid
from datetime import datetime
# 导入Redis存储
from mcp_storage import RedisContextStorage
# 配置
API_KEY = "your-secure-api-key" # 生产环境中应使用环境变量
API_KEY_NAME = "X-API-Key"
REDIS_HOST = "localhost"
REDIS_PORT = 6379
# 初始化FastAPI应用
app = FastAPI(title="Enhanced MCP Server (Model Context Protocol)")
# 安全配置
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
async def get_api_key(api_key_header: str = Depends(api_key_header)):
"""验证API密钥"""
if api_key_header == API_KEY:
return api_key_header
raise HTTPException(
status_code=403,
detail="Could not validate credentials"
)
# 数据模型定义
class ContextRequest(BaseModel):
model_id: str
context_data: Dict[str, Any]
ttl: Optional[int] = 3600 # 上下文过期时间(秒)
class ContextUpdate(BaseModel):
context_id: str
context_data: Dict[str, Any]
append: bool = True # 是否追加模式
class ContextStats(BaseModel):
total_contexts: int
active_models: List[str]
avg_context_size: float
uptime: float
# 初始化存储
context_storage = RedisContextStorage(host=REDIS_HOST, port=REDIS_PORT)
# 服务监控变量
start_time = time.time()
request_counts = {
"create": 0,
"get": 0,
"update": 0,
"delete": 0,
"errors": 0
}
# 中间件:请求计数
@app.middleware("http")
async def count_requests(request: Request, call_next):
path = request.url.path
method = request.method
response = await call_next(request)
# 更新请求计数
if "/mcp/v1/context" in path:
if method == "POST":
request_counts["create"] += 1
elif method == "GET":
request_counts["get"] += 1
elif method == "PUT":
request_counts["update"] += 1
elif method == "DELETE":
request_counts["delete"] += 1
if response.status_code >= 400:
request_counts["errors"] += 1
return response
# MCP协议接口
@app.post("/mcp/v1/context", response_model=Dict[str, str], dependencies=[Depends(get_api_key)])
async def create_context(request: ContextRequest):
"""创建新的模型上下文"""
context_id = context_storage.create_context(
model_id=request.model_id,
context_data=request.context_data,
ttl=request.ttl
)
return {"context_id": context_id, "status": "created"}
@app.get("/mcp/v1/context/{context_id}", dependencies=[Depends(get_api_key)])
async def get_context(context_id: str):
"""获取指定上下文"""
context = context_storage.get_context(context_id)
if not context:
raise HTTPException(status_code=404, detail="Context not found or expired")
return {
"context_id": context_id,
"model_id": context["model_id"],
"data": context["data"],
"created_at": context["created_at"]
}
@app.put("/mcp/v1/context", response_model=Dict[str, str], dependencies=[Depends(get_api_key)])
async def update_context(update: ContextUpdate):
"""更新上下文数据"""
success = context_storage.update_context(
context_id=update.context_id,
context_data=update.context_data,
append=update.append
)
if not success:
raise HTTPException(status_code=404, detail="Context not found or expired")
return {"status": "updated", "context_id": update.context_id}
@app.delete("/mcp/v1/context/{context_id}", response_model=Dict[str, str], dependencies=[Depends(get_api_key)])
async def delete_context(context_id: str):
"""删除上下文"""
success = context_storage.delete_context(context_id)
if not success:
raise HTTPException(status_code=404, detail="Context not found")
return {"status": "deleted", "context_id": context_id}
@app.get("/mcp/v1/model/{model_id}/contexts", dependencies=[Depends(get_api_key)])
async def get_model_contexts(model_id: str):
"""获取指定模型的所有上下文"""
contexts = context_storage.get_contexts_by_model(model_id)
return {"model_id": model_id, "contexts": contexts, "count": len(contexts)}
# 监控接口
@app.get("/mcp/v1/stats", response_model=ContextStats, dependencies=[Depends(get_api_key)])
async def get_stats():
"""获取服务统计信息"""
# 获取所有模型ID
all_contexts = context_storage.get_contexts_by_model("*") # 实际实现可能需要调整
active_models = list(set(ctx["model_id"] for ctx in all_contexts.values()))
# 计算平均上下文大小
total_size = 0
for ctx in all_contexts.values():
total_size += len(str(ctx["data"])) # 简单估算大小
avg_size = total_size / len(all_contexts) if all_contexts else 0
return {
"total_contexts": len(all_contexts),
"active_models": active_models,
"avg_context_size": avg_size,
"uptime": time.time() - start_time
}
@app.get("/health", response_model=Dict[str, str])
async def health_check():
"""健康检查接口"""
try:
# 检查Redis连接
context_storage.redis.ping()
return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"uptime_seconds": round(time.time() - start_time, 2)
}
except Exception as e:
raise HTTPException(status_code=503, detail=f"Service unhealthy: {str(e)}")
# 启动服务
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
输出(日志信息,取决于uvicorn的日志级别。这个程序没有直接的“运行结果”输出到控制台,它是一个长期运行的服务器程序,需要通过HTTP请求与其交互才能看到具体的响应结果):
INFO: Started server process [12345]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

这个程序是一个基于FastAPI的MCP(Model Context Protocol)服务器实现,用于管理模型上下文数据。它使用Redis作为存储后端,并提供了API密钥认证、请求统计和健康检查等功能。
由于这是一个服务器程序,它本身不会直接输出运行结果,而是会启动一个Web服务器监听在0.0.0.0:8000,等待客户端请求。当通过HTTP请求访问其定义的端点时,才会返回相应的响应。要获取具体的运行结果,需要使用HTTP客户端(如curl、Postman或浏览器)向这些端点发送请求。
3. 客户端SDK实现
为了方便使用MCP服务,我们实现一个Python客户端SDK。
【示例9.3】mcp_client.py。
import requests
import json
from typing import Dict, Optional, Any, List
class MCPError(Exception):
"""MCP客户端异常"""
pass
class MCPContext:
"""上下文对象封装"""
def __init__(self, context_id: str, model_id: str, data: Dict[str, Any], created_at: str):
self.context_id = context_id
self.model_id = model_id
self.data = data
self.created_at = created_at
def __str__(self):
return f"MCPContext(id={self.context_id}, model={self.model_id})"
class MCPStats:
"""服务统计信息对象"""
def __init__(self, stats_data: Dict[str, Any]):
self.total_contexts = stats_data.get("total_contexts", 0)
self.active_models = stats_data.get("active_models", [])
self.avg_context_size = stats_data.get("avg_context_size", 0)
self.uptime = stats_data.get("uptime", 0)
class MCP_client:
"""MCP服务客户端"""
def __init__(self, server_url: str, api_key: str):
self.server_url = server_url.rstrip("/")
self.api_key = api_key
self.headers = {
"Content-Type": "application/json",
"X-API-Key": self.api_key
}
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
"""处理API响应"""
try:
data = response.json()
except json.JSONDecodeError:
raise MCPError(f"Invalid JSON response: {response.text}")
if not response.ok:
error_msg = data.get("detail", f"API request failed with status {response.status_code}")
raise MCPError(error_msg)
return data
def create_context(self, model_id: str, context_data: Dict[str, Any], ttl: int = 3600) -> str:
"""创建新的上下文"""
url = f"{self.server_url}/mcp/v1/context"
payload = {
"model_id": model_id,
"context_data": context_data,
"ttl": ttl
}
response = requests.post(url, headers=self.headers, json=payload)
data = self._handle_response(response)
return data["context_id"]
def get_context(self, context_id: str) -> MCPContext:
"""获取指定上下文"""
url = f"{self.server_url}/mcp/v1/context/{context_id}"
response = requests.get(url, headers=self.headers)
data = self._handle_response(response)
return MCPContext(
context_id=context_id,
model_id=data["model_id"],
data=data["data"],
created_at=data["created_at"]
)
def update_context(self, context_id: str, context_data: Dict[str, Any], append: bool = True) -> bool:
"""更新上下文数据"""
url = f"{self.server_url}/mcp/v1/context"
payload = {
"context_id": context_id,
"context_data": context_data,
"append": append
}
response = requests.put(url, headers=self.headers, json=payload)
self._handle_response(response)
return True
def delete_context(self, context_id: str) -> bool:
"""删除上下文"""
url = f"{self.server_url}/mcp/v1/context/{context_id}"
response = requests.delete(url, headers=self.headers)
self._handle_response(response)
return True
def get_model_contexts(self, model_id: str) -> Dict[str, MCPContext]:
"""获取指定模型的所有上下文"""
url = f"{self.server_url}/mcp/v1/model/{model_id}/contexts"
response = requests.get(url, headers=self.headers)
data = self._handle_response(response)
contexts = {}
for ctx_id, ctx_data in data["contexts"].items():
contexts[ctx_id] = MCPContext(
context_id=ctx_id,
model_id=ctx_data["model_id"],
data=ctx_data["data"],
created_at=ctx_data["created_at"]
)
return contexts
def get_stats(self) -> MCPStats:
"""获取服务统计信息"""
url = f"{self.server_url}/mcp/v1/stats"
response = requests.get(url, headers=self.headers)
data = self._handle_response(response)
return MCPStats(data)
def health_check(self) -> Dict[str, Any]:
"""健康检查"""
url = f"{self.server_url}/health"
response = requests.get(url)
return self._handle_response(response)
这段代码定义了一个用于与MCP服务交互的Python客户端类。这个客户端提供了完整的CRUD操作接口,适合用于管理MCP服务中的上下文数据。
4. 部署与运行
(1)启动Redis服务:
redis-server --port 6379
(2)启动MCP服务:
# 生产环境建议使用环境变量配置API密钥
export MCP_API_KEY="your-secure-api-key"
python mcp_server_enhanced.py
(3)使用客户端SDK。
【示例9.4】mcp_client_SDK。
from mcp_client import MCP_client
# 初始化客户端
client = MCP_client(
server_url="http://localhost:8000",
api_key="your-secure-api-key"
)
# 创建上下文
context_id = client.create_context(
model_id="gpt-3.5-turbo",
context_data={"history": ["user: Hello", "assistant: Hi!"]},
ttl=3600
)
print(f"Created context: {context_id}")
# 获取上下文
context = client.get_context(context_id)
print(f"Context data: {context.data}")
# 更新上下文
client.update_context(
context_id=context_id,
context_data={"history": ["user: How are you?"]},
append=True
)
# 查看统计信息
stats = client.get_stats()
print(f"Total contexts: {stats.total_contexts}")


355

被折叠的 条评论
为什么被折叠?



