file_utils MCP
# file_utils MCP
An anchor-based file reading and editing MCP server for large text files where
the built-in `read_file` tool fails ("readline was closed") or where encoding
issues (e.g. mojibake em-dashes) cause string-matching tools to miss their
targets.
The headline insight: **content-addressed anchors survive line shifts; line
numbers don't.** When a file is edited mid-session (by you in the IDE, by a
formatter, by another tool), line numbers go stale silently. Anchors travel with
the content.
The full design lives in [`SPEC.md`](SPEC.md); the implementation plan lives in
[`TODO.md`](TODO.md).
---
## Tools
Five stdio MCP tools are exposed (see [`SPEC.md`](SPEC.md) for full parameter
tables, responses, and error codes):
| Tool | Purpose |
|------|---------|
| `read_file_range` | Read a contiguous span, addressed by anchors (primary) or line numbers (fallback). Returns a `content_hash` for staleness-guarded edits. |
| `edit_file_range` | Replace a contiguous span with new content, with an optional `expected_content_hash` staleness guard. |
| `insert_at` | Insert content before/after a single target line without replacing it. |
| `prepend_to_file` | Concatenate content at the start of a file (optional `ensure_newline_after`). |
| `append_to_file` | Concatenate content at the end of a file (optional `ensure_newline_before`). |
Anchor matching is **substring-based and case-sensitive** with a
progressive-disclosure ambiguity guard (`occurrence` / `total`). Line numbers are
1-based and support negative indexing (`-1` = last line). Edits are written via
an atomic temp-file → `fsync` → rename. The span `content_hash` is a SHA-256 of
the raw on-disk bytes, so it is encoding-independent.
---
## Requirements
- Python **3.10+**
- [`uv`](https://docs.astral.sh/uv/) for environment and dependency management
- The `mcp` Python SDK (declared in [`pyproject.toml`](pyproject.toml); installed
automatically by `uv`)
This project is managed with **uv**. [`pyproject.toml`](pyproject.toml) is the
single source of truth for dependencies — there is no `requirements.txt`. Do not
call `python`, `pip`, or `pytest` directly; go through `uv` so the correct
environment is used.
### Installing uv
If `uv` is not already installed, use the official Astral installer (PowerShell):
```powershell
powershell -ExecutionPolicy ByPass -Command "irm https://astral.sh/uv/install.ps1 | iex"
```
`uv` installs to `C:\Users\<you>\.local\bin` and is added to PATH by the
installer. A newly opened terminal will have `uv` / `uvx` on PATH; an
already-open terminal may need to be reopened.
---
## Setup & usage
### Sync the environment
```cmd
uv sync --extra dev
```
### Run the server
The server speaks the MCP JSON-RPC protocol over stdio. Run it directly:
```cmd
uv run server.py
```
### Run from a Git repo with uvx
The project is installable as a tool, so `uvx` can clone the repo into its
cache, build the wheel, install it plus dependencies into an isolated
environment, and run the `file-utils` entry point — exactly like `npx`.
From a published GitHub repo:
```cmd
uvx --from git+https://github.com/JEL-LL/file_utils.git file-utils
```
Only **committed** content is used (it clones from the repo), so commit and
push before a new version becomes available. Use `--refresh` to pick up new
commits.
### Install into a local venv
```cmd
uv pip install "git+https://github.com/JEL-LL/file_utils.git"
```
### Run the tests
```cmd
uv run --extra dev pytest
```
Run quietly with `uv run --extra dev pytest -q`.
---
## VS Code / Kilo Code configuration
Recommended: launch globally via `uvx` straight from the Git repo so any project
gets the tool without a local checkout. Add this to your global
`mcp_settings.json`:
```json
{
"mcpServers": {
"file_utils": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/JEL-LL/file_utils.git",
"file-utils"
],
"alwaysAllow": []
}
}
}
```
Alternatively, to run from a local working copy through `uv` (so dependencies
resolve from [`pyproject.toml`](pyproject.toml)):
```json
{
"mcpServers": {
"file_utils": {
"command": "uv",
"args": ["run", "--directory", "/path/to/file_utils", "server.py"],
"env": {}
}
}
}
```
Each spawned process is unique to one agent/session. In-memory state (such as a
remembered `project_root`) is per-process and safe across calls within a session.
---
## Path resolution
Every tool accepts an optional `project_root` parameter. Once supplied, it is
remembered in memory for the rest of the session.
| Path type | `project_root` state | Result |
|-----------|----------------------|--------|
| Absolute | Any | Used as-is; `project_root` is ignored |
| Relative | Set | Resolved against the remembered `project_root` |
| Relative | Not set | `INVALID_PARAMETERS` — supply an absolute path or a `project_root` |
---
## Anchor selection tips
Good anchors are Markdown section headers (`## Phase 10`), unique prose phrases,
and pure ASCII. Avoid lines containing em-dashes or curly quotes when the file
has encoding issues, very short strings that appear many times, and line numbers
(use `from_line` / `to_line` only as a fallback). See
[`SPEC.md`](SPEC.md#anchor-selection-discipline-for-ai-callers) for the full
discipline.
---
## License
Licensed under the [MIT License](LICENSE). Copyright (c) 2026 LaserLinc Inc.
Authored by Joshua Lansford. Released as open source with the permission of
LaserLinc Inc.
TDQS
Scored across 6 tools
Each tool has a distinct action and target: stripping conflict markers, reading a range, editing a range, inserting at a line, prepending, and appending. No two tools overlap in purpose; even the similar insert/prepend/append are clearly differentiated by operation type and location.
All tool names are snake_case and begin with a verb, but the pattern is inconsistent: some use verb_noun (read_file_range, edit_file_range, strip_conflict_markers) while others use verb_preposition (insert_at, prepend_to_file, append_to_file). This is a minor deviation that could be improved by renaming insert_at to insert_at_line for consistency.
Six tools is a well-scoped set for a file utilities server, covering read, edit, insert, prepend, append, and conflict marker cleanup. It is neither too sparse nor overwhelming, and each tool serves a distinct purpose.
The set covers the core file modification lifecycle: read a range, replace a range, insert line(s), prepend, append, and clean conflict markers. The main gap is the lack of an explicit delete operation, though edit_file_range with empty content can serve that purpose. Minor but workable.