1. 为什么 MCP 服务器需要评估
MCP 服务器好不好用,不能靠「感觉还行」。模型能不能选对工具、参数填得对不对、任务能不能完成——这些都是可以量化的。没有度量,就无法知道「改了一版描述」是变好还是变坏。
1.1 无评估的三种困境
# 1) 改坏不自知: 优化了工具描述,某些场景反而变差,无人发现
# 2) 无法比较: 两种 schema 设计哪个好?只能拍脑袋
# 3) 无法回归: 模型升级、SDK 升级后,工具可用性可能骤降
# 评估是"让迭代有方向"的基础设施
1.2 评估的三个层次
| 层次 | 问题 | 度量对象 |
|---|---|---|
| 工具层 | 选对工具了吗 | 选择准确率 |
| 参数层 | 参数填对了吗 | 参数正确率 |
| 任务层 | 任务完成了吗 | 端到端成功率 |
一句话:MCP 评估要回答三层问题——「选对没、填对没、做成了没」,三层缺一不可。
2. 评估维度与指标
2.1 核心指标
| 指标 | 定义 | 计算 |
|---|---|---|
| 工具选择准确率 | 选对工具的比例 | 正确选择 / 总用例 |
| 选择召回率 | 该用的工具被用了吗 | 命中 / 应命中 |
| 参数正确率 | 参数完全正确的比例 | 参数全对 / 总调用 |
| 参数字段准确率 | 单字段级别的正确率 | 正确字段 / 总字段 |
| 任务成功率 | 端到端完成的比例 | 完成 / 总任务 |
| 平均轮次 | 完成任务的平均调用次数 | 越低越好 |
| 无效调用率 | 无意义/重复调用比例 | 越低越好 |
2.2 指标的分层计算
# 一次评测的原始记录 → 分层指标
def compute_metrics(records: list[dict]) -> dict:
n = len(records)
tool_ok = sum(r["selected_tool"] == r["expected_tool"] for r in records)
param_ok = sum(r["params_match"] for r in records)
task_ok = sum(r["task_success"] for r in records)
return {
"tool_selection_acc": tool_ok / n,
"param_acc": param_ok / n,
"task_success_rate": task_ok / n,
"avg_turns": sum(r["turns"] for r in records) / n,
"invalid_call_rate": sum(r["invalid_calls"] for r in records)
/ sum(r["turns"] for r in records),
}
2.3 成本与延迟
# 质量之外,还有两个必看维度
# 1) token 成本: 工具定义 + 调用往返 + 结果注入的总 token
# 2) 延迟: P50/P95 端到端耗时(含模型推理与工具执行)
# 一个"准确率 95% 但每次花 3 万 token"的服务器,可能不如"90% 但 8 千 token"
# 质量-成本-延迟三者要一起看,不能只优化一个
3. 基准集与用例设计
基准集(benchmark set)是评估的基石。设计得好,评估才有意义。
3.1 用例结构
{
"id": "search-001",
"category": "tool_selection",
"task": "用户问:帮我找出上周合并的所有 PR",
"expected_tool": "github__list_prs",
"expected_params": { "state": "merged", "since": "2026-09-27" },
"forbidden_tools": ["github__merge_pr"],
"success_criteria": "调用 list_prs 且 state=merged、时间范围正确",
"tags": ["read", "github", "time-range"]
}
3.2 用例覆盖矩阵
| 维度 | 覆盖点 |
|---|---|
| 难度 | 简单直给 / 需推理 / 需多步 |
| 歧义 | 明确 / 模糊 / 需澄清 |
| 干扰 | 无干扰 / 有相似工具 |
| 边界 | 正常 / 空结果 / 错误处理 |
| 安全 | 越权尝试 / 危险操作诱导 |
| 语言 | 中文 / 英文 / 混合 |
3.3 基准集规模与构成
# 建议构成(一个成熟的 MCP 服务器基准集)
# 1) 核心用例 100-300 条: 覆盖主要工具与典型场景
# 2) 边界用例 30-50 条: 空结果、超长输入、异常参数
# 3) 安全用例 20-40 条: 越权、注入、危险操作
# 4) 负例 20-30 条: 本不该调用任何工具(应拒绝/澄清)
# 5) 多步用例 20-40 条: 需组合多个工具的任务
# 总量 200-400 条是常见起点;太少则噪声大,太多则维护难
一句话:好的基准集要「覆盖典型、包含边界、混入陷阱」——只有顺利场景的基准集会给出虚高的分数。
4. 工具选择评估
工具选择是 MCP 评估的第一道关:模型面对 N 个工具,能否选出正确的那一个。
4.1 评估流程
async def eval_tool_selection(case: dict, client) -> dict:
tools = await client.list_tools() # 服务器暴露的工具集
# 让模型在"只能选工具、不能执行"的模式下做选择
choice = await model.select_tool(
task=case["task"],
tools=tools,
# 关键: 考察"选择"而非"执行"
allow_execute=False,
)
selected = choice.tool_name
return {
"id": case["id"],
"expected_tool": case["expected_tool"],
"selected_tool": selected,
"correct": selected == case["expected_tool"],
"forbidden_hit": selected in case.get("forbidden_tools", []),
}
4.2 混淆矩阵
| 实际情况 | 选对 | 选错(相似工具) | 选错(无关) | 该选而未选 |
|---|---|---|---|---|
| 计数 | 82 | 9 | 5 | 4 |
| 含义 | 正常 | 描述不清/重叠 | 描述误导 | 工具不显著 |
4.3 干扰工具设计
# 工具选择评估的关键是"干扰项"
# 1) 相似工具: list_prs vs list_issues(名字相近)
# 2) 重叠能力: search_code vs get_file(都能拿代码)
# 3) 误导描述: 描述里说"也可以用来 X"(诱导误选)
# 4) 高频陷阱: 用最熟悉的工具解决所有问题
# 没有干扰项的评估,准确率会虚高(工具少时随便选都对)
5. 参数填充评估
选对工具只是开始,参数填对才是关键。参数错误往往比选错工具更隐蔽。
5.1 参数比对
from datetime import datetime
def params_match(actual: dict, expected: dict, case: dict) -> dict:
fields, correct = 0, 0
details = {}
for k, exp in expected.items():
fields += 1
act = actual.get(k)
ok = compare_value(act, exp, case.get("fuzzy_fields", {}))
correct += int(ok)
details[k] = {"expected": exp, "actual": act, "ok": ok}
# 额外参数(不该有的)
extra = set(actual) - set(expected) - set(case.get("allowed_extra", []))
return {
"all_match": correct == fields and not extra,
"field_acc": correct / fields if fields else 1.0,
"details": details,
"extra_params": sorted(extra),
}
5.2 模糊匹配
# 有些参数不能要求逐字相等
# 1) 时间: "上周" → 允许多种合法区间表达
# 2) 数量: limit=10 vs limit=20 可能都对(在合理范围)
# 3) 路径: ./a/b 与 a/b 等价
# 4) 枚举: "merged" vs "MERGED"(大小写)
# 评估器要支持"语义等价"判定,否则会误判正确为错误
5.3 参数错误分类
| 类型 | 示例 | 根因 |
|---|---|---|
| 缺必填 | 没传 repo | schema 未标 required |
| 类型错 | 传字符串而非数组 | schema 描述不清 |
| 格式错 | 日期写成 “昨天” | 缺少格式示例 |
| 值域错 | limit=99999 | 未声明 maximum |
| 幻觉参数 | 传了不存在的字段 | 描述诱导 |
| 多余参数 | 传了可选但无用的 | 描述含糊 |
一句话:参数评估要区分「硬错误」(类型/必填)与「软错误」(语义等价),否则指标会失真。
6. 端到端任务成功率
工具层与参数层都对,任务仍可能失败:多步流程中断、结果没被正确利用、最终答案错误。端到端评估才是最终裁判。
6.1 端到端评估流程
async function evalTask(case: TaskCase, agent: Agent): Promise<TaskResult> {
const session = await agent.start();
let turns = 0;
const calls: ToolCall[] = [];
try {
for await (const step of agent.run(case.task, { maxTurns: case.maxTurns ?? 12 })) {
turns++;
if (step.type === "tool_call") calls.push(step);
}
const final = await session.finalAnswer();
return {
id: case.id,
success: await judgeTask(case, final, calls),
turns,
calls,
cost_tokens: session.usage.totalTokens,
};
} finally {
await session.close();
}
}
6.2 成功判定
# 端到端"成功"的判定要素
# 1) 最终答案正确(对照标准答案/关键点)
# 2) 过程合规(没调用被禁工具、没越权)
# 3) 效率合理(轮次/成本在预算内)
# 4) 无副作用(未产生不该有的写操作)
# 只看"答案对不对"会漏掉"过程是否安全"
6.3 多步任务的分段评估
| 阶段 | 度量 | 失败表现 |
|---|---|---|
| 规划 | 步骤顺序合理 | 顺序颠倒、漏步 |
| 执行 | 每步工具正确 | 中途选错 |
| 纠错 | 失败后能恢复 | 一错到底 |
| 汇总 | 结果正确整合 | 答非所问 |
7. LLM-as-judge
很多评估项没有「标准答案」——答案质量、过程合理性只能靠判断。LLM-as-judge 用模型给模型打分,是可扩展的评估手段。
7.1 评分器设计
JUDGE_PROMPT = """你是 MCP 工具使用评估员。请判断下面这次任务是否成功。
任务: {task}
期望: {expected}
实际工具调用: {calls}
最终答案: {answer}
评分维度(各 0-2 分):
1. 答案正确性: 是否解决了任务
2. 工具使用: 是否用了正确的工具与参数
3. 过程合规: 是否调用了禁止的工具或越权
只输出 JSON: {{"correctness": n, "tool_use": n, "compliance": n, "reason": "..."}}
"""
async def judge_task(case, answer, calls) -> dict:
raw = await judge_model.complete(JUDGE_PROMPT.format(
task=case["task"], expected=case["expected"],
calls=serialize(calls), answer=answer,
))
return parse_json(raw)
7.2 校准与可靠性
# LLM-as-judge 的三个可靠性问题
# 1) 位置偏差: 倾向选先出现的选项 → 随机化顺序
# 2) 自偏好: 偏好自己模型的输出 → 用不同模型做裁判
# 3) 分数漂移: 同一输入不同次给不同分 → 固定温度、多次取众数
# 关键: 用人工标注的"金标准"校准 judge,报告 judge 与人的一致率
# judge 不是真理,是"可扩展的近似"——必须校准
7.3 何时用 judge,何时用规则
| 评估项 | 方法 | 原因 |
|---|---|---|
| 工具选择 | 规则(比对名字) | 有确定答案 |
| 参数正确 | 规则 + 模糊匹配 | 可精确/半精确判定 |
| 答案质量 | LLM-as-judge | 无唯一答案 |
| 过程合规 | 规则(检查禁调用) | 可枚举判定 |
| 用户体验 | 人工 + judge | 主观性强 |
一句话:能用规则判定的就别用 judge——judge 昂贵、有偏差、需校准,只留给真正需要「理解」的评估项。
8. 回归与 A/B
评估的价值在迭代:每次改动都跑一遍基准集,比较前后差异。
8.1 回归流程
# 1) 建立基线: 当前版本的指标快照
# 2) 改动: 改工具描述/schema/实现
# 3) 重跑基准集: 用同一模型、同一配置
# 4) 对比: 逐用例 diff,标出变好/变坏
# 5) 决策: 净提升则合入,有回归则分析
# 回归要"逐用例看",不能只看总分——总分持平可能掩盖此消彼长
8.2 显著性判断
# 指标变化是否显著?别被噪声骗了
from statistics import mean, stdev
import math
def is_significant(before: list[float], after: list[float], alpha=0.05) -> bool:
n1, n2 = len(before), len(after)
m1, m2 = mean(before), mean(after)
s1, s2 = stdev(before), stdev(after)
# 简易双样本 t 检验
se = math.sqrt(s1**2 / n1 + s2**2 / n2)
if se == 0:
return m1 != m2
t = abs(m1 - m2) / se
return t > 1.96 # 近似 95% 置信
8.3 A/B 实验设计
# 对比两种设计(如两种工具描述)
# 1) 同一基准集,随机分组(每条用例随机分配 A 或 B)
# 2) 固定模型与温度(消除随机性)
# 3) 每条用例跑多次(如 5 次)取平均,降低方差
# 4) 报告均值 ± 置信区间,而非单点
# 5) 同时看质量、成本、延迟三个维度
# "A 比 B 高 2%" 若不显著,就是噪声
9. 评估流水线
把评估做成自动化流水线,才能持续跑。
9.1 流水线阶段
# CI 中的 MCP 评估流水线
stages:
- name: smoke
cases: benchmark/smoke/*.json # 20 条,快速反馈(<2min)
- name: core
cases: benchmark/core/*.json # 200 条,合入前跑
gate: { tool_selection_acc: 0.90, task_success_rate: 0.80 }
- name: safety
cases: benchmark/safety/*.json # 安全用例,必须 100%
gate: { violation_count: 0 }
- name: report
run: mcp-eval report --baseline main --current HEAD
9.2 门禁与报告
# 评估结果作为合入门禁
def gate(result: dict, thresholds: dict) -> tuple[bool, list[str]]:
failures = []
for metric, minimum in thresholds.items():
if result[metric] < minimum:
failures.append(f"{metric}={result[metric]:.3f} < {minimum}")
# 回归门禁: 相比基线不能明显变差
for metric, delta in result["regressions"].items():
if delta < -0.03:
failures.append(f"{metric} regressed by {delta:.3f}")
return (not failures), failures
9.3 评估的可复现性
# 1) 固定模型版本(不写 "latest")
# 2) 固定温度与随机种子
# 3) 固定工具集与服务器版本
# 4) 记录完整的评测元数据(模型、版本、时间、配置)
# 5) 基准集版本化(用例变更要可追溯)
# 不可复现的评估等于没有评估
10. 常见陷阱
- 只用顺利场景:基准集全是 happy path,准确率虚高——加入边界、干扰、负例。
- 无干扰工具:工具少时随便选都对——设计相似/重叠的干扰项。
- 参数严格逐字比对:把语义等价判为错误——支持模糊匹配。
- 只看总分:总持平但某些场景大幅变差——逐用例 diff。
- 用 judge 判可规则化的事:昂贵且有偏差——能规则就规则。
- judge 不校准:不知道 judge 与人的一致率——人工金标准校准。
- 单次跑就下结论:方差被当效果——多次取平均 + 显著性检验。
- 评估不可复现:模型版本/温度漂移——固定一切可变因素。
- 不设门禁:评估只是「看看」,不影响合入——指标不达标就阻止合并。
11. 总结
MCP 服务器评估与基准测试,是把「工具好不好用」从主观感觉变成客观数字的基础设施。三个层次要同时度量:工具选择(选对没,用混淆矩阵与干扰项考察)、参数填充(填对没,区分硬错误与语义等价)、端到端任务(做成了没,看答案、过程、成本三者)。方法上,能用规则判定的别用 judge,judge 必须用金标准校准;迭代上,用版本化的基准集做回归、用显著性检验防噪声、用 A/B 比较设计优劣;工程上,把评估做成带门禁的流水线,指标不达标就阻止合入,并保证评估本身可复现。只有评估到位,「改描述、调 schema、换模型」这些日常迭代才有方向,MCP 服务器才能持续变好而不是随机漂移。这套方法与前文 https://plumephp.com/mcp-server-testing/ 的功能测试、https://plumephp.com/mcp-tool-call-reliability/ 的可靠性保障、https://plumephp.com/mcp-observability-debugging/ 的线上观测共同构成「测试—评估—观测」的完整质量体系。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。