Skip to main content
Glama
SDGLBL
by SDGLBL

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