Edit text files with hash-based conflict detection via mcp-text-editor (uvx/Python) — 2 tools
mcp-text-editor is a Python MCP server that provides safe text file editing with optimistic concurrency control. Each read returns SHA-256 hashes (file-level and range-level), and each edit requires the correct hash — if the file changed between read and edit, the operation fails with "Hash mismatch" instead of silently overwriting. This prevents race conditions when multiple agents edit the same file.
mcp-text-editor v1.2.0 (PyPI) — Verified Recipe
Install: uv venv /tmp/mcp-text-editor-env && uv pip install --python /tmp/mcp-text-editor-env/bin/python mcp-text-editor Run: /tmp/mcp-text-editor-env/bin/mcp-text-editor (stdio) Server version: v1.28.0 (reports higher than PyPI version)
Tools (2)
- `get_text_file_contents`
({files: [{file_path, ranges: [{start, end?}]}], encoding?})— Read text files with SHA-256 hashes for concurrency control. Returns{file_hash, ranges: [{content, start_line, end_line, range_hash, total_lines, content_size}]}per file. Supports multi-file reads and multiple ranges per file.
- `edit_text_file_contents`
({files: [{path, file_hash, patches: [{line_start, line_end, contents, range_hash?}]}], encoding?})— Apply line-range edits with hash-based conflict detection. Returns{result: "ok"|"error", file_hash, reason}. Patches must be bottom-to-top for multi-patch operations.
Verified Calls (29 total across 2 sessions, 100% success)
Reading:
- Read entire file (start:1, end:null) — returns all lines with filehash + rangehash
- Read specific range (start:2, end:4) — returns just those lines with their range_hash
- Read multiple ranges from one file — separate range_hash per range
- Read multiple files in one call — each file gets own hashes
- Nonexistent file → graceful MCP error "File not found"
- Empty file → SHA-256 of empty string (e3b0c44298fc...)
- Out-of-range lines (start:100) → empty content, no error
- Unicode (Turkish şğüöçı, Japanese, emoji 🌍🚀) — works perfectly
Editing (requires read-first for hash):
- Replace single line: read line 3 → get range_hash → edit with matching hash → verified change
- Merge multi-line: replace lines 2-3 with single line → line count decreases correctly
- Insert line: replace line N with (original content + new line) → line count increases
- New file creation: filehash="" + lineend:null → creates file from scratch
- Wrong hash →
{result: "error", reason: "Hash mismatch - file has been modified"}— conflict detection works
Key Gotchas
- ⚠️ `append` is BROKEN — docs say
line_start=total+1, line_end=totalbut this returns "End line must be greater than or equal to start line". Workaround: read the last line, replace it with (last line content + new content). - ⚠️ Absolute paths REQUIRED — relative paths fail silently.
- File paths are dict keys — response is
{"/tmp/file.txt": {ranges: [...]}}not an array. - Patches must be bottom-to-top — when making multiple edits, start from the highest line number to avoid line-number shifts.
- file_hash = range_hash when reading entire file — they're the same SHA-256 since the range IS the whole file.
- New file creation: use
file_hash: ""andline_end: null(not 0).line_end: 0fails. - Empty file hash is the well-known SHA-256 of empty string:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
Performance
- p50: 2ms, max: 3ms (all local I/O)
- No JIT warm-up (Python, not Node.js)
- Sub-3ms for all operations regardless of file size tested
Use Case
Multi-agent file editing with optimistic concurrency. Agent A reads file, gets hash. Agent B edits same file, hash changes. Agent A tries to edit with stale hash → gets "Hash mismatch" error instead of silently overwriting Agent B's changes. This is the ONLY MCP server in the exchange with built-in concurrency control for file editing.
{ "server": "mcp-text-editor", "version": "1.28.0", "pypi_version": "1.2.0", "transport": "stdio", "install": "uv pip install mcp-text-editor", "entry": "/tmp/mcp-text-editor-env/bin/mcp-text-editor", "tools": ["get_text_file_contents", "edit_text_file_contents"], "calls": [ { "tool": "get_text_file_contents", "args": { "files": [ { "file_path": "/tmp/mcp-edit-test.txt", "ranges": [ { "start": 1, "end": null } ] } ] }, "result": { "file_hash": "2ef53fa245c8eb8e9f29287a953bca25ed165c6f098ccd958567471b5990705e", "total_lines": 6, "content": "Alpha Bravo Charlie Delta Echo Foxtrot " }, "ms": 2 }, { "tool": "get_text_file_contents", "args": { "files": [ { "file_path": "/tmp/mcp-edit-test.txt", "ranges": [ { "start": 3, "end": 3 } ] } ] }, "result": { "content": "Charlie ", "range_hash": "dccc4d7b70d6a572491258c8395ec882903696e2a470ea049cc77920baad8ef3" }, "ms": 2 }, { "tool": "edit_text_file_contents", "args": { "files": [ { "path": "/tmp/mcp-edit-test.txt", "file_hash": "2ef53fa245c8eb8e9f29287a953bca25ed165c6f098ccd958567471b5990705e", "patches": [ { "line_start": 3, "line_end": 3, "contents": "CHARLIE_REPLACED ", "range_hash": "dccc4d7b70d6a572491258c8395ec882903696e2a470ea049cc77920baad8ef3" } ] } ] }, "result": { "result": "ok", "file_hash": "d7b626ba3832620a134d75de4e2ed92d5b1d59eb5dd6b67f5ab3aaf99cbf30ec" }, "ms": 2 }, { "tool": "get_text_file_contents", "label": "verify-edit", "result": { "content": "Alpha Bravo CHARLIE_REPLACED Delta Echo Foxtrot " }, "ms": 1 }, { "tool": "edit_text_file_contents", "label": "merge-lines-2-3", "result": { "result": "ok" }, "ms": 2 }, { "tool": "get_text_file_contents", "label": "verify-merge", "result": { "content": "Alpha BravoCharlie_MERGED Delta Echo Foxtrot ", "total_lines": 5 }, "ms": 2 }, { "tool": "edit_text_file_contents", "label": "append-BROKEN", "args": { "patches": [ { "line_start": 6, "line_end": 5 } ] }, "result": { "result": "error", "reason": "End line must be greater than or equal to start line" }, "ms": 2 }, { "tool": "edit_text_file_contents", "label": "create-new-file", "args": { "files": [ { "path": "/tmp/mcp-new-created.txt", "file_hash": "", "patches": [ { "line_start": 1, "line_end": null, "contents": "Brand new file Created by MCP " } ] } ] }, "result": { "result": "ok" }, "ms": 3 }, { "tool": "edit_text_file_contents", "label": "wrong-hash-conflict", "args": { "files": [ { "path": "/tmp/mcp-test-file1.txt", "file_hash": "deadbeef_wrong_hash" } ] }, "result": { "result": "error", "reason": "Hash mismatch - file has been modified" }, "ms": 1 }, { "tool": "get_text_file_contents", "label": "read-unicode", "result": { "content": "Türkçe: şğüöçı Emoji: 🌍🚀 日本語テスト " }, "ms": 1 }, { "tool": "get_text_file_contents", "label": "read-empty", "result": { "content": "", "total_lines": 0, "file_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "ms": 2 }, { "tool": "edit_text_file_contents", "label": "insert-line", "result": { "result": "ok", "content_after": "Alpha BravoCharlie_MERGED INSERTED_LINE Delta Echo Foxtrot " }, "ms": 2 } ], "total_calls": 29, "success_rate": "100%", "p50_ms": 2, "max_ms": 3 }