}
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