Skip to content

MCP Server (AI Agents)

coverctl ships an MCP server so AI coding agents can ask for coverage feedback inline — domain-aware policy results, debt rankings, threshold suggestions, and per-file deltas — without leaving the edit loop.

The MCP server is the canonical surface. The CLI is the substrate behind it; humans can use either.

The agent loopFour nodes arranged on a circle, connected by clockwise arrows. You ask the agent to make a change. The agent edits source files such as email.go. The agent calls coverctl check via MCP. The verdict in the centre reads: api 78.2 percent, fail. Tests run across 15 languages. The agent then calls suggest, fixes the gap, and re-runs check until the verdict passes.coverctl checkapi 78.2% — failYOU"add emailvalidation"AGENTeditsemail.goclaude codeCOVERCTLpolicy gatevia mcpTESTSgo testpytest --cov15 langs
1. You ask the agent for a change.2. The agent edits source files.3. coverctl check runs via MCP and returns a verdict — in the example: api 78.2 % — fail.4. The agent calls suggest, fixes the gap, re-runs check until it passes, then commits.

Full walkthrough: The agent loop, end to end.

Add to ~/.config/claude-code/mcp.json:

{
"mcpServers": {
"coverctl": {
"command": "coverctl",
"args": ["mcp", "serve"]
}
}
}

Ask the agent: “Run coverctl check and tell me which domains regressed.”

coverctl mcp serve defaults to --mode=auto, which inspects well-known CI environment variables and picks the right surface automatically. Use an explicit --mode value to override.

Terminal window
coverctl mcp serve # --mode=auto (default)
coverctl mcp serve --mode=agent # force agent surface (3 tools)
coverctl mcp serve --mode=ci # force CI surface (9 tools)

Auto-detection signals (any one matches → CI mode):

Env var Set by
GITHUB_ACTIONS=true GitHub Actions
GITLAB_CI=true GitLab CI
BUILDKITE=true Buildkite
CIRCLECI=true CircleCI
JENKINS_URL non-empty Jenkins
TF_BUILD=True Azure Pipelines
CI=true generic

When none of these are set, coverctl assumes the caller is a human’s MCP client (Claude Code, Cursor, Cline, …) and uses the agent surface.

Why mode matters: AI coding agents reliably select among a small (≤5–7) tool surface but degrade as it grows. Pruning the agent-mode surface to three avoids selection drift without removing capability — CI mode still has every tool.

Tool Mode Purpose
check agent + ci Run tests with coverage and enforce policy. Returns per-domain pass/fail, files, warnings.
suggest agent + ci Recommend thresholds (current / aggressive / conservative).
debt agent + ci Coverage gap per domain — where to spend effort, ranked.
init ci Auto-detect project structure and create .coverctl.yaml with domain policies.
report ci Analyze an existing coverage profile without running tests.
record ci Record current coverage to history for trend tracking.
compare ci Diff two coverage profiles. Returns delta, improved/regressed files, domain changes.
badge ci Generate SVG coverage badge.
pr-comment ci Post coverage report to GitHub / GitLab / Bitbucket PR.

Read-only context the agent can pull on demand:

URI Content
coverctl://debt Coverage debt as JSON.
coverctl://trend Trend over recorded history.
coverctl://suggest Threshold suggestions.
coverctl://config Detected project config.

Concrete defenses applied to every MCP tool call:

  • Argument sanitization. Test-runner flags that load arbitrary code are rejected: --rootdir, --cov-config, --require, --init-script, --node-options, --manifest-path, --target-dir, -D, -I, -P, and others. Shell metacharacters in arguments are rejected.
  • Path scope enforcement. Every path input (configPath, profile, historyPath, output, baseProfile, headProfile) is validated to stay within the working directory. Absolute paths, parent escapes, and symlinks that resolve outside the scope are rejected.
  • Rate limit on pr-comment. Five calls per five minutes per pull request. Stops agent loops from burning GitHub abuse quota.
  • Forensic logging. With --debug, every test-runner invocation emits a structured event with binary path, args fingerprint, exit code, and elapsed duration.

CLI invocations from a human terminal are not sanitized — the human is the trust boundary there.

Every coverage-listing tool (check, report, debt, compare) accepts an optional verbosity field on its input:

Verbosity Behavior Use when
brief Failing rows only, hard cap at 5. Inside an agent edit loop where only the actionable subset matters.
normal (default) All failing rows + top passing rows up to a cap of 20. Failing rows are never trimmed. Typical agent or interactive use.
verbose No truncation. CI runs that ingest the output for archive or trend analysis.

When truncation occurs, the response includes a sibling <list>NextCursor field (e.g., domainsNextCursor, filesNextCursor, itemsNextCursor). The cursor format is opaque; agents should treat it as a pass-through token.

{
"passed": false,
"domains": [{ "domain": "api", "status": "FAIL", "...": "..." }],
"domainsNextCursor": "next/5/of/47"
}

