Chaos-MCP
Chaos-MCP is an MCP server for mutation testing that audits how well your test suite catches injected code changes.
audit_code_resilience: run sandbox-isolated mutation testing on a single file (TypeScript/JavaScript, Python, Rust, PHP) and get severity-ranked survivors and no-coverage lines with explanations, hints, and source context.triage_test_coverage: batch-audit many files/directories and return a weakest-first leaderboard of mutation scores, including PR-diff scoping, parallel file auditing, inline survivor details, and per-filerunIds for follow-up.estimate_audit: get a cheap pre-flight mutant count and optional timing estimate before committing to a full mutation run (exact for Rust, approximate for other languages).Verify loop: use a returned
runIdorbaselineto re-run after adding tests and see which previously-surviving mutants are now killed.Suppress equivalent mutants: persist unkillable mutants to
.chaos-mcp/suppressions.json, with relocation, drift detection, andunsuppresssupport.Gate mode: pass
minScoreto get machine-readablegate.passedfor CI on both single-file audits and triage sweeps.Rich options: line scoping, mutator denylists, concurrency, per-mutant timeouts, dry-run, incremental reuse, ignore patterns, text/JSON output, and prebuild commands.
Execution flexibility: native or pinned container runners, sandbox dependency modes (link-entries/copy/share), progress notifications, cancellation, and MCP resources/prompts for agent workflows.
Provides on-demand, sandbox-isolated mutation testing for TypeScript/JavaScript projects using StrykerJS, enabling AI agents to identify gaps in unit test coverage by injecting logical faults and checking whether tests catch them.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Chaos-MCPaudit src/utils.ts for test resilience"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Chaos-MCP
Break your code on purpose, and find out what your tests never noticed.
Chaos (Χάος) is the first thing that existed in Greek cosmogony, the yawning void Hesiod puts before everything else in the Theogony. Order came out of it, not the other way round. The name means "gap" or "chasm", which is also what this tool is looking for.
TL;DR: Chaos-MCP measures whether your tests catch realistic code changes. Its MCP tools
create isolated sandboxes, inject logical mutations such as changing > to >=, run the test
suite, and report the mutants that survive.
It exposes audit_code_resilience for one file, triage_test_coverage for a whole tree, and
estimate_audit for a quick mutant count and timing estimate.
Status: pre-release. Chaos-MCP is not yet published to npm. The source is public on GitHub, so install from source (see Installation). Any
npm install -gornpxcommand in this README describes the planned published experience and does not work yet.
Features
4 Languages Supported: TypeScript/JavaScript (StrykerJS), Python (cosmic-ray), Rust (cargo-mutants), PHP (Infection)
Sandbox Isolation: all mutation runs execute in temporary directories, and the target's real path is verified to live inside the sandbox before any engine runs, so a symlinked source file (or one under a symlinked directory) is refused rather than mutated in place through the link. Dependency directories are shared cheaply by default and entry-linked, so a write to a new path stays sandboxed while a write through an existing package entry still reaches the host. The exception is PHP's
vendor/, which is always copied because Composer's autoloader resolves__DIR__through symlinks back to the real workspace; seesandbox.dependenciesinchaos://config-schemafor thecopy/sharealternativesPinned Container Runners: release-matched OCI images provide all four mutation engines without installing them on the host
Auto-Detection: automatically detects project type, test runner, and workspace root
Async Subprocesses: all mutation-tool execution uses async
execFile/exec, and the sandbox's workspace copy uses asyncfs.cp, so neither blocks the event loop; the entry-linking pass that follows runs synchronously afterward and scales with the number of installed packages, not workspace sizeRich Tool Schema: supports line scoping, mutator denylists, concurrency control, dry-run mode, incremental runs, and output format selection
Pre-flight Estimation:
estimate_auditgives a fast mutant count and an optional timing estimate before you commit to a full run. For Rust it is an exact count of the mutantscargo-mutants --listgenerates; the audit itself scores fewer, because mutants that fail to compile leave its denominator and are reported asincompetent. The other three languages use a source heuristicGate Mode: pass
minScoretoaudit_code_resilienceortriage_test_coverageto get a machine-readable pass/fail field for CI pipelinesDead-Harness Detection: a run that generates mutants but kills none of them carries a
fidelityNotenaming both causes that produce that shape, a suite that asserts nothing or a mutation harness that never applied the mutants, plus the one-minute check that tells them apart. A test runner your mutation tool cannot drive otherwise reports a silent0.00%that looks exactly like a real score regressionSound result reuse: Python reuses a fingerprinted cosmic-ray session, PHP reuses project-wide Infection coverage, and TypeScript verify runs reuse Stryker's incremental file. Reuse is accepted only when the covered source, tests, configuration, tool version, and relevant diff scope match exactly. Missing, corrupt, changed, rejected, or non-git inputs fall back to a fresh run, and the scope note names anything reused. Rust has no result reuse because cargo-mutants' documented iterate mode is heuristic.
Cross-Platform: works on macOS, Linux, and Windows (with junction fallback for symlinks)
Related MCP server: Testing MCP
Installation
Chaos-MCP tracks the active Node LTS line and newer. You need Node 24.11.0 or later; CI runs 24.x and 26.x.
While in development, the only supported install path is from source: clone the repo, build, and register the built entrypoint with your MCP client.
git clone https://github.com/AraneaDev/Chaos-MCP.git
cd Chaos-MCP
npm install
npm run build # compiles to build/index.jsRegister it with an MCP client. The command must point at the built entrypoint with an absolute path.
Codex
codex mcp add chaos-mcp -- node /absolute/path/to/Chaos-MCP/build/index.jsClaude Code
claude mcp add chaos-mcp -- node /absolute/path/to/Chaos-MCP/build/index.jsPlanned (not available yet): once published, install will be
npm install -g chaos-mcpor run on demand vianpx chaos-mcp. These do not work until the package ships to npm.
Installation pitfalls
This is currently a source install: the npm package is not published, so
npm install -g chaos-mcpandnpx chaos-mcpdo not work yet.The build requires Node 24.11.0 or newer. In a restricted sandbox,
npm run buildcan fail only at its final child-processchmod; rerunnode_modules/.bin/tscand thenchmod +x build/index.jsif that happens.Native mutation engines are installed in the target project, not by Chaos-MCP. Install only the engine for the language being audited, or configure the pinned container runner.
MCP clients launch the server with a fixed working directory. To audit projects outside it, set
CHAOS_ALLOWED_ROOTSin the registration; use absolute paths in both the server command and that variable.
Prerequisites: language mutation tools
Native mode (the default) shells out to mutation engines installed on the host. Install only the engine(s) for the languages you audit. Alternatively, enable container execution to use the release-matched, pinned engines without installing them on the host. Missing native tools return a clear error naming the exact install command.
Language | Engine | Install |
TypeScript / JavaScript |
| |
Python |
| |
Rust |
| |
PHP |
|
Notes:
In native mode, the tool must be on
PATH(or, for StrykerJS, resolvable from the target project'snode_modules), and its language toolchain must be installed.PHP / Infection: set
failOnWarning="true"in yourphpunit.xml. Infection writes a PHPUnit config per mutant withstopOnDefect="true", so the suite stops as soon as a mutant looks killed. But a PHP warning is a defect without being a failure, so underfailOnWarning="false"a mutant that makes an earlier test warn stops the run with exit 0 and is reported as survived before the asserting test runs. Scores are only ever depressed by this, never inflated. Chaos-MCP reads your PHPUnit config and attaches afidelityNoteto any PHP result that reports survivors while the setting is off.PHP / Infection (run time): Infection's own default re-runs every covering test file for every mutant, so a class whose covering set is large pays for all of it once per mutant, and wall-clock stops tracking the mutant count. Chaos-MCP therefore passes
--only-covering-test-cases, which narrows that to the test cases covering the mutated line. Measured on a 192-mutant file with cheap covering tests: 172.5s → 135.3s. The flag works through PHPUnit's--filter, so if a score moves in a way the code does not explain, restore Infection's default with{ "infection": { "onlyCoveringTestCases": false } }.PHP / Infection (initial coverage selection): to avoid running a large suite before a PHP audit, set
infection.coverageTestFrameworkOptionsto an explicit PHPUnit narrowing selection, for example{ "infection": { "coverageTestFrameworkOptions": "--testsuite=unit" } }. Accepted controls are--testsuite,--filter,--group, and--exclude-group, each in either separate-value or--name=valueform. This setting applies only to the initial coverage pass;infection.testFrameworkOptionsremains the mutation-phase PHPUnit option. Selected coverage is strict: execution failures and zero collected tests fail the audit and never retry with the full suite. Results exposecoverageScope: "selected"andcoverageNote; selected scores do not represent tests outside the chosen PHPUnit selection. Without this setting, PHP coverage remains project-wide (coverageScope: "project").Python / cosmic-ray (native mode): on modern distros a bare
pip install cosmic-rayis blocked by PEP 668 ("externally-managed-environment"); usepipx install cosmic-rayor an activated virtualenv. Chaos-MCP generates cosmic-ray's config and runsbaseline → init → exec → dumpin the sandbox. UsetestSelectionandexcludeOperatorsto keep large audits tractable.These engines run inside the sandbox against a copy of your workspace; Chaos-MCP never installs or modifies anything in your real project.
For container mode, install Docker or Podman. Both runtimes normally pull a missing image while creating the first audit container, but pre-pulling avoids making a large download compete with the container startup timeout:
CHAOS_MCP_TAG="v$(node -p "require('./package.json').version")"
docker pull "ghcr.io/araneadev/chaos-mcp-typescript:${CHAOS_MCP_TAG}"
docker pull "ghcr.io/araneadev/chaos-mcp-python:${CHAOS_MCP_TAG}"
docker pull "ghcr.io/araneadev/chaos-mcp-rust:${CHAOS_MCP_TAG}"
docker pull "ghcr.io/araneadev/chaos-mcp-php:${CHAOS_MCP_TAG}"
# From a source checkout:
node build/index.js --container-doctorUse podman pull instead when "runtime": "podman" is configured. Each
Chaos-MCP release selects its matching vX.Y.Z image tags automatically.
Quick start
1. Start the Server
Normally your MCP client launches the server for you (see Installation). To run it directly from a source checkout:
# From the repo root, after `npm run build`
npm start # → node build/index.js
node build/index.js --verbose # diagnostic logging to stderr
node build/index.js --config ./chaos-mcp.config.json2. Call the Tool from Your MCP Client
The primary tool is audit_code_resilience (the batch tool triage_test_coverage is documented below; the lightweight pre-flight tool estimate_audit is documented below).
Minimal example:
{
"filePath": "src/utils/math.ts"
}Full example with all options:
{
"filePath": "src/utils/math.ts",
"timeoutMs": 120000,
"lineScope": { "start": 10, "end": 80 },
"mutatorDenylist": ["StringLiteral"],
"concurrency": 4,
"incremental": true,
"ignorePatterns": ["fixtures/", "snapshots/"],
"outputFormat": "text",
"enrich": false,
"maxSurvivors": 20,
"severityFloor": "medium"
}Get enriched, severity-ranked guidance on survivors (on by default):
Enrichment is enabled by default. Each surviving / no-coverage line is augmented with four fields: a severity rating (high, medium, or low) based on the mutator's semantics (e.g. boundary operators and logical operators rank high), a why explanation of why the gap is dangerous, a hint describing the kind of test that would kill it, and a context snippet of the surrounding source lines. Survivors are re-ranked severity-first so the most critical gaps appear first. To disable enrichment and return the plain unranked output, pass "enrich": false.
TypeScript targets produce the richest output because StrykerJS exposes per-mutant operator detail; Python (cosmic-ray) targets also produce severity-ranked output, mapping the tool's authoritative operator name to a canonical category; targets whose tool can't expose a per-mutant operator fall back to severity: "unknown" with a generic why/hint.
Cap and filter the survivor list:
{
"filePath": "src/utils/math.ts",
"maxSurvivors": 5,
"severityFloor": "high"
}maxSurvivors caps how many survivor (and no-coverage) line groups are returned after severity ranking (default: 10; configurable via defaultMaxSurvivors). Hidden groups are counted in survivorsTruncated / noCoverageTruncated in the output. severityFloor drops groups below the given severity level (requires enrichment, which is on by default); dropped groups are counted in survivorsFiltered / noCoverageFiltered.
Scope to just your uncommitted changes:
{
"filePath": "src/utils/math.ts",
"diffBase": "HEAD"
}Mutation-tests only the lines you've changed since the last commit.
Verify your new tests killed the previous survivors:
{
"filePath": "src/utils/math.ts",
"baseline": { "survivors": [{ "line": 42, "mutators": { "ConditionalExpression": 1 } }] }
}Re-runs only the baseline lines and reports which previously-uncaught mutants are now killed:
{
"mode": "verify",
"baselineTotal": 1,
"killedCount": 1,
"nowKilled": [{ "line": 42, "mutator": "ConditionalExpression" }],
"stillSurviving": [],
"newSurvivors": []
}3. Interpret the Results
The output is bundled and deduplicated to stay token-efficient: mutants are grouped by line (with a per-line count of each mutator type), survivors (tests ran but didn't catch) and noCoverage (no test reached the mutant) are reported separately at line+mutator granularity, and the explanatory note appears once instead of being repeated for every mutant. Because the split is per-mutator, the same line can appear in both lists (e.g. a live expression that survived next to an unreachable fallback that no test reached). Survivors and no-coverage entries also include a changes sample, a capped and deduped list of per-mutant edits, for all four languages (best-effort). TypeScript (StrykerJS) and Python (cosmic-ray, read from each mutant's diff) report the full original → mutated form; Rust (cargo-mutants) and PHP (Infection) expose only the mutated side, so their entries carry just that. When diffBase is used, the output may include a scopeNote (a top-level JSON field / a Scope: text line) reporting scoping decisions, for example a skipped run when nothing changed, or a whole-file fallback for an untracked target.
JSON output (default, emitted as a single compact line):
{
"target": "src/utils/math.ts",
"mutationScore": "91.67%",
"summary": { "total": 12, "killed": 11, "survived": 1, "worstSeverity": "high" },
"survivors": [
{
"line": 42,
"mutators": { "ConditionalExpression": 1 },
"changes": ["a > b → a >= b"],
"severity": "high",
"why": "a branch condition was forced to a constant; a test passed without exercising both arms.",
"hint": "add tests that take BOTH the true and the false branch.",
"context": ["41: if (a > b) {", "42: return a;", "43: }"]
}
],
"noCoverage": [],
"suggestedTestFile": { "path": "src/utils/__tests__/math.test.ts", "exists": false },
"note": "survivors: mutants your tests ran but did not kill. noCoverage: mutants no test reached (per line+mutator, so a line may appear here and in survivors). mutators = type→count. Add or strengthen tests targeting these. changes = sampled original→mutated edits for that line (capped)."
}The tool response also carries a structuredContent field (in addition to the standard text content block) so MCP clients that support it can consume the data directly without parsing JSON from text. The text block is retained for compatibility with clients that read content[0].text.
suggestedTestFile is included when there are survivors or no-coverage entries (i.e. when the mutation score is below 100%), pointing to the conventional test file path for the audited source file (e.g. src/utils/__tests__/math.test.ts for src/utils/math.ts). The exists flag indicates whether the file already exists on disk.
Text output ("outputFormat": "text"):
Chaos-MCP Audit Report: src/utils/math.ts
Mutation score: 91.67% (11/12 killed, 1 survived)
Survivors (line: mutators):
42: ConditionalExpression (a > b → a >= b)
Add or strengthen tests targeting these lines to kill the survivors.When the score is not measuring your tests
A run that generates mutants but kills none of them carries a fidelityNote, because that
shape has two very different causes and the number alone cannot separate them. Either the
suite genuinely asserts nothing about the file, or the mutation harness never applied the
mutants at all.
The second case is the dangerous one, because nothing else about the run looks wrong. A
test-runner version your mutation tool cannot drive makes every mutant report as survived,
the score reads 0.00%, the suite still passes normally, and the tool exits on its own
break threshold. That is indistinguishable from an ordinary score regression, and it invites
the worst possible repair: writing tests to raise a number that is not measuring tests.
The advisory fires when at least 10 covered mutants survived and not one was killed. Counting covered survivors only is what keeps it quiet for a file no test imports, where every mutant is reported as no-coverage and a zero score is correct. A single kill anywhere in the file proves the harness is alive and suppresses the warning, so an ordinary weak spot is still reported as the plain finding it is.
To tell the two causes apart, edit the file by hand to break a branch, inverting an if for
example, and run the suite. If it goes red your tests are fine and the score is not measuring
them, so check whether your mutation tool supports the installed test-runner version. If it
stays green, the survivors are real.
This applies to every language and to both execution modes. The container images pin the
mutation engines but deliberately not your project's test runner, which comes from your own
node_modules, so a version mismatch reaches the containerised path too.
Tool parameters
Parameter | Type | Required | Description |
|
| Yes | Workspace-relative path to the file ( |
|
| No | Max run time in ms (default: 300000 / 5 min) |
|
| No | 1-based line range (StrykerJS only) |
|
| No | Auto-scope mutation to git-changed lines. |
|
| No | Verify mode. Pass back a prior run's |
|
| No | Not supported by StrykerJS, rejected with an error (use |
|
| No | Stryker mutator names to exclude |
|
| No | Parallel mutation workers (StrykerJS only) |
|
| No | Validate test suite only, no mutations (StrykerJS only) |
|
| No | Output format (default: |
|
| No | Reuse previous run results (StrykerJS only). State is cached per (workspace, file) OUTSIDE the sandbox, because the sandbox is deleted after each run and without that the option would have nothing to reuse |
|
| No | Path segments to exclude from the sandbox, in addition to the built-in exclusions. A path is skipped when any of its segments equals the pattern exactly, not a substring match, so |
|
| No | Annotate each survivor with severity, why-it-matters, a test hint, and source context, and ranks severity-first. Default: |
|
| No | Cap on how many survivor (and no-coverage) line groups are returned after severity ranking. Hidden groups counted in |
|
| No | Drop survivor groups below this severity (requires enrichment, on by default). Dropped groups counted in |
|
| No | Verify mode by cached id: re-run against the survivor baseline saved from a prior audit (the |
|
| No | Mark mutants as equivalent (unkillable). Each entry: |
|
| No | Remove previously-suppressed mutants for this file. Each entry: |
|
| No | Gate threshold. When the mutation score is below this value, the output includes |
See CONTRIBUTING.md for development setup and the full parameter semantics.
State & the verify loop
Verify loop via runId
Every successful, non-verify audit_code_resilience call returns a runId (an 8-character id) in its JSON output. Use it to re-verify without copying the full baseline object:
Audit:
{ "filePath": "src/utils/math.ts" }→ response includes"runId": "a1b2c3d4".Fix or add tests.
Verify:
{ "filePath": "src/utils/math.ts", "runId": "a1b2c3d4" }→ reports which previously-surviving mutants are now killed.
runId is mutually exclusive with baseline, diffBase, and lineScope. The baseline cache lives in os.tmpdir()/chaos-mcp-runs/<hash-of-server-cwd>/, partitioned per server working directory so two checkouts served by two servers never read each other's entries, and is ephemeral (default TTL: 24 h; default max: 200 entries). Passing an unknown or expired runId returns an error.
triage_test_coverage also mints and returns a runId per ranking row, so you can drill into a weak file and immediately verify after fixing its tests.
Suppressing equivalent mutants
Some mutants are equivalent, logically identical to the original under all possible inputs, and cannot be killed by any test. Suppress them so they stop appearing in the output and stop dragging down the score:
{
"filePath": "src/utils/math.ts",
"suppress": [
{ "line": 99, "mutator": "StringLiteral", "reason": "guard always true for this type" }
]
}Suppressed mutants are:
Persisted to
<workspaceRoot>/.chaos-mcp/suppressions.json(keyed by workspace-relative file path).Identified by content: an entry names the mutator and the change the mutant makes, not the line it sits on, so it follows the code when an edit moves it.
Auto-excluded from every future
auditandtriagecall for that file, with no flag needed.Removed from the score denominator:
mutationScorerises and the output fieldsuppressedCounttells you how many were excluded.Excluded from verify mode: suppressed mutants won't appear as "still surviving".
To undo a wrong suppression:
{
"filePath": "src/utils/math.ts",
"unsuppress": [{ "line": 99, "mutator": "StringLiteral" }]
}.gitignore or commit? Add .chaos-mcp/ to .gitignore if the suppression list is personal, or commit it to share the equivalent-mutant list with the team. Suppression keys are workspace-relative, so the file is portable across machines.
Naming which mutant: the change field
One line can carry several mutants of the same mutator. This single line emits four ConditionalExpression mutants and two LogicalOperator mutants:
if (typeof g !== 'object' || g === null || Array.isArray(g)) return false;A { line, mutator } pair cannot say which of them you mean, so an entry identifies its mutant by the change it makes: the "original → mutated" string, the same form the report's changes field uses:
{
"suppress": [
{
"line": 331,
"mutator": "ConditionalExpression",
"change": "g === null → false",
"reason": "the null case is rejected by the group.line check below"
}
]
}change is optional. Omit it and Chaos-MCP resolves it from that run's survivors, so the ordinary two-field call still works. When several distinct mutants match, the entry is refused rather than suppressing all of them, and the response lists the candidate change values to choose from. The report's own changes field is capped at three per line and aggregated across mutators, so on exactly these lines it cannot show them all.
Replacement text alone is not enough to identify a mutant: several ConditionalExpression mutants on one line can all replace their span with true, and only the original span tells them apart. That is why the change carries both halves.
Staleness is detected, and usually repaired
Each entry also stores a fingerprint: a digest of the normalized source line (trimmed, internal whitespace collapsed) it was recorded against. Every run resolves each stored entry through a ladder, and ambiguity at any step is a refusal, nothing is ever guessed:
Outcome | Meaning | Applied? |
applied | the stored line still matches the stored fingerprint | yes, counted in |
relocated | the line moved, or was edited and the mutant found by its change; entry re-pointed | yes, also counted in |
drifted | the mutant could not be placed, or more than one candidate matched | no, counted in |
unverified | the entry has no fingerprint (written before v2) | no, counted in |
orphaned | the entry was placed but matched no surviving mutant, inert | n/a, counted in |
A relocation is written back to suppressions.json, so the repair happens once rather than on every run. Expect the file to show up in git status after an edit that moved a suppressed line.
An entry relocates in one of two ways. If the line's content is unchanged and simply moved, the match is exact and the move is reported as a bare count. If the line itself was edited, reflowed or a variable renamed or a comment appended, the mutant is found by its change instead, and that one can be wrong: if the original site was deleted and an unrelated site happens to produce the same change, the entry lands on code its reason was never written about. Those moves are reported individually, with the reason quoted, so you can confirm or drop them.
The bias throughout is deliberate: a suppression that is not applied lowers your score visibly, while one applied to the wrong code hides a real coverage gap invisibly. To restore a drifted or unverified entry, re-issue the same suppress argument, which re-stamps the fingerprint and keeps the existing reason and addedAt (a new reason, if you pass one, replaces the old).
An orphaned entry means one of three things and Chaos-MCP cannot tell which: the mutant is now killed, its identity no longer exists, or a mutatorDenylist entry stopped it being generated. It is inert either way, so drop it with unsuppress unless you know the mutant still exists.
Migrating an older file. version: 1 and version: 2 files load unchanged; every entry keeps its line, mutator, reason and addedAt, and nothing is deleted or back-filled.
v1 entries have no fingerprint and report as
unverifieduntil re-confirmed.v2 entries have no
change, so they fall back to mutator-only identity, broader than the mutant they were filed against, since one entry then covers every mutant of that mutator on its line, including ones added later.
To migrate a v2 corpus, run node scripts/migrate-suppressions-v3.mjs --write. It re-points entries whose line moved but whose content is unchanged, refuses to guess at anything ambiguous, and writes a replay payload; feeding those entries back through audit_code_resilience resolves each change from that run's survivors.
Config keys for state
Key | Default | Description |
|
| Path to the suppression file (workspace-relative or absolute) |
|
| Run-cache entry TTL in milliseconds |
|
| Max cached run entries; oldest are evicted when exceeded |
Batch triage: triage_test_coverage
A second tool ranks where your test suite is weakest across many files in one call.
{ "paths": ["src/utils", "src/index.ts"], "maxFiles": 25 }Directories are recursively expanded to supported source files (test files skipped), audited in bounded parallel (default max(1, min(4, cpus-1)) files at a time; capped at maxFiles; precedence maxFiles arg → defaultMaxFiles config → 25), and ranked weakest-first by mutation score:
{
"mode": "triage",
"summary": { "filesDiscovered": 30, "filesAudited": 25, "filesSkipped": 5, "filesErrored": 0 },
"ranking": [
{
"file": "src/a.ts",
"mutationScore": "62.50%",
"total": 16,
"killed": 10,
"survived": 5,
"noCoverage": 1
}
],
"errors": [],
"note": "Ranked weakest-first by mutation score. Drill into a file with audit_code_resilience for survivor detail."
}The tool response carries a structuredContent field (in addition to the text block) so MCP clients can consume the ranked payload directly without parsing JSON. The outputSchema on the tool definition describes the payload shape.
Drill into a weak file with audit_code_resilience for per-mutant survivor detail.
PR-diff scan (diffBase):
Pass diffBase to limit the triage to files changed in a PR or branch. paths becomes optional in this mode:
{ "diffBase": "main" }diffBase alone audits every changed supported source file in the workspace (relative to main via merge-base). Passing both limits the scan to changed files under those paths:
{ "diffBase": "main", "paths": ["src/utils"] }All four languages are mutated only on the changed lines (a per-file scopeNote is included in the ranking row); an untracked file, or one a diff scope could not be built for, falls back to whole-file with its own note.
Inline survivor detail (survivorsPerFile):
{ "paths": ["src"], "survivorsPerFile": 3 }survivorsPerFile (default 0, scores-only) inlines the top-N severity-ranked, enriched survivor groups into each ranking row so you can triage and inspect in one call. Set it to 0 for the compact leaderboard; raise it when you want to see the worst gaps immediately.
Parallel file auditing (fileConcurrency):
{ "paths": ["src"], "fileConcurrency": 8 }fileConcurrency controls how many files are audited in parallel (default max(1, min(4, cpus-1)); range 1–64). When fileConcurrency > 1 and the file is TypeScript, each StrykerJS run's worker count is automatically capped (floor((cpus-1) / fileConcurrency)) so total CPU use stays near the core count rather than oversubscribing. Other languages run their mutation tool without a worker-count override (they ignore the concurrency cap).
Parameters:
Parameter | Type | Description |
|
| Workspace-relative files/dirs to triage. Optional when |
|
| Cap on files audited (precedence: arg → |
|
| Per-file mutation-run timeout in ms (default: 300000). Also clamped by whatever remains of |
|
| Wall-clock budget for the whole sweep (default: 900000 = 15 min). Files not started before it runs out are returned in |
|
| Stryker mutator names to exclude, applied to every TypeScript/JS file. |
|
| Output format (default: |
|
| Auto-scope to git-changed files. |
|
| Inline top-N enriched survivors per ranked file (default |
|
| Files audited in parallel (default |
|
| Gate threshold. Per-row |
Pre-flight estimate: estimate_audit
Before committing to a full mutation run, use estimate_audit to check how many mutants a file will produce and (optionally) how long the run will take. It never runs the mutation test cycle by default.
{ "filePath": "src/utils/math.ts" }Output:
{
"target": "src/utils/math.ts",
"language": "typescript",
"mutants": 47,
"fidelity": "approx",
"basis": "source heuristic: 23 constructs",
"note": "Approximate mutant count from a source-parse heuristic; the real audit may differ. Run audit_code_resilience for exact results."
}With timing (withTiming: true): runs the test suite once to measure a baseline, then estimates total wall-clock time as mutants × baseline / concurrency. This provisions a sandbox and counts against your machine's resources, so use it when you want a time budget before a large audit.
{ "filePath": "src/utils/math.ts", "withTiming": true }Additional output fields when withTiming: true:
{
"baselineMs": 4200,
"estimatedMs": 197400,
"concurrency": 1
}Fidelity
Language | Fidelity | Basis |
Rust |
|
|
TypeScript / JavaScript |
| source-parse heuristic |
Python |
| source-parse heuristic |
For Rust, the estimate is exact for the mutants cargo mutants --list generates without running tests. The audit itself scores fewer, since mutants that fail to compile are excluded from its denominator and reported as incompetent. For all other languages the count is approximate, a lightweight heuristic over the source AST, and the actual audit may differ. Run audit_code_resilience for exact results.
If cargo-mutants is not installed, the Rust path falls back to the heuristic and reports fidelity: "approx" with a note.
Parameters
Parameter | Type | Required | Description |
|
| Yes | Workspace-relative path to the file to estimate. |
|
| No | When |
Use case
Call estimate_audit first when you are unsure whether a file is too large to audit interactively:
estimate_audit { "filePath": "src/big.ts" }→ 300 mutants, approx.Consider scoping with
lineScopeordiffBase, or scheduling the full run with a longertimeoutMs.audit_code_resilience { "filePath": "src/big.ts", "diffBase": "HEAD" }→ audits only your changed lines.
Gate mode: minScore
Both audit_code_resilience and triage_test_coverage accept a minScore parameter (0–100). When the mutation score falls below the threshold, the result reports the gate as failed. A failing gate is never an error. It is a data field for an agent or CI pipeline to read and act on.
Gate on a single file
{ "filePath": "src/utils/math.ts", "minScore": 80 }If the mutation score is below 80, the output includes:
{ "gate": { "minScore": 80, "passed": false } }If the score meets or exceeds the threshold, gate.passed is true. The field is absent when minScore is not provided.
The gate uses the suppression-adjusted mutation score (i.e. equivalent mutants excluded via suppress are not counted against the denominator).
Gate on a triage run
{ "paths": ["src"], "minScore": 75 }Each ranking row gains a passed field. The top-level output includes:
{
"gate": {
"minScore": 75,
"passed": false,
"failingFiles": ["src/utils/math.ts", "src/parser.ts"],
"notGraded": { "errored": 0, "unaudited": 0 },
"reason": "below_threshold"
}
}The triage gate fails closed. gate.passed is false if any file's score is below minScore or if any requested file was never measured: one that errored during the sweep (also listed in errors[]) or that the totalTimeoutMs budget never reached (also listed in unaudited[]). Grading a sweep on whichever subset happened to finish would let a CI step keyed on gate.passed go green over ungraded code, so an incomplete sweep never passes. A file audited only partially (its complete is false) fails on the same basis.
The gate object always carries minScore, passed, failingFiles, and notGraded whenever minScore was supplied:
failingFiles: workspace-relative paths that were measured and scored belowminScore.notGraded:{ "errored": <count>, "unaudited": <count> }, the files that produced no score at all.reason: present only on a failure, and the only machine-readable way to tell the two causes apart:"below_threshold": at least one file was measured and scored too low (failingFilesis non-empty)."files_not_graded": every measured file passed, but something was never measured (failingFilesis empty andnotGradedis non-zero).
A passed: false with an empty failingFiles is therefore expected, not a bug: check reason and notGraded before assuming a score problem.
CI use case
# Fail CI if any audited file scores below 80%
mcp call triage_test_coverage '{"paths":["src"],"minScore":80}' \
| jq -e '.gate.passed'An agent or CI script reads gate.passed and decides whether to block the build, open an issue, or continue. The tool call itself always succeeds (never isError) regardless of the gate outcome.
Configuration
Create a chaos-mcp.config.json in your workspace root for default settings:
{
"defaultTimeoutMs": 300000,
"mutatorDenylist": ["StringLiteral"],
"concurrency": 4,
"defaultMaxFiles": 25,
"defaultMaxSurvivors": 10,
"defaultSeverityFloor": "medium",
"defaultFileConcurrency": 4,
"container": {
"mode": "auto",
"runtime": "docker",
"cpus": 2,
"memoryMb": 4096
}
}Tool call arguments override config defaults.
Config key | Type | Default | Description |
|
|
| Per-file timeout in ms |
|
|
| Mutator names to exclude globally |
|
|
| Parallel mutation workers |
|
|
| Default triage file cap (integer ≥ 1); overridden by the |
|
|
| Default cap on survivor/no-coverage groups returned by |
|
| – | Default severity floor for survivor reporting; overridden by the |
|
|
| Default parallel file count for |
|
| – | PHP/Infection overrides. |
|
|
| Optional shared OCI execution backend for TypeScript, Python, Rust, and PHP |
|
|
| Sandbox provisioning. |
|
| computed from the machine | Memory governance. |
|
| – | StrykerJS settings for TypeScript/JavaScript targets. |
Overriding the StrykerJS test runner
Chaos-MCP detects the target's test runner from its config files, dependencies
and test script, then maps it to something StrykerJS can drive. vitest 2, 3
and 4 use StrykerJS's native @stryker-mutator/vitest-runner, which reports
per-mutant coverage so only the tests that actually cover a mutant are run.
Runners with no Stryker plugin (bun, node:test), and vitest outside the
verified 2 to 4 window, fall back to Stryker's built-in command runner: it drives
any framework as a black box, at the cost of re-running the whole related-test
set for every mutant.
The upper end of that window is measured, not read off a peer range. The runner
declares an open vitest: >=2.0.0, but on vitest 5 it reports every mutant as
Survived: no error, and the dry run still succeeds. Since a survivor is how
this tool reports a coverage hole, that turns a healthy suite into a wall of
false findings. vitest 5 projects therefore get the command runner, which was
checked against the same fixture and returns the correct split. The window moves
up again when a newer vitest is measured, not when a peer range permits it.
To override the detection, name the runner yourself:
{ "stryker": { "testRunner": "vitest" } }Your config is trusted, so this wins over anything detected in the workspace.
Use it when detection guesses wrong, or to force the command runner ("command")
if a native runner misbehaves on your project.
Sandbox dependencies
{ "sandbox": { "dependencies": "link-entries" } }
| What the sandbox gets | A write under it |
| Default. A real directory holding one symlink per installed package | To a new path ( |
| A full copy of the tree | Always stays in the sandbox. The only mode that fully contains a suite which writes through its own dependencies , and the slowest |
| One symlink for the whole directory (the pre-1.8 behaviour) | Always reaches your real workspace. Opt in knowingly |
PHP's vendor/ is always copied regardless of this setting, because Composer's
autoloader resolves __DIR__ through symlinks back to the real workspace.
An unrecognised value is dropped and the default applies.
Container execution
Container mode removes the need to install StrykerJS, Cosmic Ray,
cargo-mutants, or Infection on the host. Chaos-MCP starts one hardened,
short-lived container per audit, mounts the temporary sandbox at /workspace,
and runs both prebuild and mutation commands in that session. The real
workspace is never mounted. Recognized dependency trees linked into the
sandbox may be mounted separately and read-only, as described below.
{
"container": {
"mode": "container",
"runtime": "docker",
"network": "bridge",
"cpus": 2,
"memoryMb": 4096,
"pidsLimit": 512,
"startupTimeoutMs": 60000,
"tmpfsSizeMb": 2048
}
}On Windows, the container process runs as root. On Linux and macOS Chaos-MCP passes
--user <your uid>:<your gid>, so the audited suite runs as you. Windows has no POSIX uid/gid to pass, and hardcoding one risks making the/workspacebind mount unwritable under Docker Desktop, so no user is set and the container falls back to its image default. The container is still short-lived, resource-capped and network-scoped, and the audited code was already going to execute either way, but it is worth knowing that on Windows it executes as uid 0 inside that container.
Modes:
native(default) preserves the existing host-subprocess behavior.containerrequires Docker or Podman and fails clearly when unavailable.autouses containers when the configured runtime is reachable and otherwise falls back to native. Image or project failures do not silently fall back.
Each image carries exactly one language runtime, so a suite that spawns another language's toolchain cannot run inside it. Override the mode for just that language rather than giving up containers everywhere:
{
"container": {
"mode": "container",
"modes": { "php": "native" }
}
}Container settings:
Key | Type | Default | Description |
|
|
| Select the execution backend or runtime-only fallback behavior |
| per-language mode map | none | Override |
|
|
| OCI-compatible command used to create and manage audit containers |
|
|
| Container network mode or name; use |
| positive number |
| CPU limit for each audit container |
| positive integer |
| Memory limit in MiB for each audit container |
| positive integer |
| Maximum number of processes in each audit container |
| positive integer | 60 s startup; 10 s probe | Override the timeout for runtime probing, container creation, and startup |
| positive integer |
| Size of the writable |
| per-language image map | release-matched GHCR tags | Override the |
The images pin the language runtime and mutation engine, while the project
still supplies its own test dependencies. Which dependency trees get mounted
follows sandbox.dependencies: under link-entries (the default) and share
the host trees (node_modules, .venv/venv, and vendor) are
bind-mounted read-only at their own absolute paths, which is what makes the
sandbox's symlinks into them resolve inside the container, with one exception:
node_modules/.vite-temp gets a small writable tmpfs, because Vite writes a
bundled copy of the config it is loading there and a read-only tree would fail
the config load of every vitest project. The scratch is discarded with the
container and project test code still cannot write to the real dependency tree.
Under copy nothing extra is mounted and no tmpfs is needed, because the copies
already live inside the sandbox, which is itself mounted writable at
/workspace.
Dependencies containing native
extensions must be compatible with the selected Linux image; use an image
override when the project requires another runtime or platform build.
Chaos-MCP selects release-matched GHCR images for all four languages by
default; the optional images map accepts per-language tags or digest-pinned
references for private mirrors and custom runtimes.
The official images are published for Linux AMD64 and ARM64:
Language | Default image |
TypeScript / JavaScript |
|
Python |
|
Rust |
|
PHP |
|
The server never installs target-project dependencies. It reuses the recognized
dependency directories the project already has (node_modules, .venv/venv,
and vendor), mounting the host trees read-only under link-entries and
share and using the sandbox's own copies under copy. Install the target
project's dependencies before auditing it.
Containers run with a read-only root filesystem, all Linux capabilities
dropped, no-new-privileges, a private temporary filesystem, and configurable
CPU, memory, and PID limits. Resource usage defaults to a conservative two CPUs
and 4096 MiB of memory. The entire container is forcibly removed on timeout,
cancellation, normal completion, or engine failure.
Network mode is part of the audit's isolation boundary. The bridge default
allows outbound access for tests or dependency resolution; use
"network": "none" for untrusted code or offline audits that do not require
network access. Avoid host networking unless the target project explicitly
requires it and you accept the reduced isolation.
Check runtime connectivity and whether all four configured images are already present without pulling anything:
node build/index.js --container-doctorThe doctor exits non-zero when the runtime is unavailable or any configured
image is missing. Pull the reported image, or set a matching entry in
container.images, and run it again. startupTimeoutMs only governs runtime
probing and container startup; defaultTimeoutMs and per-tool timeoutMs
govern the mutation audit itself.
Enabling prebuildCommand
The prebuildCommand tool argument runs an arbitrary shell command inside the sandbox, which can reach outside it. It is disabled by default. Enable it explicitly with "allowPrebuild": true in chaos-mcp.config.json, or by setting the CHAOS_MCP_ALLOW_PREBUILD=1 environment variable. The auto-detected prebuild for Rust (cargo check) runs without this flag.
Python test commands declared by the audited project
Mutation testing runs the audited project's test suite. That is the job, but
the Python engine resolves its test command partly from the audited
project's own pyproject.toml, via the [tool.mutmut] runner key, and
cosmic-ray executes that string through a shell once per mutant. Accepting an
arbitrary command line from repository content is the same hazard
prebuildCommand is gated for, so it is bounded the same way:
A bare executable name (
nose2,ward,green) is accepted. It can name a program to run and nothing else: no arguments, no;,|,&&,$(...), or redirects.Anything else is refused with an explanation rather than silently replaced with pytest, which would quietly change which tests can kill a mutant.
To run such a command deliberately, either set it in your config (which is trusted, being your file):
{ "cosmicray": { "testRunner": "python -m unittest discover" } }or set CHAOS_MCP_ALLOW_REPO_TEST_COMMAND=1 to trust project-declared commands
in this workspace.
Auditing workspaces outside the working directory
By default Chaos-MCP only audits files beneath the directory the process was
launched in: the workspace-root walk stops at process.cwd(), and the sandbox
refuses to copy anything that escapes it. For an MCP server, which is launched
once with a fixed cwd, that means a server started in project A cannot audit
project B at all.
Set CHAOS_ALLOWED_ROOTS to name additional roots, separated by the platform
path delimiter (: on POSIX, ; on Windows):
{
"mcpServers": {
"chaos-mcp": {
"command": "node",
"args": ["/path/to/Chaos-MCP/build/index.js"],
"env": { "CHAOS_ALLOWED_ROOTS": "/srv/project-b:/srv/project-c" }
}
}
}A workspace is accepted when it is inside the working directory or inside one of these roots; everything else is still refused. Descendants of a listed root are included, siblings and parents are not. When roots nest (a monorepo and one of its packages), the innermost match bounds the root walk. Leaving the variable unset keeps the cwd-only behaviour exactly as before.
Supported test runners (auto-detected)
Language | Mutation Tool | Detected Runners |
TypeScript/JS | StrykerJS | vitest, jest, mocha, jasmine, bun, node:test |
Python | cosmic-ray | pytest, unittest |
Rust | cargo-mutants | cargo test, cargo-nextest |
PHP | Infection | phpunit |
CLI flags
chaos-mcp [flags]
--version Print version and exit
--help Show help text and exit
--config Path to a JSON config file
--container-doctor
Check runtime connectivity and whether all four configured
container images are present without pulling them
--verbose Enable diagnostic logging to stderrProtocol features
Progress notifications
When an MCP client includes a progressToken in a tool call's _meta field, Chaos-MCP emits notifications/progress events during the run. Clients that omit progressToken receive no notifications , there is zero overhead for clients that do not opt in.
Triage emits one notification per file as it completes:
Field | Value |
| files completed so far |
| total files to audit |
|
|
Audit emits four coarse milestones:
|
|
|
1 | 4 |
|
2 | 4 |
|
3 | 4 |
|
4 | 4 |
|
Estimate does not emit progress notifications.
Cancellation
Cancelling an in-flight MCP request aborts the run cleanly:
The abort signal propagates through the tool handler into
RunOptions.signaland from there into the mutation engine subprocess, terminating it.The sandbox is always cleaned up even if cancellation occurs mid-run.
The cancelled call returns
"Operation cancelled."as a tool error rather than throwing.
All three tools (audit_code_resilience, triage_test_coverage, estimate_audit) respect cancellation.
Resources
The server exposes three static resources, discoverable via resources/list and readable via resources/read:
URI | MIME type | Contents |
|
| Per-language entry: engine name, |
|
| Every |
|
| All three tools (args summary) and the triage → audit → verify workflow loop. |
Prompts
The server exposes two prompts, discoverable via prompts/list and retrieved via prompts/get:
Prompt | Required argument | Purpose |
|
| Returns a |
|
| Returns a |
Development
npm run check # Full CI pipeline: build + lint + format + test
npm run test:watch # Watch mode for iterative development
npm run test:coverage # Tests with coverage reportThe suite runs on every push/PR to main via CI (Node 24/26). v8 line/statement coverage of src/ sits at ~99%, and the source is additionally hardened by running Chaos-MCP against its own code, so the suite is graded by mutation score rather than by line coverage alone.
See CONTRIBUTING.md for detailed development setup and contribution guidelines.
Further reading
License
MIT. See LICENSE for details.
Links
Built by Tim Schipper and released as open source under Aranea Development.
Available Tools
3 toolsaudit_code_resilienceA
Runs on-demand, sandbox-isolated mutation testing against a single source file to identify gaps in unit test coverage. Chaos-MCP generates mutants (logical faults like changing > to >=) and checks whether the local test suite catches them. Surviving mutants indicate test coverage holes. Supports TypeScript/JavaScript (StrykerJS), Python (cosmic-ray), Rust (cargo-mutants), and PHP (Infection). PATHS: filePath is resolved against the SERVER's working directory (or given absolute). The target in the result is relative to the audited file's own WORKSPACE, which differs whenever the file sits in a monorepo package or another root — packages/api/src/math.ts comes back as src/math.ts. When the two differ the result carries workspace (an absolute path) and join(workspace, target) is a filePath you can pass straight back; when it is absent, target already is one. A file from triage_test_coverage is always a valid filePath as-is.
| Name | Required | Description | Default |
|---|---|---|---|
| runId | No | Verify mode by id: re-run against the cached survivor baseline from a prior audit (the runId it returned). Auto-scoped to the baseline lines (StrykerJS) or whole-file (other languages). Mutually exclusive with baseline, diffBase, and lineScope. Example: "a1b2c3d4". | |
| dryRun | No | If true, run only the dry-run phase to validate the test suite passes before mutation testing (StrykerJS only). Useful for pre-flight checks. Example: false | |
| enrich | No | Augment each surviving / no-coverage line with deterministic guidance: severity (high/medium/low), a "why it matters" explanation, a test-writing hint, and a source-context snippet — and rank survivors severity-first. Defaults to TRUE; pass false to disable and return the plain (unranked, unclassified) output. Richest for TypeScript; Python and PHP report severity "unknown". | |
| baseline | No | Verify mode: pass back the `survivors` and `noCoverage` arrays from a PRIOR run to re-test only those mutants and get a delta — which are now killed vs still surviving (plus any new regressions on the same lines). The re-run is auto-scoped to the baseline lines (StrykerJS) or whole-file (other languages). Mutually exclusive with diffBase and lineScope. Example: { "survivors": [{ "line": 42, "mutators": { "ConditionalExpression": 1 } }] } | |
| diffBase | No | Auto-scope mutation to only the lines changed in git. The value selects the base to diff against: "HEAD" (all uncommitted changes), "staged" (staged changes only), or any git ref/branch/SHA (e.g. "main", resolved via merge-base with HEAD). Mutually exclusive with lineScope. Line-level scoping is StrykerJS-only; Python/Rust/PHP targets run whole-file with a note. If the file has no changes vs the base, the run is skipped. Example: "HEAD" | |
| filePath | Yes | Path to the file to audit, resolved against the server's working directory (an absolute path is also accepted). NOT relative to the audited file's workspace — in a monorepo that is the package root, and the two differ. Must end in .ts, .js, .tsx, .jsx, .mjs, .cjs, .mts, .cts, .py, .rs, or .php. Example: "src/utils/math.ts" | |
| minScore | No | Gate: if the mutation score is below this (0–100), the result reports gate.passed=false (never an error). Example: 80. | |
| suppress | No | Mark mutants as equivalent (unkillable) so future runs exclude them from the score and output. Appended to .chaos-mcp/suppressions.json for this file, stamped with a fingerprint of the source line so a later edit to that line retires the suppression instead of silently re-pointing it. Re-issue the same entry to re-confirm one reported as drifted or unverified. A suppression is identified by its mutator and the CHANGE it makes, not by its line, so it follows the code when an edit moves it. Supply `change` (the "original → mutated" string from a survivor's `changes`) to name WHICH mutant when one line carries several of the same mutator; omit it and Chaos-MCP resolves it from this run's survivors, refusing the entry rather than suppressing all of them if several match. Example: [{ "line": 42, "mutator": "ConditionalExpression", "reason": "guard unreachable" }]. | |
| lineScope | No | Constrain mutations to a 1-based line range (inclusive). Only supported by StrykerJS; ignored for Python, Rust, and PHP targets. Useful for surgically auditing a specific function or block. Example: { "start": 10, "end": 45 } | |
| timeoutMs | No | Maximum time in milliseconds for the entire mutation run. Default: 300000 (5 minutes). Increase for large files or slow test suites. Must be <= 2147483647 (the largest delay a timer accepts). Example: 120000 for a 2-minute cap. | |
| unsuppress | No | Remove previously-suppressed mutants for this file (undo a wrong suppress). Supply `change` to remove one specific entry; omit it to remove every entry for that mutator. The `line` is ignored when matching, so an entry that has relocated is still removable. | |
| concurrency | No | Number of parallel mutation workers. Honoured by StrykerJS (--concurrency), cargo-mutants (-j) and Infection (--threads); cosmic-ray has no worker flag and reports this as an ignored option. When omitted, StrykerJS auto-detects CPU core count while cargo-mutants deliberately stays low (2 jobs, or 1 on a small machine) because each job wants its own multi-GB target directory. Lower this on memory-constrained machines; raise it on CI with spare cores. Must be an integer between 1 and 64. Example: 4 | |
| incremental | No | Enable incremental mode to reuse results from a previous run and skip unchanged mutants (StrykerJS only). Speeds up repeat audits of the same file. Example: true | |
| maxSurvivors | No | Cap on how many survivor (and how many no-coverage) line groups are returned, after severity ranking. Hidden groups are counted in survivorsTruncated/noCoverageTruncated. Precedence: this arg > config.defaultMaxSurvivors > 10. Example: 20 | |
| outputFormat | No | Output format for the result. "json" (default) returns a structured MutationResult object. "text" returns a human-readable summary. Example: "json" | |
| severityFloor | No | Report-time filter: drop survivor groups below this severity (requires enrichment, which is on by default). Dropped groups are counted in survivorsFiltered/noCoverageFiltered. "unknown"-severity groups are below "low" and are dropped by any floor. Ignored (with a note) when enrich is false. Example: "high" | |
| ignorePatterns | No | Path segments for files/directories to exclude from the sandbox, applied in addition to built-in exclusions. A path is skipped when any of its segments equals the pattern exactly. This now also suppresses the dependency-directory link, so excluding "node_modules" leaves the sandbox without it — which will usually break the run. A pattern is never a suffix or a substring: ".test.ts" excludes only a path segment named exactly that, not "billing.test.ts". One trailing separator is stripped, so "fixtures/" and "fixtures" are the same pattern. Example: ["fixtures/", "testdata"] | |
| mutatorDenylist | No | Stryker mutator names to exclude — these are filtered out. StrykerJS only. Useful for skipping noisy or irrelevant mutators. Example: ["StringLiteral"] | |
| prebuildCommand | No | Shell command to run in the sandbox BEFORE mutation testing begins. Use this to compile/build the target — the sandbox has a full workspace copy. Essential for TypeScript projects ("npm run build") and Rust projects ("cargo build"). DISABLED BY DEFAULT: because it runs an arbitrary shell command that can reach outside the sandbox, the server must opt in via "allowPrebuild": true in its config file or the CHAOS_MCP_ALLOW_PREBUILD=1 environment variable. Counts against the overall timeoutMs budget. Example: "npm run build" | |
| mutatorAllowlist | No | NOT SUPPORTED by StrykerJS — REJECTED: passing this fails the call with an error. v9 has no way to express "only these mutators" without the full mutator list. Use mutatorDenylist to exclude noisy mutators instead, or supply your own stryker.config.json. | |
| perMutantTimeoutMs | No | Maximum time in milliseconds per individual mutant test (StrykerJS and Rust). Distinct from timeoutMs (total run cap). Use this to prevent a single slow mutant from hanging the entire mutation run. Default: StrykerJS default (~5000ms). Must be <= 2147483647 (the largest delay a timer accepts). Example: 10000 for a 10-second per-mutant ceiling. |
Output Schema
| Name | Required | Description |
|---|---|---|
| gate | No | |
| mode | No | |
| note | No | |
| runId | No | |
| target | No | |
| summary | No | |
| complete | No | |
| nowKilled | No | |
| resources | No | |
| scopeNote | No | |
| survivors | No | |
| workspace | No | Absolute workspace root that `target` is relative to. Present only when that root is not the server's working directory (a monorepo package, or another root via CHAOS_ALLOWED_ROOTS). `join(workspace, target)` is a path this tool accepts as `filePath`; when the field is absent, `target` already is one. |
| enrichNote | No | |
| noCoverage | No | |
| incompetent | No | |
| killedCount | No | |
| coverageNote | No | |
| fidelityNote | No | |
| newSurvivors | No | |
| baselineTotal | No | |
| coverageScope | No | |
| mutationScore | No | |
| stoppedReason | No | |
| batchesPlanned | No | |
| ignoredOptions | No | |
| stillSurviving | No | |
| suppressedCount | No | |
| batchesCompleted | No | |
| unsuppressMissed | No | |
| suggestedTestFile | No | |
| survivorsFiltered | No | |
| unsuppressedCount | No | |
| noCoverageFiltered | No | |
| survivorsTruncated | No | |
| driftedSuppressions | No | |
| noCoverageTruncated | No | |
| orphanedSuppressions | No | |
| rejectedSuppressions | No | |
| relocatedSuppressions | No | |
| unverifiedSuppressions | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure, and it delivers. It reveals sandbox isolation, side effects (suppression file append, prebuildCommand escaping the sandbox), language-specific limitations (lineScope Stryker-only, concurrency ignored by cosmic-ray), and the subtle path-resolution semantics (filePath vs workspace-relative target). This goes well beyond a simple 'mutates source' statement and gives agents the safety and operational expectations they need.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-organized: a crisp opening statement followed by a focused PATHS paragraph. Every sentence earns its place by covering cross-language differences, path resolution, and safety-relevant side effects that are not in the schema. It could be slightly tighter, but the length is justified by the tool's 21-parameter complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 21 parameters, nested objects, and zero annotations, the description is thoroughly complete. It covers operational constraints (language support, path resolution, timeouts), side effects, and limitations across languages. An output schema exists so return values are covered elsewhere; the description provides everything else needed to call the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds genuine extra meaning via the PATHS paragraph, explaining how the result's 'target' relates to the input 'filePath' and workspace in monorepo scenarios, and explicitly noting that a 'file' from triage_test_coverage is a valid filePath. This resolves the kind of ambiguity the schema alone leaves open.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb, resource, and method: 'Runs on-demand, sandbox-isolated mutation testing against a single source file to identify gaps in unit test coverage.' It clearly distinguishes the tool from siblings by describing the concrete mechanism (mutants, survivors) and the supported language/tool mappings, so an agent knows exactly what this tool does without reading the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains what the tool does and how it behaves, but never tells the agent when to choose it over the siblings 'estimate_audit' or 'triage_test_coverage' or when not to use it. There is no explicit 'use this when...' or 'for that, use X instead' guidance, so an agent must infer the appropriate usage context from the purpose alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
estimate_auditA
Cheap pre-flight estimate of how big/long auditing a file will be, WITHOUT running the full mutation test cycle. Returns an approximate mutant count (for Rust, an exact count of the mutants cargo-mutants --list GENERATES — the audit scores fewer, excluding unviable ones as incompetent; a source heuristic for TS/JS/Python/PHP, labeled fidelity:"approx"). Set withTiming:true to also run the test suite once and estimate wall-clock time. Use this before audit_code_resilience to decide whether to audit now, scope down, or skip.
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | Path to the source file to estimate, within the workspace. Example: "src/math.ts". | |
| timeoutMs | No | The audit budget in milliseconds this estimate is GRADED against: budgetMs echoes it, and fitsBudget/recommendation compare the estimated time to it. Resolved exactly as audit_code_resilience resolves its own timeoutMs (same argument, config keys, and per-language defaults), so an estimate answers the question for the audit you would actually run. Default: 300000 (5 minutes). Must be <= 2147483647 (the largest delay a timer accepts). Example: 120000. | |
| withTiming | No | When true, run the test suite once to measure a baseline and estimate total wall-clock time (mutants × baseline / concurrency). Default false (count only, no test run). |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | Yes | |
| basis | Yes | |
| target | Yes | |
| mutants | Yes | |
| budgetMs | No | |
| fidelity | Yes | |
| language | Yes | |
| workspace | No | Absolute workspace root that `target` is relative to. Present only when that root is not the server's working directory (a monorepo package, or another root via CHAOS_ALLOWED_ROOTS). `join(workspace, target)` is a path this tool accepts as `filePath`; when the field is absent, `target` already is one. |
| baselineMs | No | |
| fitsBudget | No | |
| concurrency | No | |
| estimatedMs | No | |
| optimisticMs | No | |
| upperBoundMs | No | |
| recommendation | No | |
| timingConfidence | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full transparency burden. It reveals that no full mutation cycle runs, that Rust counts are exact cargo-mutants candidates, that other languages use a heuristic labeled 'approx', and that withTiming:true adds a test-suite run. It does not mention errors, permissions, or edge-cases, but the main behavioral trade-offs are well disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and front-loaded: purpose first, then key behavioral caveats, then usage guidance. The parenthetical about Rust versus TS/JS/Python/PHP is technical but earns its place since that fidelity distinction is essential for acting on the estimate.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the output schema exists and all parameters are documented in the schema, the description covers purpose, behavior, language-specific fidelity, and integration with a sibling tool. It could add preconditions or failure modes, but it is substantially complete for this tool's scope.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema already thoroughly documents filePath, timeoutMs, and withTiming. The description adds some context around withTiming and the estimate's relationship to audit_code_resilience, but it does not meaningfully expand on the schema's parameter details. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: it is a 'Cheap pre-flight estimate' of auditing a file, and it explicitly distinguishes itself by noting it runs WITHOUT the full mutation test cycle. It also differentiates from sibling audit_code_resilience by framing this tool as the pre-flight decision aid before running that audit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly says 'Use this before audit_code_resilience to decide whether to audit now, scope down, or skip,' giving explicit usage context and naming the primary alternative. It lacks an explicit 'when not to use' or comparison with triage_test_coverage, so it falls just short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
triage_test_coverageA
Batch triage: audit a set of files and/or directories and return a weakest-first ranked leaderboard of mutation scores, so you can see where the test suite is most fragile in one call. Directories are recursively expanded to supported source files (.ts/.js/.py/.rs/.php), skipping test files. Files are audited in parallel (see fileConcurrency, default min(4, cpus-1)), under a shared wall-clock budget (see totalTimeoutMs). Drill into a weak file with audit_code_resilience for per-mutant survivor detail: each row's file is relative to the server's working directory, so it can be passed straight back as that tool's filePath (its own target is spelled differently — see that tool's description).
| Name | Required | Description | Default |
|---|---|---|---|
| paths | No | Files and/or directories to triage, resolved against the server's working directory. Directories are recursively expanded to supported source files. Each ranked row reports its `file` in that same spelling, so a row can be fed straight back to this tool or to audit_code_resilience. Example: ["src/utils", "src/index.ts"] | |
| diffBase | No | Auto-scope the triage to files changed in git. "HEAD" (uncommitted), "staged", or any ref/branch/SHA (merge-base with HEAD). Makes "paths" optional: diffBase alone scans all changed supported source files; diffBase + paths intersects with those paths. TypeScript files are mutated only on changed lines; other languages run whole-file. Example: "main" | |
| maxFiles | No | Cap on the number of files audited (precedence: this arg > config.defaultMaxFiles > 25). Files beyond the cap are skipped (reported in the summary). Example: 25 | |
| minScore | No | Gate: if any file's mutation score is below this (0–100), the result reports gate.passed=false and lists the failing files. Never causes an error. Example: 80. | |
| timeoutMs | No | Per-file mutation-run timeout in milliseconds. Default: 300000 (5 minutes). Must be <= 2147483647 (the largest delay a timer accepts). Also clamped by whatever remains of totalTimeoutMs. | |
| outputFormat | No | Output format. "json" (default) or "text". | |
| totalTimeoutMs | No | Wall-clock budget for the WHOLE sweep in milliseconds. Default: 900000 (15 minutes). Files not started before it runs out are returned in "unaudited" rather than audited, so a large sweep still returns the ranking it produced. Must be <= 2147483647 (the largest delay a timer accepts). Example: 1800000 | |
| fileConcurrency | No | How many files to audit in parallel. Default min(4, cpus-1). When >1, the per-file worker count is capped for every engine that has one (StrykerJS --concurrency, cargo-mutants -j, Infection --threads), and for StrykerJS each mutant's test run is additionally pinned to a single vitest worker, so those three layers multiply out to roughly the core count. Rust is the exception to watch: `cargo build`/`cargo test` parallelise internally and take no cap, so a Rust sweep runs fileConcurrency concurrent cargo builds, each of which wants its own multi-GB target directory — lower this to 1 or 2 on a memory-constrained machine. Raise with care on a workstation: a sweep is still the most resource-hungry thing this server does. Example: 4 | |
| mutatorDenylist | No | Stryker mutator names to exclude, applied to every TypeScript/JS file. | |
| survivorsPerFile | No | How many top (severity-ranked, enriched) survivor groups to inline per ranked file. 0 (default) returns a scores-only leaderboard. Example: 3 |
Output Schema
| Name | Required | Description |
|---|---|---|
| gate | No | |
| mode | Yes | |
| note | Yes | |
| errors | Yes | |
| ranking | Yes | |
| summary | Yes | |
| resources | No | |
| scopeNote | No | |
| unaudited | No | |
| stoppedReason | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It thoroughly covers recursive directory expansion, test-file skipping, parallel execution with defaults, shared wall-clock budget, unaudited-file reporting, gate behavior, resource warnings, Rust-specific caveats, and path relativity.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but every sentence carries operational information. The core purpose is front-loaded, and the rest is organized by concern; for a 10-parameter tool with resource caveats, the length is earned.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema available and a description covering purpose, routing to a sibling, behavioral traits, resource limits, and parameter interplay, an agent has everything needed to select and invoke the tool correctly. The only minor gap is no guidance on estimate_audit, which does not block correct use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds meaningful cross-parameter semantics beyond the schema: fileConcurrency layers with engine worker counts, timeoutMs vs. totalTimeoutMs interaction, maxFiles precedence, diffBase+paths intersection, and the direct handoff to audit_code_resilience.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('triage'), resource (files/directories, mutation scores), and output (weakest-first ranked leaderboard). It clearly distinguishes itself from audit_code_resilience by framing itself as a batch overview vs. drill-down tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly positions the tool as a one-call fragility sweep and directs the agent to audit_code_resilience for per-mutant detail, including the file-path handoff. It does not mention the estimate_audit sibling, so exclusion guidance is not fully complete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v5.1.1- Changed
audit_code_resilience5 fields changed- changed
Input schema / properties / perMutantTimeoutMs / descriptionPrevious value: -"Maximum time in milliseconds per individual mutant test (StrykerJS only). Distinct from timeoutMs (total run cap). Use this to prevent a single slow mutant from hanging the entire mutation run. Default: StrykerJS default (~5000ms). Must be <= 2147483647 (the largest delay a timer accepts). Example: 10000 for a 10-second per-mutant ceiling."New value: +"Maximum time in milliseconds per individual mutant test (StrykerJS and Rust). Distinct from timeoutMs (total run cap). Use this to prevent a single slow mutant from hanging the entire mutation run. Default: StrykerJS default (~5000ms). Must be <= 2147483647 (the largest delay a timer accepts). Example: 10000 for a 10-second per-mutant ceiling." - changed
Output schema / oneOfPrevious value: -[ - { - "required": [ - "target", - "mutationScore", - "summary", - "survivors", - "noCoverage", - "note" - ] - }, - { - "required": [ - "target", - "mode", - "baselineTotal", - "killedCount", - "nowKilled", - "stillSurviving", - "newSurvivors", - "note" - ] - } -]New value: +[ + { + "required": [ + "target", + "mutationScore", + "summary", + "survivors", + "noCoverage", + "note", + "resources" + ] + }, + { + "required": [ + "target", + "mode", + "baselineTotal", + "killedCount", + "nowKilled", + "stillSurviving", + "newSurvivors", + "note" + ] + } +] - added
Output schema / properties / coverageNoteAdded value: +{ + "type": "string" +} - added
Output schema / properties / coverageScopeAdded value: +{ + "enum": [ + "project", + "selected" + ], + "type": "string" +} - added
Output schema / properties / resourcesAdded value: +{ + "properties": { + "availableAtStartBytes": { + "type": "integer" + }, + "fileConcurrency": { + "type": "integer" + }, + "limitBytes": { + "type": "integer" + }, + "overBudget": { + "type": "boolean" + }, + "perFileWorkers": { + "type": "integer" + }, + "source": { + "enum": [ + "host", + "cgroup", + "unavailable" + ], + "type": "string" + }, + "watchdogTrips": { + "type": "integer" + } + }, + "required": [ + "availableAtStartBytes", + "limitBytes", + "source", + "fileConcurrency", + "perFileWorkers", + "overBudget", + "watchdogTrips" + ], + "type": "object" +}
- Changed
triage_test_coverage4 fields changed- added
Output schema / properties / ranking / items / properties / coverageNoteAdded value: +{ + "type": "string" +} - added
Output schema / properties / ranking / items / properties / coverageScopeAdded value: +{ + "enum": [ + "project", + "selected" + ], + "type": "string" +} - added
Output schema / properties / ranking / items / properties / groupedAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / resourcesAdded value: +{ + "properties": { + "availableAtStartBytes": { + "type": "integer" + }, + "fileConcurrency": { + "type": "integer" + }, + "limitBytes": { + "type": "integer" + }, + "overBudget": { + "type": "boolean" + }, + "perFileWorkers": { + "type": "integer" + }, + "source": { + "enum": [ + "host", + "cgroup", + "unavailable" + ], + "type": "string" + }, + "watchdogTrips": { + "type": "integer" + } + }, + "required": [ + "availableAtStartBytes", + "limitBytes", + "source", + "fileConcurrency", + "perFileWorkers", + "overBudget", + "watchdogTrips" + ], + "type": "object" +}
2 tool updates
v4.2.2- Changed
audit_code_resilience1 field changed- changed
Input schema / properties / mutatorAllowlist / descriptionPrevious value: -"NOT SUPPORTED in StrykerJS v9 — REJECTED: passing this fails the call with an error. v9 has no way to express \"only these mutators\" without the full mutator list. Use mutatorDenylist to exclude noisy mutators instead, or supply your own stryker.config.json."New value: +"NOT SUPPORTED by StrykerJS — REJECTED: passing this fails the call with an error. v9 has no way to express \"only these mutators\" without the full mutator list. Use mutatorDenylist to exclude noisy mutators instead, or supply your own stryker.config.json."
- Changed
triage_test_coverage1 field changed- added
Output schema / properties / ranking / items / properties / fidelityNoteAdded value: +{ + "type": "string" +}
3 tool updates
v3.0.1- Changed
audit_code_resilience13 fields changed- changed
Input schema / properties / concurrency / descriptionPrevious value: -"Number of parallel mutation workers (StrykerJS only). When omitted, StrykerJS auto-detects CPU core count. Lower this on memory-constrained machines; raise it on CI with spare cores. Must be an integer between 1 and 64. Example: 4"New value: +"Number of parallel mutation workers. Honoured by StrykerJS (--concurrency), cargo-mutants (-j) and Infection (--threads); cosmic-ray has no worker flag and reports this as an ignored option. When omitted, StrykerJS auto-detects CPU core count while cargo-mutants deliberately stays low (2 jobs, or 1 on a small machine) because each job wants its own multi-GB target directory. Lower this on memory-constrained machines; raise it on CI with spare cores. Must be an integer between 1 and 64. Example: 4" - changed
Input schema / properties / filePath / descriptionPrevious value: -"Workspace-relative path to the file to audit. Must end in .ts, .js, .tsx, .jsx, .mjs, .cjs, .mts, .cts, .py, .rs, or .php. Example: \"src/utils/math.ts\""New value: +"Path to the file to audit, resolved against the server's working directory (an absolute path is also accepted). NOT relative to the audited file's workspace — in a monorepo that is the package root, and the two differ. Must end in .ts, .js, .tsx, .jsx, .mjs, .cjs, .mts, .cts, .py, .rs, or .php. Example: \"src/utils/math.ts\"" - changed
Input schema / properties / ignorePatterns / descriptionPrevious value: -"Substring patterns for files/directories to exclude from the sandbox copy, applied in addition to built-in exclusions. Any path containing the pattern string is skipped. Example: [\".test.ts\", \"fixtures/\", \"snapshots/\"]"New value: +"Path segments for files/directories to exclude from the sandbox, applied in addition to built-in exclusions. A path is skipped when any of its segments equals the pattern exactly. This now also suppresses the dependency-directory link, so excluding \"node_modules\" leaves the sandbox without it — which will usually break the run. A pattern is never a suffix or a substring: \".test.ts\" excludes only a path segment named exactly that, not \"billing.test.ts\". One trailing separator is stripped, so \"fixtures/\" and \"fixtures\" are the same pattern. Example: [\"fixtures/\", \"testdata\"]" - changed
Input schema / properties / suppress / descriptionPrevious value: -"Mark mutants as equivalent (unkillable) so future runs exclude them from the score and output. Appended to .chaos-mcp/suppressions.json for this file, stamped with a fingerprint of the source line so a later edit to that line retires the suppression instead of silently re-pointing it. Re-issue the same entry to re-confirm one reported as drifted or unverified. Example: [{ \"line\": 42, \"mutator\": \"ConditionalExpression\", \"reason\": \"guard unreachable\" }]."New value: +"Mark mutants as equivalent (unkillable) so future runs exclude them from the score and output. Appended to .chaos-mcp/suppressions.json for this file, stamped with a fingerprint of the source line so a later edit to that line retires the suppression instead of silently re-pointing it. Re-issue the same entry to re-confirm one reported as drifted or unverified. A suppression is identified by its mutator and the CHANGE it makes, not by its line, so it follows the code when an edit moves it. Supply `change` (the \"original → mutated\" string from a survivor's `changes`) to name WHICH mutant when one line carries several of the same mutator; omit it and Chaos-MCP resolves it from this run's survivors, refusing the entry rather than suppressing all of them if several match. Example: [{ \"line\": 42, \"mutator\": \"ConditionalExpression\", \"reason\": \"guard unreachable\" }]." - added
Input schema / properties / suppress / items / properties / changeAdded value: +{ + "type": "string" +} - changed
Input schema / properties / unsuppress / descriptionPrevious value: -"Remove previously-suppressed mutants for this file (undo a wrong suppress)."New value: +"Remove previously-suppressed mutants for this file (undo a wrong suppress). Supply `change` to remove one specific entry; omit it to remove every entry for that mutator. The `line` is ignored when matching, so an entry that has relocated is still removable." - added
Input schema / properties / unsuppress / items / properties / changeAdded value: +{ + "type": "string" +} - added
Output schema / properties / orphanedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / rejectedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / relocatedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / unsuppressMissedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / unsuppressedCountAdded value: +{ + "type": "integer" +} - added
Output schema / properties / workspaceAdded value: +{ + "description": "Absolute workspace root that `target` is relative to. Present only when that root is not the server's working directory (a monorepo package, or another root via CHAOS_ALLOWED_ROOTS). `join(workspace, target)` is a path this tool accepts as `filePath`; when the field is absent, `target` already is one.", + "type": "string" +}
- Changed
estimate_audit1 field changed- added
Output schema / properties / workspaceAdded value: +{ + "description": "Absolute workspace root that `target` is relative to. Present only when that root is not the server's working directory (a monorepo package, or another root via CHAOS_ALLOWED_ROOTS). `join(workspace, target)` is a path this tool accepts as `filePath`; when the field is absent, `target` already is one.", + "type": "string" +}
- Changed
triage_test_coverage5 fields changed- changed
Input schema / properties / fileConcurrency / descriptionPrevious value: -"How many files to audit in parallel. Default min(4, cpus-1). When >1, each StrykerJS run's worker count is capped, and each mutant's test run is pinned to a single vitest worker, so the three layers multiply out to roughly the core count rather than to fileConcurrency x strykerConcurrency x vitestWorkers. Raise with care on a workstation: a sweep is still the most resource-hungry thing this server does. Example: 4"New value: +"How many files to audit in parallel. Default min(4, cpus-1). When >1, the per-file worker count is capped for every engine that has one (StrykerJS --concurrency, cargo-mutants -j, Infection --threads), and for StrykerJS each mutant's test run is additionally pinned to a single vitest worker, so those three layers multiply out to roughly the core count. Rust is the exception to watch: `cargo build`/`cargo test` parallelise internally and take no cap, so a Rust sweep runs fileConcurrency concurrent cargo builds, each of which wants its own multi-GB target directory — lower this to 1 or 2 on a memory-constrained machine. Raise with care on a workstation: a sweep is still the most resource-hungry thing this server does. Example: 4" - changed
Input schema / properties / paths / descriptionPrevious value: -"Workspace-relative files and/or directories to triage. Directories are recursively expanded to supported source files. Example: [\"src/utils\", \"src/index.ts\"]"New value: +"Files and/or directories to triage, resolved against the server's working directory. Directories are recursively expanded to supported source files. Each ranked row reports its `file` in that same spelling, so a row can be fed straight back to this tool or to audit_code_resilience. Example: [\"src/utils\", \"src/index.ts\"]" - added
Output schema / properties / ranking / items / properties / orphanedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / rejectedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / relocatedSuppressionsAdded value: +{ + "type": "integer" +}
3 tool updates
v1.7.0- Changed
audit_code_resilience25 fields changed- added
Input schema / properties / baseline / anyOfAdded value: +[ + { + "required": [ + "survivors" + ] + }, + { + "required": [ + "noCoverage" + ] + } +] - added
Input schema / properties / baseline / properties / noCoverage / items / properties / line / maximumAdded value: +100000 - added
Input schema / properties / baseline / properties / noCoverage / items / properties / mutators / additionalProperties / minimumAdded value: +1 - added
Input schema / properties / baseline / properties / survivors / items / properties / line / maximumAdded value: +100000 - added
Input schema / properties / baseline / properties / survivors / items / properties / mutators / additionalProperties / minimumAdded value: +1 - changed
Input schema / properties / filePath / descriptionPrevious value: -"Workspace-relative path to the file to audit. Must end in .ts, .js, .tsx, .jsx, .py, .rs, or .php. Example: \"src/utils/math.ts\""New value: +"Workspace-relative path to the file to audit. Must end in .ts, .js, .tsx, .jsx, .mjs, .cjs, .mts, .cts, .py, .rs, or .php. Example: \"src/utils/math.ts\"" - added
Input schema / properties / lineScope / properties / end / maximumAdded value: +100000 - added
Input schema / properties / lineScope / properties / start / maximumAdded value: +100000 - changed
Input schema / properties / mutatorAllowlist / descriptionPrevious value: -"NOT SUPPORTED in StrykerJS v9 and ignored — passing it has no effect. v9 has no way to express \"only these mutators\" without the full mutator list. Use mutatorDenylist to exclude noisy mutators instead, or supply your own stryker.config.json."New value: +"NOT SUPPORTED in StrykerJS v9 — REJECTED: passing this fails the call with an error. v9 has no way to express \"only these mutators\" without the full mutator list. Use mutatorDenylist to exclude noisy mutators instead, or supply your own stryker.config.json." - added
Input schema / properties / mutatorAllowlist / minItemsAdded value: +1 - changed
Input schema / properties / perMutantTimeoutMs / descriptionPrevious value: -"Maximum time in milliseconds per individual mutant test (StrykerJS only). Distinct from timeoutMs (total run cap). Use this to prevent a single slow mutant from hanging the entire mutation run. Default: StrykerJS default (~5000ms). Example: 10000 for a 10-second per-mutant ceiling."New value: +"Maximum time in milliseconds per individual mutant test (StrykerJS only). Distinct from timeoutMs (total run cap). Use this to prevent a single slow mutant from hanging the entire mutation run. Default: StrykerJS default (~5000ms). Must be <= 2147483647 (the largest delay a timer accepts). Example: 10000 for a 10-second per-mutant ceiling." - added
Input schema / properties / perMutantTimeoutMs / exclusiveMinimumAdded value: +0 - added
Input schema / properties / perMutantTimeoutMs / maximumAdded value: +2147483647 - changed
Input schema / properties / suppress / descriptionPrevious value: -"Mark mutants as equivalent (unkillable) so future runs exclude them from the score and output. Appended to .chaos-mcp/suppressions.json for this file. Example: [{ \"line\": 42, \"mutator\": \"ConditionalExpression\", \"reason\": \"guard unreachable\" }]."New value: +"Mark mutants as equivalent (unkillable) so future runs exclude them from the score and output. Appended to .chaos-mcp/suppressions.json for this file, stamped with a fingerprint of the source line so a later edit to that line retires the suppression instead of silently re-pointing it. Re-issue the same entry to re-confirm one reported as drifted or unverified. Example: [{ \"line\": 42, \"mutator\": \"ConditionalExpression\", \"reason\": \"guard unreachable\" }]." - added
Input schema / properties / suppress / items / properties / line / maximumAdded value: +100000 - added
Input schema / properties / suppress / minItemsAdded value: +1 - changed
Input schema / properties / timeoutMs / descriptionPrevious value: -"Maximum time in milliseconds for the entire mutation run. Default: 300000 (5 minutes). Increase for large files or slow test suites. Example: 120000 for a 2-minute cap."New value: +"Maximum time in milliseconds for the entire mutation run. Default: 300000 (5 minutes). Increase for large files or slow test suites. Must be <= 2147483647 (the largest delay a timer accepts). Example: 120000 for a 2-minute cap." - added
Input schema / properties / timeoutMs / exclusiveMinimumAdded value: +0 - added
Input schema / properties / timeoutMs / maximumAdded value: +2147483647 - added
Input schema / properties / unsuppress / items / properties / line / maximumAdded value: +100000 - added
Input schema / properties / unsuppress / minItemsAdded value: +1 - added
Output schema / properties / driftedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / fidelityNoteAdded value: +{ + "type": "string" +} - added
Output schema / properties / gate / properties / reasonAdded value: +{ + "enum": [ + "partial_audit" + ], + "type": "string" +} - added
Output schema / properties / unverifiedSuppressionsAdded value: +{ + "type": "integer" +}
- Changed
estimate_audit1 field changed- added
Input schema / properties / timeoutMsAdded value: +{ + "description": "The audit budget in milliseconds this estimate is GRADED against: budgetMs echoes it, and fitsBudget/recommendation compare the estimated time to it. Resolved exactly as audit_code_resilience resolves its own timeoutMs (same argument, config keys, and per-language defaults), so an estimate answers the question for the audit you would actually run. Default: 300000 (5 minutes). Must be <= 2147483647 (the largest delay a timer accepts). Example: 120000.", + "exclusiveMinimum": 0, + "maximum": 2147483647, + "type": "number" +}
- Changed
triage_test_coverage17 fields changed- added
Input schema / anyOfAdded value: +[ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "diffBase" + ] + } +] - changed
Input schema / properties / fileConcurrency / descriptionPrevious value: -"How many files to audit in parallel. Default min(4, cpus-1). When >1, each StrykerJS run's worker count is capped so total CPU use stays near the core count. Example: 4"New value: +"How many files to audit in parallel. Default min(4, cpus-1). When >1, each StrykerJS run's worker count is capped, and each mutant's test run is pinned to a single vitest worker, so the three layers multiply out to roughly the core count rather than to fileConcurrency x strykerConcurrency x vitestWorkers. Raise with care on a workstation: a sweep is still the most resource-hungry thing this server does. Example: 4" - changed
Input schema / properties / timeoutMs / descriptionPrevious value: -"Per-file mutation-run timeout in milliseconds. Default: 300000 (5 minutes)."New value: +"Per-file mutation-run timeout in milliseconds. Default: 300000 (5 minutes). Must be <= 2147483647 (the largest delay a timer accepts). Also clamped by whatever remains of totalTimeoutMs." - added
Input schema / properties / timeoutMs / exclusiveMinimumAdded value: +0 - added
Input schema / properties / timeoutMs / maximumAdded value: +2147483647 - added
Input schema / properties / totalTimeoutMsAdded value: +{ + "description": "Wall-clock budget for the WHOLE sweep in milliseconds. Default: 900000 (15 minutes). Files not started before it runs out are returned in \"unaudited\" rather than audited, so a large sweep still returns the ranking it produced. Must be <= 2147483647 (the largest delay a timer accepts). Example: 1800000", + "exclusiveMinimum": 0, + "maximum": 2147483647, + "type": "number" +} - added
Output schema / properties / gate / properties / notGradedAdded value: +{ + "properties": { + "errored": { + "type": "integer" + }, + "unaudited": { + "type": "integer" + } + }, + "required": [ + "errored", + "unaudited" + ], + "type": "object" +} - added
Output schema / properties / gate / properties / reasonAdded value: +{ + "enum": [ + "below_threshold", + "files_not_graded" + ], + "type": "string" +} - added
Output schema / properties / gate / requiredAdded value: +[ + "minScore", + "passed", + "failingFiles", + "notGraded" +] - added
Output schema / properties / ranking / items / properties / batchesCompletedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / batchesPlannedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / completeAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / ranking / items / properties / driftedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / unverifiedSuppressionsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / stoppedReasonAdded value: +{ + "enum": [ + "time_budget_exhausted" + ], + "type": "string" +} - added
Output schema / properties / summary / properties / filesUnauditedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / unauditedAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +}
2 tool updates
v1.6.0- Changed
audit_code_resilience4 fields changed- added
Output schema / properties / batchesCompletedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / batchesPlannedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / completeAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / stoppedReasonAdded value: +{ + "enum": [ + "time_budget_exhausted" + ], + "type": "string" +}
- Changed
estimate_audit6 fields changed- added
Output schema / properties / budgetMsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / fitsBudgetAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / optimisticMsAdded value: +{ + "type": "integer" +} - added
Output schema / properties / recommendationAdded value: +{ + "type": "string" +} - added
Output schema / properties / timingConfidenceAdded value: +{ + "enum": [ + "low", + "medium" + ], + "type": "string" +} - added
Output schema / properties / upperBoundMsAdded value: +{ + "type": "integer" +}
2 tool updates
v1.2.1- Changed
audit_code_resilience18 fields changed- added
Input schema / properties / baseline / properties / noCoverage / items / propertiesAdded value: +{ + "line": { + "minimum": 1, + "type": "integer" + }, + "mutators": { + "additionalProperties": { + "type": "integer" + }, + "type": "object" + } +} - added
Input schema / properties / baseline / properties / noCoverage / items / requiredAdded value: +[ + "line", + "mutators" +] - added
Input schema / properties / baseline / properties / survivors / items / propertiesAdded value: +{ + "line": { + "minimum": 1, + "type": "integer" + }, + "mutators": { + "additionalProperties": { + "type": "integer" + }, + "type": "object" + } +} - added
Input schema / properties / baseline / properties / survivors / items / requiredAdded value: +[ + "line", + "mutators" +] - added
Input schema / properties / lineScope / properties / end / minimumAdded value: +1 - changed
Input schema / properties / lineScope / properties / end / typePrevious value: -"number"New value: +"integer" - added
Input schema / properties / lineScope / properties / start / minimumAdded value: +1 - changed
Input schema / properties / lineScope / properties / start / typePrevious value: -"number"New value: +"integer" - added
Input schema / properties / lineScope / requiredAdded value: +[ + "start", + "end" +] - added
Output schema / oneOfAdded value: +[ + { + "required": [ + "target", + "mutationScore", + "summary", + "survivors", + "noCoverage", + "note" + ] + }, + { + "required": [ + "target", + "mode", + "baselineTotal", + "killedCount", + "nowKilled", + "stillSurviving", + "newSurvivors", + "note" + ] + } +] - added
Output schema / properties / baselineTotalAdded value: +{ + "type": "integer" +} - added
Output schema / properties / incompetentAdded value: +{ + "type": "integer" +} - added
Output schema / properties / killedCountAdded value: +{ + "type": "integer" +} - added
Output schema / properties / modeAdded value: +{ + "enum": [ + "verify" + ], + "type": "string" +} - added
Output schema / properties / newSurvivorsAdded value: +{ + "items": { + "properties": { + "line": { + "type": "integer" + }, + "mutator": { + "type": "string" + } + }, + "required": [ + "line", + "mutator" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / nowKilledAdded value: +{ + "items": { + "properties": { + "line": { + "type": "integer" + }, + "mutator": { + "type": "string" + } + }, + "required": [ + "line", + "mutator" + ], + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / stillSurvivingAdded value: +{ + "items": { + "properties": { + "line": { + "type": "integer" + }, + "mutator": { + "type": "string" + } + }, + "required": [ + "line", + "mutator" + ], + "type": "object" + }, + "type": "array" +} - removed
Output schema / requiredRemoved value: -[ - "target", - "mutationScore", - "summary", - "survivors", - "noCoverage", - "note" -]
- Changed
triage_test_coverage12 fields changed- added
Output schema / properties / ranking / items / properties / fileAdded value: +{ + "type": "string" +} - added
Output schema / properties / ranking / items / properties / killedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / mutationScoreAdded value: +{ + "type": "string" +} - added
Output schema / properties / ranking / items / properties / noCoverageAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / noCoverageGroupsAdded value: +{ + "items": { + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / ranking / items / properties / noMutableLogicAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / ranking / items / properties / scopeNoteAdded value: +{ + "type": "string" +} - added
Output schema / properties / ranking / items / properties / survivedAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / survivorsAdded value: +{ + "items": { + "type": "object" + }, + "type": "array" +} - added
Output schema / properties / ranking / items / properties / totalAdded value: +{ + "type": "integer" +} - added
Output schema / properties / ranking / items / properties / worstSeverityAdded value: +{ + "enum": [ + "high", + "medium", + "low", + "unknown" + ], + "type": "string" +} - added
Output schema / properties / ranking / items / requiredAdded value: +[ + "file", + "mutationScore", + "total", + "killed", + "survived", + "noCoverage" +]
3 tool updates
v1.1.1- First observed
audit_code_resilience - First observed
estimate_audit - First observed
triage_test_coverage
TDQS
Scored across 3 tools
Each tool occupies a distinct stage of the workflow: estimate_audit is pre-flight sizing, audit_code_resilience is a deep single-file mutation audit, and triage_test_coverage is a batch leaderboard. There is no meaningful overlap in purpose, and the descriptions make the differences explicit.
All three tool names follow a consistent verb_noun snake_case pattern: estimate_audit, audit_code_resilience, triage_test_coverage. The verbs clearly indicate the operation and the objects describe the domain artifact being acted on.
Three tools is minimal but well-scoped for a focused mutation-testing server: estimate, single-file audit, and batch triage. Each tool earns its place and there is no filler or redundant utility.
The tool set covers the full workflow: triage to find weak files, estimate to decide whether to audit, and audit to get detailed per-mutant survivor information. No obvious gaps exist for the stated purpose of mutation-testing coverage analysis.
Maintenance
Related MCP Connectors
Writes adversarial test suites for AI-built code. Your agent's test engineer.
Proves AI-generated Python does what you asked: lint, types, security, sandbox run, exact fixes.
Generates unit tests for Python code with coverage before/after reports and concrete edge cases.
Pre-commit code quality guardian. Detects semantic drift in AI-generated code.
Related MCP Servers
- AlicenseAqualityCmaintenanceAI-powered characterization test generator that reads Python functions or class methods, synthesizes inputs, captures behavior in a sandbox, and emits pytest files to lock legacy code behavior for safe refactoring.4Apache 2.0
- AlicenseNot gradedqualityBmaintenanceA universal AI-powered testing server built on the Model Context Protocol (MCP). Allows AI agents to inspect, execute, test, monitor, debug, and report on software projects.3GNU Lesser General Public v2.1 only
- AlicenseNot gradedqualityBmaintenanceEnables generation of test cases, edge cases, and test matrices for software testing, integrated with MCP protocol and EU AI Act compliance.3 npm40 PyPIMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI coding agents to analyze git diffs for behavior-aware change reports, map downstream blast radius, and enforce deterministic risk gates through the Model Context Protocol.1,542 npm4MIT