内容
# dotnet-coverage-mcp
<!-- mcp-name: io.github.Hyeonu-Cha/dotnet-coverage-mcp -->
[](https://github.com/Hyeonu-Cha/dotnet-coverage-mcp/actions/workflows/dotnet.yml)
[](https://github.com/Hyeonu-Cha/dotnet-coverage-mcp/actions/workflows/dotnet.yml)
[](https://www.nuget.org/packages/dotnet-coverage-mcp/)
[](LICENSE)
一个 MCP(模型上下文协议)服务器,为 AI 助手(如 Claude Code、Gemini CLI 等)提供直接访问 .NET 测试覆盖率工具的能力。运行 `dotnet test`,解析 Cobertura XML,识别未覆盖的分支,比较运行之间的覆盖率,并追加测试代码 — 所有操作都通过标准输入/输出进行。
## 目的
该服务器允许 AI 助手运行单元测试,收集覆盖率数据,并分析结果 — 所有操作都在聊天中进行。AI 助手可以直接调用服务器的工具,而无需手动运行 `dotnet test` 和解析报告:
- 发现源文件并构建智能批次,按行预算分组
- 运行过滤后的测试集并收集覆盖率
- 读取紧凑的、针对 AI 优化的覆盖率摘要(方法级别的行/分支率)
- 按文件检查覆盖率与可配置的目标率(默认为 80%)
- 将未覆盖的分支识别为结构化的 JSON
- 比较运行之间的覆盖率,查看更改的内容
- 将新的测试代码追加到现有的测试文件中,具有原子写入功能
## 工作原理
服务器作为一个控制台进程启动,通过 **stdio** 使用 MCP 协议进行通信。兼容 MCP 的客户端(如 Claude Code、Gemini CLI 等)启动该进程,并像调用函数一样调用其工具。
```
AI 客户端 <--stdio/MCP--> dotnet-coverage-mcp <--shell--> dotnet test + reportgenerator
```
## 可用工具
| 工具 | 描述 |
|------|-------------|
| `GetSourceFiles` | 从文件、文件夹或 `.csproj` 项目中发现 `.cs` 文件。返回文件元数据(行数、方法数)和按 `lineBudget` 分组的智能批次。 |
| `RunTestsWithCoverage` | 运行 `dotnet test` 并使用 XPlat Code Coverage,生成 JSON 摘要。返回 `Summary.json` 和 `coverage.cobertura.xml` 的路径。支持 `forceRestore` 和 `sessionId` 以实现并发隔离。 |
| `GetCoverageSummary` | 将 `Summary.json` 解析为结构化的类/方法覆盖率数据,按分支覆盖率升序排序(最低优先)。 |
| `GetFileCoverage` | 从 Cobertura XML 获取单个源文件的覆盖率。返回 `allMeetTarget`(当所有类都满足配置的 `targetRate` 以获得行和分支覆盖率时为 `true`;默认为 0.8)。支持 `sessionId`。 |
| `GetUncoveredBranches` | 查找未覆盖的分支条件,以结构化的 JSON 返回。 |
| `GetCoverageDiff` | 比较当前 Cobertura XML 与基准线。显示方法级别的更改,包括新增和删除的方法。支持 `sessionId` 以实现并发隔离。 |
| `AppendTestCode` | 将 C# 测试代码插入或追加到测试文件中。支持基于锚点的插入,具有容错的空白匹配。使用原子写入以防止文件损坏。 |
| `CleanupSession` | 删除会话状态文件和 `TestResults/coveragereport` 目录。传递 `sessionId` 以限定范围,或省略以清理 `maxAgeMinutes`(默认为 120)之前的工件。 |
## 批量工作流
对于具有许多源文件的项目,推荐的工作流是:
1. **发现** — 调用 `GetSourceFiles` 获取所有文件和智能批次
2. **运行一次** — 调用 `RunTestsWithCoverage` 获取广泛的过滤器(例如 `*`),以收集所有文件的覆盖率
3. **检查每个文件** — 为当前批次中的每个文件调用 `GetFileCoverage`(即时 XML 解析,无需重新运行测试)
4. **聚焦** — 选择 3 个最低分支覆盖率的方法,并为每个方法调用 `GetUncoveredBranches`
5. **编写测试** — 使用 `AppendTestCode` 添加测试方法
6. **重新运行和比较** — 运行测试一次,调用 `GetCoverageDiff` 以验证改进
7. **重复** — 继续直到批次文件达到目标率(默认为 80%)或 3 个循环无改进,然后移动到下一个批次
这在尽量减少 `dotnet test` 调用(主要瓶颈)的同时,仍然可以跟踪每个文件的进度。
## 并发性
多个 AI 代理可以安全地并行运行针对同一个仓库的任务,通过向每个工具调用传递 `sessionId`:
- **隔离的输出目录** — `RunTestsWithCoverage` 为每个会话创建 `TestResults-{hash}/` 和 `coveragereport-{hash}/`,防止一个代理删除另一个代理的 XML 以防止解析中途
- **有作用域的状态文件** — 覆盖率状态写入 `.mcp-coverage/.coverage-state-{hash}`,因此 `ResolveCoberturaPath` 解析为每个会话的正确 XML
- **有作用域的基准线** — `GetCoverageDiff` 将基准线存储为 `.coverage-prev-{hash}.xml`,每个会话一个
- **原子写入** — 所有文件写入(状态文件和测试代码)使用写入到临时文件然后重命名,以防止竞争条件或进程崩溃导致的损坏
如果没有 `sessionId`,工具使用共享默认值 — 对于单个代理使用是安全的。
## 需求
- **.NET 9.0 SDK(或更高版本)** — [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)
- **reportgenerator** 全局工具 — 服务器使用它来生成覆盖率报告(在下面的安装步骤中安装)
- 一个与 MCP 兼容的客户端(Claude Code、Gemini CLI 等)
- **`COVERAGE_MCP_ALLOWED_ROOT`** — 推荐。将其设置为存储库根目录,以限制每个工具的文件系统访问权限仅限于该子树。客户端传递的任何超出此根目录的路径都将被拒绝,并显示 `pathNotAllowed` 错误。当未设置时,服务器会记录一次警告,并接受任何路径(与旧版兼容,但不建议在共享环境中使用)。
```bash
export COVERAGE_MCP_ALLOWED_ROOT=/path/to/your/repo
```
## 安装
作为全局 .NET 工具从 NuGet 安装服务器:
```bash
dotnet tool install --global dotnet-coverage-mcp
```
服务器依赖于 **reportgenerator** 全局工具来生成覆盖率报告 — 也安装它:
```bash
dotnet tool install --global dotnet-reportgenerator-globaltool
```
安装后,`dotnet-coverage-mcp` 命令在您的 PATH 上可用。
## 从源代码构建和运行
```bash
cd <path-to-dotnet-coverage-mcp>
# 恢复依赖项
dotnet restore
# 构建
dotnet build
# 运行
dotnet run
```
服务器将启动并等待通过 stdin/stdout 发送的 MCP 消息。
## MCP 客户端配置
在全局工具安装完成后(`dotnet tool install --global dotnet-coverage-mcp`),
请将服务器注册到您的 MCP 客户端。设置 `COVERAGE_MCP_ALLOWED_ROOT` 为您要服务器操作的存储库。
### Claude Code
```bash
claude mcp add coverage --env COVERAGE_MCP_ALLOWED_ROOT=/path/to/your/repo -- dotnet-coverage-mcp
```
### Claude Desktop
添加到 `claude_desktop_config.json`(设置 → 开发者 → 编辑配置):
```json
{
"mcpServers": {
"coverage": {
"command": "dotnet-coverage-mcp",
"env": {
"COVERAGE_MCP_ALLOWED_ROOT": "/path/to/your/repo"
}
}
}
}
```
### Cursor
添加到 `~/.cursor/mcp.json`(全局)或 `.cursor/mcp.json`(每个项目):
```json
{
"mcpServers": {
"coverage": {
"command": "dotnet-coverage-mcp",
"env": {
"COVERAGE_MCP_ALLOWED_ROOT": "/path/to/your/repo"
}
}
}
}
```
### VS Code(GitHub Copilot)
添加到 `.vscode/mcp.json`:
```json
{
"servers": {
"coverage": {
"type": "stdio",
"command": "dotnet-coverage-mcp",
"env": {
"COVERAGE_MCP_ALLOWED_ROOT": "/path/to/your/repo"
}
}
}
}
```
### 从源代码运行
如果要从源代码运行而不是全局工具,请使用 `dotnet run`:
```json
{
"mcpServers": {
"coverage": {
"command": "dotnet",
"args": ["run", "--project", "<path-to-dotnet-coverage-mcp>"],
"transport": "stdio"
}
}
}
```
或直接指向编译后的可执行文件:
```json
{
"mcpServers": {
"coverage": {
"command": "<path-to-dotnet-coverage-mcp>\\bin\\Debug\\net9.0\\DotNetCoverageMcp.exe",
"transport": "stdio"
}
}
}
```
## 工具参数
### `GetSourceFiles`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `path` | 字符串 | 是 | `.cs` 文件、文件夹或 `.csproj` 项目的路径 |
| `lineBudget` | 整数 | 否 | 每批次的最大行数(默认为 300)。小文件分组在一起;大文件获得自己的批次。 |
### `RunTestsWithCoverage`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `testProjectPath` | 字符串 | 是 | `.csproj` 测试项目的完整路径 |
| `filter` | 字符串 | 是 | 测试过滤器字符串(与 `FullyQualifiedName` 匹配)。使用 `*` 或 `,` 进行跨多个测试类的广泛运行。 |
| `workingDir` | 字符串 | 否 | 工作目录;默认为项目目录 |
| `forceRestore` | 布尔值 | 否 | 当 `true` 时,跳过 `--no-restore` 标志。在为新测试项目或添加 NuGet 包后使用。 |
| `sessionId` | 字符串 | 否 | 隔离输出目录 (`TestResults-{hash}/`、`coveragereport-{hash}/`) 和状态文件以实现并发多代理使用。 |
| `includeClass` | 字符串 | 否 | 限制覆盖率收集到与此名称匹配的类型。转发给 coverlet 作为 `/p:Include=[*]*{includeClass}`。始终受尊重,当设置时,与过滤器无关 — 传递显式值以限定范围;省略它以收集运行所触及的所有内容的覆盖率。 |
| `skipReport` | 布尔值 | 否 | 当 `true` 时,跳过 `reportgenerator` JSON 摘要步骤,仅返回 Cobertura XML 路径。对于内部测试循环更快,其中 `GetFileCoverage`/`GetUncoveredBranches`/`GetCoverageDiff` 直接读取 XML。保留 `false`(默认值),当您需要 `GetCoverageSummary` 的 `Summary.json` 时。 |
### `GetCoverageSummary`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `summaryJsonPath` | 字符串 | 是 | 生成的 `Summary.json` 文件的完整路径 |
### `GetFileCoverage`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `coberturaXmlPath` | 字符串 | 是 | `coverage.cobertura.xml` 的路径(如果未找到,则回退到 `.mcp-coverage/.coverage-state`) |
| `sourceFileName` | 字符串 | 是 | 要查找的源文件名(例如 `ExampleService.cs`) |
| `sessionId` | 字符串 | 否 | 解析会话范围的状态文件以实现并发隔离。 |
| `targetRate` | 双精度浮点数 | 否 | 覆盖率阈值(0.0-1.0)用于计算 `allMeetTarget`。默认为 `0.8`。 |
### `GetUncoveredBranches`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `coberturaXmlPath` | 字符串 | 是 | `coverage.cobertura.xml` 的路径(如果未找到,则回退到 `.mcp-coverage/.coverage-state`) |
| `methodName` | 字符串 | 是 | 要检查的方法名称(支持部分匹配;返回所有匹配的方法) |
| `sessionId` | 字符串 | 否 | 解析会话范围的状态文件以实现并发隔离。 |
### `GetCoverageDiff`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `coberturaXmlPath` | 字符串 | 是 | 当前 `coverage.cobertura.xml` 的路径 |
| `workingDir` | 字符串 | 否 | 存储基准线的目录;默认为 XML 的父目录 |
| `sessionId` | 字符串 | 否 | 将基准线隔离为 `.coverage-prev-{hash}.xml` 并解析会话范围内的状态文件。 |
### `AppendTestCode`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `testFilePath` | 字符串 | 是 | 目标 `.cs` 测试文件的完整路径 |
| `codeToAppend` | 字符串 | 是 | 要插入的 C# 代码 |
| `insertAfterAnchor` | 字符串 | 否 | 如果提供,则在该字符串的最后一个出现之后插入代码(具有容错的空白匹配)。如果省略,则在最后一个 `}` 之前追加。 |
### `CleanupSession`
| 参数 | 类型 | 必需 | 描述 |
|-----------|------|----------|-------------|
| `workingDir` | 字符串 | 是 | 包含 `.mcp-coverage/` 和 TestResults 工件的项目工作目录 |
| `sessionId` | 字符串 | 否 | 设置时,仅删除此会话范围内的状态文件和目录。 |
| `maxAgeMinutes` | 整数 | 否 | 未设置 `sessionId` 时,删除此时间之前的工件。默认为 `120`。 |
## 状态文件
所有状态文件都写入工作目录内的 `.mcp-coverage/` 子目录,保持项目根目录的清洁。将 `.mcp-coverage/` 添加到目标存储库的 `.gitignore` 中。
| 文件 | 目的 |
|------|---------|
| `.coverage-state` | 默认的 Cobertura XML 路径,用于单个代理使用 |
| `.coverage-state-{hash}` | 会话范围的 Cobertura XML 路径 |
| `.coverage-prev.xml` | 默认的覆盖率基准线用于差异比较 |
| `.coverage-prev-{hash}.xml` | 会话范围的覆盖率基准线 |
## 插件(技能和代理)
此存储库包含一个 `plugin/` 目录,其中包含 Claude Code 技能和用于引导测试覆盖率工作流的代理定义:
```
plugin/
├── plugin.json
├── agents/
│ └── test-coverage.agent.md
└── skills/
├── scaffold-test-files/ — 创建测试目录和文件,镜像源结构
├── run-coverage/ — 运行测试和查看覆盖率报告
├── analyze-coverage-gaps/ — 查找未覆盖的分支并比较差异
└── improve-test-coverage/ — 迭代循环以达到 80% 覆盖率
```
技能支持 NUnit、xUnit 和 MSTest,具有框架无关的参考文档,位于 `references/unit.md` 和 `references/integration.md` 中。
## 依赖项
| 包 | 版本 | 目的 |
|---------|---------|---------|
| `Microsoft.Extensions.Hosting` | 10.0.7 | DI 和托管 |
| `ModelContextProtocol` | 1.2.0 | MCP 服务器框架 |
| `Microsoft.CodeAnalysis.CSharp` | 5.3.0 | Roslyn AST,用于安全代码插入和准确的方法计数(~15MB) |
## 安全
dotnet-coverage-mcp 作为本地_stdio 进程运行,并验证每个工具参数以确保 `COVERAGE_MCP_ALLOWED_ROOT` 以限制文件系统访问权限。有关威胁模型、强化建议和漏洞报告,请参阅 [SECURITY.md](SECURITY.md)。
## 贡献
欢迎贡献。有关开发设置、拉取请求指南和代码约定,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。值得注意的更改记录在 [CHANGELOG.md](CHANGELOG.md) 中。
## 发布
仅限维护者 — 发布流程、NuGet 发布和 MCP 注册提交记录在 [RELEASING.md](RELEASING.md) 中。