Assessment: sweep-mcp
I'll start with my own code, and I'll be harder on it than on anyone else's. sweep-mcp is a server that lets an AI delete directories to reclaim disk space — which makes it exactly the kind of tool a review exists for, because the failure mode is a model deleting the wrong thing. Here's the whole server run against the method — both tiers — including the one place it was wrong and how that was caught, and the tier-two hardening it still hasn't reached.
list_targets (read-only), scan (read-only, finds reclaimable directories and issues an id for each), and reclaim (deletes, but only directories a prior scan found, named by id, never by path). All the dangerous logic lives in one file — guard.py — that imports no MCP at all, so the rules can be tested without a client in the loop.1 — Tool annotations · passes
All three tools declare every hint, and they're correct. reclaim — the one that deletes — is marked destructive_hint=True, read_only_hint=False; the two read tools are the reverse. A test, test_the_destructive_tool_is_declared_destructive, fails the build if that ever drifts.
Aside worth stating: an automated "trust index" recently graded this server a C for "missing" all four annotation hints. They are present on every tool. The scanner had matched the protocol's camelCase names against Python source that spells them in snake_case, as the SDK requires, and reported them absent. It's a clean illustration of why this checklist is run by reading the code, not scanning it.
2 — Destructive-operation gating · passes
reclaim is a dry run unless confirm is set to an exact phrase; any other value stays a dry run rather than erroring, so a fumbled confirmation deletes nothing. It accepts ids, never paths — so a path the server never scanned cannot be deleted however it's spelled — and an id cannot be redeemed twice (test_an_id_cannot_be_redeemed_twice). Ids are random, not sequential, so an agent cannot reach a valid target by counting.
3 — Path handling · passes, and this is where it was wrong once
The design is sound: a root allowlist that denies everything when empty, containment enforced through contain(), and — the check that matters most — a re-validation at the moment of deletion rather than trust in the earlier scan. Scanning and deleting are separate calls with a model thinking in between, and that gap is attackable: swap a scanned node_modules for a symlink pointing somewhere that matters, and a server trusting its stale scan would follow it.
The finding. An earlier version got the order of that re-check wrong. It called contain() first — which runs Path.resolve(), following a symlink to its destination — and only then checked is_link. So the link check inspected wherever the link pointed, not the link itself. A directory swapped for a symlink aimed at a second real target inside the same allowed root would resolve cleanly, pass containment, and reach the delete as if it were the original find.
The fix is a two-line reorder: check is_link on the unresolved path first, before contain() resolves anything.
test_revalidate_refuses_a_swap_aimed_inside_the_root, covering the in-root swap the original test missed — because the first swap test aimed outside the root, where containment happened to deny it for the wrong reason. It is written up in the commit history in plain language rather than quietly patched, which is the whole point: you can read the mistake, not just the fix.This is the exact class of bug I flag in other servers, and my own had it. That's not an embarrassment to hide — it's the argument for the review. The bugs that matter here are invisible while the thing works; they surface only when someone reads the order of operations and asks what happens in the gap.
4 — Query and injection safety · not applicable
No database or query surface. Nothing to assess.
5 — Untrusted content · passes
The server returns directory names and regeneration commands, not arbitrary file contents, and tool descriptions are static string literals rather than assembled from disk — checked by test_tool_descriptions_are_static, so a crafted filename cannot become an instruction in the model's context.
6 — Access model · passes
Three refusals, by design: the agent never chooses the roots (fixed at startup; empty means deny-all, not allow-all); the agent never names a path to delete (only redeems an id the server itself issued); and every find is re-checked at deletion. A misconfigured server is useless rather than dangerous — the correct direction to fail.
7 — Tests and CI · passes
Forty tests, and they target the dangerous paths specifically — traversal judged after resolution, swaps aimed both outside and inside the root, unguessable ids, no delete-by-path, dry-run-by-default, double-redeem refused. CI runs them. The guard is separated from the protocol layer precisely so these can run without a client, transport or agent.
8 — Claim versus reality · passes
The README's promise — deletes only what a marker file proves can be regenerated, only inside configured roots, only by id — is enforced in code and proven by tests, not asserted in prose. There is no unused write path waiting to be wired up; the only thing that deletes is the one tool declared to.
Tier two — the hardening
Where the method gets demanding, sweep-mcp does better than most — and still isn't finished, which is the honest state of every server I've read.
Confirmation (8) · partial. reclaim is a dry run unless a confirmation phrase is passed, so there is a real gate — but through a tool parameter, not the spec's elicitation. For a CLI-adjacent server that's a defensible choice; a host-driven elicitation prompt would be stronger.
Non-replayable authorization (9) · passes. What authorises a delete is a one-time id, and an id cannot be redeemed twice — a test proves it. The confirmation phrase is a shared constant, but the per-action authorisation is single-use.
Re-validation at action time (10) · passes — its strongest point. Every find is re-checked against the filesystem at the moment of deletion. That is the fix at the heart of this assessment.
Defense in depth (11) · passes. Four independent layers must all agree before anything is deleted: the root allowlist, the id, the re-validation, and a marker file proving the directory is regenerable.
Blast radius (12) · the open item. A single reclaim call deletes every id it's given, with no explicit cap on how many. It's bounded in practice by what a scan found, but an upper limit — and a per-call size ceiling — would bound what a confused agent could do in one shot. This is the tier-two item I'd add to my own server.
Verdict
Sound on the basics, honest about the one place it wasn't, and honest that even it hasn't finished tier two. Every tier-one check passes; the path-handling one shows exactly how a real bug looks, gets caught, and gets closed with a test; and tier two is candid — strong on re-validation and depth, with blast-radius limits still open. That's the shape of every assessment I do: what's excellent, and what's left to harden — including on my own code.
This is what a review looks like.
Run against the same method, both tiers, every server, every time. Want yours read this way? lesliekadenge@gmail.com — or see the field notes for the state of thirteen production servers.
sweep-mcp is open source at github.com/les-k/sweep-mcp — every claim above is checkable against the source and the commit history in a few minutes, which is the only kind of assessment worth publishing.