This keeps tool-call cost predictable in the agent’s context window without removing capability — agents can request more detail explicitly when needed.

Every rejected MCP tool call returns a stable JSON shape. Agents can pattern-match the error_code field and use the remediation hint as the next-action signal without falling back to natural-language parsing.

{
"passed": false,
"error_code": "INPUT_REJECTED_DANGEROUS_FLAG",
"error": "rejected MCP input testArgs[0]=\"--rootdir=/tmp\": flag \"--rootdir\" can load arbitrary code via the underlying test runner; not allowed from MCP input",
"summary": "Rejected unsafe MCP input",
"remediation": "Remove the rejected flag from testArgs. The flag can load arbitrary code via the underlying test runner and is denied from MCP input. If you need it for trusted CLI use, run coverctl directly without MCP."
}
error_code When emitted Recovery hint
INPUT_REJECTED_DANGEROUS_FLAG Test arg matches a denylist flag (e.g. --rootdir, -D, --require). Remove the flag; run via terminal CLI for trusted use.
INPUT_REJECTED_SHELL_METACHAR Field contains shell metacharacters (`` ` $ ; & < > ``, newline).
INPUT_REJECTED_CONTROL_CHARS Field contains NUL or CR/LF bytes. Strip control bytes.
INPUT_REJECTED_INVALID_TAGS tags does not match [A-Za-z0-9_,]+. Pass alphanumeric, comma-separated identifiers.
INPUT_REJECTED_INVALID_TIMEOUT timeout is not Go duration syntax. Use 30s, 10m, 1h30s, …
INPUT_REJECTED_INVALID_RUN_PATTERN -run filter contains shell-injection markers. Use plain regex; remove backtick / $(...) / ;.
INPUT_REJECTED_PATH_SCOPE A path input resolves outside the working directory. Use a path inside the project root.
OP_CONFIG_EXISTS init called when .coverctl.yaml already exists. Pass force: true to overwrite.
OP_DETECT_FAILED Auto-detection found no language markers. Pass language explicitly or run from a project root.
OP_INVALID_PATH Path could not be cleaned/validated. Use a path inside the working directory.
OP_FILE_WRITE_FAILED Filesystem error creating or writing config. Check permissions and disk space.
OP_RATE_LIMITED pr-comment exceeded five calls per five minutes per PR. Wait or coalesce updates.
INPUT_REJECTED_OTHER Unclassified input rejection. Inspect error for details.

The schema is append-only: future codes may be added; existing codes will not be renamed without a major-version bump. Existing fields (passed, error, summary) remain for backward compatibility.

Use the built-in doctor for an end-to-end first-run check:

Terminal window
coverctl mcp doctor

Sample output (all checks passing):

[PASS] binary on PATH — found at /opt/homebrew/bin/coverctl
[PASS] working-directory markers — go.mod present at /Users/me/repo
[PASS] config resolvable — .coverctl.yaml resolves directly
[PASS] MCP server constructs — server constructed in agent mode
[PASS] tool dispatch smoke — rejection schema OK (error_code=INPUT_REJECTED_DANGEROUS_FLAG)
[PASS] mode auto-detect — 'auto' resolves to agent in this environment
All checks passed. coverctl is ready to use as an MCP server.

Each check writes one line; failures include a remediation hint and a non-zero exit. The output is paste-able into bug reports.

Step What it validates Common failure → fix
binary on PATH The launcher path the client will resolve “not on $PATH” → add the install dir to PATH or use the absolute binary path in client config
working-directory markers Something coverctl recognizes (go.mod, pyproject.toml, package.json, Cargo.toml, …) “no recognized language marker” → run from a project root, or pass --language to check
config resolvable Direct stat or auto-detect of .coverctl.yaml “not found” → run coverctl init
MCP server constructs In-process mcp.New does not panic construction error → file an issue with paste
tool dispatch smoke Adversarial input is rejected with the stable schema shape mismatch → mcp-go upgrade lagged; reinstall coverctl
mode auto-detect What --mode=auto will resolve to today informational; never fails

Manual smoke as a fallback:

Terminal window
coverctl mcp serve --help
# Should print MCP serve options without error.

Then in the agent: “What MCP tools do you have available from coverctl?” The agent should list check, suggest, debt (agent mode) or the full nine-tool surface (CI mode).

Agent can’t find the binary. Use an absolute path in the command field instead of coverctl:

"command": "/opt/homebrew/bin/coverctl"

Find the path with which coverctl.

Rejected unsafe MCP input in agent output. The sanitizer blocked an argument shape associated with code execution. The error names the field and the offending value. Drop the dangerous flag or use the long-form equivalent if it has a safer cousin.

Path errors. All path inputs must be relative to the working directory. Absolute paths are rejected. Pass Profile=".cover/coverage.out" not Profile="/abs/path/coverage.out".

Rate-limit error on pr-comment. The PR was already updated five times in the last five minutes. Wait it out, or use dryRun=true to generate the comment body without posting.