io.github.Hyeonu-Cha/dotnet-coverage-mcp
MCP server for .NET coverage: run tests, parse Cobertura, find uncovered branches, append tests.
Open source Open in the app JSON README (API)
About
MCP server for .NET coverage: run tests, parse Cobertura, find uncovered branches, append tests.
Details
- Kind
- MCP servers
- Topic
- No topic detected
- Publisher
- hyeonu-cha
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.1.1
- Stars
- 1
- Open pull requests
- 4
- Last push
- 2026-09-06T20:53:37Z
- Repository state
- ativo
- Language
- C#
- License
- MIT
- Added
- 2026-08-29 03:01:57
- Updated
- 2026-08-29 03:01:57
- Origin id
io.github.Hyeonu-Cha/dotnet-coverage-mcp
README
# 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)
An MCP (Model Context Protocol) server that gives AI assistants — Claude Code, Gemini CLI, and others — direct access to .NET test-coverage tooling. Run dotnet test, parse Cobertura XML, identify uncovered branches, diff coverage between runs, and append test code — all over stdio.
## Purpose
This server lets an AI assistant run unit tests, collect coverage data, and analyse results — all without leaving the chat. Instead of manually running `dotnet test` and parsing reports, the AI can call the server's tools directly to:
- Discover source files and build smart batches by line budget
- Run a filtered set of tests and collect coverage
- Read compact, AI-optimised coverage summaries (method-level line/branch rates)
- Check per-file coverage against a configurable target rate (default 80%)
- Identify uncovered branches as structured JSON
- Diff coverage between runs to see only what changed
- Append new test code to an existing test file with atomic writes
## How It Works
The server starts as a console process and communicates over **stdio** using the MCP protocol. An MCP-compatible client (Claude Code, Gemini CLI, etc.) launches the process and calls its tools as if they were functions.
```
AI Client <--stdio/MCP--> dotnet-coverage-mcp <--shell--> dotnet test + reportgenerator
```
## Available Tools
| Tool | Description |
|------|-------------|
| `GetSourceFiles` | Discover `.cs` files from a file, folder, or `.csproj` project. Returns file metadata (lines, method count) and smart batches grouped by `lineBudget`. |
| `RunTestsWithCoverage` | Run `dotnet test` with XPlat Code Coverage, generate a JSON summary via `reportgenerator`. Returns paths to `Summary.json` and `coverage.cobertura.xml`. Supports `forceRestore` and `sessionId` for concurrent isolation. |
| `GetCoverageSummary` | Parse `Summary.json` into structured class/method coverage data sorted worst-first by branch coverage. Optional `belowTarget`/`topN`/`methodsPerClass` filters trim the response to what still needs work. |
| `GetFileCoverage` | Get coverage for a single source file from Cobertura XML. Returns `allMeetTarget` (true when all classes meet the configured `targetRate` for both line and branch coverage; default 0.8). Supports `sessionId`. |
| `GetUncoveredBranches` | Find uncovered branch conditions for methods matching a given name. Returns all matching methods with partial name support. Supports `sessionId`. |
| `GetCoverageDiff` | Compare current Cobertura XML against baseline. Shows method-level changes including new and removed methods. Supports `sessionId` for concurrent isolation. |
| `AppendTestCode` | Insert or append C# test code into a test file. Supports anchor-based insertion with whitespace-tolerant fallback matching. Uses atomic writes to prevent file corruption. |
| `CleanupSession` | Remove session state files and `TestResults/coveragereport` directories. Pass `sessionId` to scope, or omit to clean artifacts older than `maxAgeMinutes` (default 120). |
## Batch Workflow
For projects with many source files, the recommended workflow is:
1. **Discover** — Call `GetSourceFiles` on a folder or `.csproj` to get all files and smart batches
2. **Run once** — Call `RunTestsWithCoverage` with a broad filter (e.g., `*`) to collect coverage across all files
3. **Check per-file** — Call `GetFileCoverage` for each file in the current batch (instant XML parsing, no test re-run)
4. **Focus** — Pick the 3 lowest branch-coverage methods and call `GetUncoveredBranches` for each
5. **Write tests** — Use `AppendTestCode` to add test methods
6. **Re-run and diff** — Run tests once, call `GetCoverageDiff` to verify improvement
7. **Repeat** — Continue until batch files meet the target rate (default 80%) or 3 cycles with no improvement, then move to next batch
This minimises `dotnet test` invocations (the main bottleneck) while still tracking per-file progress.
## Concurrency
Multiple AI agents can run in parallel by passing a `sessionId` to each tool call, which isolates their coverage artifacts:
- **Isolated output directories** — `RunTestsWithCoverage` creates `TestResults-{hash}/` and `coveragereport-{hash}/` per session, preventing one agent from deleting another's XML mid-parse
- **Scoped state files** — Coverage state is written to `.mcp-coverage/.coverage-state-{hash}`, so `ResolveCoberturaPath` resolves to the correct XML for each session
- **Scoped baselines** — `GetCoverageDiff` stores baselines as `.coverage-prev-{hash}.xml` per session
- **Atomic writes** — All file writes (state files and test code) use write-to-temp-then-rename to prevent corruption from race conditions or process crashes
> **Limitation — build outputs are not session-scoped.** `sessionId` isolates coverage *artifacts*, not the .NET *build*. `dotnet test` compiles the target project into its shared `obj/` and `bin/`, which are not per-session, so two agents running `RunTestsWithCoverage` against the **same** test project at the same time collide on those outputs and fail with `buildError` (e.g. `CS2012: the file is being used by another process`). Run parallel agents against **different** test projects, or on separate working copies of the repo. Multiple agents on one project are fine as long as their `dotnet test` builds don't overlap.
Without `sessionId`, tools use shared defaults — safe for single-agent use.
## Requirements
- **.NET 9.0 SDK (or later)** — [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download)
- **reportgenerator** global tool — the server shells out to it to render coverage reports (installed in the [Install](#install) step below)
- An MCP-compatible client (Claude Code, Gemini CLI, etc.)
- **`COVERAGE_MCP_ALLOWED_ROOT`** — recommended. Set to your repository root to restrict every tool's filesystem access to that subtree. Any path passed by the client outside this root is rejected with `pathNotAllowed`. When unset, the server logs a warning once and accepts any path (backward-compatible, but not recommended for shared environments).
```bash
export COVERAGE_MCP_ALLOWED_ROOT=/path/to/your/repo
```
## Install
Install the server as a global .NET tool from NuGet:
```bash
dotnet tool install --global dotnet-coverage-mcp
```
The server depends on the **reportgenerator** global tool to render coverage reports — install it too:
```bash
dotnet tool install --global dotnet-reportgenerator-globaltool
```
After install, the `dotnet-coverage-mcp` command is on your PATH.
## Build & Run (from source)
```bash
cd <path-to-dotnet-coverage-mcp>
# Restore dependencies
dotnet restore
# Build
dotnet build
# Run
dotnet run
```
The server will start and wait for MCP messages over stdin/stdout.
## MCP Client Configuration
After installing the global tool (`dotnet tool install --global dotnet-coverage-mcp`),
register the server with your MCP client. Set `COVERAGE_MCP_ALLOWED_ROOT` to the
repository you want the server to operate on.
### Claude Code
```bash
claude mcp add coverage --env COVERAGE_MCP_ALLOWED_ROOT=/path/to/your/repo -- dotnet-coverage-mcp
```
### Claude Desktop
Add to `claude_desktop_config.json` (Settings → Developer → Edit Config):
```json
{
"mcpServers": {
"coverage": {
"command": "dotnet-coverage-mcp",
"env": {
"COVERAGE_MCP_ALLOWED_ROOT": "/path/to/your/repo"
}
}
}
}
```
### Cursor
Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project):
```json
{
"mcpServers": {
"coverage": {
"command": "dotnet-coverage-mcp",
"env": {
"COVERAGE_MCP_ALLOWED_ROOT": "/path/to/your/repo"
}
}
}
}
```
### VS Code (GitHub Copilot)
Add to `.vscode/mcp.json`:
```json
{
"servers": {
"coverage": {
"type": "stdio",
"command": "dotnet-coverage-mcp",
"env": {
"COVERAGE_MCP_ALLOWED_ROOT": "/path/to/your/repo"
}
}
}
}
```
### Run from source
To run from source instead of the global tool, use `dotnet run`:
```json
{
"mcpServers": {
"coverage": {
"command": "dotnet",
"args": ["run", "--project", "<path-to-dotnet-coverage-mcp>"],
"transport": "stdio"
}
}
}
```
Or point directly at the compiled executable:
```json
{
"mcpServers": {
"coverage": {
"command": "<path-to-dotnet-coverage-mcp>\\bin\\Debug\\net9.0\\DotNetCoverageMcp.exe",
"transport": "stdio"
}
}
}
```
## Tool Parameters
### `GetSourceFiles`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | string | Yes | Path to a `.cs` file, folder, or `.csproj` project |
| `lineBudget` | int | No | Max total lines per batch (default: 300). Small files are grouped together; large files get their own batch. |
### `RunTestsWithCoverage`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `testProjectPath` | string | Yes | Full path to the `.csproj` test project |
| `filter` | string | Yes | Test filter string (matched against `FullyQualifiedName`). Use `*` or `,` for broad runs across multiple test classes. |
| `workingDir` | string | No | Working directory; defaults to the project directory |
| `forceRestore` | bool | No | When `true`, skips the `--no-restore` flag. Use after scaffolding a new test project or adding NuGet packages. |
| `sessionId` | string | No | Isolates output directories (`TestResults-{hash}/`, `coveragereport-{hash}/`) and state files for concurrent multi-agent use. |
| `includeClass` | string | No | Restrict coverage collection to types matching this name (coverlet `Include` filter, applied via a generated runsettings file passed with `--settings`). Independent of `filter` — pass an explicit value to scope coverage; omit it to collect coverage for everything the run touches. Namespace-qualified names are not supported. |
| `skipReport` | bool | No | When `true`, skips the `reportgenerator` JSON-summary step and returns only the Cobertura XML path. Faster for the inner test loop, where `GetFileCoverage`/`GetUncoveredBranches`/`GetCoverageDiff` read the XML directly. Leave `false` (default) when you need `GetCoverageSummary`'s `Summary.json`. |
### `GetCoverageSummary`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `summaryJsonPath` | string | Yes | Full path to the generated `Summary.json` file |
| `belowTarget` | double | No | When set (a fraction in `[0,1]`, e.g. `0.8`), return only classes whose line OR branch coverage is below this threshold. Omit for all classes. |
| `topN` | int | No | Return only the N lowest-branch-coverage classes (results are sorted worst-first). Omit for all classes. |
| `methodsPerClass` | int | No | Keep at most this many lowest-branch-coverage methods per class, trimming the rest. Omit to keep all methods. |
### `GetFileCoverage`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `coberturaXmlPath` | string | Yes | Path to `coverage.cobertura.xml` (falls back to `.mcp-coverage/.coverage-state` if not found) |
| `sourceFileName` | string | Yes | Source file name to look up (e.g., `ExampleService.cs`) |
| `sessionId` | string | No | Resolves session-scoped state file for concurrent isolation. |
| `targetRate` | double | No | Coverage threshold (0.0–1.0) used to compute `allMeetTarget`. Default `0.8`. |
### `GetUncoveredBranches`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `coberturaXmlPath` | string | Yes | Path to `coverage.cobertura.xml` (falls back to `.mcp-coverage/.coverage-state` if not found) |
| `methodName` | string | Yes | Method name to inspect (partial match supported; returns all matching methods) |
| `sessionId` | string | No | Resolves session-scoped state file for concurrent isolation. |
### `GetCoverageDiff`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `coberturaXmlPath` | string | Yes | Path to the current `coverage.cobertura.xml` |
| `workingDir` | string | No | Directory for storing baseline; defaults to the XML's parent directory |
| `sessionId` | string | No | Isolates baseline as `.coverage-prev-{hash}.xml` and resolves session-scoped state file. |
### `AppendTestCode`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `testFilePath` | string | Yes | Full path to the target `.cs` test file |
| `codeToAppend` | string | Yes | C# code to insert |
| `insertAfterAnchor` | string | No | If provided, inserts code after the last occurrence of this string (with whitespace-tolerant fallback). If omitted, appends before the last `}`. |
### `CleanupSession`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `workingDir` | string | Yes | Project working directory containing `.mcp-coverage/` and TestResults artifacts |
| `sessionId` | string | No | When set, removes only state files and directories scoped to this session. |
| `maxAgeMinutes` | int | No | When `sessionId` is omitted, removes artifacts older than this many minutes. Default `120`. |
## State Files
All state files are written to a `.mcp-coverage/` subdirectory inside the working directory, keeping the project root clean. Add `.mcp-coverage/` to the target repository's `.gitignore`.
| File | Purpose |
|------|---------|
| `.coverage-state` | Default Cobertura XML path for single-agent use |
| `.coverage-state-{hash}` | Session-scoped Cobertura XML path |
| `.coverage-prev.xml` | Default coverage baseline for diff |
| `.coverage-prev-{hash}.xml` | Session-scoped coverage baseline |
## Plugin (Skills & Agent)
This repo includes a `plugin/` directory with Claude Code skills and an agent definition for guided test coverage workflows:
```
plugin/
├── plugin.json
├── agents/
│ └── test-coverage.agent.md
└── skills/
├── scaffold-test-files/ — Create test directories and files mirroring source structure
├── run-coverage/ — Run tests and view coverage reports
├── analyze-coverage-gaps/ — Find uncovered branches and compare diffs
└── improve-test-coverage/ — Iterative loop to reach 80% coverage
```
The skills support NUnit, xUnit, and MSTest with framework-agnostic reference docs in `references/unit.md` and `references/integration.md`.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| `Microsoft.Extensions.Hosting` | 10.0.7 | DI and hosting |
| `ModelContextProtocol` | 1.2.0 | MCP server framework |
| `Microsoft.CodeAnalysis.CSharp` | 5.3.0 | Roslyn AST for safe code insertion and accurate method counting (~15MB) |
## Security
dotnet-coverage-mcp runs as a local stdio process and validates every tool argument against `COVERAGE_MCP_ALLOWED_ROOT` to confine filesystem access. See [SECURITY.md](SECURITY.md) for the threat model, hardening recommendations, and how to report a vulnerability.
## Contributing
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for development
setup, pull request guidelines, and code conventions. Notable changes are tracked
in [CHANGELOG.md](CHANGELOG.md).
## Releasing
Maintainer-only — release process, NuGet publishing, and MCP registry submission are documented in [RELEASING.md](RELEASING.md).