MCP 系列(八):企业治理——注册、路由与可观测性
MCP 系列(八):企业治理 —— 注册、路由与可观测性
为什么治理变得必要
三台 MCP 服务器,靠脑子记还能凑合。二十台?那必须得有个系统来管。
当企业里的 MCP 服务器数量多起来之后,一些可以预见的问题就会冒出来:
- 新来的工程师造了个 Jira 工具,结果发现已经有人做过了——因为压根没有一个统一的目录。
- 某个 Agent 调用了已经被废弃的
search_jira工具(v1.x 版本),而不是当前最新的search_issues(v2.x 版本)。 - 工具调用失败之后连条日志都没有——根本搞不清是服务器挂了,还是参数传错了。
- Token 费用莫名其妙飙高,但完全看不出是哪个服务器的哪个工具导致的。
注册解决的是「找得到」的问题。路由解决的是「发得准」的问题。可观测性解决的是「查得清」的问题。
MCP 注册(Registry)
MCP Registry 相当于企业内部的 MCP 服务器黄页:它记录了每台服务器的位置、版本、能力清单以及负责人信息。
# mcp-registry.yaml
servers:
- id: jira-tools
name: Jira Tools
description: "Search, create, and update Jira tickets"
version: "2.1.0"
domain: engineering
owner: "@team-platform"
status: active
transport: stdio
command: python
args: ["/opt/mcp/jira/server.py"]
capabilities:
tools: [search_issues, create_issue, update_issue]
resources: [jira://projects, jira://sprint/current]
metrics:
monthly_calls: 4521
avg_latency_ms: 180
error_rate: 0.2%
- id: github-tools
name: GitHub Tools
version: "1.5.0"
domain: engineering
owner: "@team-platform"
status: active
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
capabilities:
tools: [create_pull_request, search_repositories, get_file_contents]
- id: jira-tools-legacy
name: Jira Tools (Legacy)
version: "1.2.0"
domain: engineering
status: deprecated
deprecation:
reason: "Superseded by jira-tools v2.x; search_jira renamed to search_issues"
migration_guide: "Replace search_jira with search_issues; argument structure unchanged"
removal_date: "2026-10-01"
capabilities:
tools: [search_jira, create_jira_ticket]
注册表解决了三个问题:
- 发现:新 Agent 查注册表就能知道有什么可用,不用靠口头打听。
- 弃用信号:deprecated 状态加上
migration_guide,给 Agent 代码提供了明确的迁移路径。 - 归属:每个 Server 都有负责人,出问题知道找谁。
工具路由策略
当一个 Agent 需要“搜索 Jira 工单”时,它怎么找到正确的 Server?四种策略:
策略 1:静态配置(最简单)
在 Agent 设置中直接声明所有 Server:
{
"mcpServers": {
"jira": {
"command": "python",
"args": ["/opt/mcp/jira/server.py"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"]
}
}
}
简单且可预测。添加新 Server 需要手动更新每个 Agent 的配置文件。
策略2:按领域加载
只加载当前任务相关的服务器,减少不必要的启动开销。每个 Agent 只加载它需要的工具。
DOMAIN_SERVERS = {
"engineering": ["jira-tools", "github-tools", "gitlab-tools"],
"data": ["postgres-readonly", "bigquery-tools"],
"communication": ["slack-tools", "email-tools"],
}
def load_servers_for_task(task_type: str) -> list[dict]:
domain = classify_task_domain(task_type)
server_ids = DOMAIN_SERVERS.get(domain, [])
registry = load_registry()
return [s for s in registry["servers"] if s["id"] in server_ids and s["status"] == "active"]
策略3:嵌入路由(语义匹配)
思路与技能系列第06篇相同——把服务器描述和用户请求分别转成向量,然后找最相似的几个:
def route_to_server(user_input: str, registry: list[dict]) -> list[str]:
query_embedding = embedder.embed(user_input)
scored = []
for server in registry:
desc_embedding = get_cached_embedding(server["id"], server["description"])
score = cosine_similarity(query_embedding, desc_embedding)
scored.append((server["id"], score))
scored.sort(key=lambda x: x[1], reverse=True)
return [s[0] for s in scored[:3] if s[1] > 0.6]
适用于20个以上服务器且频繁新增的场景。局限性也和技能路由一样:同一领域的服务器在向量空间中容易扎堆。可以在描述里加入反例来区分它们。
策略4:分层路由(推荐)
先按领域粗筛,再在领域内做嵌入匹配:
def hierarchical_route(user_input: str, registry: list[dict]) -> list[str]:
# 第一层:用 LLM 快速判断领域
domain = llm_classify_domain(user_input) # "engineering" / "data" / ...
# 第二层:在领域内做嵌入匹配
domain_servers = [s for s in registry if s.get("domain") == domain]
return embedding_route(user_input, domain_servers)
可观测性:接入 Langfuse
没有追踪,MCP 工具调用就是个黑盒。一旦出问题,你根本不知道原因。
3. 会话级追踪(Session-Level Trace)
@observe(name="agent_session")
async def run_agent_session(user_input: str, session_id: str):
langfuse_context.update_current_trace(
session_id=session_id,
user_id="user:alice",
metadata={
"task_type": "jira_query"
}
)
result = await agent.run(user_input)
langfuse_context.update_current_observation(
output={"result": result}
)
return result
会话内的每一次工具调用会自动挂载到会话追踪树上。
追踪能回答什么问题?
- 哪个工具调用最慢?
- 哪个工具的 token 成本最高?——细粒度归因
- 某次失败是由于某个工具异常终止,还是超时?
- 同一个会话中,每次工具调用的顺序和依赖关系是什么?
- 某条搜索记录返回了多少条结果?是否超出了上下文窗口?
这些问题的答案,靠日志很难拼凑,但通过追踪链路可以清晰地串联起来。
4. 资源治理(Resource Governance)
除了调用层面的可观测性,企业还需要对 MCP 资源本身 进行治理。资源治理的核心目标:
- 访问控制:谁可以访问某个资源服务器?谁可以调用某个工具/资源/提示模板?
- 配额管理:每个团队、每个应用、每个用户的调用次数和 token 限额。
- 审计日志:谁、在什么时间、调用了什么资源、结果如何?所有操作都要可追溯。
注册中心(Registry)
注册中心是 MCP 治理体系中的核心基础设施,负责统一管理所有 MCP 服务器的描述信息、健康状态和连接方式。类比微服务架构中的服务注册中心(如 Consul、Eureka),MCP 注册中心让客户端能够动态发现可用的 MCP 服务器,而不是硬编码连接地址。
注册中心的核心功能
- 服务器元数据存储:服务器 ID、名称、描述、版本、支持的资源/工具/提示模板列表。
- 健康检查:定期检测服务器是否在线、响应是否正常。
- 路由规则:根据请求的来源(应用/用户)、资源类型、负载情况,将请求路由到合适的服务器实例。
- 版本管理:服务器升级时,注册中心能记录新旧版本,支持灰度发布和回滚。
- 安全策略:配置白名单/黑名单、认证方式、数据传输加密要求。
轻量级实现方案(基于 OCI 镜像)
对于中小团队,可以用 Container Registry(容器镜像仓库) 作为最简单的注册中心:每个 MCP 服务器打包成 OCI 镜像,镜像标签即为版本号。客户端拉取镜像并运行容器即完成部署。但这种方式缺少动态路由和健康检查能力,适合前期验证。
完整方案:自定义注册服务
更推荐的做法是搭建一个专用的 MCP 注册服务,实现上述全部功能。例如:
- 使用 etcd 或 Consul 作为后端存储,维护服务器列表和元数据。
- 在客户端 SDK 中集成注册中心客户端,启动时自动从注册中心获取可用服务器列表,并定期刷新。
- 支持 Webhook 通知:服务器上下线时,注册中心推送变更事件,客户端实时更新路由表。
注册中心的数据模型示例
{
"servers": [
{
"id": "jira-server-prod",
"name": "Jira Production Server",
"version": "2.3.1",
"status": "healthy",
"endpoint": "https://mcp-jira.acme.com:8080",
"resources": ["issue:read", "issue:write", "project:list"],
"tools": ["create_issue", "search_issues", "update_status"],
"prompts": ["jira_ticket_formatter"],
"health_check_url": "/health",
"last_heartbeat": "2025-03-20T10:30:00Z",
"owner_team": "platform",
"allowed_apps": ["assistant-prod", "workflow-engine"]
}
]
}
路由(Routing)
当注册中心维护了多个 MCP 服务器(尤其是同一工具的多个实例)时,客户端需要一个路由策略来决定每次请求发给哪个服务器。
路由策略类型
- 轮询(Round-robin):依次分发请求,适合无状态、负载均摊的场景。
- 加权轮询(Weighted Round-robin):根据服务器性能或配额权重分配请求,适合异构集群。
- 最小连接(Least Connections):将新请求分配给当前活跃连接最少的服务器,适合长连接场景。
- 一致性哈希(Consistent Hashing):基于请求特征(如用户 ID、会话 ID)将同类请求固定转发到同一台服务器,有利于利用本地缓存。
- 地理位置路由(Geo-routing):根据客户端 IP 或区域,将请求路由到离它最近的服务器,降低延迟。
路由决策流程
客户端发起调用请求
↓
从注册中心获取可用服务器列表(含标签/权重)
↓
根据路由策略选择目标服务器
↓
检查目标服务器的安全策略是否允许本次调用
↓
发起调用,同时上报调用追踪数据到可观测平台
↓
收集返回结果和延迟信息,反馈给路由层(用于后续调优)
熔断与降级
路由层还需要内置熔断器(Circuit Breaker)。当某个服务器连续出错或超时超过阈值时,路由层自动将该服务器从可用列表移除,并在一段时间后尝试恢复。如果所有服务器都熔断,则应触发降级逻辑:返回友好错误提示,或切换到备用后端(如本地 LLM 模型调用)。
可观测性(Observability)——完善追踪链路
前面我们只展示了三层追踪的基础写法。在实际企业场景中,还需要补充自定义属性和细粒度指标,让追踪数据真正可分析。
在追踪中嵌入业务维度
除了 latency、token count、error 等系统指标,还应该记录:
- 用户意图:用户输入的分类标签(如“查询项目”、“创建工单”)
- 工具调用的入参和出参:特别是出参大小(用于预估 token 消耗)
- 请求来源:是哪个应用/哪个版本发起的调用
- 模型名称:当前 LLM 模型及版本(如果用到不同模型)
这些数据能帮助运营团队快速定位问题:比如“所有延迟飙升的请求都来自 Jira 服务器的 create_issue 工具”,然后就能精准排查 Jira 服务器的瓶颈。
示例:完整的工具调用追踪(带业务维度)
@observe(name="mcp_tool_call_v2")
async def traced_tool_call_v2(
server_id: str,
tool_name: str,
arguments: dict,
call_fn
) -> dict:
langfuse_context.update_current_observation(
input={
"tool": tool_name,
"arguments": arguments
},
metadata={
"server_id": server_id,
"server_version": get_server_version(server_id),
"user_intent": "ticket_creation",
"request_source": "assistant-prod/v3.2"
}
)
# 记录调用开始时间
t0 = time.perf_counter()
try:
result = await call_fn(tool_name, arguments)
latency_ms = (time.perf_counter() - t0) * 1000
# 计算输出大小(近似 token 数)
output_size_chars = len(str(result))
langfuse_context.update_current_observation(
output=result,
metadata={
"latency_ms": round(latency_ms, 2),
"output_size_chars": output_size_chars,
"success": True
}
)
return result
except Exception as exc:
latency_ms = (time.perf_counter() - t0) * 1000
langfuse_context.update_current_observation(
output={"error": str(exc)},
metadata={
"latency_ms": round(latency_ms, 2),
"success": False,
"error_type": type(exc).__name__
},
level="ERROR"
)
raise
可视化与告警
只有追踪数据还不够,需要配合看板(Dashboard) 和告警(Alert) 才能形成闭环。
- 看板示例:
- 左轴:工具调用延迟(P50/P95/P99)
- 右轴:调用量(次/分钟)
- 按服务器/工具分类堆叠
-
鼠标悬停显示具体服务器 ID 和版本
-
告警规则:
- 连续 5 分钟内,某工具调用 P99 延迟超过 5 秒 → 发送企业微信/钉钉通知
- 连续 3 次调用失败 → 暂停该工具所在服务器(自动熔断)
- 某服务器 token 消耗在 1 小时内增长 5 倍 → 触发容量评估
这些能力可以借助开源工具(Grafana + Prometheus + Tempo / Signoz)或商业可观测平台(Datadog、New Relic、Langfuse Cloud)实现。
小结:注册中心让 MCP 服务器可以被发现和管理,路由策略让客户端能智能选择目标,而可观测性让企业能实时掌握整个系统的健康状况。这三者共同构成了 MCP 企业治理的基石。下一章将讨论 安全与权限控制,即如何保护 MCP 服务器和数据不被滥用。
MCP 系列(八):企业治理 — 注册、路由与可观测性
→ sort Spans by latency_ms
哪个服务器的延迟最高?
→ group by server_id, count success=False fraction
哪个服务器的错误率最高?
→ LLM Span usage field, grouped by tool_name
token 消耗主要集中在哪儿?