Patchloom
OfficialPatchloom MCP server exposes structured, parser-backed file editing and code-aware operations for AI agents.
Read, list, search, and replace text files; create, append, prepend, move, delete files; preview or apply unified/Codex/Aider patches.
Edit JSON/YAML/TOML/env/ini/properties by selector: get/query, set/update, append/prepend, delete/delete-where, ensure, merge, move, and diff.
Edit Markdown by heading: insert/replace/move sections, append table rows, upsert bullets, dedupe headings, and lint rules files.
Analyze and modify source code with AST tools: list/read/search/refs/impact/deps/diff/validate, rename/replace/insert/delete/wrap/group/reorder/move/extract/split/rewrite signatures/manage imports.
Apply constrained freeform fragments with required anchors; batch multi-file replacements and whitespace fixes.
Execute atomic multi-step transaction plans with rollback/strict options and stale-file detection via expected_sha256.
Edit Jupyter notebook cell source by cell id.
Inspect git status, list/restore undo backup sessions, retrieve operation schemas, and get server/workspace info.
Enforce workspace path containment, rejecting ../ and outside-workspace absolute paths; tools carry read-only or destructive annotations.
Performs heading-aware edits on Markdown documents, including appending rows to tables and modifying sections and bullets.
Edits TOML files by selector, preserving comments and formatting for safe configuration changes.
Edits YAML files by selector, preserving comments and formatting while supporting value updates and array operations.
Patchloom
One binary. Every platform. Structured file edits for AI agents.
Patchloom is a single-binary CLI that gives AI coding agents safe, structured file editing on any operating system. It edits JSON, YAML, and TOML by selector (not regex), preserves comments, understands code structure across 20 languages, batches multiple file edits into one tool call, and works identically on Linux, macOS, and Windows.
Not a generic filesystem MCP. Default MCP filesystem servers read/write files as text. Patchloom adds dry-run previews, parser-backed config and markdown edits, AST ops, multi-file batch/tx with undo, and stable error_kind peels for hosts. Full coding agents (Claude Code, Codex, Cursor) own the loop; Patchloom is the tool layer they (or a Rust embedder) call.

