code-laundry
Runs ESLint with typescript-eslint, including type-aware rules, to detect and automatically fix lint issues while respecting the project's flat config.
Checks dependencies against the npm registry and OSV data to flag nonexistent, malicious, typosquatted, deprecated, vulnerable, or undeclared packages and suggest upgrades.
Formats submitted code with Prettier, using the project's configuration when available, as part of the cleaning pipeline.
Parses and type-checks TypeScript code with the TypeScript compiler and project tsconfig, detecting syntax and type errors before repairs are attempted.
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., "@code-laundrylaunder this diff with fix mode and return the cleaned code"
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.
๐งบ Code Laundry
Clean AI-generated code before you commit to it.
Code Laundry is an MCP server and CLI that AI coding agents call to check, fix, and sanitize the code they generate before a full build. You submit code as a string, a file, or a diff. You get back cleaned code, a structured report of what was wrong, and one of three verdicts: pass, fixed, or reject.
It runs deterministic checks first, using real tools (TypeScript, ESLint + typescript-eslint, Prettier, Semgrep, gitleaks, osv-scanner, the npm registry). An LLM is used only for repairs those tools can't make, and every LLM edit is checked again before it's accepted.
Status: v0.1. TypeScript and JavaScript. See Limitations.
Why
AI-generated code fails in predictable ways:
Hallucinated packages: imports that don't exist on npm, typosquats, packages npm removed as malware
Security holes: SQL and command injection,
eval, path traversal, SSRF, XSS, weak cryptoLeaked secrets: API keys and tokens pasted inline
Silent bugs: missing
await, swallowed errors, overly broadtry/catchSlop: truncated output (
// ... rest of the code), placeholder TODOs, mock data left in, dead code,console.logInvented APIs: calls to functions that don't exist (
fs.readFileAsync) or not in the installed version of a package
Related MCP server: MCP Code Reviewer
How it works
code โโบ detect โโบ parse โโบ format โโบ lint+fix โโฌโบ typecheck โโ
โ โโบ semgrep โโโบ slop โโบ repair loop โโบ report
โโ syntax error โ reject โโบ secrets โ (fix) (fix/strict:
โโบ deps โโโโโโโ LLM, re-verified)Stage | Tool | Fixes |
Parse | TypeScript parser | |
Format | Prettier (project config if present) | โ |
Lint | ESLint + typescript-eslint, including type-aware rules such as | โ |
Type check | TypeScript compiler (project | |
Security | Semgrep with bundled rules ( | |
Secrets | gitleaks + built-in patterns; values are always redacted | |
Dependencies | npm registry, OSV (osv-scanner, falling back to the OSV API), typosquat list | |
AI slop | AST heuristics; type errors that point at a package's own declarations become |
|
Repair | Claude, structured line-range edits, only inside flagged ranges | โ |
Speed. ESLint, Prettier and TypeScript run in-process and stay warm in the MCP server. Analysis stages run in parallel. A typical check takes 2โ3 s, most of it Semgrep.
No silent skips. If a tool isn't installed or a service can't be reached,
stagesRunsays so, with the reason. Instrictmode, a security or dependency check that didn't run rejects the code.
Verdicts
Verdict | Meaning |
โ
| Nothing blocking was found. Unfixed low/medium findings are returned as warnings. |
๐ง | Issues were found and nothing blocking remains. Use |
โ | Any of: an unfixed critical/high finding, a syntax error, a hardcoded secret, or a hallucinated/malicious dependency |
All of these are configurable (see Configuration). In strict mode, medium findings also reject.
Installation
Requirements:
Node 20+ and pnpm
Strongly recommended, otherwise the related stages report themselves as skipped:
Semgrep (
pip install semgrep)
git clone https://github.com/stevenleon30/code-laundry.git
cd code-laundry
pnpm install # also builds dist/Optional LLM repair (fix/strict modes):
export ANTHROPIC_API_KEY=sk-ant-...
export CODELAUNDRY_MODEL=claude-opus-5-5 # optional; default claude-opus-5-5
export CODELAUNDRY_EFFORT=high # optional; low | medium | high | xhigh | maxWithout a key, every deterministic check still runs, and the repair stage reports skipped.
Use with Claude Code
claude mcp add code-laundry -- node /absolute/path/to/code-laundry/dist/mcp/server.jsOr commit a .mcp.json in your project:
{
"mcpServers": {
"code-laundry": {
"command": "node",
"args": ["/absolute/path/to/code-laundry/dist/mcp/server.js"],
"env": { "ANTHROPIC_API_KEY": "${ANTHROPIC_API_KEY}" }
}
}
}To make the agent use it, add a line like this to your CLAUDE.md:
Before writing generated code to disk, call
launder_codewithmode: "fix"andcontext.projectRootset to this repo. UsecleanedCode; if the verdict isreject, fix the findings and call it again. Callcheck_dependenciesbefore adding any package.
Use with Claude Desktop
Add this to claude_desktop_config.json:
macOS:
~/Library/Application Support/Claude/Windows:
%APPDATA%\Claude\
{
"mcpServers": {
"code-laundry": {
"command": "node",
"args": ["/absolute/path/to/code-laundry/dist/mcp/server.js"],
"env": { "ANTHROPIC_API_KEY": "sk-ant-..." }
}
}
}Claude Desktop starts servers with a minimal PATH. If Semgrep or gitleaks show as skipped, point to them explicitly with "CODELAUNDRY_SEMGREP": "/path/to/semgrep" and "CODELAUNDRY_GITLEAKS": "/path/to/gitleaks" in env.
MCP tools
launder_code
Input | |
| Full source, or a unified diff for one file. A diff is applied to |
|
|
| Path relative to |
|
|
|
|
Returns { verdict, verdictReasons, language, cleanedCode, findings[], diff, stagesRun[], durationMs }. Each finding has:
{
"ruleId": "semgrep/sql-injection",
"source": "semgrep",
"category": "security",
"severity": "critical",
"message": "User-controlled data flows into a SQL query string. ...",
"location": { "line": 8, "column": 35, "endLine": 8, "endColumn": 90 },
"fixable": false,
"fixed": false,
"snippet": "..."
}check_dependencies
Input is { packageJson } (object or string) or { imports: ["lodash", "lodash@4.17.15", "@scope/pkg/sub"] }. It flags:
packages that don't exist, or that npm replaced with a security placeholder
typosquats of popular packages
known vulnerabilities, with the version to upgrade to
deprecated, very new, or rarely downloaded packages
imports missing from
package.json
explain_findings
Input is { findings }. Returns plain-language explanations and concrete fix guidance, blocking findings first. It is deterministic; no LLM is called.
CLI
codelaundry check src/handler.ts # report only
codelaundry fix src/handler.ts # apply fixes (and LLM repair if configured), write the file
codelaundry fix src/handler.ts --stdout # print cleaned code instead
cat gen.ts | codelaundry check - --filename gen.ts
codelaundry deps # check ./package.json
codelaundry deps axois lodash@4.17.15 # check specific packagesOptions: --strict, --project <dir> (default: nearest package.json), --no-project, --config <file>, --diff, --json.
Exit codes: 0 for pass or fixed, 1 for reject, 2 for a usage or internal error. fix never writes a file that contained a secret: the cleaned code has the secret redacted, and writing it would break the file.
During development, use pnpm cli check <file> and pnpm mcp.
Configuration
Put a .codelaundry.json file at your project root. It is validated with Zod, and unknown keys are errors.
{
"stages": { "ts.semgrep": true, "ts.deps": true, "repair": true },
"thresholds": {
"rejectOn": ["critical", "high"],
"strictRejectOn": ["medium"],
"rejectOnSyntaxError": true,
"rejectOnSecret": true,
"rejectOnHallucinatedDependency": true
},
"ignoreRules": ["slop/console-log", "eslint/@typescript-eslint/no-explicit-any"],
"severityOverrides": { "slop/todo": "medium" },
"allowedPackages": ["@my-org/*"],
"repair": { "maxIterations": 3, "contextLines": 5, "model": "claude-opus-5-5" },
"deps": { "minWeeklyDownloads": 500, "minAgeDays": 30 },
"useProjectConfigs": true
}Stage ids: ts.parse, ts.format, ts.lint, ts.typecheck, ts.semgrep, secrets, ts.deps, ts.slop, repair.
Environment variable | Purpose |
| Enables LLM repair |
| Repair model and effort |
| Explicit tool paths |
| Use osv-scanner's offline database (when |
| Registry URL (default |
| Restrict which |
| Input size limit (default 1 MB) |
| Serve dependency data from a JSON fixture (tests and evals) |
Safety
Submitted code is never executed.
It is parsed and analysed in memory.
Semgrep and gitleaks read a copy in a private temporary directory, which is deleted afterwards.
The test-run stage is not implemented in v1. If added, it will be opt-in and run only in a network-less, resource-limited container.
Secrets are never returned or sent anywhere.
Detected secret values are redacted from findings, messages, diffs and
cleanedCode.Secrets are never included in what is sent to the LLM.
External tools run without your API keys in their environment.
Submitted code is treated as untrusted data.
Instructions in comments or strings have no effect on the deterministic stages.
The LLM gets the code inside
<untrusted_code>delimiters, is told to ignore embedded instructions, and can only answer with schema-validated line edits.
LLM edits are restricted.
Edits outside the flagged lines (plus
repair.contextLinesof context) are rejected.Each candidate is re-verified by all deterministic stages and kept only if findings decrease.
The loop runs at most 3 rounds.
Project configs are executed. With
projectRoot, ESLint and Prettier configs from that project are loaded, and they are JavaScript. Only point Code Laundry at projects you trust, and useCODELAUNDRY_ALLOWED_ROOTSto restrict callers.
Evals
evals/ holds 89 labeled cases: 74 bad AI-style snippets and 15 clean controls. Each case is a snippet plus a JSON file listing the findings expected for it.
Category | Cases |
Security (command injection, path traversal, SSRF, XSS, weak crypto, TLS) | 11 |
Wrong/invented API usage | 9 |
Hallucinated, typosquatted or removed packages | 8 |
SQL injection | 7 |
Leaked secrets | 7 |
Missing | 7 |
Bad types | 7 |
AI slop | 7 |
Swallowed errors | 6 |
Dead code | 5 |
pnpm evals # detection + repair
pnpm evals --no-repair # detection only
pnpm evals --filter sqliThe runner reports recall, precision (medium+ findings), the false-positive rate on clean controls, and the repair success rate. It fails below the thresholds in evals/thresholds.json. Dependency lookups use a recorded fixture, so results are deterministic.
The GitHub Action runs type-checking, unit tests and detection evals on every PR. LLM repair evals call the API for every repairable case, so they run only on manual dispatch with an ANTHROPIC_API_KEY secret.
The current detection results are 100% recall, 100% precision and a 0% false-positive rate, including the 16-case held-out subset. Read that with care: the same author wrote the rules and the cases, so it shows coverage of the anticipated failure modes, not real-world accuracy. A corpus of real agent output is the next step.
Limitations
TypeScript/JavaScript only in v1. See Adding a language.
Invented APIs in third-party packages are detected only with
projectRoot, because the installed type declarations are needed. Node built-ins are always checked.Semgrep's open-source taint analysis is per-function. Flows across functions or files are missed.
Typosquat detection compares against a hand-curated list of about 400 popular packages.
Private packages 404 on the public registry. They are accepted when installed in
projectRoot/node_modules; otherwise add them toallowedPackages.Download counts come from
api.npmjs.org. If it's unreachable, the check is noted and never blocks, because it is a weak signal.Diff input produces findings for the whole resulting file, not only the changed lines.
ESLint 10 dropped legacy
.eslintrc. Such projects get the bundled config.Fixed-finding attribution after ESLint autofix or LLM repair is counted per rule, not per occurrence.
Adding a language
The core is language-agnostic. A language is a LanguageModule (detection plus an ordered list of Stages) registered in src/core/registry.ts. For Python:
Add
src/stages/python/withruff format,ruff check --fix,mypy,bandit, a Semgrep Python ruleset, and a PyPI dependency provider.Register the module.
The pipeline, verdict logic, repair loop, MCP server, CLI and eval runner don't change. See docs/ARCHITECTURE.md.
Development
pnpm test # unit and integration tests (vitest)
pnpm typecheck
pnpm evals
pnpm buildContributing
Issues and PRs are welcome. Every new detection should come with eval cases in evals/cases/, so that recall and false-positive rate stay measurable. Fake credentials go in evals/synthetic-secrets.ts and are referenced as __SECRET_NAME__, so no secret-shaped literal is committed.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Preflight QA for AI-agent deliverables with structured verdicts and repair guidance.
Lints + auto-fixes how AI coding agents discover any new product. 24 rules, 6 tools, score 0-100.
Check AI work against requirements and return structured verdicts, findings, and repair steps.
Enforces architectural invariants and code quality rules inside your AI agent's edit loop.
Related MCP Servers
AlicenseAqualityDmaintenanceEnables AI agents to review code diffs for bugs, security issues, and bad patterns, and generate fixes.4MIT- AlicenseNot gradedqualityDmaintenanceEnables AI-powered code review and improvement, including analysis, refactoring suggestions, and automatic test generation, with an optional agentic loop for iterative refinement.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to scan code for security and quality issues and receive machine-readable reports with suggested fixes and verification criteria.63 npm2MIT
- FlicenseNot gradedqualityBmaintenanceEnables AI agents to submit code snippets for review and receive structured quality reports covering correctness, security, performance, maintainability, and best practices.2-