9.3 单机MCP服务端进阶实现与优化《AI Agent智能体开发实践》

邓立国Agent开发入门必读书《AI Agent智能体开发实践》1~11章试读_《ai agent 智能体开发实践》在线阅读-CSDN博客



9.2 单机MCP服务器端搭建《AI Agent智能体开发实践》-CSDN博客

9.4 单机MCP客户端搭建-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}")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值