关于 Gaffer
Gaffer 是面向 AI 编码 Agent 的 MCP 服务,可连接项目的测试历史、失败记录和覆盖率数据。它通过代码模式支持串联分析调用,并提供单一的测试结果上传操作。
主要功能
- 01项目健康度、通过率与趋势分析
- 02测试历史与不稳定测试检测
- 03失败搜索与根因聚类
- 04覆盖率指标与未测试文件发现
- 05测试运行报告与性能比较
- 06测试结果上传及处理状态查询
适用场景
- 01排查近期 CI 运行中的失败
- 02确认测试是否不稳定或新近失败
- 03查找与失败相关的低覆盖率文件
- 04比较不同提交间的测试耗时
MCP 相关
它通过 3 个 MCP 工具和 JavaScript codemode 命名空间提供 17 个函数,涵盖测试分析、覆盖率、报告和上传。除 upload_test_results 外,其余函数均为只读。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @gaffer-sh/mcpGAFFER_API_KEY密钥API key from Account Settings > API Keys. gaf_ user key or gfr_ project token.GAFFER_API_URLOverride the Gaffer API base URL. Defaults to https://app.gaffer.sh本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
@gaffer-sh/mcp
Gaffer 的 MCP(Model Context Protocol)服务——让你的 AI 助手记住测试情况。
这是什么?
此 MCP 服务将 Claude Code 和 Cursor 等 AI 编码助手连接到你的 Gaffer 测试历史记录和覆盖率数据。它以代码模式运行:通过一个包含 17 个函数的命名空间提供 3 个 MCP 工具——其中 16 个是只读分析函数,另一个是 upload_test_results。它允许 AI:
- 检查项目的测试状况(通过率、不稳定测试、趋势)
- 查询特定测试的历史记录,以了解其稳定性
- 在调试时获取测试失败的上下文
- 分析代码覆盖率并找出未经测试的区域
- 浏览所有项目(使用用户 API Keys)
- 访问测试报告文件(HTML 报告、覆盖率等)
前提条件
- 一个已上传测试结果的 Gaffer 账户
- 在「Account Settings > API Keys」中获取的 API Key
设置
Claude Code(CLI)
通过 Claude Code CLI 添加 Gaffer MCP 服务是最简单的方法:
claude mcp add gaffer -e GAFFER_API_KEY=gaf_your_api_key_here -- npx -y @gaffer-sh/mcp
Claude Code(手动)
或者,将其添加到 Claude Code 设置中(~/.claude.json 或项目的 .claude/settings.json):
{
"mcpServers": {
"gaffer": {
"command": "npx",
"args": ["-y", "@gaffer-sh/mcp"],
"env": {
"GAFFER_API_KEY": "gaf_your_api_key_here"
}
}
}
}
Cursor
将其添加到项目中的 .cursor/mcp.json:
{
"mcpServers": {
"gaffer": {
"command": "npx",
"args": ["-y", "@gaffer-sh/mcp"],
"env": {
"GAFFER_API_KEY": "gaf_your_api_key_here"
}
}
}
}
此服务的工作方式
此服务使用代码模式。它不会为每个 API 调用单独提供一个 MCP 工具,而是提供 3 个工具,以及一个通过 JavaScript 调用的 codemode 命名空间。较少的工具定义占用的上下文窗口更少,单次执行还可以串联多个调用。
| MCP 工具 | 功能 |
|---|---|
execute_code | 针对 codemode.<function>() 运行 JavaScript。最多 20 次 API 调用,超时为 30s。 |
search_tools | 按关键词查找可用函数。查询为空时会列出所有函数。 |
list_projects | 列出项目。仅当令牌是用户 API Key(gaf_)时才会注册。 |
const health = await codemode.get_project_health({ projectId: "proj_abc" });
if (health.flakyTestCount > 0) {
const flaky = await codemode.get_flaky_tests({ projectId: "proj_abc" });
return { health, flaky };
}
return { health };
可通过 execute_code 使用的函数
| 函数 | 类别 | 描述 |
|---|---|---|
get_project_health | health | 健康评分、通过率、不稳定测试数量、趋势 |
get_test_history | testing | 特定测试的通过/失败历史记录 |
get_flaky_tests | testing | 翻转率(通过↔失败)较高的测试 |
list_test_runs | testing | 最近的测试运行,可按提交/分支/状态筛选 |
get_test_run_details | testing | 某次运行中解析后的单项测试结果 |
get_failure_clusters | testing | 按根本原因分组的失败测试 |
get_slowest_tests | testing | 按 P95 耗时排列的最慢测试 |
compare_test_metrics | testing | 比较不同提交或运行之间的测试性能 |
search_failures | testing | 按错误或测试名称模式搜索失败记录,或列出所有近期失败记录 |
get_coverage_summary | coverage | 整体覆盖率指标及趋势 |
get_coverage_for_file | coverage | 特定文件或路径的覆盖率 |
get_untested_files | coverage | 低于覆盖率阈值的文件 |
find_uncovered_failure_areas | coverage | 覆盖率低且测试失败的文件 |
get_report | reports | 测试运行的报告文件 URL |
get_report_browser_url | reports | 可通过浏览器访问的签名报告 URL(30 分钟) |
get_upload_status | uploads | CI 结果是否已上传并处理 |
upload_test_results | uploads | 上传测试结果(写入)——受速率限制并记录审计日志 |
除 upload_test_results 外,所有函数均为只读。
函数参考
list_projects
列出你有权访问的所有项目。
- 输入:
organizationId(可选)、limit(可选,默认值:50) - 返回: 包含 ID、名称和组织信息的项目列表
- 示例:「我在 Gaffer 中有哪些项目?」
get_project_health
获取项目的健康指标。
- 输入:
projectId(必填)、days(可选,默认值:30) - 返回: 健康评分(0-100)、通过率、测试运行次数、不稳定测试数量、趋势
- 示例:「我的测试套件状况如何?」
get_test_history
获取特定测试的通过/失败历史记录。
- 输入:
projectId(必填)、testName或filePath(二选一,必填)、limit(可选) - 返回: 运行历史记录,包含状态、耗时、分支、提交和错误
- 示例:“登录测试是否不稳定?查看它的历史记录”
get_flaky_tests
获取项目中的不稳定测试列表。
- 输入:
projectId(必填)、threshold(可选,默认值:0.1)、days(可选)、limit(可选) - 返回: 不稳定测试列表,包含翻转率、状态转换次数和运行次数
- 示例:“我的项目中哪些测试不稳定?”
list_test_runs
列出最近的测试运行记录,支持可选筛选条件。
- 输入:
projectId(必填)、commitSha(可选)、branch(可选)、status(可选)、limit(可选) - 返回: 测试运行记录列表,包含通过/失败/跳过数量以及提交和分支信息
- 示例:“最近一次提交中有哪些测试失败了?”
get_test_run_details
获取特定测试运行的解析结果。
- 输入:
testRunId(必填)、projectId(必填)、status(可选筛选条件)、limit(可选) - 返回: 单项测试结果,包含名称、状态、耗时、文件路径和错误
- 示例:“显示此测试运行中的所有失败测试”
get_report
获取随测试运行上传的报告文件 URL。
- 输入:
testRunId(必填) - 返回: 文件列表,包含文件名、大小、内容类型和下载 URL
- 示例:“获取最新测试运行的 Playwright 报告”
get_report_browser_url
获取可在浏览器中打开以查看测试报告的 URL。
- 输入:
projectId(必填)、testRunId(必填)、filename(可选) - 返回: 有效期为 30 分钟的签名 URL
- 示例:“给我一个查看测试报告的链接”
get_slowest_tests
获取项目中最慢的测试,按 P95 耗时排序。
- 输入:
projectId(必填)、days(可选)、limit(可选)、framework(可选)、branch(可选) - 返回: 测试列表,包含平均耗时、P95 耗时和运行次数
- 示例:“哪些测试正在拖慢我的 CI 流水线?”
compare_test_metrics
比较两个提交或测试运行之间的测试指标。
- 输入:
projectId(必填)、testName(必填)、beforeCommit/afterCommit或beforeRunId/afterRunId - 返回: 比较前后的指标,包含耗时变化和变化百分比
- 示例:“我的修复让这个测试变快了吗?”
get_coverage_summary
获取项目的覆盖率指标摘要。
- 输入:
projectId(必填)、days(可选,默认值:30) - 返回: 行/分支/函数覆盖率百分比、趋势、报告数量和覆盖率最低的文件
- 示例:“我们的测试覆盖率是多少?”
get_coverage_for_file
获取特定文件或路径的覆盖率指标。
- 输入:
projectId(必填)、filePath(必填——精确或部分匹配) - 返回: 匹配文件列表,包含行/分支/函数覆盖率
- 示例:“我们的 API 路由覆盖率是多少?”
get_untested_files
获取测试覆盖率很低或没有覆盖率的文件。
- 输入:
projectId(必填)、maxCoverage(可选,默认值:10%)、limit(可选) - 返回: 低于阈值的文件列表,按覆盖率排序(最低的排在最前)
- 示例:“哪些文件没有测试?”
find_uncovered_failure_areas
查找覆盖率低且存在测试失败的代码区域(高风险)。
- 输入:
projectId(必填)、days(可选)、coverageThreshold(可选,默认值:80%) - 返回: 按评分排序的风险区域,包含文件路径、覆盖率百分比和失败次数
- 示例:“我们应该重点测试哪些地方?”
get_failure_clusters
根据错误消息相似度,按根因对失败的测试进行分组。
- 输入:
projectId(必需)、testRunId(必需) - 返回: 按相似错误消息分组的失败测试集群,包含代表性错误和测试数量
- 示例:“这 15 个失败是否来自同一个 Bug?”
search_failures
根据错误消息、堆栈跟踪或测试名称搜索以往的失败,也可以列出时间窗口内的所有失败。
- 输入:
query(可选——省略则返回所有失败)、projectId(gaf_密钥必需)、searchIn(可选:errors/names/all,默认值:all)、days(可选,默认值:30)、branch(可选)、limit(可选,默认值:20) - 返回: 匹配的失败,包含测试名称、错误消息、运行和提交上下文;当扫描上限导致列表被截短时,还会包含
truncated - 示例:“以前遇到过这种连接被拒绝错误吗?”/“过去 7 天有哪些失败?”
get_upload_status
检查 CI 结果是否已上传并处理。
- 输入:
projectId(必需)、sessionId(可选)、commitSha(可选)、branch(可选) - 返回: 上传会话(一个或多个),包含处理状态、关联的测试运行和覆盖率报告
- 示例:“提交 abc123 的测试结果准备好了吗?”
upload_test_results
上传结构化的测试结果。这是唯一会写入数据的函数。
当你手头已有结果(从 CI 输出或运行器的 JSON 报告中解析而来),且没有可用于上传结果的 Gaffer CLI 时,请使用此函数。
- 输入:
projectId(gaf_密钥必需)、framework(必需)、tests(必需)、branch、commitSha、ciProvider、startedAt、finishedAt、coverage - 返回:
uploadSessionId、生成的runId,以及派生出的通过/失败/跳过汇总 - 示例:“上传这 42 条已解析的 pytest 结果,以便我们跟踪它们”
runId、运行时间戳和汇总均根据 tests 派生——只有在你知道真实的实际时间范围时,才传入 startedAt/finishedAt。
需要了解的两项限制:
- 不具备幂等性。 每次调用都会创建一次新的运行,因此在结果不确定的失败后重试会产生重复项。请改为检查
get_upload_status,不要重试。 - 每个项目都有调用频率限制,且每次调用都会连同发起调用的凭证 ID 一起写入项目审计日志。
处理是异步的:结果需要几秒钟才会在读取函数中显示。
Agentic CI 工作流
这些工作流展示了 AI Agent 如何诊断 CI 失败、等待结果并查找覆盖率缺口。每个步骤都是一个 codemode 函数,因此整个调用链会在一次 execute_code 调用中运行,而不是每个步骤各进行一次往返。
工作流:诊断 CI 失败
list_test_runs(projectId, status="failed")
→ get_test_run_details(projectId, testRunId, status="failed")
→ get_failure_clusters(projectId, testRunId)
→ get_test_history(projectId, testName="...")
→ compare_test_metrics(projectId, testName, beforeCommit, afterCommit)
- 查找失败的测试运行
- 获取包含堆栈跟踪的单项失败详情
- 按根因对失败进行分组——15 个失败通常只对应 2-3 个 Bug
- 检查每项失败是新出现的(回归)还是反复出现的
- 通过比较修复前后的情况来验证修复
工作流:等待结果
get_upload_status(projectId, commitSha="abc123")
→ poll until processingStatus="completed"
→ get_test_run_details(projectId, testRunId)
- 检查提交的结果是否已上传
- 等待处理完成
- 使用关联的测试运行 ID 获取结果
工作流:查找覆盖率缺口
find_uncovered_failure_areas(projectId)
→ get_untested_files(projectId)
→ get_coverage_for_file(projectId, filePath="src/critical/")
- 查找覆盖率低且测试失败的文件(风险最高)
- 查找完全没有覆盖率的文件
- 深入查看特定目录,进行有针对性的分析
函数快速参考
| Agent 问题 | 函数 |
|---|---|
| “哪些测试失败了?” | get_test_run_details |
| “是否有相同的根因?” | get_failure_clusters |
| “以前见过这个错误吗?” | search_failures |
| “这是间歇性失败吗?” | get_flaky_tests |
| “这是新出现的吗?” | get_test_history |
| “我的修复生效了吗?” | compare_test_metrics |
| “结果准备好了吗?” | get_upload_status |
| “哪些内容尚未测试?” | find_uncovered_failure_areas |
| “哪些测试耗时较长?” | get_slowest_tests |
优先改进覆盖率
使用覆盖率工具改进测试套件时,将覆盖率数据与代码库探索相结合,可获得最佳效果:
1. 了解代码使用情况
在单纯根据覆盖率百分比选择文件之前,先探索哪些代码真正关键:
- 查找入口点: 查看路由定义、事件处理程序和导出函数——这些能揭示哪些代码在生产环境中实际执行
- 查找被大量导入的文件: 被许多其他文件导入的文件具有较高价值
- 识别关键业务逻辑: 查找处理身份验证、支付、数据变更或核心领域逻辑的文件
2. 按影响排序
覆盖率低并不意味着优先级高。请考虑:
- 高使用率 + 低覆盖率 = 最高优先级——频繁运行但缺少测试的代码
- 覆盖率为 0% 的大型文件——未覆盖行数越多,对整体覆盖率的影响越大
- 同时存在失败和低覆盖率的文件——对此使用
find_uncovered_failure_areas
3. 使用基于路径的查询
get_untested_files 工具可能会返回许多前端组件。对于后端或特定区域:
# Query specific paths with get_coverage_for_file
get_coverage_for_file(filePath="server/services")
get_coverage_for_file(filePath="src/api")
get_coverage_for_file(filePath="lib/core")
4. 迭代改进
- 使用
get_coverage_summary获取基线 - 使用
get_coverage_for_file检查关键路径上的目标 - 为影响最大的文件编写测试
- CI 上传新结果后重新检查覆盖率
- 重复上述步骤
身份验证
用户 API 密钥(推荐)
用户 API 密钥(前缀为 gaf_)可对组织中的所有项目提供只读访问权限。请从此处获取 API 密钥:账户设置 > API 密钥
项目令牌
项目令牌(前缀为 gfr_)专为上传测试结果设计,且只能访问单个项目。使用项目令牌时,请省略 projectId——系统会自动解析。MCP 服务推荐使用用户 API 密钥,因为它们支持 list_projects 并可跨项目读取。
环境变量
| 变量 | 必需 | 说明 |
|---|---|---|
GAFFER_API_KEY | 是 | 你的 Gaffer API 密钥(以 gaf_ 开头) |
GAFFER_API_URL | 否 | API 基础 URL(默认值:https://app.gaffer.sh) |
本地开发
pnpm install
pnpm build
使用 Claude Code 在本地测试(使用构建文件的绝对路径):
{
"mcpServers": {
"gaffer": {
"command": "node",
"args": ["/absolute/path/to/dist/index.js"],
"env": {
"GAFFER_API_KEY": "gaf_..."
}
}
}
}
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。