MCP Setup Guide
Patchloom includes an MCP (Model Context Protocol) server for structured tool calls. MCP-capable AI agents can call Patchloom tools directly with JSON parameters, with no shell command construction, no quoting issues, and no --apply flag needed.
Official MCP Registry
Patchloom publishes to the
official MCP Registry as
io.github.patchloom/patchloom. The listing points clients at the crates.io
and npm packages and starts the stdio server with mcp-server.
Repo metadata lives in server.json at the repository
root. Ownership markers (required by the registry) are:
- crates.io: a visible
mcp-name: io.github.patchloom/patchloomline in the crate README (HTML comments are stripped on crates.io, so the token must be plain markdown text). - npm:
mcpNamein the publishedpackage.json(injected during the releasepublish-npmjob; cargo-dist does not emit it).
On each release, after crates.io and npm publish succeed, the Release workflow
calls Publish MCP Registry, which stamps the version into server.json,
confirms those markers, authenticates with GitHub OIDC, and runs
mcp-publisher publish. You can re-run it manually via workflow_dispatch
with an explicit version (for example after a failed publish).
Smithery (local MCPB)
Smithery can list local stdio servers via an MCPB bundle (URL publish is for hosted Streamable HTTP endpoints; Patchloom's primary agent path is local stdio).
The bundle lives under mcpb/ and runs:
patchloom mcp-serverif the binary is onPATH, elsenpx -y patchloom@<version> mcp-server(Node.js 18+).
make pack-mcpb # writes target/mcpb/patchloom-<version>.mcpb (override with VERSION=x.y.z)
# One-time auth + CI secret:
# smithery auth login
# export SMITHERY_API_KEY=$(smithery auth whoami --full | sed -n 's/^SMITHERY_API_KEY=//p')
# gh secret set SMITHERY_API_KEY --repo patchloom/patchloom
bash scripts/publish-smithery.sh # REST upload (reliable for stdio MCPB)
CI: .github/workflows/publish-smithery.yml packs after each GitHub Release and
publishes when the SMITHERY_API_KEY secret is set (soft-skip otherwise).
Glama directory
Glama indexes open-source MCP servers for discovery, quality scores, and optional hosted connectors.
Listing (live): glama.ai/mcp/servers/patchloom/patchloom
(search API id pk95432szu). Repo ownership metadata is in root
glama.json (maintainers GitHub usernames).
Canonical short description (match server.json / MCP Registry, max 100
chars):
Agent-safe structured edits: JSON/YAML/TOML/md/AST, dry-run, batch/tx. Not a filesystem MCP.
If the Glama profile still shows the older “AI agent file editing via MCP…” blurb, open the listing as a maintainer and set the description to the text above (GitHub sync does not always replace an existing Glama description).
First-time submit (already done for Patchloom)
- Sign in at glama.ai (GitHub OAuth is supported).
- Open MCP Servers and click Add MCP Server.
- Submit GitHub URL
https://github.com/patchloom/patchloomwith the description above. - Confirm search:
GET https://glama.ai/api/mcp/v1/servers?query=patchloomreturns a non-emptyserverslist.
There is no public unauthenticated submit API.
Verify MCP support
The MCP server is included by default in all pre-built binaries and in
recommended installs (Homebrew, Scoop, npm npx / global, crates.io).
winget usually tracks the latest GitHub Release after Microsoft publishes
the version PR; Chocolatey often lags while versions wait for moderation.
Prefer Scoop or a GitHub Release binary when you need a known-current MCP
server. Verify it works:
patchloom mcp-server --help
Configure your agent
Set command to an absolute path to the binary, or to patchloom if the
CLI is already on PATH (Homebrew, Scoop, cargo install, installer).
Examples:
| Platform | Typical command value |
|---|---|
| Unix (Homebrew / cargo) | patchloom or /opt/homebrew/bin/patchloom |
| Windows (Scoop) | patchloom or C:\\Users\\you\\scoop\\shims\\patchloom.exe |
| Windows (portable zip) | C:\\path\\to\\patchloom.exe |
JSON configs need escaped backslashes on Windows paths
(C:\\\\Users\\\\...\\\\patchloom.exe). Prefer the bare name patchloom
when the shim is on PATH.
Grok (config.toml)
Add to ~/.grok/config.toml:
[mcp_servers.patchloom]
command = "patchloom"
args = ["mcp-server"]
env = { PATCHLOOM_MCP_SURFACE = "core" }
Claude Desktop (JSON)
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"patchloom": {
"command": "patchloom",
"args": ["mcp-server"],
"env": {
"PATCHLOOM_MCP_SURFACE": "core"
}
}
}
}
VS Code (.vscode/mcp.json)
Create .vscode/mcp.json in your workspace root:
{
"servers": {
"patchloom": {
"command": "patchloom",
"args": ["mcp-server"],
"env": {
"PATCHLOOM_MCP_SURFACE": "core"
}
}
}
}
Cursor (.cursor/mcp.json)
Create .cursor/mcp.json in your workspace root:
{
"servers": {
"patchloom": {
"command": "patchloom",
"args": ["mcp-server"],
"env": {
"PATCHLOOM_MCP_SURFACE": "core"
}
}
}
}
Or use the Patchloom VS Code extension to configure MCP automatically via the Patchloom: Configure MCP command.
Generic stdio MCP
Any MCP client that supports stdio transport can connect by spawning patchloom mcp-server as a subprocess. The server communicates via JSON-RPC over stdin/stdout.
Available tools
Patchloom exposes two registration paths for MCP tools (see
src/cmd/mcp/surface.rs for the inventory and policy):
| Path | Rule | Examples |
|---|---|---|
| Registry (default) | 1:1 with a plan write Operation; schema from the variant | doc_set, create_file, fix_whitespace, most md_* writers |
| Custom (justified exception) | Multi-file scan, multi-op/batch/plan, readonly query, AST analyze, patch/meta | search_files, replace_text, batch_*, execute_plan, doc_get / doc_query, all ast_* |
Prefer the registry for new simple write tools. Do not force custom tools into the registry when that would lose multi-file, batch, or read UX.
Custom tools are inventoried in CUSTOM_MCP_TOOLS_CORE (always registered)
and CUSTOM_MCP_TOOLS_AST (only when the ast feature is enabled). Default
builds expose 58 tools (registry + custom). Builds without ast omit the
AST tools so list_tools stays honest about what is callable.
| Tool | Description |
|---|---|
doc_set | Set a value by selector path in a JSON, YAML, or TOML file |
doc_delete | Delete a value by selector path from a structured file |
doc_merge | Deep-merge an object into a document (optional selector, e.g. 0 for multi-doc YAML) |
doc_append | Append a value to an array |
doc_prepend | Prepend a value to an array |
doc_ensure | Set a value only if the selector path does not exist |
doc_delete_where | Delete array elements matching a predicate field (field=value) |
doc_update | Update values at selector paths that use predicates or wildcards (items[name=a].v, items[*].enabled). Not a predicate field; doc_delete_where is the predicate tool. |
doc_move | Move a value from one selector path to another |
doc_get | Read a value by selector path (read-only) |
doc_query | Query a structured file: has, keys, len, select, or flatten (read-only). Selector optional for keys/len (defaults to .). |
doc_diff | Compare two structured files (read-only) |
search_files | Search text files for a pattern, including literal, case-insensitive, count, file-only (files_with_matches / files_without_match), multiline, invert-match, and assert-count modes. Binary and invalid UTF-8 files are skipped (read-only) |
list_files | Bounded directory inventory with the same ignore/exclude/glob rules as search (read-only). Prefer this over a second generic filesystem MCP for list/tree. Optional max_depth prunes the walk per root (does not enter deeper dirs). max_results (default 500) still counts all in-depth matches then truncates and sets truncated / total_matched |
git_status | Show uncommitted file changes vs git HEAD (read-only) |
server_info | Return server identity and workspace root: cwd, surface (full|core), tool_count, package version, and MCP protocol_version (read-only). Use cwd before relative path ops |
read_file | Read file contents with optional line range |
replace_text | Replace text in a text file (literal or regex). Binary and invalid UTF-8 files are skipped |
apply_fragment | Freeform fragment at a required after/before/old anchor; strips Morph-style // ... existing code ... markers (no cloud merge). Prefer when the agent has a snippet plus a known placement |
md_upsert_bullet | Add a bullet under a markdown heading |
md_table_append | Append a row to a markdown table |
md_replace_section | Replace a section by heading (through next same-or-higher heading) |
md_insert_after_heading | Insert content immediately after a heading line (before body) |
md_insert_after_section | Insert content after the full section body (sibling section) |
md_insert_before_heading | Insert content before a markdown heading |
md_move_section | Move a heading section through next same-or-higher heading (same-file or cross-file) |
md_dedupe_headings | Remove later whole sections with a duplicate heading (second body discarded) |
md_lint | Lint an AGENTS.md file; returns {ok, path, issue_count, issues} (CLI lint-agents --json parity). Branch on ok; isError stays false when issues are present |
fix_whitespace | Fix whitespace and line endings in a text file. Binary and invalid UTF-8 files are skipped |
create_file | Create a new file with content |
append_file | Append content to an existing file |
prepend_file | Prepend content to an existing file |
delete_file | Delete a file |
move_file | Move or rename a file (binary-safe) |
apply_patch | Apply a unified diff, Codex Begin Patch, or SEARCH/REPLACE / DiffFenced document (unique unless replace_all). Empty-hunk +++ /dev/null unlinks. A hunked delete applies minus first; leftover rewrites. Stale is ambiguous and does not unlink |
batch_replace | Replace the same text across multiple files atomically |
batch_tidy | Fix whitespace in multiple files atomically |
execute_plan | Execute a full multi-op transaction plan atomically (recommended for complex/multi-file edits). Supports inline plan or plan_path. MCP strips format/validate; CLI tx still runs those lifecycle steps. |
ast_list | List symbol definitions (functions, classes, structs, enums, methods) in a file or directory (20 languages). Filter by kind. |
ast_read | Read a specific symbol's source code by name from a file. |
ast_rename | Rename identifiers across files using AST-aware renaming (skips strings and comments). |
ast_validate | Validate syntax of source files. Returns parse errors with line numbers. |
ast_search | Structural search using AST queries. Supports S-expression syntax and code patterns with meta-variables. |
ast_refs | Find all references to a symbol across files. Distinguishes definitions from references. |
ast_deps | Extract import/dependency statements from source files (Rust, Python, JS/TS, Go, Java, C/C++, Ruby, PHP). |
ast_map | Generate a ranked repository map using PageRank over the symbol reference graph. Token-budget-aware output. |
ast_diff | Structural diff between two versions of a file. Shows added, removed, and modified symbols. |
ast_impact | Transitive impact analysis: trace the reference graph to find all dependents of a symbol. |
ast_replace | Replace text only within a specific symbol's body using AST scoping. |
ast_insert | Insert code before/after a symbol or inside a container (module, class, impl block). |
ast_wrap | Wrap a symbol in a container (module, class, namespace, impl block, or custom wrapper). |
ast_imports | List, add, remove, or deduplicate import statements in source files. |
ast_reorder | Reorder symbols by strategy: alphabetical, reverse, kind-first, or custom order. |
ast_group | Move symbols into a new or existing module block within the same file. |
ast_move | Move symbols between files with configurable insertion position. Optional update_imports plus old_module_path / new_module_path rewrites consumer imports. |
ast_extract_to_file | Extract a symbol to a new file, optionally unwrapping module blocks. Optional update_imports plus old_module_path / new_module_path rewrites consumer imports. |
ast_split | Split a file by distributing symbols across multiple target files. |
How MCP mode differs from CLI mode
| Aspect | CLI mode | MCP mode |
|---|---|---|
| Apply behavior | Requires --apply flag | Auto-applies (writes are the default) |
| Input format | Shell arguments | Structured JSON parameters |
| Path security | No restriction | Paths must stay within working directory |
| Error format | stderr text | MCP error response with structured content |
| Discovery | Agent reads AGENTS.md | Agent discovers tools via MCP protocol |
| Lifecycle steps | CLI tx runs format/validate | MCP execute_plan strips format/validate |
Multi-step plans and concurrency guidance (important for agents)
For any work involving more than one edit (especially on the same file or related files), prefer the execute_plan tool over issuing many individual tools:
- One
execute_plancall = atomic execution of a mixed plan (doc.set + md.replace_section + create + replace + ...). - MCP
execute_planstripsformatandvalidatelifecycle steps from the submitted plan. Those steps do not run (they are CLItx/ project-config only). Use.patchloom.tomlif the workspace needs lifecycle shells. - Top-level MCP
strictoverrides the plan only when the caller sends it. Omitting it leavesplan.strictunchanged (so{"plan":{"strict":false}}is honored). write_policyon the plan is still accepted.
Example inline plan (JSON):
{
"version": 1,
"strict": true,
"operations": [
{ "op": "doc.set", "path": "package.json", "selector": "version", "value": "2.0.0" },
{ "op": "md.replace_section", "path": "AGENTS.md", "heading": "## Commands", "content": "Run make check.\n" },
{ "op": "file.create", "path": "REPORT.md", "content": "# Summary\n" }
]
}
Critical rules for agents (to avoid lost updates and races):
- Do not issue concurrent write tools against the same path(s) unless using
execute_plan. - Serialize writes per path. Parallelize only across completely disjoint paths.
- Per-call "ok" does not mean the combined result is coherent if you interleave writers yourself.
- Use one
execute_planfor any logical multi-edit task.
These semantics are also documented in the tool instructions returned by the MCP server and in patchloom agent-rules --mode mcp.
Tool surface (full vs core)
By default the server registers the full tool inventory (registry + custom handlers, including AST when built with ast). That default stays for compatibility.
Recommended for coding agents (Grok, Claude, Cursor, dual native tools + MCP): start with the core pack so tool schemas stay small. Upgrade to full when you need AST or advanced standalone tools.
export PATCHLOOM_MCP_SURFACE=core
patchloom mcp-server
| Value | Effect |
|---|---|
unset or full | Full inventory (default; backward compatible) |
core | Only: read_file, search_files, list_files, replace_text, batch_replace, doc_get, doc_set, doc_query, md_replace_section, execute_plan, server_info |
| anything else | Server fails to start with a clear error |
server_info includes "surface": "core"|"full", "tool_count", package "version", MCP "protocol_version" (same as the initialize handshake), and when surface is full a "recommendation" string pointing coding agents at core. Invalid PATCHLOOM_MCP_SURFACE values are rejected (do not silently fall back).
Handshake instructions match the active surface: core mode lists only the core tools (it does not advertise full-inventory names such as create_file or ast_*). Instructions also include a short canonical name map (CLI / plan / MCP) and an explore rule (prefer list_files / search_files / read_file over shell cat/find/ls and over a second generic filesystem MCP when Patchloom MCP is connected).
Note: execute_plan remains on core, so multi-op plans can still run create/delete/AST-style plan ops (and plan apply.fragment) when the host sends a full plan. Standalone tools such as apply_fragment, create_file, and ast_* are full-inventory only. The env flag reduces the tool schema surface for small agents; it is not a capability sandbox for the plan catalog.
Short system-prompt rules (not the full ~50KB dump):
patchloom agent-rules --surface core
Coding-agent install recipes (core by default)
Coding agents should use the core pack so tool schemas stay small. Full inventory remains the product default when the env is unset (compatibility). Set PATCHLOOM_MCP_SURFACE=core in the client config for Grok, Claude, Cursor, and Codex-style hosts.
Grok / config.toml
[mcp_servers.patchloom]
command = "patchloom"
args = ["mcp-server"]
env = { PATCHLOOM_MCP_SURFACE = "core" }
Cursor (~/.cursor/mcp.json or project .cursor/mcp.json)
{
"mcpServers": {
"patchloom": {
"command": "patchloom",
"args": ["mcp-server"],
"env": {
"PATCHLOOM_MCP_SURFACE": "core"
}
}
}
}
Claude Desktop / Claude Code style MCP config
{
"mcpServers": {
"patchloom": {
"command": "patchloom",
"args": ["mcp-server"],
"env": {
"PATCHLOOM_MCP_SURFACE": "core"
}
}
}
}
Codex / generic stdio MCP
{
"name": "patchloom",
"command": "patchloom",
"args": ["mcp-server"],
"env": {
"PATCHLOOM_MCP_SURFACE": "core"
}
}
Do not also install a generic filesystem MCP only for list/read when Patchloom MCP is connected: use list_files, search_files, and read_file instead. Use full surface (unset env or full) when you need standalone AST tools or advanced md/doc tools as first-class MCP tools (execute_plan on core can still run plan ops).
Example fragment for copy-paste: mcp-core.example.json.
Design notes: mcp-surface-tiers.md.
Debugging and logging
The MCP server can log every tool call to a JSONL file for debugging and performance analysis. Each line records the tool name, duration, and success/failure status.
Enable logging with the --log flag:
patchloom mcp-server --log /tmp/patchloom-mcp.log
Or set the PATCHLOOM_MCP_LOG environment variable (the --log flag takes precedence):
export PATCHLOOM_MCP_LOG=/tmp/patchloom-mcp.log
patchloom mcp-server
Each line is a JSON object:
{"ts":1749123456789,"tool":"replace_text","duration_ms":3,"ok":true}
{"ts":1749123456800,"tool":"doc_set","duration_ms":5,"ok":false,"error":"file not found"}
| Field | Type | Description |
|---|---|---|
ts | number | Unix timestamp in milliseconds |
tool | string | Tool name that was called |
duration_ms | number | Execution time in milliseconds |
ok | boolean | Whether the call succeeded |
error | string | Error message (only present on failure) |
Configuring logging in your MCP client
Grok (config.toml) -- pass the env var to the MCP server subprocess:
[mcp_servers.patchloom]
command = "/path/to/patchloom"
args = ["mcp-server"]
env = { PATCHLOOM_MCP_LOG = "/tmp/patchloom-mcp.log" }
Or use --log in the args:
[mcp_servers.patchloom]
command = "/path/to/patchloom"
args = ["mcp-server", "--log", "/tmp/patchloom-mcp.log"]
Claude Desktop / VS Code / Cursor (JSON) -- use --log in the args:
{
"mcpServers": {
"patchloom": {
"command": "/path/to/patchloom",
"args": ["mcp-server", "--log", "/tmp/patchloom-mcp.log"]
}
}
}
Or pass the env var (Claude Desktop supports env in server config):
{
"mcpServers": {
"patchloom": {
"command": "/path/to/patchloom",
"args": ["mcp-server"],
"env": { "PATCHLOOM_MCP_LOG": "/tmp/patchloom-mcp.log" }
}
}
}
Security model
The MCP server enforces path containment: every path must resolve inside the
working directory where patchloom mcp-server was started. Relative paths are
preferred. Absolute paths that resolve inside that workspace are allowed
(agents often concatenate server_info.cwd with a relative path). ../
traversal, absolute paths outside the workspace, and symlinks that escape the
workspace are rejected. This prevents an agent from editing files outside the
project.
Each individual tool validates every path before execution.
Streamable HTTP transport
By default, the MCP server uses stdio transport (ideal for local IDE/agent integration). With --http, the server switches to Streamable HTTP transport, allowing remote MCP clients to connect over the network.
Streamable HTTP has no authentication and no token. The default bind is loopback (127.0.0.1). Binding a non-loopback address (0.0.0.0, a LAN IP, or a public IP) is refused unless you pass --allow-unauthenticated.
Basic HTTP
# Default: listen on 127.0.0.1:8080 (loopback)
patchloom mcp-server --http
# Custom port on loopback
patchloom mcp-server --http --port 3000
# Explicit loopback bind
patchloom mcp-server --http --host 127.0.0.1
The MCP endpoint is served at /mcp (e.g., http://127.0.0.1:8080/mcp).
Do not copy-paste --host 0.0.0.0 as a default. All-interfaces HTTP is unauthenticated (there is no token) and requires an explicit opt-in:
# All interfaces: unauthenticated, opt-in required
patchloom mcp-server --http --host 0.0.0.0 --allow-unauthenticated
HTTPS with native TLS
TLS encrypts the connection but still does not authenticate clients. Non-loopback binds still need --allow-unauthenticated. There is no bearer token.
patchloom mcp-server --http --host 0.0.0.0 --port 443 \
--tls-cert cert.pem --tls-key key.pem --allow-unauthenticated
Both --tls-cert and --tls-key must be provided together. The server uses rustls (no OpenSSL dependency).
HTTP transport flags
| Flag | Default | Description |
|---|---|---|
--http | off | Use Streamable HTTP transport instead of stdio |
--host | 127.0.0.1 | Bind address (requires --http). Non-loopback binds require --allow-unauthenticated |
--port | 8080 | Bind port (requires --http). Use 0 for an OS-assigned ephemeral port (printed in the startup banner) |
--allow-unauthenticated | off | Permit HTTP on a non-loopback bind. Streamable HTTP has no token or other client auth |
--tls-cert | none | TLS certificate PEM file; enables HTTPS (requires --http and --tls-key) |
--tls-key | none | TLS private key PEM file (requires --http and --tls-cert) |
Connecting a remote MCP client
Use any MCP client that supports Streamable HTTP transport. Example with the rmcp Rust client:
#![allow(unused)] fn main() { // Requires `rmcp` 3.x with client + HTTP transport features (see crates.io). use rmcp::ServiceExt; use rmcp::transport::StreamableHttpClientTransport; let transport = StreamableHttpClientTransport::from_uri("http://localhost:8080/mcp"); let client = ().serve(transport).await?; // Negotiate initialize; tool calls go through client.peer().call_tool(...). }
Logging with HTTP transport
The --log flag works identically with HTTP transport:
patchloom mcp-server --http --log /tmp/mcp.log
Graceful shutdown
The HTTP server shuts down gracefully on Ctrl+C (SIGINT): active SSE streams are terminated, in-flight requests complete, and the server exits cleanly.
Example tool call
An MCP-capable agent sends:
{
"method": "tools/call",
"params": {
"name": "doc_set",
"arguments": {
"path": "config.yaml",
"selector": "database.port",
"value": 5432
}
}
}
Patchloom parses the YAML, changes database.port to 5432, preserves all comments and formatting, and writes the file. The agent receives a success response with no further action needed.