defluff
defluff
The deterministic slop check for AI-generated prose. Point it at a changelog, a doc, or an agent's own output and get back the filler phrases to cut — plus a CI exit code and a pinnable score, identical on every run. No model, no API key.
Every flagged span carries no information, so cutting them loses nothing. Clean text, same tool, passes straight through:
What makes defluff worth installing over a one-off grep is the engine around the list: bring your own phrases, per-project overlays, and an MCP server your agents pick up with no wiring.
Install
pip install defluffOr on macOS/Linux via Homebrew:
brew install ahmedak/defluff/defluffThat's it. No model download. No API key. Runs anywhere Python does.
Related MCP server: WSC - Writing Style Checker
Quick start
# Lint a file — exit 1 on slop, 0 when clean
defluff lint essay.md
# Pipe text
cat draft.md | defluff lint
# Get a bare score for scripts (0.0 – 1.0)
defluff score essay.md
# Machine-readable JSON for downstream tooling
defluff lint essay.md --jsonMCP server
Exposes three tools so any MCP-aware agent can self-check prose without bespoke wiring — including its own draft, before returning it.
Zero-install via uvx (recommended) — pulls the package and the mcp extra on first run:
{
"mcpServers": {
"defluff": {
"command": "uvx",
"args": ["--from", "defluff[mcp]", "defluff-mcp"]
}
}
}Or install it and run the entry point directly:
pip install "defluff[mcp]"
defluff-mcp{
"mcpServers": {
"defluff": { "command": "defluff-mcp" }
}
}Published to the MCP Registry as io.github.ahmedak/defluff (see server.json).
mcp-name: io.github.ahmedak/defluff
Tool | Args | Returns |
|
|
|
|
| adds a phrase to the lexicon overlay |
|
| suppresses a phrase (e.g. domain jargon) |
Common use cases
Agent self-correction — call
slop_detecton a draft and revise the flagged phrases before returning it. Zero wiring, one session, no second model in the loop.CI gate on generated content — fail the build when an AI-drafted changelog or doc ships full of "furthermore" and "robust." Deterministic + exit codes + a pinnable lexicon is what an LLM-judge gate can't give you.
Writing assistant feedback — highlight the exact phrases an editor would cut, instead of a vague "this sounds AI."
A reward component for fine-tuning — see reward loops for caveats; on its own it's gameable.
Why defluff?
Every "AI detector" tries to classify whether text was AI-generated — a hard, unreliable problem. defluff asks a different question: does this text contain removable filler? That's deterministic, and it's true whether a human or an LLM wrote "at the end of the day."
proselint is the closest prior art — deterministic, no model — but emits yes/no warnings rather than a tunable density score, and isn't built around a list you swap, overlay, or pin.
A grep over a word list gives you raw hits. defluff gives you what you'd otherwise have to build around that list:
An MCP server — agents self-check with zero wiring.
Markdown- and code-aware — strips code fences, inline code, and URLs first.
Whole-word matching —
"foster"won't fire inside"fostering".A normalized score instead of a hit count — filler density, so one threshold works on a tweet or a 5,000-word doc.
Overlap handling — "at the end of the day" overlapping "end of the day" counts each word once (longest-match-wins).
Exit codes, JSON, and char-offset spans — drop into CI, pre-commit, and editor tooling.
A pinnable lexicon hash — prove the ruler didn't move between runs.
Bring your own slop
defluff lint draft.md --lexicon team-slop.md# team-slop.md
- circle back
- low-hanging fruit
- boil the ocean
paradigm shiftOne phrase per line (# comments and -/* markers ignored). These layer on top of the built-in defaults and report under a neutral custom category. For real categories and per-phrase weights, use a .json list (see Lexicon overlays).
Ready-made domain packs
defluff lint post.md --pack marketing-growth
defluff lint post.md --pack marketing-growth,ai-llm # stack severalPack | Catches | Pack | Catches |
| office jargon |
| crypto hype |
| pitch-deck speak |
| press-release boilerplate |
| hype copy |
| research hedging |
| LLM tells |
| influencer-speak |
| X/Twitter engagement-bait |
List them with defluff packs. High-false-positive terms (e.g. pivot, detox) ship commented-out so they're inert until you opt in. See the packs README.
Batteries-included defaults
~130 curated patterns across five weighted categories, case-insensitive, whole-word matched:
Category | What it catches | Examples |
| Words disproportionately overused by LLMs | delve, tapestry, nuanced, pivotal, robust, showcase |
| Hollow idioms that add no information | at the end of the day, move the needle, circle back, game changer |
| Empty qualifiers | it should be noted that, needless to say, basically, essentially |
| Buzzword inflation | leverage, synergy, actionable insights, cutting-edge, scalable |
| Filler connectives LLMs reach for by default | furthermore, moreover, in conclusion, first and foremost |
Rhetorical patterns (beyond the list)
Some AI tells are sentence shapes, not fixed phrases — the antithesis: it's not X, it's Y, not just a list but a runtime. A regex pattern layer catches these under a rhetoric category:
Mode | What it looks for | Default | Why |
Compound (confident) | full shape: | on | the second clause proves the rhetorical move — rarely a false alarm |
Fragment (guessing) | bare | off, | also fires on plain corrections ( |
Each match counts as one unit toward the score regardless of length. Turning on fragment mode changes the lexicon hash.
defluff lint draft.md # compound antithesis caught by default
defluff lint draft.md --pack rhetoric # also catch the punchy "X, not Y" fragment
defluff lint draft.md --category rhetoric # gate CI on antithesis alonePython API
import defluff
report = defluff.detect("It is worth noting that we should leverage synergies.")
print(report.slop_score) # 0.0 – 1.0
print(report.spans) # flagged phrase locations + categories
score = defluff.score(text) # bare float
clean = defluff.is_slop(text) # bool at default threshold
lex = defluff.load_lexicon() # pin for reproducible runs
score = defluff.score(text, lexicon=lex)SlopReport fields:
Field | Type | Notes |
|
| Clamped |
|
| Raw unclamped — better gradient for reward loops |
|
| Per hit: |
|
| Per-category density |
|
| Token count |
|
|
|
|
| SHA prefix of resolved entry set |
Lexicon overlays and versioning
The bundled lexicon is the baseline; layer on top without editing the package:
# "synergy" is slop on this machine
defluff lexicon add "synergy" --category corporate --scope user
# "leverage" is fine in this repo (finance context) — commit this with the repo
defluff lexicon rm "leverage" --scope project
git add .defluff/ignore.json && git commit -m "allow 'leverage' in finance context"User overlay (
~/.config/defluff/) — machine-wide, not committedProject overlay (
.defluff/at git root) — per-repo, commit it for your whole team
Writes are atomic and cross-process locked; a corrupt overlay is warned and skipped — detect() never crashes.
Every resolved lexicon (bundled + overlays + packs) carries a short content hash, printed on every run (lexicon: 2cc05ba84457) and exposed as SlopReport.lexicon_version. Pass lexicon=defluff.load_lexicon() once and every call scores against the same ruler — pinnable for CI baselines and RL rewards, and auditable since the hash changes if and only if the resolved entry set changes. Each release ships a dated lexicon with a changelog (CHANGELOG.md); ai-vocab is expected to turn over release to release, cliche/hedge/corporate/transition are near-stable.
Reading the output and setting the threshold
The spans — the exact filler phrases, with category. Deterministic, reliable. Act on these.
The score —
slop_score= flagged words ÷ total, weighted by category (ai-vocabcounts a little more,transitiona little less).0.20≈ "a fifth of this text is listed filler." Usually0.0–1.0; can edge slightly above on text that's almost nothing but slop. Instead of being a quality quantfier, its just a tripwire that drives the CI exit code.
Pick a threshold based on how filler-dense the text is allowed to be:
| Meaning | Good for |
| ~5% filler — strict | marketing copy, landing pages, customer-facing text |
| ~8% filler — a tripwire for triage | general prose, blog drafts |
| only flag heavy padding | technical docs that legitimately use |
The default 0.08 is provisional — hand-chosen, not yet calibrated on a labeled corpus (hence the [threshold provisional] tag). For a hard CI gate, set your own threshold and suppress your domain's vocabulary first (below).
Use in CI
Technical writing — API docs, ADRs, RFCs — legitimately uses words like
robust,scalable,in order to. Suppress your project's domain vocabulary first:defluff lexicon rm "scalable" --scope project defluff lexicon rm "in order to" --scope project git add .defluff/ignore.json && git commit -m "defluff: allow domain vocabulary"Then gate only on the categories you trust, not all five:
- name: Check AI-generated content for slop
# --category ai-vocab,hedge gates only on the highest-precision categories
run: cat generated_output.md | defluff lint --category ai-vocab,hedge --threshold 0.1Exit code 1 fails the step, 0 passes.
Use with pre-commit
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: defluff
name: defluff slop check
entry: defluff lint
language: system
types: [markdown]Use in reward loops (experimental)
A deterministic, non-differentiable scalar for filler density can be a small component of a reward mix — but it's gameable alone: a model optimized purely against a fixed phrase list learns to paraphrase the filler rather than remove it. Pair it with a real quality signal (human or LLM judge); we don't yet have a published training run showing it helps.
lex = defluff.load_lexicon() # pin once
reward = lambda text: -defluff.detect(text, lexicon=lex).slop_density # unclamped, better gradient
delta = defluff.compare(draft_v1, draft_v2, lexicon=lex)
# {"score_a": 0.31, "score_b": 0.18, "delta": -0.13,
# "improved": [...], "regressed": [...]} # set diff of flagged phrases, not a semantic diffCLI reference
defluff lint [FILE] [--json] [--threshold FLOAT] [--category CATS] [--lexicon PATH] [--pack NAMES] [--no-project-overlay]
defluff score [FILE] [--pack NAMES] [--no-project-overlay]
defluff packs # list bundled domain packs
defluff lexicon list [--category CATEGORY] [--scope SCOPE] [--json]
defluff lexicon add PATTERN --category CATEGORY [--scope SCOPE] [--weight FLOAT]
defluff lexicon rm PATTERN [--scope SCOPE]--category— comma-separated; only spans in those categories count toward the exit-code decision (still reports all hits). Valid:ai-vocab,cliche,hedge,corporate,transition,custom,rhetoric.--lexicon PATH— layers your phrases on top of the defaults..txt/.mdis one phrase per line (lands incustom);.jsoncarries explicit categories and weights.--pack NAMES— comma-separated domain packs.rhetoricis reserved for the pattern pack, enabling the opt-inX, not Yantithesis fragment.
Exit codes for defluff lint: 0 = clean · 1 = slop · 2 = bad input.
Accuracy
defluff is a deterministic matcher, not a trained classifier, so the metric that matters is precision — when it flags something, is it actually removable filler? On a 50-example hand-labeled set (eval/validation.jsonl) spanning clear slop, clean prose, and jargon-as-content traps (e.g. "the robust standard errors", "pivotal trials"), at the default threshold:
Metric | Score | Reading |
Precision | 1.00 | 0 false positives — clean prose and legitimate jargon were not flagged |
Recall | 0.65 | bounded by lexicon coverage |
Reproduce: python eval/score.py eval/validation.jsonl
Misses are novel buzzwords the lexicon hasn't seen yet (e.g. "operationalize the ideation funnel") — the known limit of a list-based matcher, not noise. Recall on listed filler is 1.00 and will rise as the lexicon grows, but won't reach 1.00 against open-ended novel jargon without a semantic layer.
Caveats: the set is small and labeled by the author — a sanity check on precision, not an independently adjudicated benchmark.
Limitations
It matches a known list by design — it doesn't understand text. Novel buzzwords are missed; this is the trade for being deterministic, local, and reproducible (no model, no API key, pinnable hash). Pair with an LLM judge if you need semantic detection of novel filler.
Domain jargon is contextual.
"leverage"in a finance document is real content. Read the flagged spans; suppress false positives withdefluff lexicon rm(adds to an ignore list, doesn't delete from the bundled lexicon).Span offsets are into the cleaned text. defluff strips code fences, inline code, URLs, and markdown markup before matching, so offsets won't line up with your original document — match on
span.text, not raw offsets, against marked-up source.customis read-only-via-file. Phrases from a--lexiconfile land incustom, butdefluff lexicon add --category customis rejected —addonly takes the five curated categories.English only in v0.
Short texts (< 20 words) get
low_confidence: true— the denominator is floored at 20 so one phrase can't read as 100% slop on a two-sentence input.
Contributing
The easiest contribution is adding a missed filler phrase:
Add it to
src/defluff/data/lexicon-v1.jsonwith the right categorypytest— smoke tests catch boundary errorsPR with one or two examples of the phrase in the wild
See CONTRIBUTING.md for code setup and guidelines.
License
MIT
Available Tools
3 toolsslop_addA
Add a phrase to the slop lexicon so future detections flag it.
Use when the user says something is slop / a banned phrase. scope='project' (this repo, shared via git) or 'user' (machine-wide).
| Name | Required | Description | Default |
|---|---|---|---|
| pattern | Yes | ||
| category | Yes | ||
| scope | No | project |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations, so description carries full burden. It discloses that addition flags future detections and explains scope persistence. However, it does not detail side effects like overwriting existing patterns, limits, or reversibility.
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?
Two sentences, each adding value. First states purpose, second gives usage guidance and parameter detail. No redundancy.
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 simple tool with 3 params and no output schema, the description covers purpose, when-to-use, and scope. Lacks details on pattern format or category, but adequate for basic 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 0%, but description adds meaning for scope (project vs user). Pattern and category are under-explained. Baseline 3 for minimal compensation.
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 clearly states the verb 'Add' and resource 'slop lexicon', with the goal of flagging future detections. It distinguishes from siblings 'slop_detect' and 'slop_ignore' by indicating this tool adds rather than detects or ignores.
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 says 'Use when the user says something is slop / a banned phrase' and explains scope options. Does not specify when not to use, but the purpose is clear enough to avoid misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
slop_detectA
Detect AI-slop (clichés, hedges, filler, AI-vocab) in text.
Returns slop_score 0-1 + exact slop spans. Deterministic, local, no LLM. Use before returning generated prose to self-check, or to gate/rank drafts.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full burden. Discloses key traits: deterministic, local, no LLM. Does not mention performance or constraints, but these are reasonable omissions given simplicity.
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?
Three sentences front-loaded with purpose, then returns, then use cases. No wasted words; every sentence adds value.
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?
Covers purpose, return format, behavior, and use case. Lacks output schema, but description mentions return values (slop_score + spans) sufficiently. Complete for a simple detection tool.
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?
Only one parameter 'text' with no schema description (0% coverage). Description adds minimal context: text to detect slop in. Could specify format or max length, but adequate for simple use.
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?
Description clearly states verb 'Detect' and resource 'AI-slop in text'. It also distinguishes from siblings (slop_add, slop_ignore) by being a detection tool rather than modification or ignoring.
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?
Provides explicit use cases: 'before returning generated prose to self-check, or to gate/rank drafts.' Lacks explicit exclusion of when not to use, but context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
slop_ignoreA
Mark a phrase as NOT slop (e.g. domain jargon) so it stops being flagged.
Use when the user says a flagged phrase is actually fine in this context. scope='project' (this repo) or 'user' (machine-wide).
| Name | Required | Description | Default |
|---|---|---|---|
| pattern | Yes | ||
| scope | No | project |
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. It discloses the effect (stop flagging) and scope options, but doesn't detail persistence, reversibility, or what happens to existing flags. Adequate but not rich.
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?
Three sentences, front-loaded with the core purpose, no wasted words. Every sentence adds value.
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?
The description covers the core functionality and scope, but there is no output schema and no mention of return values or side effects. For a simple tool, this is acceptable but not complete.
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 0%, but the description explains 'pattern' as the phrase to ignore and 'scope' as project or user with a default. This adds meaningful context beyond the schema's bare parameter names and types, though it could specify if pattern supports regex.
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 clearly states the tool marks a phrase as NOT slop, distinguishing it from siblings slop_add (adds slop) and slop_detect (detects slop). The verb 'Mark' and resource 'phrase' are specific, and the purpose is unambiguous.
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 explicitly says 'Use when the user says a flagged phrase is actually fine in this context', providing clear usage guidance. It also explains the scope parameter. It lacks explicit when-not-to-use or direct comparison with siblings, but the context is clear enough.
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. Dates show when Glama detected each change.
3 tool updates
v0.1.2- First observed
slop_add - First observed
slop_detect - First observed
slop_ignore
TDQS
Each tool targets a distinct operation: detection, addition to lexicon, and ignoring phrases. No overlap in functionality.
All tools follow the consistent 'slop_verb' pattern (slop_add, slop_detect, slop_ignore), making names predictable and clear.
With 3 tools, the server covers the core operations for a slop detection lexicon (add, detect, ignore). It is well-scoped and not excessive.
The set covers detection, addition, and whitelisting, but misses removal of phrases from the lexicon and listing the current lexicon, which are minor gaps.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Prose linter + AI-slop detector: weasel words, passive voice, hedging, and research-cited AI tells
Deterministic prompt-injection detector; signed, offline-verifiable verdicts. Not an LLM.
Find AI-isms with evidence and fingerprint a writing voice from samples. 3 of 5 free.
Is this prose AI-written? A probability with the tells behind it. Free to start, no key.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceDetects and fixes LLM prose patterns in text, exposing tools for auditing and improving writing quality in MCP-compatible hosts.292MIT
- AlicenseAqualityBmaintenanceFlag AI tells, weasel words, passive voice, duplicate words, long sentences, nominalizations, hedging, and filler adverbs45MIT
- AlicenseAqualityAmaintenanceThree deterministic MCP tools that score text for AI-writing tells (em-dash density, hedge words, tricolons, boilerplate openers) and grade landing-page copy. No LLM, no network calls, no API key — same input always yields the same score. Published on the official MCP registry as io.github.parweb/ai-slop-checker.3MIT
- AlicenseAqualityAmaintenanceBilingual (EN/ES) AI-writing detection that shows the evidence instead of a percentage: named tells with line and column, hidden-character inspection, and citation cross-checking against a document's own bibliography. Seven of its nine tools run entirely locally and never touch the network.1020MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ahmedak/defluff'
If you have feedback or need assistance with the MCP directory API, please join our Discord server