edit_file
Modify text files line-by-line with defined edits using the MCP Filesystem Server. Specify path, text changes, and encoding, and optionally preview changes with dry-run mode. Returns a Git-style diff of modifications.
Instructions
Make line-based edits to a text file.
Args:
path: Path to the file
edits: List of {oldText, newText} dictionaries
encoding: Text encoding (default: utf-8)
dry_run: If True, return diff but don't modify file
ctx: MCP context
Returns:
Git-style diff showing changes
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dry_run | No | ||
| edits | Yes | ||
| encoding | No | utf-8 | |
| path | Yes |
Implementation Reference
- mcp_filesystem/server.py:298-322 (handler)MCP tool handler for 'edit_file': decorated with @mcp.tool(), validates input via docstring/args, delegates execution to FileOperations.edit_file and handles errors.@mcp.tool() async def edit_file( path: str, edits: List[Dict[str, str]], ctx: Context, encoding: str = "utf-8", dry_run: bool = False, ) -> str: """Make line-based edits to a text file. Args: path: Path to the file edits: List of {oldText, newText} dictionaries encoding: Text encoding (default: utf-8) dry_run: If True, return diff but don't modify file ctx: MCP context Returns: Git-style diff showing changes """ try: components = get_components() return await components["operations"].edit_file(path, edits, encoding, dry_run) except Exception as e: return f"Error editing file: {str(e)}"
- mcp_filesystem/operations.py:494-573 (helper)Core implementation of edit_file in FileOperations class: reads file, applies sequential text replacements based on oldText/newText, generates git-style diff, writes changes if not dry_run, with path validation and error handling.async def edit_file( self, path: Union[str, Path], edits: List[Dict[str, str]], encoding: str = "utf-8", dry_run: bool = False, ) -> str: """Edit a text file by replacing line sequences. Args: path: Path to the file edits: List of {oldText, newText} dictionaries encoding: Text encoding (default: utf-8) dry_run: If True, return diff but don't modify file Returns: Git-style diff showing changes Raises: ValueError: If path is outside allowed directories FileNotFoundError: If file does not exist """ abs_path, allowed = await self.validator.validate_path(path) if not allowed: raise ValueError(f"Path outside allowed directories: {path}") try: # Read the file content current_content = await anyio.to_thread.run_sync( abs_path.read_text, encoding ) new_content = current_content # Create a diff-style output diff_lines = [f"--- {path}", f"+++ {path}"] # Apply each edit for edit in edits: if "oldText" not in edit or "newText" not in edit: continue old_text = edit["oldText"] new_text = edit["newText"] if old_text in new_content: # Add to diff context = new_content.split(old_text, 1) line_number = context[0].count("\n") + 1 old_lines = old_text.count("\n") + 1 new_lines = new_text.count("\n") + 1 diff_lines.append( f"@@ -{line_number},{old_lines} +{line_number},{new_lines} @@" ) for line in old_text.splitlines(): diff_lines.append(f"-{line}") for line in new_text.splitlines(): diff_lines.append(f"+{line}") # Replace the text new_content = new_content.replace(old_text, new_text, 1) # If this is not a dry run, write the changes if not dry_run and new_content != current_content: await anyio.to_thread.run_sync( abs_path.write_text, new_content, encoding ) if new_content == current_content: return "No changes made" return "\n".join(diff_lines) except FileNotFoundError: raise FileNotFoundError(f"File not found: {path}") except PermissionError: raise ValueError(f"Permission denied: {path}") except UnicodeDecodeError: raise ValueError(f"Cannot decode file as {encoding}: {path}")