Skip to content

feat: MCP server for artifact cleanup (supersedes #17, with delete guard) - #19

Merged
kunjee17 merged 2 commits into
mainfrom
fix/mcp-clean-guard
Jul 26, 2026
Merged

feat: MCP server for artifact cleanup (supersedes #17, with delete guard)#19
kunjee17 merged 2 commits into
mainfrom
fix/mcp-clean-guard

Conversation

@kunjee17

Copy link
Copy Markdown
Owner

Supersedes #17. Contains @NotYash1066's original commit unchanged, plus one fix commit on top.

I could not push directly to the fork branch for #17, so this branch carries their work forward. Their commit is preserved, so authorship survives in history. Close #17 once this merges.

Why the extra commit

clean_artifacts deleted any path handed to it. Caller-supplied strings went straight to remove_dir_all with no validation. The caller here is a model, so a hallucinated or prompt-injected path meant silent recursive deletion. Verified against the built binary before the fix:

$ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"clean_artifacts",
  "arguments":{"paths":["/tmp/irona-notartifact/my-documents"]}}}' | irona --mcp
{"result":{"structuredContent":{"deleted_count":1,"total_freed_bytes":10, ...
>>> DELETED

That directory held a text file and no marker. destructiveHint: true is advisory — clients are not obliged to confirm on it.

Every path is now canonicalized and gated on a new scanner::is_artifact, which accepts a directory only when a marker file sits beside it (target/ next to a Cargo.toml) or a .gitignore rule matches it. One bad path fails the whole call and deletes nothing. Same input after the fix:

{"error":{"code":-32602,"message":"refusing to delete /tmp/irona-notartifact/my-documents:
 not a build artifact directory. Only paths returned by scan_artifacts can be cleaned."}}
>>> SURVIVED

The TUI lost its streaming scan. Splitting scan into scan_artifacts made the parallel dir_size phase collect into a Vec before sending anything, so the list stayed empty until the whole scan finished — noticeable on a large workspace, since dir_size is the slow phase. Phase 1 is now a shared collect_candidates helper: scan streams each entry as its size lands, scan_artifacts collects for MCP.

Also flips isError when some deletions fail, instead of always reporting false.

Verification

  • cargo nextest run — 63 pass (54 from feat: add MCP server for artifact cleanup #17, 9 new)
  • cargo clippy --all-targets -- -D warnings — clean
  • new tests: non-artifact refused, mixed batch refused wholesale, gitignore-detected dir still cleanable, is_artifact accepts marker-backed and gitignore-matched dirs, rejects plain dirs / target/ without Cargo.toml / denylisted .git, and scan emits all Found messages before Done
  • live stdio check of both the refusal and a real target/ still being cleaned

Not addressed

initialize still echoes back whatever protocolVersion the client sends, including unsupported ones, and scan_artifacts has no cap on result count. Both are minor and worth a follow-up rather than holding this up.

🤖 Generated with Claude Code

NotYash1066 and others added 2 commits May 11, 2026 11:09
Expose irona over stdio MCP while reusing the existing scanner and deleter paths. Document PATH-based configuration so MCP clients can call irona portably instead of hardcoding a user-local binary.

Constraint: MCP clients launch servers by command and args, so the binary must support a non-TUI stdio mode.

Rejected: Separate irona-mcp crate | unnecessary split for two tools that reuse existing core behavior.

Confidence: high

Scope-risk: moderate

Directive: Keep MCP cleanup destructive only through explicit clean_artifacts path arguments; do not make scan_artifacts delete anything.

Tested: cargo test; pre-commit fmt and clippy hook; installed binary smoke-tested with initialize and tools/list; Codex MCP command verified after local config wiring.

Not-tested: End-to-end startup from a freshly restarted Codex process.
clean_artifacts passed caller-supplied paths straight to remove_dir_all
with no validation. Since the caller is a model, a hallucinated or
injected path meant silent recursive deletion of arbitrary directories —
verified against the built binary on a plain directory holding a text
file. Every path is now canonicalized and checked with scanner::is_artifact,
which accepts a directory only when a marker file sits beside it or a
.gitignore rule matches it. One bad path fails the whole call.

Splitting scan into scan_artifacts also cost the TUI its streaming: the
parallel dir_size phase collected into a Vec before sending anything, so
the list stayed empty until the whole scan finished. Phase 1 is now a
shared collect_candidates helper, letting scan keep sending each entry as
its size lands while scan_artifacts collects for MCP.

Also reports isError when some deletions fail rather than always false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kunjee17
kunjee17 merged commit ecba2e4 into main Jul 26, 2026
1 check failed
@kunjee17
kunjee17 deleted the fix/mcp-clean-guard branch July 26, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants