This document provides guidance for AI coding agents working on this codebase.
MCP Text Editor Server is a Python MCP (Model Context Protocol) server providing line-oriented text file editing capabilities. It's optimized for LLM tools with efficient partial file access to minimize token usage.
Package manager: uv with hatchling as build backend.
# Install dependencies
make install # or: uv sync --all-extras
# Run all tests
make test # or: uv run pytest
# Run a single test file
uv run pytest tests/test_text_editor.py -v
# Run a single test function
uv run pytest tests/test_text_editor.py::test_calculate_hash -v
# Run tests matching a pattern
uv run pytest -k "test_edit" -v
# Run tests with coverage
make coverage
# Format code
make format
# Lint + typecheck
make check
# Format + typecheck + coverage (default)
make allsrc/mcp_text_editor/
├── server.py # FastMCP server implementation
├── text_editor.py # Core text editor functionality
├── service.py # Core service logic
├── models.py # Pydantic data models
├── utils.py # Security utilities (path validation, file locking)
└── handlers/ # MCP tool handlers (inherit from base.py)
tests/ # Test files (pytest)
- Line length: 88 characters (Black)
- Formatter: Black (Python 3.13 target)
- Import sorting: isort with "black" profile
- Linter: Ruff (pycodestyle, pyflakes, isort, flake8-comprehensions, flake8-bugbear)
# 1. Standard library
from typing import Any, Dict, List, Optional, Sequence, Tuple
# 2. Third-party
from mcp.types import TextContent
from pydantic import BaseModel, Field, model_validator
# 3. Local (relative imports within package)
from ..text_editor import TextEditor
from .base import BaseHandlerUse comprehensive type annotations:
Optional[T]orX | Nonefor nullable typesSequencefor read-only collections,Listfor mutable
async def read_file_contents(
self, file_path: str, start: int = 1, end: Optional[int] = None,
) -> Tuple[str, int, int, str, int, int]:def method(self, arg: str) -> Dict[str, Any]:
"""Short description.
Args:
arg: Description of argument
Returns:
Description of return value
"""- Classes: PascalCase (
TextEditor,EditPatch) - Functions/Methods/Variables: snake_case (
read_file_contents,file_path) - Constants: UPPER_SNAKE_CASE
- Test functions:
test_<description>
class EditPatch(BaseModel):
"""Model for a single edit patch operation."""
start: int = Field(1, description="Starting line for edit")
end: Optional[int] = Field(None, description="Ending line for edit")
@model_validator(mode="after")
def validate_range_hash(self) -> "EditPatch":
...
return selfUse standardized error response patterns:
return {
"result": "error",
"reason": error_message,
"suggestion": suggestion,
"hint": hint,
}class MyHandler(BaseHandler):
def __init__(self, editor: TextEditor | None = None):
super().__init__(editor)
async def run_tool(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
"""Execute the tool with given arguments."""import pytest
from mcp_text_editor.text_editor import TextEditor
@pytest.fixture
def editor():
return TextEditor()
@pytest.mark.asyncio
async def test_something(editor, tmp_path):
test_file = tmp_path / "test.txt"
test_file.write_text("content\n")
result = await editor.some_method(str(test_file))
assert result["result"] == "ok"- Use
tmp_pathfixture for temporary files - Use
mockerfrom pytest-mock for mocking
- Use path validation utilities from
utils.py - Use
locked_file()context manager for file operations - Use
secure_compare_hash()for hash comparisons - Never expose sensitive information in error messages
Run make all before pushing. Pre-commit hooks run make check test and validate ASCII-only commit messages.