Skip to main content
Glama

edit

Replace specific text in code files with exact string matching and validation of replacement counts to modify existing code precisely.

Instructions

Performs exact string replacements in files with strict occurrence count validation.

Usage:

  • When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.

  • ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathYesThe absolute path to the file to modify (must be absolute, not relative)
old_stringYesThe text to replace (must match the file contents exactly, including all whitespace and indentation)
new_stringYesThe edited text to replace the old_string
expected_replacementsNoThe expected number of replacements to perform. Defaults to 1 if not specified.

Implementation Reference

  • Core handler function for the 'edit' tool. Handles parameter extraction, validation, file operations, exact string replacement with occurrence checking, new file creation, unified diff generation, and error handling.
    async def call( self, ctx: MCPContext, **params: Unpack[EditToolParams], ) -> str: """Execute the tool with the given parameters. Args: ctx: MCP context **params: Tool parameters Returns: Tool result """ tool_ctx = self.create_tool_context(ctx) self.set_tool_context_info(tool_ctx) # Extract parameters file_path: FilePath = params["file_path"] old_string: OldString = params["old_string"] new_string: NewString = params["new_string"] expected_replacements = params.get("expected_replacements", 1) # Validate parameters path_validation = self.validate_path(file_path) if path_validation.is_error: await tool_ctx.error(path_validation.error_message) return f"Error: {path_validation.error_message}" # Only validate old_string for non-empty if we're not creating a new file # Empty old_string is valid when creating a new file file_exists = Path(file_path).exists() if file_exists and old_string.strip() == "": await tool_ctx.error( "Parameter 'old_string' cannot be empty for existing files" ) return "Error: Parameter 'old_string' cannot be empty for existing files" if ( expected_replacements is None or not isinstance(expected_replacements, (int, float)) or expected_replacements < 0 ): await tool_ctx.error( "Parameter 'expected_replacements' must be a non-negative number" ) return ( "Error: Parameter 'expected_replacements' must be a non-negative number" ) await tool_ctx.info(f"Editing file: {file_path}") # Check if file is allowed to be edited allowed, error_msg = await self.check_path_allowed(file_path, tool_ctx) if not allowed: return error_msg try: file_path_obj = Path(file_path) # If the file doesn't exist and old_string is empty, create a new file if not file_path_obj.exists() and old_string == "": # Check if parent directory is allowed parent_dir = str(file_path_obj.parent) if not self.is_path_allowed(parent_dir): await tool_ctx.error(f"Parent directory not allowed: {parent_dir}") return f"Error: Parent directory not allowed: {parent_dir}" # Create parent directories if they don't exist file_path_obj.parent.mkdir(parents=True, exist_ok=True) # Create the new file with the new_string content with open(file_path_obj, "w", encoding="utf-8") as f: f.write(new_string) await tool_ctx.info(f"Successfully created file: {file_path}") return ( f"Successfully created file: {file_path} ({len(new_string)} bytes)" ) # Check file exists for non-creation operations exists, error_msg = await self.check_path_exists(file_path, tool_ctx) if not exists: return error_msg # Check is a file is_file, error_msg = await self.check_is_file(file_path, tool_ctx) if not is_file: return error_msg # Read the file try: with open(file_path_obj, "r", encoding="utf-8") as f: original_content = f.read() # Apply edit if old_string in original_content: # Count occurrences of the old_string in the content occurrences = original_content.count(old_string) # Check if the number of occurrences matches expected_replacements if occurrences != expected_replacements: await tool_ctx.error( f"Found {occurrences} occurrences of the specified old_string, but expected {expected_replacements}" ) return f"Error: Found {occurrences} occurrences of the specified old_string, but expected {expected_replacements}. Change your old_string to uniquely identify the target text, or set expected_replacements={occurrences} to replace all occurrences." # Replace all occurrences since the count matches expectations modified_content = original_content.replace(old_string, new_string) else: # If we can't find the exact string, report an error await tool_ctx.error( "The specified old_string was not found in the file content" ) return "Error: The specified old_string was not found in the file content. Please check that it matches exactly, including all whitespace and indentation." # Generate diff original_lines = original_content.splitlines(keepends=True) modified_lines = modified_content.splitlines(keepends=True) diff_lines = list( unified_diff( original_lines, modified_lines, fromfile=f"{file_path} (original)", tofile=f"{file_path} (modified)", n=3, ) ) diff_text = "".join(diff_lines) # Determine the number of backticks needed num_backticks = 3 while f"```{num_backticks}" in diff_text: num_backticks += 1 # Format diff with appropriate number of backticks formatted_diff = ( f"```{num_backticks}diff\n{diff_text}```{num_backticks}\n" ) # Write the file if there are changes if diff_text: with open(file_path_obj, "w", encoding="utf-8") as f: f.write(modified_content) await tool_ctx.info( f"Successfully edited file: {file_path} ({expected_replacements} replacements applied)" ) return f"Successfully edited file: {file_path} ({expected_replacements} replacements applied)\n\n{formatted_diff}" else: return f"No changes made to file: {file_path}" except UnicodeDecodeError: await tool_ctx.error(f"Cannot edit binary file: {file_path}") return f"Error: Cannot edit binary file: {file_path}" except Exception as e: await tool_ctx.error(f"Error editing file: {str(e)}") return f"Error editing file: {str(e)}" @override
  • TypedDict defining the input parameters and their annotated types for the 'edit' tool schema (FilePath, OldString, NewString, ExpectedReplacements defined earlier with descriptions).
    class EditToolParams(TypedDict): """Parameters for the Edit tool. Attributes: file_path: The absolute path to the file to modify (must be absolute, not relative) old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation) new_string: The edited text to replace the old_string expected_replacements: The expected number of replacements to perform. Defaults to 1 if not specified. """ file_path: FilePath old_string: OldString new_string: NewString expected_replacements: ExpectedReplacements
  • Instantiation of the Edit tool instance within the list of filesystem tools.
    def get_filesystem_tools(permission_manager: PermissionManager) -> list[BaseTool]: """Create instances of all filesystem tools. Args: permission_manager: Permission manager for access control Returns: List of filesystem tool instances """ return [ ReadTool(permission_manager), Write(permission_manager), Edit(permission_manager), MultiEdit(permission_manager), DirectoryTreeTool(permission_manager), Grep(permission_manager), ContentReplaceTool(permission_manager), GrepAstTool(permission_manager), ]
  • Registration function for filesystem tools that invokes ToolRegistry.register_tools, which calls each tool's register method including Edit's.
    def register_filesystem_tools( mcp_server: FastMCP, permission_manager: PermissionManager, ) -> list[BaseTool]: """Register all filesystem tools with the MCP server. Args: mcp_server: The FastMCP server instance permission_manager: Permission manager for access control Returns: List of registered tools """ tools = get_filesystem_tools(permission_manager) ToolRegistry.register_tools(mcp_server, tools) return tools
  • Top-level call to register_filesystem_tools in the main tools registration function, ensuring the 'edit' tool is registered with the MCP server.
    # Register all filesystem tools filesystem_tools = register_filesystem_tools(mcp_server, permission_manager) for tool in filesystem_tools: all_tools[tool.name] = tool

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SDGLBL/mcp-claude-code'

If you have feedback or need assistance with the MCP directory API, please join our Discord server