uni-diff-patch
MCP server + CLI tool for generating and maintaining valid unified diff patches deterministically.
LLM provides structured file changes, the tool computes correct diffs via mpatch (Rust), and output patch files can be inspected, appended, upserted, overwritten, created, or pruned entry-by-entry without tool-specific metadata in the .diff file.
MCP 服务器 + CLI 工具,确定性生成并维护合法的 unified diff patch。
LLM 只需提供结构化文件变更,工具通过 mpatch (Rust) 计算正确 diff,并支持对已有 patch 文件做条目级 inspect / append / upsert / overwrite / create_only / remove。生成的 .diff 文件不包含工具私有元数据。
Install / 安装
uv add uni-diff-patch # as dependency / 作为依赖
uvx uni-diff-patch --help # run directly / 直接运行
CLI / 命令行
# Generate patch from JSON spec / 从 JSON spec 生成 patch
uni-diff-patch generate --spec changes.json -o output.diff
# Upsert entries in an existing patch / 更新已有 patch 中的条目
uni-diff-patch generate --spec changes.json -o output.diff --write-mode upsert
# Generate with project_root_dir for absolute paths / 指定项目根目录(用于绝对路径)
uni-diff-patch generate --spec changes.json --project-root-dir /path/to/project
# Inspect entries covered by a patch / 枚举 patch 覆盖的文件条目
uni-diff-patch inspect output.diff
# Remove entries by path or entry index / 按路径或条目序号删除 patch 条目
uni-diff-patch remove output.diff --path src/main.py
uni-diff-patch remove output.diff --entry-index 0
# Validate a patch / 验证 patch
uni-diff-patch validate output.diff
uni-diff-patch validate output.diff --git-check --project-root-dir /path/to/repo
# Start MCP server / 启动 MCP 服务器
uni-diff-patch serve
MCP Configuration / MCP 配置
{
"mcpServers": {
"uni-diff-patch": {
"command": "uvx",
"args": ["uni-diff-patch", "serve"]
}
}
}
Entry Lifecycle / 条目生命周期
Patch files are treated as an ordered store of per-file entries. The patch path stored in the --- / +++ headers is the identity key. Rename entries occupy both the source and destination paths.
Patch 文件会被当作有序的文件条目集合。--- / +++ header 中的 patch path 是身份键;rename 条目同时占用源路径和目标路径。
| Mode / 模式 | Behavior / 行为 |
|---|---|
append |
Default. Add new entries and fail if any occupied path already exists. / 默认模式。追加新条目;若路径已存在则失败。 |
upsert |
Replace matching entries, append absent entries, and remove a matching entry when the recomputed diff is empty. / 替换已有条目、追加不存在的条目;若重新计算后无 diff,则删除已有条目。 |
overwrite |
Replace the entire output patch file. / 覆写整个输出 patch 文件。 |
create_only |
Create a new output patch file and fail if it already exists. / 仅新建;文件已存在则失败。 |
When using absolute file paths, pass project_root_dir and keep it constant for the lifetime of a given output patch file. If a later mutation computes no exact path match but finds an existing entry with the same basename, the result includes a non-blocking POSSIBLE_PROJECT_ROOT_DIR_DRIFT warning. The .diff file itself intentionally remains metadata-free, so callers are responsible for using one stable project root per output patch.
使用绝对路径时请传入 project_root_dir,并在同一个输出 patch 文件的整个生命周期中保持不变。若后续操作没有精确路径匹配,但发现已有条目 basename 相同,结果会包含非阻塞 POSSIBLE_PROJECT_ROOT_DIR_DRIFT 警告。.diff 文件本身不会写入工具私有元数据,因此调用方需要保证同一输出 patch 使用同一项目根目录。
MCP Result Fields / MCP 返回字段
generate_patch_tool keeps the existing fields patch, files_changed, self_valid, and skipped_files, and adds:
removed_entries: entries removed byupsert; empty-diff removals includereason: "empty_diff_upsert".warnings: non-blocking warnings such as possible project-root drift.
inspect_patch_entries_tool returns entries, entry_count, duplicate_paths, and tool_policy_ok.
remove_patch_entries_tool returns removed_entries, remaining_count, and warnings. Removing the last entry leaves an empty patch file in place.
validate_patch_tool reports structural validity and also returns duplicate_paths plus tool_policy_ok, so callers can distinguish parseable patches from patches that violate this tool's single-entry-per-path policy.
JSON Spec Format / JSON Spec 格式
All modes are specified in a changes array. Each change requires file_path and mode.
所有模式通过 changes 数组指定,每个变更需要 file_path 和 mode。
file_content — Full new content / 完整新内容
Tool reads original from disk and diffs. / 工具从磁盘读取原文件并生成 diff。
{
"changes": [{
"file_path": "src/main.py",
"mode": "file_content",
"new_content": "def main():\n print('hello')\n"
}]
}
search_replace — Targeted replacements / 精准替换(最省 token)
{
"changes": [{
"file_path": "src/main.py",
"mode": "search_replace",
"replacements": [
{"search": "print('hello')", "replace": "print('world')"},
{"search": "old_func()", "replace": "new_func()", "line_hint": 42}
]
}]
}
line_hint: approximate line number for disambiguation when search matches multiple locations.
line_hint:当 search 匹配多处时,用大致行号消歧(选最近的匹配)。
create_file — New file / 新建文件
{
"changes": [{
"file_path": "src/new_module.py",
"mode": "create_file",
"new_content": "# New module\ndef init():\n pass\n"
}]
}
delete_file — Remove file / 删除文件
{
"changes": [{
"file_path": "src/deprecated.py",
"mode": "delete_file"
}]
}
text_pair — Both old and new text / 直接传入新旧文本(不读磁盘)
{
"changes": [{
"file_path": "virtual/path.py",
"mode": "text_pair",
"old_text": "x = 1\n",
"new_text": "x = 2\n"
}]
}
rename_file — Rename/move / 重命名/移动文件
Optionally with content change. / 可同时修改内容。
{
"changes": [{
"file_path": "src/old_name.py",
"mode": "rename_file",
"new_path": "src/new_name.py",
"new_content": "# Optional modified content\n"
}]
}
chmod — Permission change / 权限变更
Optionally with content change. / 可同时修改内容。
{
"changes": [{
"file_path": "scripts/deploy.sh",
"mode": "chmod",
"old_mode": "100644",
"new_mode": "100755"
}]
}
Multi-file patch / 多文件合并 patch
{
"changes": [
{"file_path": "src/a.py", "mode": "file_content", "new_content": "..."},
{"file_path": "src/b.py", "mode": "search_replace", "replacements": [{"search": "old", "replace": "new"}]},
{"file_path": "src/c.py", "mode": "create_file", "new_content": "..."}
],
"output_path": "changes.diff",
"write_mode": "upsert",
"project_root_dir": "/path/to/project"
}
Optional Fields / 可选字段
| Field / 字段 | Default / 默认值 | Description / 说明 |
|---|---|---|
encoding |
"utf-8" |
File encoding for disk-read modes / 磁盘读取模式的文件编码 |
context_lines |
3 |
Context lines in diff / diff 中的上下文行数 |
output_path |
— | Top-level, writes or mutates a patch file / 顶层字段,写入或修改 patch 文件 |
write_mode |
"append" |
Top-level, one of append/upsert/overwrite/create_only / 顶层字段,控制写入模式 |
project_root_dir |
current directory | Top-level, project root for absolute path relativization / 顶层字段,绝对路径相对化的项目根目录 |
Development / 开发
uv sync
uv run pytest tests/ -v
Metadata
Release files for uni-diff-patch 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| uni_diff_patch-0.1.0.tar.gz | 27.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uni_diff_patch-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.3 kB
Release files / uni_diff_patch-0.1.0.tar.gz
| Download URL | uni_diff_patch-0.1.0.tar.gz |
|---|---|
| Size | 27.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6822e308265552ea25670c8568c501e41138aa1b81fa5a327867b44db8d899df
|
|
BLAKE2b-256 checksum How to use checksums |
5359c3adf1066244ca154029b1a3ef05ba478ce801d16f7c131b9240d00a0aed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / uni_diff_patch-0.1.0-py3-none-any.whl
| Download URL | uni_diff_patch-0.1.0-py3-none-any.whl |
|---|---|
| Size | 36.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
051cc7d2fec07556b33ded25d8b7dc418e64f0066685e68f660a5b98454ed5d6
|
|
BLAKE2b-256 checksum How to use checksums |
68e72af60264cb98662525875d4dd829c26eda0117506b096c672344b732232d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|