patch-file-tool
v1.0.0
Published
Atomic exact-match file-surgery tool and MCP server for AI agent runtimes.
Downloads
138
Maintainers
Readme
AgenticEconomy 🛠️
A zero-dependency core, atomic file-surgery tool for AI Agents. Natively supports programmatic TypeScript imports and the Model Context Protocol (MCP).
Stop letting your agents execute full-file rewrites that burn tokens and increase latency. AgenticEconomy enforces an exact-match patching mechanism that guarantees predictable, lightweight workspace edits.
🚀 Quick Start (MCP Configuration)
Add this to your client configuration (for example Claude Desktop, Cursor, or Windsurf) to integrate the tool instantly:
{
"mcpServers": {
"agentic-economy": {
"command": "npx",
"args": ["-y", "patch-file-tool"]
}
}
}Set PATCH_FILE_WORKSPACE_ROOT in the MCP server environment when your client supports it. If omitted, the server uses its current working directory as the workspace sandbox.
📦 Programmatic Usage (TypeScript)
import { patchFile } from "patch-file-tool";
const result = await patchFile({
filePath: "src/index.ts",
searchBlock: "const x = 1;",
replaceBlock: "const x = 2;",
}, { workspaceRoot: process.cwd() });Why This Exists
AI agents often waste tokens and introduce corruption by emitting entire replacement files for small edits. Full-file rewrites can accidentally drop imports, comments, generated sections, formatting, or user changes that appeared after the agent last read the file.
patch_file makes the safer path the default: send only the smallest exact original block that identifies the edit, plus the replacement block. Exact-once matching prevents fuzzy or partial edits from landing in the wrong place.
The MCP tool uses three snake_case fields: file_path, search_block, and replace_block. The programmatic TypeScript API accepts those names plus ergonomic camelCase aliases: filePath, searchBlock, and replaceBlock.
Token and Latency Comparison
For small edits in large files, patch_file keeps the agent response proportional to the edit instead of proportional to the whole file. The exact token count depends on the tokenizer, model, and payload, so this repository includes a reproducible benchmark instead of relying on estimates.
Run it locally:
npm run tokensBenchmark fixture:
- tokenizer:
gpt-tokenizer/model/gpt-4o - file: synthetic 1,000-line TypeScript file
- edit: one line changed at line 500
Measured output-token counts:
| Strategy | Agent output | Exact output tokens | Output characters | Latency profile | Failure mode |
| --- | ---: | ---: | ---: | --- | --- |
| Full-file rewrite | Entire 1,000-line file | 9,002 | 31,894 | Slowest: model must generate, transmit, and the runtime must write the whole file | Can drop unrelated lines, stale edits, comments, formatting, or concurrent user changes |
| Unified diff | Diff hunk with line metadata | 99 | 339 | Moderate: compact output, but patch application may need parsing and hunk matching | Can fail or misapply if line numbers/context are stale or generated hunks are malformed |
| patch_file | Minimal exact search_block plus replace_block | 48 | 156 | Fastest for small edits: model emits only the necessary text and the engine does one exact match | Fails closed if the block is missing or not unique |
Example payload for the same one-line change:
{
"file_path": "src/generated-config.ts",
"search_block": "export const setting0500 = 500;\n",
"replace_block": "export const setting0500 = 9001;\n"
}That payload is small enough for an agent loop to retry cheaply. If the file changed, the tool returns a structured error instead of guessing, and the agent can reread only the relevant file content before trying again. End-to-end model latency still depends on the provider and model, but output generation and transmission scale directly with these measured output-token counts.
How Exact-Once Matching Works
- The target file is read from disk.
search_blockis matched with strict string matching.- No whitespace normalization, fuzzy matching, or partial patching is performed.
- If the block appears exactly once, that single occurrence is replaced.
- If the block appears zero times or more than once, nothing is written.
For normal text block replacement, occurrences are counted as non-overlapping exact matches.
Installation and Local Usage
npm install
npm run build
npm testDuring local development:
npm run typecheck
npm run cleanTypeScript Usage
import { patchFile, PATCH_FILE_ERROR_CODES } from "patch-file-tool";
const result = await patchFile(
{
file_path: "src/config.ts",
search_block: "export const retries = 2;\n",
replace_block: "export const retries = 3;\n",
},
{ workspaceRoot: process.cwd() },
);
if (!result.ok) {
if (result.code === PATCH_FILE_ERROR_CODES.SEARCH_BLOCK_NOT_FOUND) {
// Reread the file and retry with exact current text.
}
console.error(result.code, result.message);
}Public exports:
patchFilePATCH_FILE_ERROR_CODESpatchFileSchemaPatchFileInputPatchFileOptionsPatchFileResultPatchFileSuccessPatchFileFailurePatchFileErrorCodePatchFileSchema
MCP Usage
This package also includes a stdio MCP server that exposes the same engine as a tool named patch_file.
Build it first:
npm run buildRun the MCP server:
PATCH_FILE_WORKSPACE_ROOT=/path/to/workspace npx patch-file-mcpFor local development from this repository:
PATCH_FILE_WORKSPACE_ROOT=/path/to/workspace node dist/mcp-server.jsExample Codex MCP config:
[mcp_servers.patch_file]
command = "node"
args = ["/path/to/patch-file-tool/dist/mcp-server.js"]
[mcp_servers.patch_file.env]
PATCH_FILE_WORKSPACE_ROOT = "/path/to/workspace"PATCH_FILE_WORKSPACE_ROOT is the sandbox root. If it is omitted, the MCP server uses its current working directory.
JSON Schema
The schema is exported as patchFileSchema and is also available at schemas/patch_file.schema.json.
{
"name": "patch_file",
"description": "Safely edit one file by replacing one exact block of current text with replacement text. Use this instead of rewriting whole files.",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["file_path", "search_block", "replace_block"],
"properties": {
"file_path": {
"type": "string",
"description": "Path to the file to edit, constrained by the configured workspace/root directory."
},
"search_block": {
"type": "string",
"minLength": 1,
"description": "Exact original text currently present in the file. Copy it exactly from the current file, preserving indentation and line endings. Provide only the minimal lines needed to uniquely identify the edit, with enough surrounding context so this block appears exactly once. Avoid full-file rewrites. If the tool reports zero matches or multiple matches, reread the file and retry with a larger or more precise search_block."
},
"replace_block": {
"type": "string",
"description": "Replacement text to insert in place of search_block. It may be empty. Preserve intended indentation and line endings inside this block. Avoid full-file rewrites; replace only the minimal exact block needed for the edit."
}
}
}
}Success Result
{
"ok": true,
"file_path": "/workspace/src/config.ts",
"replacements": 1,
"line_number": 1,
"before_chars": 26,
"after_chars": 26,
"before_bytes": 26,
"after_bytes": 26
}Failure Results
SEARCH_BLOCK_NOT_FOUND means the file was not modified:
{
"ok": false,
"code": "SEARCH_BLOCK_NOT_FOUND",
"message": "search_block was not found. Reread the file and retry with exact current text plus minimal surrounding context.",
"file_path": "/workspace/src/config.ts",
"match_count": 0
}SEARCH_BLOCK_AMBIGUOUS means the file was not modified:
{
"ok": false,
"code": "SEARCH_BLOCK_AMBIGUOUS",
"message": "search_block matched multiple locations. Retry with additional surrounding context so the block is unique.",
"file_path": "/workspace/src/config.ts",
"match_count": 3
}Other stable error codes include:
INVALID_INPUTPATH_OUTSIDE_WORKSPACETARGET_IS_DIRECTORYBINARY_FILE_REJECTEDFILE_READ_ERRORFILE_WRITE_ERROR
Retry Guidance for Agents
When a patch fails, do not guess and do not switch to a full-file rewrite.
- On
SEARCH_BLOCK_NOT_FOUND, reread the file, copy the exact current text, and retry with minimal context. - On
SEARCH_BLOCK_AMBIGUOUS, include a few more surrounding lines sosearch_blockappears exactly once. - Preserve indentation and line endings inside both blocks.
- Keep
search_blockas small as possible while still unique. - Use an empty
replace_blockwhen deleting text.
Security Notes
patchFile(input, { workspaceRoot }) constrains edits to the configured workspace root. If no root is provided, the current working directory is used.
The engine:
- rejects path traversal outside the workspace;
- rejects absolute paths outside the workspace;
- rejects symlinks that resolve outside the workspace;
- rejects directories;
- rejects likely binary files;
- preserves existing file permissions where practical;
- writes temporary files in the same directory as the target file;
- cleans temporary files after write failures;
- replaces files with a same-directory rename;
- uses best-effort file and directory sync where supported by the platform.
Limitations
- The tool edits local filesystem paths only.
- It is intentionally strict: no fuzzy matching, no regex mode, and no whitespace normalization.
- Binary detection is conservative and rejects files containing NUL bytes in the sampled prefix.
- Atomic replacement is as strong as practical in Node.js and the host filesystem, but durability semantics vary by operating system and filesystem.
- Encoding preservation currently covers UTF-8, UTF-8 with BOM, UTF-16LE with BOM, and UTF-16BE with BOM.
Compared With Unified Diffs
Unified diffs are excellent for human review and version-control workflows, but agents can produce malformed hunks, stale line numbers, or patches that require fuzzy application. patch_file uses a smaller contract: replace this exact current text if and only if it is unique.
Compared With Normal Search/Replace
Normal search/replace tools often replace every match, the first match, or a fuzzy match. patch_file refuses to write unless there is exactly one strict match, which makes failures safe and machine-readable.
License
MIT. See LICENSE.
