Building an MCP Server for YAML Surgery: Why Diff Blocks Beat Full-File Rewrites
Hook
When an AI assistant edits your 500-line Home Assistant configuration, do you want it to rewrite the entire file or apply a 3-line diff? The wrong answer has corrupted more YAML files than you'd think.
Context
The Model Context Protocol (MCP) from Anthropic gives AI assistants controlled access to external systems through standardized tool interfaces. Early MCP filesystem implementations followed the obvious pattern: expose read_file and write_file tools that let Claude Desktop or other MCP clients read entire files, modify them in-context, and write them back. This works fine for small files, but it's a disaster waiting to happen for large configuration files.
The problem becomes acute with YAML-heavy systems like Home Assistant, where a single configuration.yaml might contain hundreds of lines across dozens of integration blocks. When you ask an AI to "add a new light entity," you don't want it to reconstruct the entire file from memory—you want it to insert exactly three lines in the right place. Traditional MCP filesystem servers lack this surgical precision, forcing developers to choose between risky full-file rewrites or abandoning AI assistance for complex configs. The max-rousseau/mcp-filesystem repository emerged from this gap: a specialized MCP server that treats YAML editing as a diff operation rather than a rewrite operation, with additional support for remote SMB filesystems commonly used in homelab scenarios.
Technical Insight
The architecture centers on a YAMLDiffEngine that accepts SEARCH/REPLACE blocks instead of complete file contents. When an AI assistant wants to modify a file, it provides the exact lines to search for and their replacements, similar to how you'd describe a manual edit to another developer. The engine validates that the search block exists exactly once, applies the replacement, and confirms the result is valid YAML before committing.
Here's the diff block format in action:
# Example tool call from Claude Desktop
{
"tool": "edit_yaml_diff",
"arguments": {
"path": "configuration.yaml",
"diff": """<<<<<<< SEARCH
light:
- platform: hue
host: 192.168.1.100
=======
light:
- platform: hue
host: 192.168.1.100
- platform: mqtt
name: "Office LED Strip"
command_topic: "office/led/set"
>>>>>>> REPLACE"""
}
}
The YAMLDiffEngine parses this block, locates the exact match in the file, performs the replacement, and validates the resulting YAML structure. If the search text appears zero times or multiple times, the operation fails with a detailed error message, preventing silent corruption. This is vastly superior to asking the AI to regenerate the entire file, where hallucinations or context window limitations could drop existing sections.
The filesystem abstraction layer is equally thoughtful. Rather than hardcoding file operations, the codebase defines a FileSystemBackend abstract base class with two concrete implementations: LocalFileSystem and SMBFileSystem. This matters because many homelab users store Home Assistant configs on a NAS, and traditional approaches require mounting SMB shares at the OS level with root privileges. The SMBFileSystem implementation uses the smbprotocol library to access remote shares directly:
class SMBFileSystem(FileSystemBackend):
def __init__(self, server, share, username, password):
self.server = server
self.share = share
self.username = username
self.password = password
def read_file(self, path):
with smbclient.open_file(
f"\\\\{self.server}\\{self.share}\\{path}",
mode="r",
username=self.username,
password=self.password
) as f:
return f.read()
This design eliminates the need for mount points or FUSE filesystems—the MCP server handles SMB authentication and file operations transparently. For developers running Claude Desktop on a workstation while configs live on a Synology NAS, this removes significant deployment friction.
The transport layer flexibility is the final architectural piece worth examining. The server supports both stdio (for local MCP client integration) and HTTP with optional Google OAuth. When running via pipx, it communicates with Claude Desktop over stdin/stdout using MCP's JSON-RPC protocol. The Docker deployment exposes an HTTP endpoint, enabling remote access with authentication. This dual-mode design handles both "Claude on my laptop editing local configs" and "Claude in a browser editing NAS configs" scenarios without separate codebases.
Security boundaries rely on path traversal protection and extension whitelisting. The server rejects any path containing ".." sequences and enforces that all operations target .yaml or .yml files. This prevents an AI from accidentally (or intentionally) accessing /etc/passwd or modifying Python source files. For SMB scenarios, security additionally depends on share-level permissions—the server doesn't perform client-side symlink resolution or additional permission checks beyond what the SMB share enforces.
Gotcha
The HTTP mode lacks production-grade hardening. There's no built-in rate limiting, no connection pooling limits, no request size caps. The README explicitly states that production deployments require an external reverse proxy like nginx or Caddy to handle these concerns. If you're exposing this to the internet (even with OAuth), you're responsible for DDoS protection, TLS termination, and request filtering. The codebase assumes a trusted network or proper gateway configuration.
The YAML-only constraint is both a strength and a limitation. The extension whitelist prevents operating on JSON, TOML, or INI files that might live alongside your YAML configs. If your Home Assistant setup includes custom Python scripts or shell scripts in the config directory, this MCP server can't touch them. You'll need a separate, more general-purpose filesystem MCP server for non-YAML operations, which means juggling multiple tool configurations in your MCP client. The diff-based editing approach also assumes well-structured YAML—if your config files mix YAML with Jinja2 templates or other embedded DSLs, the search/replace logic might struggle with exact matching.
Verdict
Use if: You're managing large YAML configuration files (especially Home Assistant) where AI-assisted edits need surgical precision without full-file rewrites, you need SMB share access without OS-level mounts, or you want both local and remote MCP deployment options with the same codebase. The diff block approach is genuinely superior for targeted config changes in complex files. Skip if: You need general-purpose filesystem access beyond YAML, require production-ready HTTP security features without external dependencies, work with mixed configuration formats in the same workflow, or just have simple single-file YAML needs where a generic MCP filesystem server would suffice. For pure Home Assistant use cases with the instance running, direct API integration might be more robust than direct config file manipulation.