Source code for klea_utils.mcp.tool_impls.write_file
#!/usr/bin/env python3
"""
Whole-file write implementation for the Klea MCP tools.
See ``devdocs/system/edit-format-strategy.md``; ADR-0039.
File: klea_utils/mcp/tool_impls/write_file.py
Copyright 2026 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail dot com>
"""
import logging
from pathlib import Path
from typing import Any
from klea_utils.mcp.errors import FileEditError
from klea_utils.mcp.tool_impls import file_ops
logger = logging.getLogger(__name__)
def _result(
path: str,
*,
created: bool = False,
bytes_written: int = 0,
additions: int = 0,
deletions: int = 0,
diff: str = "",
error: str = "",
) -> dict[str, Any]:
"""Build the standard write result dict.
:param path: The path written (or attempted).
:param created: Whether the file did not exist before.
:param bytes_written: Final file size in bytes.
:param additions: Lines added relative to the previous content.
:param deletions: Lines removed relative to the previous content.
:param diff: Unified diff of the change (possibly truncated).
:param error: Empty on success; a message otherwise.
:returns: The result dict returned to the MCP wrapper.
"""
return {
"path": path,
"created": created,
"bytes_written": bytes_written,
"additions": additions,
"deletions": deletions,
"diff": diff,
"error": error,
}
[docs]
def write_file(
path: str,
content: str,
project_root: str | None = None,
) -> dict[str, Any]:
"""Create or overwrite a file with *content*.
Framework-agnostic implementation shared across Klea MCP servers. Apps
wrap this in an MCP tool (see ``klea_utils.mcp.server.bundled_tools``).
The write is atomic (a temporary file is replaced into place). An
existing file keeps its mode; a UTF-8 BOM is preserved when either the
old or the new content has one; the existing file's line ending is kept
and the new content normalised to it. Missing parent directories are
created. The result carries a unified diff against the previous content.
:param path: File path to write.
:param content: Complete file content.
:param project_root: Retained for callers; the boundary is enforced by the
client-side gate, not here (ADR-0007 update 2026-10-08).
:returns: dict with path, created, bytes_written, additions, deletions,
diff, error.
"""
logger.debug(f"Writing file\n{path = }\n{len(content) = }\n{project_root = }")
the_path = Path(path)
if the_path.is_dir():
logger.warning(f"Path is a directory: {path}")
return _result(path, error=f"Path is a directory: {path}")
content_text, content_bom = file_ops.split_bom(content)
existing = None
if the_path.exists():
try:
existing = file_ops.read_whole_text(the_path)
except FileEditError as exc:
logger.warning(f"Cannot overwrite {path}: {exc}")
return _result(str(the_path), error=str(exc))
if existing is not None:
bom = existing.bom or content_bom
newline = existing.newline
old_text = file_ops.normalize_newlines(existing.text)
created = False
else:
bom = content_bom
newline = file_ops.detect_newline(content_text)
old_text = ""
created = True
new_text = file_ops.normalize_newlines(content_text)
logger.debug(
f"Write plan\n"
f"{the_path = }\n"
f"{created = }\n"
f"{bom = }\n"
f"{newline = }\n"
f"{len(old_text) = }\n"
f"{len(new_text) = }"
)
diff, additions, deletions = file_ops.diff_payload(
old_text, new_text, str(the_path)
)
try:
file_ops.write_whole_text(
the_path, content_text, bom=bom, newline=newline, mode=None
)
except FileEditError as exc:
logger.warning(f"Could not write {path}: {exc}")
return _result(str(the_path), error=str(exc))
try:
bytes_written = the_path.stat().st_size
except OSError:
bytes_written = 0
logger.debug(f"Wrote {the_path}\n{created = }\n{bytes_written = }")
return _result(
str(the_path),
created=created,
bytes_written=bytes_written,
additions=additions,
deletions=deletions,
diff=diff,
)