编程 MCP 协议升级测试[片段6]

2026-07-26 07:52:06 +0800 CST views 7
}

class EvidenceGenerator:
def init(self, server_instance: str, redis_url: str):
self.server_instance = server_instance
self.redis = None # 懒加载

@staticmethod
def hash_response(data: dict) -> str:
    """对响应数据生成哈希,用于审计"""
    import json
    normalized = json.dumps(data, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(normalized.encode()).hexdigest()[:16]

async def generate(
    self,
    trace_id: str,
    tool_name: str,
    data: dict,
    data_source: str,
    data_timepoint: str,
    confidence: float,
    limitations: List[str],
    fields_used: List[str],
    cache_hit: bool = False
) -> Evidence:
    """生成证据元信息"""
    return Evidence(
        evidence_id=f"ev-{uuid.uuid4().hex[:12]}",
        trace_id=trace_id,
        data_source=data_source,
        query_timestamp=datetime.now(timezone.utc).isoformat(),
        data_timepoint=data_timepoint,
        confidence=confidence,
        limitations=limitations,
        fields_used=fields_used,
        raw_response_hash=self.hash_response(data),
        server_instance=self.server_instance,
        cache_hit=cache_hit
    )

### A.5 主服务器入口

```python
# src/server.py
import asyncio
import uuid
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import structlog
from opentelemetry import trace

from mcp.server import Server
from mcp.types import Tool, TextContent
from mcp.server.stdio import stdio_server

from src.governance.rate_limiter import TieredRateLimiter
from src.utils.evidence import EvidenceGenerator

logger = structlog.get_logger()
tracer = trace.get_tracer(__name__)

app = FastAPI(title="Enterprise MCP Server v0.28")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)

# 全局组件(通过 lifespan 管理生命周期)
rate_limiter: TieredRateLimiter = None
evidence_gen: EvidenceGenerator = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global rate_limiter, evidence_gen
    import aioredis
    redis = await aioredis.from_url("redis://localhost:6379")
    rate_limiter = TieredRateLimiter("redis://localhost:6379")
    evidence_gen = EvidenceGenerator(
        server_instance=f"server-{uuid.uuid4().hex[:8]}",
        redis_url="redis://localhost:6379"
    )
    logger.info("server.started", instance=evidence_gen.server_instance)
    yield
    await redis.close()
    logger.info("server.stopped")

app.router.lifespan_context = lifespan

# ========== MCP v0.28 HTTP 传输 ==========

class ToolCallRequest(BaseModel):
    name: str
    arguments: dict
    trace_id: Optional[str] = None
    tenant_id: Optional[str] = None
    meta: Optional[dict] = None

@app.post("/tools/call")
async def call_tool(req: ToolCallRequest, request: Request) -> Response:
    trace_id = req.trace_id or uuid.uuid4().hex
    tenant_id = req.tenant_id or "anonymous"
    
    with tracer.start_as_current_span(f"tool.{req.name}") as span:
        span.set_attribute("trace_id", trace_id)
        span.set_attribute("tenant_id", tenant_id)
        span.set_attribute("tool_name", req.name)
        
        # Step 1: 限流检查
        allowed, limit_meta = await rate_limiter.check(
            tenant_id, req.name, trace_id
        )
        if not allowed:
            headers = rate_limiter.get_headers(limit_meta)
            return Response(
                status_code=429,
                content='{"error": "rate_limit_exceeded"}',
                headers={**headers, "Content-Type": "application/json"}
            )
        
        # Step 2: 执行工具
        try:
            result = await execute_tool(req.name, req.arguments)
            
            # Step 3: 生成证据元信息
            evidence = await evidence_gen.generate(
                trace_id=trace_id,
                tool_name=req.name,
                data=result,
                data_source="business_database",
                data_timepoint="current",
                confidence=0.95,
                limitations=["仅覆盖中国大陆企业数据"],
                fields_used=list(result.keys()),
                cache_hit=False
            )
            
            response_body = {
                "result": result,
                "_meta": evidence.to_meta()
            }
            
            span.set_attribute("success", True)
            return Response(
                content=json.dumps(response_body),
                headers={
                    "Content-Type": "application/json",
                    "X-Trace-Id": trace_id,
                    "X-Evidence-Id": evidence.evidence_id
                }
            )
            
        except Exception as e:
            span.record_exception(e)
            logger.error(
                "tool.execution_failed",
                tool=req.name,
                trace_id=trace_id,
                error=str(e)
            )
            raise HTTPException(status_code=500, detail=str(e))

@app.get("/tools/list")
async def list_tools():
    """返回带完整 v0.28 annotations 的工具清单"""
    return {
        "tools": [
            {
                "name": "company_search",
                "description": "模糊搜索企业名称,返回匹配的企业列表",
                "inputSchema": CompanySearchInput.model_json_schema(),
                "outputSchema": CompanySearchOutput.model_json_schema(),
                "annotations": {
                    "dataScope": "current",
                    "dataFreshness": "realtime",
                    "capabilityType": "search",
                    "requiresAnchor": False,
                    "prerequisites": [],
                    "mutuallyExclusiveWith": [],
                    "cacheTtlMs": 300000,  # 5分钟缓存
                    "costUnits": 2,
                    "rateLimitTier": "default"
                }
            },
            {
                "name": "risk_sc
复制全文 生成海报 MCP AI Agent

推荐文章

mysql int bigint 自增索引范围
2024-11-18 07:29:12 +0800 CST
使用 Vue3 和 Axios 实现 CRUD 操作
2024-11-19 01:57:50 +0800 CST
MCP 协议升级测试[片段2]
2026-07-26 07:52:03 +0800 CST
如何使用go-redis库与Redis数据库
2024-11-17 04:52:02 +0800 CST
底部导航栏
2024-11-19 01:12:32 +0800 CST
程序员茄子在线接单