# Edit a YAML value by selector without breaking comments or formatting
patchloom doc set config.yaml database.port 5432 --apply
# Batch 6 file edits into a single tool call
patchloom batch --apply <<'EOF'
doc.set package.json version "2.0.0"
doc.set config.yaml app.version "2.0.0"
doc.set config.toml project.version "2.0.0"
replace README.md "1.0.0" "2.0.0"
replace CHANGELOG.md "1.0.0" "2.0.0"
file.create VERSION "2.0.0"
EOFWhy Patchloom? | Install | Quick start | Commands | Comparison | When to use what | Architecture | Status
Why Patchloom?
The problem
AI agents edit files through tool calls. Each call is a round-trip back to the LLM. When a task touches config files, that process has three failure modes:
Syntax corruption. The agent uses text replacement on JSON, YAML, or TOML and produces invalid output (mismatched braces, broken indentation, lost comments).
Round-trip tax. Editing 6 files means 6 separate tool calls. Each one waits for the LLM to generate, execute, read the result, and plan the next call.
Platform fragmentation. On Linux the agent uses
sed,jq,grep. On Windows, none of those exist. The agent falls back to verbose PowerShell or makes errors with unfamiliar syntax.
How patchloom solves each one
Problem | How patchloom solves it |
Syntax corruption |
|
Round-trip tax |
|
Platform fragmentation | Single static binary with zero dependencies. Same commands, same flags, same behavior on Linux, macOS, and Windows. |
What changes with patchloom
Without patchloom (6 tool calls)
Agent: edit file 1 ─── tool call ───▶ 15s
Agent: edit file 2 ─── tool call ───▶ 15s
Agent: edit file 3 ─── tool call ───▶ 15s
Agent: edit file 4 ─── tool call ───▶ 15s
Agent: edit file 5 ─── tool call ───▶ 15s
Agent: edit file 6 ─── tool call ───▶ 15s
Total: ~90sWith patchloom batch (1 tool call)
Agent: batch with
all 6 edits ─── tool call ───▶ 25s
5 round-trips saved
Total: ~25sKey capabilities
Capability | What it does | Example |
Parser-backed edits | Edit JSON/YAML/TOML by selector, preserving comments and formatting |
|
Batch N files in 1 call |
|
|
Comment preservation | YAML/TOML comments survive all edits, including array resizing |
|
Heading-aware markdown | Edit sections, tables, and bullets by heading, not line number |
|
AST-aware code ops | List, rename, replace, and analyze symbols across 20 languages |
|
Atomic rollback |
|
|
MCP server | Expose all operations as structured MCP tool calls |
|
Optional CLI sandbox | Reject |
|
Cross-platform | Identical behavior on Linux, macOS, Windows. No | Same binary everywhere |
When to use patchloom vs native tools
Patchloom is not a replacement for all file operations. Its instructions tell agents exactly when to use it and when native tools are faster:
Task | Use patchloom? | Why |
Edit a JSON/YAML/TOML value by selector | Yes | Parser guarantees valid output, preserves comments |
Edit 3+ files in one task | Yes |
|
Append a row to a markdown table | Yes | Heading-aware, no line number guessing |
Read a single file | No | Native |
Simple text search | No | Native |
Single-file text replacement | No | Native |
Correctness over speed
Patchloom is not faster than native tools for simple, single-file edits. Use native tools for those. But native text replacement cannot safely edit structured files: a sed on YAML can corrupt indentation, strip comments, or produce invalid syntax. doc set parses the file, changes the value by selector, and writes valid output. That guarantee is the point.
Where patchloom is faster is multi-file batching. Six file edits via native tools means six round-trips to the LLM. One batch call does the same work in a single round-trip.
Task PL-CLI MCP Native
────────────────────── ────── ────── ──────
search 18.5s 12.7s 13.9s ◀ ~same
replace 36.1s 26.6s 26.1s ◀ ~same
doc_set 30.9s 16.9s 13.7s ◀ native fastest
md_table 15.5s 13.5s 15.3s ◀ MCP fastest
tx_multi_file 41.4s 28.5s 22.9s ◀ native fastest
batch_6_files 50.6s 46.6s 30.3s ◀ native fastest
batch_mixed_ops 24.7s 13.6s 20.9s ◀ MCP fastest
yaml_comment_preserve 18.1s 11.6s 16.1s ◀ MCP fastest
md_insert 15.0s 11.7s 15.7s ◀ MCP fastest
file_ops 26.0s 16.6s 17.2s ◀ ~same
tidy 45.0s 30.3s 41.7s ◀ MCP fastest
────────────────────── ────── ────── ──────
TOTAL 321.9s 228.5s 233.8sMCP mode wins overall (228.5s vs 233.8s native) because structured tool calls skip shell syntax construction entirely. MCP wins 5/11 tasks; native wins 3/11; 3 are ties. CLI mode is always slowest due to shell construction overhead.
Related MCP server: ast-editor
Install
Prefer channels that track each GitHub Release (Homebrew, Scoop, crates,
npm, Releases). On Windows, Scoop is recommended. winget
(Patchloom.Patchloom) is published per release and is usually current
after Microsoft's publish pipeline (winget source update if search is
stale). Chocolatey often lags while community moderation runs.
# Homebrew (macOS/Linux)
brew install patchloom/tap/patchloom
# crates.io (requires Rust 1.95+, includes MCP server)
cargo install patchloom
# npm / npx (downloads the platform binary from GitHub Releases)
npx patchloom --version
# or: npm install -g patchloom# Scoop (Windows; recommended Windows channel)
scoop bucket add patchloom https://github.com/patchloom/scoop-bucket
scoop install patchloom/patchloomPre-built binaries for Linux, macOS, and Windows are on the Releases page. See Installation for shell installer scripts, source builds, shell completions, and winget / Chocolatey notes.
MCP Registry name:
mcp-name: io.github.patchloom/patchloom
Editor extension
Install the companion extension for VS Code, Cursor, Windsurf, or VSCodium:
The extension auto-discovers the CLI (or installs it for you), generates AGENTS.md, configures MCP servers, and adds Quick Actions to the command palette. See the Editor Extension guide for details.
Quick start
1. Set up your project
patchloom initThis creates AGENTS.md in a new project or appends the rules to an existing agent instructions file, offers shell completions, and detects MCP configuration opportunities. Pass -y to skip confirmation prompts.
If you only want the rules text:
patchloom agent-rules >> AGENTS.md
# Or tailor the output:
patchloom agent-rules --mode mcp >> AGENTS.md # MCP-only (no CLI examples)
patchloom agent-rules --platform windows >> AGENTS.md # Windows-only syntaxIf .vscode/ or .cursor/ exists, init also prints ready-to-copy .vscode/mcp.json or .cursor/mcp.json snippets.
Your AI agent reads AGENTS.md and learns when to use patchloom vs native tools.
2. Edit a config file safely
# Parser-backed: changes the value, preserves comments and formatting
patchloom doc set config.yaml database.port 5432 --apply3. Batch multiple edits into one call
patchloom batch --apply <<'EOF'
doc.set config.json version '"2.0.0"'
md.upsert_bullet AGENTS.md "Rules" "- Always test"
replace src/main.rs "v1" "v2"
EOFValues are JSON-first: unquoted 2.0 becomes a number. Force a string with nested
quotes as above (Unix shells).
On Windows PowerShell (no bash heredoc), write ops to a file:
@'
doc.set config.json version "\"2.0.0\""
md.upsert_bullet AGENTS.md "Rules" "- Always test"
replace src/main.rs "v1" "v2"
'@ | Set-Content ops.txt -Encoding utf8
patchloom batch --apply ops.txtOr use a JSON plan with format and validate lifecycle:
{
"version": 1,
"operations": [
{ "op": "doc.set", "path": "config.json", "selector": "version", "value": "2.0.0" },
{ "op": "md.upsert_bullet", "path": "AGENTS.md", "heading": "Rules", "bullet": "- Always test" },
{ "op": "replace", "path": "src/main.rs", "old": "v1", "new": "v2" }
],
"format": [{ "cmd": "cargo fmt --all" }],
"validate": [{ "cmd": "cargo test", "required": true }]
}patchloom tx plan.json --applytx plans are trusted input. format and validate run their cmd fields through the host shell (sh -c on Unix, cmd /C on Windows), so only run plans you trust.
4. Or use MCP for structured tool calls (no shell syntax)
After installing with MCP support, start the server:
PATCHLOOM_MCP_SURFACE=core patchloom mcp-serverMCP-capable agents call patchloom tools directly as structured JSON, with no shell quoting or command construction. The agent sends {"path": "config.json", "selector": "version", "value": "2.0"} instead of building patchloom doc set config.json version '"2.0"' --apply.
Coding agents: set PATCHLOOM_MCP_SURFACE=core for a 12-tool pack (list_files, search/read/replace, doc/md, execute_plan, operation_schema, server_info) so schemas stay small. Product default remains full inventory when the env is unset. Prefer Patchloom MCP alone for list+edit (no second filesystem MCP). See the MCP setup guide for Cursor / Claude / Codex paste configs and the full security model.
Using VS Code, Cursor, or Windsurf? The Patchloom extension handles setup automatically: it installs the binary, runs init, and configures your editor's MCP settings.
As a Rust library
Host teams embedding Patchloom instead of a private edit stack: see the
embedder host case study
(for_agent, peels, fuzzy refuse, apply_fragment, path-only ops).
Add patchloom as a dependency (omit CLI/MCP/AST with default-features = false):
[dependencies]
patchloom = { version = "0.37.7", default-features = false } <!-- x-release-please-version -->use patchloom::api::{self, ApplyMode, ReplaceOptions, edit_error_kind, EditErrorKind};
use std::path::Path;
// Replace text (preview only, no disk write)
let result = api::replace_text(
Path::new("src/config.rs"),
"old_value", "new_value",
&ReplaceOptions::default(),
ApplyMode::Preview,
None,
)?;
println!("{}", result.diff);
// Agent hosts: shared primary+fallback policy (unique, require_change, fuzzy @ 0.90)
let opts = ReplaceOptions::for_agent();
// Fail closed: zero matches become EditErrorKind::NoMatch
match api::replace_in_content("body", "missing", "x", &opts) {
Ok(r) => println!("changed={}", r.changed),
Err(e) => assert_eq!(edit_error_kind(&e), Some(EditErrorKind::NoMatch)),
}
// for_agent auto-refuses over-wide fuzzy; custom options use api::fuzzy_span_suspicious
// Buffer multi-op + host write: api::refuse_batch_if_suspicious_fuzzy after apply_content_edits
// Invalid options and bad regex peel InvalidInput (CLI/tx typed errors included)
match api::replace_in_content("body", "", "x", &ReplaceOptions::default()) {
Err(e) => assert_eq!(edit_error_kind(&e), Some(EditErrorKind::InvalidInput)),
Ok(_) => panic!("empty pattern must error"),
}
// Set a value in a JSON file
api::doc_set(
Path::new("config.json"),
"version",
serde_json::json!("2.0"),
ApplyMode::Apply,
None,
)?;
// Multi-doc YAML: merge into document 0 (selector None = root only)
api::doc_merge(
Path::new("stream.yaml"),
serde_json::json!({"env": "prod"}),
ApplyMode::Apply,
None,
Some("0"),
)?;
// Sole-path text load: binary → EditErrorKind::Binary; invalid UTF-8 → InvalidEncoding
let _text = api::load_text(Path::new("notes.md"))?;All API types are Send + Sync. Beyond the api module, utility modules are also public: containment (workspace path guarding), exec (shell command execution), files (file-walking, load_text_strict, binary detection), backup (restore_path_from_latest_backup for post-Apply validate/revert), and write (atomic file writes with policy transformations). Library users needing temp dirs (e.g. agents) can use PathGuard::builder(cwd).allow_temp_directory() (handles /tmp on macOS); see the containment and api module rustdocs. Multi-doc bare keys and wrong-root merges peel to EditErrorKind::TypeError via edit_error_kind. Create/rename dest-exists peels to EditErrorKind::AlreadyExists (or api::is_already_exists / api::error_kind_str for CLI-stable "already_exists" strings). Fine-grained kinds also have bool peels (is_not_found, is_conflicts, is_changes_detected, is_type_error, is_format_failed, is_guard_rejected, is_invalid_input, is_no_match, is_ambiguous) matching edit_error_kind.
Replace fail-closed / shell-token options: CLI replace --require-change and --command-position (also plan/MCP fields and ReplaceOptions on the library). Agent hosts: ReplaceOptions::for_agent() on primary and fallback replace paths (auto span refuse); custom options still call api::fuzzy_span_suspicious / FuzzySpanPolicy after fuzzy Apply; buffer multi-op hosts call api::refuse_batch_if_suspicious_fuzzy after apply_content_edits (#2064). Library-only AST mutators: ast_rename / ast_replace_in_symbol / ast_rename_batch (feature ast + files), and FunctionSigEdit::parse_rust. Ordered host onboarding: Embedder host checklist (#2009). Full surface: docs.rs/patchloom.
Getting started
Resource | What you'll learn |
Install options and shell completions | |
Write modes, transaction plans, exit codes | |
Configure patchloom as an MCP server for your agent | |
VS Code, Cursor, Windsurf, and VSCodium integration | |
5-minute walkthrough | |
Every command, operation, and mode | |
Transaction plan templates |
Commands
Agent-optimized (these are faster or safer than native tools)
Command | What it does | When to use |
| Line-oriented multi-file edits in 1 call | Editing 3+ files with simple syntax |
| JSON plan with format/validate lifecycle | Complex multi-file edits with rollback |
| Parser-backed JSON/YAML/TOML edits | Changing config values without breaking syntax |
| Heading-aware markdown edits | Updating tables, sections, bullets in docs |
| AST-aware symbol operations (20 languages) | Renaming identifiers, listing symbols, impact analysis |
| Apply unified diffs with stale detection | Replaying patches safely |
| Text-file whitespace and newline normalization | CI checks for text tidiness |
| MCP protocol server | MCP-capable agents (no shell syntax) |
General-purpose (also useful in scripts and CI)
Command | Description |
| Fast literal or regex search across text files (supports --glob/--exclude/--ignore-file for layered custom ignore files, --max-results, -C context, etc.) |
| Mechanical string replacement across text files with diff preview |
| Freeform fragment with required anchors (MorphLLM-style markers stripped; no cloud merge) |
| Append content to an existing file |
| Prepend content to an existing file |
| Create a new file with content |
| Delete a file |
| Move (rename) a file |
| Read file contents with optional line range |
| Show which files have uncommitted changes |
| Summarize a tx plan in plain English |
| Restore files from a backup created by |
| Generate shell completions (bash, zsh, fish, elvish) |
| Set up patchloom in a project (agent rules, completions, MCP) |
| Export operation schemas with tier filtering and system prompts |
| Generate agent instructions for your project |
How patchloom compares
Tool | Strength | Where patchloom differs |
jq | JSON query/transform | patchloom also handles YAML, TOML, env, ini, properties, and markdown; batches across files; preserves comments |
yq | YAML/JSON query/transform | patchloom preserves YAML comments via CST editing; also edits TOML, env, ini, properties, and markdown; adds batching and atomic transactions |
dasel | Multi-format get/set | patchloom |
sd | Regex find/replace | patchloom adds parser-backed structured edits; batching; never produces invalid JSON/YAML |
comby | Structural code patterns | patchloom targets config files and agent workflows, not source code pattern matching |
The key difference: patchloom is designed for AI agent workflows. One batch or tx call replaces N sequential tool calls, cutting round-trips and eliminating partial-failure states.
vs agent-native editing tools
The table above compares patchloom to human CLI tools. But agents already have built-in editing: Claude Code's edit_file, Cursor's apply, Grok Build's search_replace, Aider's /code blocks. Why add patchloom on top?
Agent-native tools use text matching. They find a block of text and replace it. This works for source code but fails on structured config files:
Agent uses search_replace on YAML
database:
# Production settings
host: db.prod.internal
port: 5432 # PostgreSQL default
pool_size: 10The agent replaces port: 5432 with port: 5433. Result depends on implementation. Many agents lose the inline comment, break indentation, or fail to match because of surrounding context changes.
Agent uses patchloom doc set
patchloom doc set config.yaml \
database.port 5433 --applyThe YAML parser changes the value at the selector path. Comments, indentation, key ordering, and all other formatting are preserved. The output is always valid YAML.
Limitation of agent-native tools | How patchloom addresses it |
Comment destruction | CST-level YAML/TOML editing preserves all comments |
One file per tool call |
|
No rollback |
|
Platform-dependent | Same binary and syntax on Linux, macOS, Windows |
Stale context risk |
|
When to keep using native tools: Single-file reads, simple text search, single-file text replacement where comments don't matter. Patchloom's agent-rules tell agents exactly when to use each approach.
When to use what
Need | Prefer | Prefer something else |
JSON, YAML, TOML, env, ini, or properties by path | Patchloom | Generic filesystem MCP / blind text replace |
Multi-doc YAML stream | Patchloom selectors ( | Bare key on stream root |
Structural code pattern search | Text-only grep for shapes | |
Identifier rename in code | Patchloom | Fuzzy text replace for symbols |
Multi-file atomic apply + undo | Patchloom | N sequential shell edits |
Freeform snippet with known anchor (after/before/old) | Patchloom | Whole-file rewrite or guessing placement |
Freeform snippet without anchors (cloud model merge) | Morph Fast Apply or similar | Patchloom (anchor-less Morph merge is a non-goal) |
Full agent product | Claude Code / Codex / Cursor | Patchloom alone |
Longer write-ups: Comparisons · Embedder host checklist · MCP setup
Library hosts: Embedder host checklist: dual-path ReplaceOptions::for_agent() (primary + fallback, includes refuse_suspicious_fuzzy), peels via edit_error_kind / is_* / is_fuzzy_span_suspicious with #[non_exhaustive] _ arm, multi-op ContentEditsResult.op_honesty + refuse_batch_if_suspicious_fuzzy after buffer multi-op (#2064), plan/tx widest matched_text, optional apply_content_edits_to_file_with_span_policy. Custom options still use api::fuzzy_span_suspicious / FuzzySpanPolicy after fuzzy Apply.
Context budget: line-range read, search --count / --files-with-matches, one batch/tx, --jsonl for large streams.
How it works with your AI agent
Two integration modes, same capabilities:
flowchart LR
subgraph CLI["CLI mode (any agent)"]
direction TB
A["patchloom agent-rules >> AGENTS.md"] --> B["Agent reads AGENTS.md"]
B --> C{"What kind of edit?"}
C -->|Simple edit| D["Native tool (faster)"]
C -->|Config edit| E["patchloom doc (safer)"]
C -->|Markdown edit| F["patchloom md (smarter)"]
C -->|Multi-file edit| G["patchloom batch (batched)"]
end
subgraph MCP["MCP mode (MCP-capable agents)"]
direction TB
H["patchloom mcp-server"] --> I["Agent discovers tools via MCP"]
I --> J["Structured JSON tool calls"]
J --> K["No shell syntax needed"]
endStatus
5900+ tests across 25 commands. Tested with Grok 4.3, GPT-5.4, and Claude Opus 4.6.
Component | Status |
CLI | Install from crates.io, Homebrew, Scoop, npm ( |
MCP server | Official MCP Registry name |
Editor extension | Published on VS Code Marketplace and Open VSX |
Full command reference
Every command, flag, transaction operation, and exit code is documented in the Command Reference (also available at docs/reference/README.md).
License
Licensed under either of:
MIT license (LICENSE)
Apache License, Version 2.0 (LICENSE-APACHE)
at your option.
Contributing
See CONTRIBUTING.md.
For local verification before opening a pull request, run make check. It covers the main Linux test and lint gate: formatting, clippy, unit tests (including feature-matrix jobs), integration tests, PTY tests, release-notes structure, test hygiene, generated-doc freshness (check-patchloom-md, check-readme), server-json-test, verify-homebrew-version-test, packaging-script unit tests (scoop-manifest-test, chocolatey-package-test, pack-mcpb-test, force-release-version-test), workflow-sanity-test, apply-release-notes-test, and dco-check-test. It is not every GitHub required check: audit, deny, and Windows stay in GitHub Actions. While iterating locally, make check-fast is the same except it skips only check-patchloom-md (it still runs check-readme, server-json-test, verify-homebrew-version-test, the packaging-script tests, workflow-sanity-test, apply-release-notes-test, and dco-check-test).
Pull request titles must use one Conventional Commits prefix (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert; no commas or +). See CONTRIBUTING.md.
All commits must be signed off with git commit -s.
Agent integration tests
make agent-test runs 19 pytest scenarios that verify AI agents correctly use patchloom when given instructions. make bench-agent runs 3-way benchmarks (CLI vs MCP vs native) across 11 tasks. Use MODEL=X to switch models and RUNS=N for variance reduction. Requires an LLM API key. Not part of make check. See tests/agent/README.md for details.
Security
For current security reporting guidance, see SECURITY.md.
Available Tools
66 toolsappend_fileBDestructive
Append content to an existing file. Inserts the file's line ending first when the file does not already end with one. Example: {"content":"#[test]\nfn new_test() {}\n","path":"tests/test.rs"}
Inputs: path (required), content (required), agent_preset, expected_sha256.
apply_fragmentADestructive
Constrained freeform fragment apply (#2018). Strips Morph-style // ... existing code ... marker lines from fragment, then inserts or replaces at a required unique anchor (exactly one of after, before, old). Fail-closed: no anchor-less Morph model merge. Prefer for lazy-snippet agent output when anchors are known; use replace/ast for precise edits. Morph-class freeform fragment with required placement (exactly one of after/before/old). Lazy // ... existing code ... lines are stripped; no model merge without anchors (#2018). Example: {"fragment":"// ... existing code ...\n bar();\n// ... existing code ...\n","path":"src/lib.rs","after":"fn foo() {"}
Inputs: path (required), fragment (required), old, after, +5 more.
apply_patchADestructive
Apply a unified diff, a Codex *** Begin Patch document, or an Aider SEARCH/REPLACE / DiffFenced document. Default apply=true writes (same as CLI patch apply --apply). Set apply=false for check-only preview (CLI patch check; disk is unchanged). The diff parameter is the full unified diff text, a *** Begin Patch ... *** End Patch envelope (Add/Update/Delete/Move), or <<<<<<< SEARCH / ======= / >>>>>>> REPLACE blocks (dest path above ------- after SEARCH). SEARCH/REPLACE is unique by default (multi-match is ambiguous, no write); set replace_all=true to update every exact match. Empty-hunk +++ /dev/null (git deleted file mode, no hunks) unlinks. A hunked delete applies minus lines first; leftover bytes rewrite the file (preview --diff). Stale minus lines are ambiguous and the file is not removed; regenerate minus lines or use file.delete for path-only unlink. Use on_stale=merge for three-way merge on stale unified-diff context; allow_conflicts=true writes conflict markers. Never commit files containing conflict markers. Example: {"diff": "--- a/file.txt\n+++ b/file.txt\n@@ -1 +1 @@\n-old\n+new", "on_stale": "fail"}
Inputs: diff (required), apply, strict, on_stale, +2 more.
ast_delete_symbolBDestructive
Delete a whole symbol span, including leading doc comments and attributes, and collapse surrounding blank lines to one. Deletes the full symbol span (docs and attributes included) and collapses surrounding blank lines. Example: {"symbol":"unused_helper","path":"src/lib.rs"}
Inputs: path (required), symbol (required), lang, agent_preset, +1 more.
ast_depsBRead-only
Extract import/dependency statements from source files. Supports Rust, Python, JS/TS, Go, Java, C/C++, Ruby, PHP. Use reverse=true to find what imports a file. Example: {"path": "src/main.rs"}
Inputs: path (required), lang, reverse.
ast_diffARead-only
Structural diff between two versions of a file. Shows added, removed, and modified symbols (not line-level diff). Compares against git refs. Example: {"path": "src/lib.rs", "from": "HEAD~1"}
Inputs: path (required), to, from, lang.
ast_extract_to_fileBDestructive
Extract a symbol (module, function, struct) to a separate file. For modules with unwrap=true, content is un-indented. Example: {"source": "src/lib.rs", "symbol": "tests", "target": "src/lib_tests.rs", "replacement": "mod tests;", "prepend": "use super::*;"}
Inputs: source (required), symbol (required), target (required), lang, +7 more.
ast_groupBDestructive
Group symbols into a named module within a file. Creates the module if it doesn't exist, or appends to it. Example: {"path": "src/tests.rs", "module": "line_endings", "symbols": ["test_crlf", "test_lf"], "preamble": "use super::*;"}
Inputs: path (required), module (required), symbols (required), lang, +2 more.
ast_impactBRead-only
Transitive impact analysis: what symbols are affected by changing a given symbol. Traces the reference graph to find all direct and indirect dependents. Example: {"symbol": "parse_config", "path": "src/", "depth": 3}
Inputs: path (required), symbol (required), depth.
ast_importsADestructive
Manage import/use statements: add (idempotent), remove, deduplicate. With no mutation args, lists existing imports. Example: {"path": "src/main.rs", "add": ["use std::collections::HashMap;"]}
Inputs: path (required), add, lang, dedupe, +1 more.
ast_insertADestructive
Insert code at a structurally-aware position: inside a module/impl/struct (at start or end), or after/before a named symbol. Indentation is auto-detected. Example: {"path": "src/lib.rs", "content": "fn new_fn() {}", "after": "existing_fn"}
Inputs: path (required), content (required), lang, after, +3 more.
ast_listARead-only
List symbol definitions (functions, classes, structs, enums, methods, etc.) in a file or directory. Supports 20 languages. Example: {"path": "src/"} or {"path": "main.py", "kind": "function,class"}
Inputs: path (required), kind, lang.
ast_mapBRead-only
Generate a ranked repository map using PageRank over the symbol reference graph. Shows the most important symbols with token-budget-aware output. Example: {"path": "src/", "max_tokens": 2048}
Inputs: path (required), boost, focus, max_tokens.
ast_moveBDestructive
Move symbols between files. Removes from source, inserts into target (creating it if needed). Example: {"path": "src/big.rs", "target": "src/helpers.rs", "symbols": ["helper_fn"], "target_prepend": "use super::*;"}
Inputs: path (required), target (required), symbols (required), lang, +5 more.
ast_readARead-only
Read a specific symbol's source code by name from a file. Uses AST parsing to find the exact definition. Example: {"path": "src/main.rs", "symbol": "run"}
Inputs: path (required), symbol (required), lang, context.
ast_refsARead-only
Find all references to a symbol across files using AST analysis. Distinguishes definitions from references. Example: {"symbol": "process_data", "path": "src/"}
Inputs: path (required), symbol (required), lang, include_def.
ast_renameADestructive
Rename identifiers across files using AST-aware renaming (skips strings and comments). Example: {"path": "src/", "old": "process_data", "new": "transform_data"}
Inputs: new (required), old (required), path (required), lang.
ast_reorderBDestructive
Reorder symbols within a file or scope by name, kind, or custom order. Example: {"path": "src/lib.rs", "order": "alphabetical"} or {"path": "src/lib.rs", "order": ["Struct", "impl Struct", "helper"], "inside": "mod tests"}
Inputs: path (required), order (required), lang, inside.
ast_replaceADestructive
Replace text only within a specific symbol's body using AST scoping. Precise: only changes code inside the named symbol, leaving everything else untouched. Example: {"path": "src/lib.rs", "symbol": "parse_config", "old": "unwrap()", "new": "expect("parse failed")"}
Inputs: new (required), old (required), path (required), symbol (required), +2 more.
ast_replace_symbolBDestructive
Replace a whole symbol span, including leading doc comments and attributes, with new source re-indented to the symbol column. Replaces the full symbol span (docs and attributes included). Example: {"content":"fn default_timeout() -> u64 { 60 }","path":"src/lib.rs","symbol":"default_timeout"}
Inputs: path (required), symbol (required), content (required), lang, +2 more.
ast_rewrite_signatureBDestructive
Rewrite a function signature with structured fields (visibility, parameters, return_type) or a full new_signature string. Multi-language via tree-sitter. Example: {"path": "src/lib.rs", "old": "process", "parameters": "(x: i32)", "return_type": "-> String"}
Inputs: old (required), path (required), lang, parameters, +3 more.
ast_searchARead-only
Structural search using AST queries. Use S-expression syntax or set pattern=true for code patterns with $VAR meta-variables (pattern must be valid source after substitution, e.g. fn $NAME() {}). Literal tokens match exactly. $$$MULTI is not implemented. Example: {"query": "(function_item name: (identifier) @name)", "path": "src/"}
Inputs: path (required), query (required), lang, pattern, +1 more.
ast_splitADestructive
Split a file into multiple target files by distributing symbols. Atomic: all targets succeed or all roll back. Example: {"source": "src/big.rs", "targets": [{"path": "src/types.rs", "symbols": ["Config", "Mode"], "prepend": "use super::*;"}], "keep_in_source": ["main"], "source_suffix": "mod types;"}
Inputs: source (required), targets (required), lang, source_prefix, +3 more.
ast_validateARead-only
Validate syntax of source files. Returns parse errors with line numbers. Supports 20 languages. Example: {"path": "src/main.rs"}
Inputs: path (required), lang.
ast_wrapBDestructive
Wrap existing code in a structural block (module, impl, cfg, etc.). Specify symbols by name or a line range. Example: {"path": "src/lib.rs", "symbols": ["helper_fn", "HelperStruct"], "wrapper": "mod helpers"}
Inputs: path (required), wrapper (required), lang, lines, +2 more.
batch_replaceADestructive
Replace the same text across multiple files in one call. Engine staging is atomic for applied writes (all written files succeed or none change). Pattern misses are soft by default: matching files still apply and total misses appear in refused[]; set require_change=true to fail the whole batch if any file has no match. Canonical field is files (array); singular file is accepted as an alias for one path. Optional fuzzy enables similarity fallback; when exact old is absent, refuse by default unless allow_absent_old=true (#1758). JSON reports match_mode (exact/fuzzy/anchored), optional match_score, optional matched_text, match_count per change and aggregate (#1674). Example: {"files": ["Cargo.toml", "README.md"], "old": "0.1.0", "new": "0.2.0"}
Inputs: new (required), old (required), file, files, +11 more.
batch_tidyADestructive
Fix whitespace in multiple files in one call: trims trailing spaces and ensures final newline. Atomic: all files succeed or none change. Canonical field is files (array); singular file is accepted as an alias for one path. Example: {"files": ["src/main.rs", "src/lib.rs"]}
Inputs: file, files, strict.
create_fileADestructive
Create a new file with specified content. Fails if the file exists unless force=true. Example: {"content":"{"version": "1.0"}","path":"config.json"}
Inputs: path (required), content (required), force, agent_preset, +1 more.
delete_fileADestructive
Delete a file. Default fails with not_found if the path is missing. Set if_exists=true to soft-skip a missing file (no write, success). Set if_exists=true to soft-skip when the file is missing. Example: {"path":"tmp/scratch.txt"}
Inputs: path (required), if_exists, agent_preset, expected_sha256.
doc_appendBDestructive
Append a value to an array at a selector path. Example: {"value":"new_item","path":"data.json","selector":"items"}
Inputs: path (required), value (required), selector (required), agent_preset, +1 more.
doc_deleteADestructive
Delete a value at a selector path in a JSON, YAML, or TOML file. CLI --json and MCP/tx success include changed and removed (0 on missing key; exit 0 / ok is idempotent). Example: {"selector":"deprecated_key","path":"config.json"}
Inputs: path (required), selector (required), agent_preset, expected_sha256.
doc_delete_whereADestructive
Delete array elements matching a key=value predicate via --predicate (CLI) or the predicate field (plans). For scalar arrays use .=x, =x, or value=x. Different from doc.update, which filters inside the selector path. CLI --json and MCP/tx success include changed and removed (0 when no elements match; exit 0 / ok is idempotent). For object arrays: predicate='role=admin'. For simple arrays: predicate='=value'. Nested paths: predicate='settings.theme=dark'. Example: {"predicate":"value=a","path":"config.toml","selector":"tags"}
Inputs: path (required), selector (required), predicate (required), agent_preset, +1 more.
doc_diffBRead-only
Compare two structured files (JSON, YAML, or TOML) and show differences. Example: {"file_a": "old.json", "file_b": "new.json"}
Inputs: file_a (required), file_b (required).
doc_ensureADestructive
Set a value only if the selector path does not already exist. Example: {"value":30,"path":"config.json","selector":"timeout"}
Inputs: path (required), value (required), selector (required), agent_preset, +1 more.
doc_getARead-only
Read a value from a JSON, YAML, or TOML file by selector path. Example: {"path": "package.json", "selector": "version"}
Inputs: path (required), selector (required).
doc_mergeBDestructive
Deep-merge a JSON object into a document root, or into a selector path (e.g. multi-doc YAML 0 / [0]). Example: {"value":{"database":{"host":"localhost","port":5432}},"path":"config.yaml"}
Inputs: path (required), value (required), selector, agent_preset, +1 more.
doc_moveBDestructive
Move a value from one selector path to another within the same file. Example: {"to":"new_key","path":"config.json","from":"old_key"}
Inputs: to (required), from (required), path (required), agent_preset, +1 more.
doc_prependBDestructive
Prepend a value to the beginning of an array at a selector path.
Inputs: path (required), value (required), selector (required), agent_preset, +1 more.
doc_queryARead-only
Query a JSON, YAML, or TOML file. Actions: "has" (exists, true/false), "keys" (keys of one object; omit selector for .; e.g. database or items[0]), "len" (length of one object or array; omit selector for .; e.g. items or database), "select" (filter via selector predicates, e.g. users[role=admin]; no separate predicate field), "flatten" (leaf paths). keys need one object; len needs one object or array; items[*] is fail-closed ambiguous (use items[0] / items[1]). Example: {"action": "has", "path": "config.json", "selector": "database.host"}
Inputs: path (required), action (required), selector.
doc_setADestructive
Set a value at a single concrete selector path in a JSON, JSONC, YAML, TOML, .env, .ini, or .properties file (keys and indexes such as server.port or items.0.v). Parser-backed; output is always valid. Not for predicates or wildcards (items[name=foo].v, items[].v); use doc.update for multi-match writes. Default creates a missing key and fails with not_found if the file is missing. Set if_exists=true to soft-skip when the file is missing or the selector is not present (does not create the key). Single concrete path only (keys/indexes). For selector predicates or wildcards (items[name=foo].v, items[].v) use doc_update / plan doc.update. Set if_exists=true to soft-skip a missing file or missing selector (does not create the key). Example: {"value":"2.0.0","path":"package.json","selector":"version"}
Inputs: path (required), value (required), selector (required), strict, +3 more.
doc_updateADestructive
Set a new value at every location matching a selector. Use wildcards (items[].enabled) or selector predicates (items[name=foo].v). Not a separate --where flag; the filter is part of the selector string. Multi-match writes: use selector predicates or wildcards (items[name=foo].v, items[].enabled). Prefer this over doc_set when filtering by field. Example: {"value":7,"path":"config.toml","selector":"items[name=a].v"}
Inputs: path (required), value (required), selector (required), agent_preset, +1 more.
execute_planADestructive
Execute a multi-step transaction plan atomically (MCP equivalent of patchloom tx). Provide an inline plan object or a plan_path. plan requires version and operations (alias ops). Each operation requires string op. Other plan and op fields are allowed. Call operation_schema with the op name for that operation's fields. Optional plan.cwd must be a relative path under the server workspace. Absolute plan.cwd and ../ escapes are rejected. Do not set both plan.cwd and for_each. plan.format/validate are ignored on MCP. Example: {"plan": {"version": 1, "operations": [{"op": "doc.set", "path": "app.yaml", "selector": "name", "value": "x"}]}}
Inputs: plan, strict, plan_path, agent_preset.
explain_planBRead-only
Explain a tx plan in structured JSON (same as CLI patchloom explain --json). Provide inline plan text (plan) or a plan file (path). Format hint optional (json/yaml/toml). Read-only: does not execute the plan. Example: {"plan": "{"version": 1, "operations": [{"op": "file.create", "path": "a.txt", "content": "x"}]}"}
Inputs: path, plan, format.
fix_whitespaceADestructive
Normalize whitespace in a file or directory. A directory path expands with the same ignore-aware walker as CLI tidy fix (one write per text file). When op fields are omitted, defaults match CLI tidy fix (trim trailing whitespace + ensure final newline; mixed line endings flatten to the dominant style; uniform CRLF/LF stays). Set normalize_eol to keep to opt out of flattening. Precedence: defaults → plan write_policy → op fields. Plan write_policy is not re-applied at commit for paths last written by tidy.fix so op fields stick (#1840, #1847). Defaults: trim trailing whitespace and ensure final newline when those fields are omitted. Example: {"trim_trailing_whitespace":true,"path":"src/main.rs","ensure_final_newline":true}
Inputs: path (required), lines, dedent, indent, +6 more.
git_statusARead-only
Show uncommitted file changes vs git HEAD. Returns lists of modified, created, and deleted files. Omits .patchloom/ backup paths from --apply undo sessions. No parameters required.
list_filesARead-only
List files under the workspace (or given roots) with the same ignore/exclude/glob rules as search. Use this instead of a generic filesystem MCP list_dir/tree. Caps results (default max_results=500) and reports truncated+total_matched when capped. max_depth prunes the walk at each root (does not enter deeper dirs). max_results still counts all in-depth matches then truncates (total_matched remains honest). Prefer relative paths. Example: {"path": "src/", "exclude_patterns": ["target/**"], "max_results": 100, "max_depth": 3}
Inputs: path, globs, paths, max_depth, +4 more.
md_dedupe_headingsCDestructive
Remove later whole sections whose heading text+level already appeared (heading and body until next same-or-higher heading; unique second-section content is discarded). Removes later whole sections with the same heading level+text (body under the second heading is discarded, not merged).
Inputs: path (required), agent_preset, expected_sha256.
md_insert_after_headingBDestructive
Insert content immediately after a markdown heading line (before any existing body). For a sibling section after the full body, use md.insert_after_section. Inserts immediately under the heading line (before existing body). For a sibling ## section after the full body, use md_insert_after_section.
Inputs: path (required), content (required), heading (required), agent_preset, +1 more.
md_insert_after_sectionBDestructive
Insert content after the full section body (sibling placement). Use when adding a new ## section after this section's content. Prefer md.insert_after_heading for content under the heading line. Inserts after the full section body (sibling placement). Prefer md_insert_after_heading for content under the heading. Example: {"content":"## FAQ\n\nCommon questions.\n","path":"README.md","heading":"## Config"}
Inputs: path (required), content (required), heading (required), agent_preset, +1 more.
md_insert_before_headingCDestructive
Insert content immediately before a markdown heading line.
Inputs: path (required), content (required), heading (required), agent_preset, +1 more.
md_lintARead-only
Lint a markdown rules file for duplicate headings, dangerous git commands, and missing final newline. Returns object envelope {ok, path, issue_count, issues} (CLI lint-agents --json parity; not a bare array). isError stays false when issues are present; branch on ok / issue_count. Example: {"path": "AGENTS.md"}
Inputs: path (required).
md_move_sectionADestructive
Move a markdown heading section to a new position (same file reorder or cross-file). Exactly one of before or after is required. Omit to for same-file reorder. Example: {"path": "spec.md", "heading": "## Appendix", "to": "notes.md", "before": "## References"}
Inputs: path (required), heading (required), to, after, +1 more.
md_replace_sectionADestructive
Replace the body of a markdown section identified by heading (section ends at the next same-or-higher-level heading; nested lower-level headings are included). Section ends at the next same-or-higher-level heading; nested lower-level headings are included in the replaced range. Prefer peer-level headings when siblings must survive. Example: {"content":"Run npm install.\n","path":"README.md","heading":"## Installation"}
Inputs: path (required), content (required), heading (required), agent_preset, +1 more.
md_table_appendBDestructive
Append a row to a markdown table under a heading. Example: {"row":"| GET | /health | Health check |","path":"README.md","heading":"## API"}
Inputs: row (required), path (required), heading (required), agent_preset, +1 more.
md_upsert_bulletADestructive
Insert or update a bullet point under a markdown heading. Idempotent: skipped if the bullet is already present. Example: {"bullet":"- Added new feature","path":"CHANGELOG.md","heading":"## Changes"}
Inputs: path (required), bullet (required), heading (required), agent_preset, +1 more.
move_fileADestructive
Rename (move) a file. Use force=true to overwrite an existing destination. Example: {"to":"new.txt","from":"old.txt"}
Inputs: to (required), from (required), force, agent_preset, +1 more.
notebook_editADestructive
Replace the source of one Jupyter notebook cell by its id. Pass the new cell body as one string. It is stored as an nbformat array of lines. Cell type, outputs, and the rest of the file stay put. A missing id is no_matches. A duplicate id is ambiguous. This does not run the cell. Address the cell by its id, not by index. source is the whole new cell body as one string. Does not run the cell. Example: {"source":"import pandas as pd\ndf.head()\n","path":"analysis.ipynb","cell_id":"load"}
Inputs: path (required), source (required), cell_id (required), agent_preset, +1 more.
operation_schemaARead-only
Return the JSON schema for one plan operation. Pass op, for example doc.set. Use this for execute_plan field names. Does not run the operation. Example: {"op": "doc.set"}
Inputs: op (required).
prepend_fileADestructive
Prepend content to an existing file. Inserts the file's line ending after the new text when that text does not already end with one. Example: {"content":"// Copyright 2026\n","path":"src/main.rs"}
Inputs: path (required), content (required), agent_preset, expected_sha256.
read_fileBRead-only
Read file contents with optional line range. Optional lines range uses start:end (1-based).
Inputs: path (required), limit, lines, offset, +2 more.
replace_textADestructive
Replace text in a file. Literal by default; set regex=true for regex. Options: nth, insert_before, insert_after, case_insensitive, multiline, if_exists, whole_line, range, word_boundary, fuzzy, min_fuzzy_score, allow_absent_old. Set word_boundary=true to match only whole words (prevents 'SetupFile' matching inside 'BenchSetupFile'). Set whole_line=true to replace entire lines containing a match (use with new="" to delete lines). Fuzzy: when exact old is absent, refuse by default even if score ≥ min_fuzzy_score (#1758); set allow_absent_old=true only for deliberate approximate recovery. Prefer ast_rename for identifiers. Example: {"path": "README.md", "old": "1.0.0", "new": "2.0.0"}. Insert after anchor (mutually exclusive with new): {"path": "src/main.rs", "old": "use std::io;", "insert_after": "use std::fs;"}
Inputs: old (required), path (required), new, nth, +20 more.
search_filesBRead-only
Search text files for a pattern (regex by default, use literal=true for exact match). Supports advanced layered ignores for LLM agents: globs (include), exclude_patterns, custom_ignore_filenames (e.g. .agentignore), max_results. Other options: files_with_matches, files_without_match, count, case_insensitive, multiline, invert_match, assert_count, before/after_context. Canonical multi-root field is paths (array); singular path is accepted as an alias for one root (same as paths:[path]). Example: {"pattern": "TODO", "paths": ["src/"], "literal": true, "custom_ignore_filenames": [".agentignore"], "exclude_patterns": ["target/**"], "max_results": 20}
Inputs: pattern (required), path, count, globs, +14 more.
server_infoARead-only
Return server identity and workspace root: cwd, surface (full|core), tool_count, package version, MCP protocol_version from handshake, and optional recommendation (coding agents may prefer core). Prefer relative path parameters under cwd; absolute paths are allowed only when they resolve inside the workspace (AllowIfContained). Outside-workspace and ../ escapes are rejected.
tidy_checkARead-only
Scan files for missing final newline, mixed EOL, and trailing whitespace (CLI tidy check). Read-only: does not rewrite files. Canonical field is paths (array); singular path is accepted as an alias for one root. Empty issues is ok:true. Issues present still return a tool success with error_kind changes_detected. Example: {"path": "src/"}
Inputs: path, paths.
undo_listARead-only
List backup sessions created by --apply (including nested monorepo roots). Same as CLI patchloom undo --list. Returns items (timestamp, project_root, file_count, entries) plus warnings. Empty tree is error_kind no_matches. Example: {}
undo_restoreADestructive
Preview or restore files from a --apply backup session. Default is dry-run (applied:false, error_kind:changes_detected). Set apply=true to restore. Omit session to use the newest. Optional path[] restores only those session files (unknown path is no_matches). Paths stay inside the MCP workspace (AllowIfContained). Example: {"session": "<id-from-undo_list>", "apply": true}
Inputs: path, apply, session.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 66 tools
Tools are largely separated by domain prefixes (ast_, doc_, md_, file/apply/undo), and descriptions explicitly scope overlapping-looking operations such as doc_set vs doc_update and replace_text vs ast_replace. Still, the large doc/markdown/AST clusters contain several near-neighbors that require careful reading to select correctly.
Almost all tools use snake_case with a consistent domain-prefix convention (ast_, md_, doc_) plus verb_noun file/search/plan names. A few names are noun-like (server_info, operation_schema, git_status) rather than verb_noun, but the overall pattern is predictable.
66 tools is far beyond the recommended 3-15 range and exceeds the 25+ 'too many' threshold; the broad patch/edit domain justifies more than a small set, but this volume imposes high selection overhead on an agent. The surface would benefit from consolidation or tiered exposure.
Coverage is extensive across file editing, AST manipulation, markdown, structured documents, search, planning, and undo, with CRUD-style coverage for most resources. Minor gaps remain, such as markdown section/table deletion, notebook read/create, and directory operations, but core workflows are not dead-ended.
Maintenance
Related MCP Connectors
OCR, transcription, file extraction, and image generation for AI agents via MCP.
AI document editing: edits the exact section, leaves the rest. 38 MCP tools; agent self-signup.
Convert files, URLs, and documents to clean, AI-ready Markdown via MCP.
GitHub Copilot MCP: code, repo, PR, workflow tools for AI assistants.
Related MCP Servers
- AlicenseBqualityDmaintenanceAdvanced code search and transformation MCP server for AI assistants. Combines ugrep's speed with intelligent replace capabilities, dry-run previews, and language-aware refactoring across 11 tools.110MIT
- AlicenseAqualityBmaintenanceA robust, language-agnostic Model Context Protocol (MCP) server that provides AI coding agents with the ability to edit files surgically via Abstract Syntax Trees (AST) instead of relying on token-heavy, brittle search-and-replace or diff operations.2811MIT
- AlicenseAqualityDmaintenanceAdmission-control MCP server for AI coding agents — rejects bad file edits without coaching the LLM.31MIT
- AlicenseAqualityBmaintenanceAST-targeted code editing MCP server with 28 surgical tools for structural edits across 11 languages. Built on tree-sitter, replaces brittle search/replace with byte-correct edits keyed by symbol names.228MIT