Skip to main content
Glama

Replace Content

replace_content
Destructive

Replace text patterns in files using literal or regex matching to modify content efficiently in Serena's coding environment.

Instructions

Replaces one or more occurrences of a given pattern in a file with new content.

This is the preferred way to replace content in a file whenever the symbol-level tools are not appropriate.

VERY IMPORTANT: The "regex" mode allows very large sections of code to be replaced without fully quoting them! Use a regex of the form "beginning.*?end-of-text-to-be-replaced" to be faster and more economical! ALWAYS try to use wildcards to avoid specifying the exact content to be replaced, especially if it spans several lines. Note that you cannot make mistakes, because if the regex should match multiple occurrences while you disabled allow_multiple_occurrences, an error will be returned, and you can retry with a revised regex. Therefore, using regex mode with suitable wildcards is usually the best choice!.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
relative_pathYesThe relative path to the file.
needleYesThe string or regex pattern to search for. If `mode` is "literal", this string will be matched exactly. If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module, with flags DOTALL and MULTILINE enabled).
replYesThe replacement string (verbatim). If mode is "regex", the string can contain backreferences to matched groups in the needle regex, specified using the syntax $!1, $!2, etc. for groups 1, 2, etc.
modeYesEither "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
allow_multiple_occurrencesNoIf True, the regex may match multiple occurrences in the file and all of them will be replaced. If this is set to False and the regex matches multiple occurrences, an error will be returned (and you may retry with a revised, more specific regex).

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The ReplaceContentTool class provides the main handler (apply method) and core replacement logic (replace_content method) for the replace_content tool.
    class ReplaceContentTool(Tool, ToolMarkerCanEdit):
        """
        Replaces content in a file (optionally using regular expressions).
        """
    
        def apply(
            self,
            relative_path: str,
            needle: str,
            repl: str,
            mode: Literal["literal", "regex"],
            allow_multiple_occurrences: bool = False,
        ) -> str:
            r"""
            Replaces one or more occurrences of a given pattern in a file with new content.
    
            This is the preferred way to replace content in a file whenever the symbol-level
            tools are not appropriate.
    
            VERY IMPORTANT: The "regex" mode allows very large sections of code to be replaced without fully quoting them!
            Use a regex of the form "beginning.*?end-of-text-to-be-replaced" to be faster and more economical!
            ALWAYS try to use wildcards to avoid specifying the exact content to be replaced,
            especially if it spans several lines. Note that you cannot make mistakes, because if the regex should match
            multiple occurrences while you disabled `allow_multiple_occurrences`, an error will be returned, and you can retry
            with a revised regex.
            Therefore, using regex mode with suitable wildcards is usually the best choice!
    
            :param relative_path: the relative path to the file
            :param needle: the string or regex pattern to search for.
                If `mode` is "literal", this string will be matched exactly.
                If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module,
                with flags DOTALL and MULTILINE enabled).
            :param repl: the replacement string (verbatim).
                If mode is "regex", the string can contain backreferences to matched groups in the needle regex,
                specified using the syntax $!1, $!2, etc. for groups 1, 2, etc.
            :param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
            :param allow_multiple_occurrences: if True, the regex may match multiple occurrences in the file
                and all of them will be replaced.
                If this is set to False and the regex matches multiple occurrences, an error will be returned
                (and you may retry with a revised, more specific regex).
            """
            return self.replace_content(
                relative_path, needle, repl, mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, require_not_ignored=True
            )
    
        @staticmethod
        def _create_replacement_function(regex_pattern: str, repl_template: str, regex_flags: int) -> Callable[[re.Match], str]:
            """
            Creates a replacement function that validates for ambiguity and handles backreferences.
    
            :param regex_pattern: The regex pattern being used for matching
            :param repl_template: The replacement template with $!1, $!2, etc. for backreferences
            :param regex_flags: The flags to use when searching (e.g., re.DOTALL | re.MULTILINE)
            :return: A function suitable for use with re.sub() or re.subn()
            """
    
            def validate_and_replace(match: re.Match) -> str:
                matched_text = match.group(0)
    
                # For multi-line match, check if the same pattern matches again within the already-matched text,
                # rendering the match ambiguous. Typical pattern in the code:
                #    <start><other-stuff><start><stuff><end>
                # When matching
                #    <start>.*?<end>
                # this will match the entire span above, while only the suffix may have been intended.
                # (See test case for a practical example.)
                # To detect this, we check if the same pattern matches again within the matched text,
                if "\n" in matched_text and re.search(regex_pattern, matched_text[1:], flags=regex_flags):
                    raise ValueError(
                        "Match is ambiguous: the search pattern matches multiple overlapping occurrences. "
                        "Please revise the search pattern to be more specific to avoid ambiguity."
                    )
    
                # Handle backreferences: replace $!1, $!2, etc. with actual matched groups
                def expand_backreference(m: re.Match) -> str:
                    group_num = int(m.group(1))
                    group_value = match.group(group_num)
                    return group_value if group_value is not None else m.group(0)
    
                result = re.sub(r"\$!(\d+)", expand_backreference, repl_template)
                return result
    
            return validate_and_replace
    
        def replace_content(
            self,
            relative_path: str,
            needle: str,
            repl: str,
            mode: Literal["literal", "regex"],
            allow_multiple_occurrences: bool = False,
            require_not_ignored: bool = True,
        ) -> str:
            """
            Performs the replacement, with additional options not exposed in the tool.
            This function can be used internally by other tools.
            """
            self.project.validate_relative_path(relative_path, require_not_ignored=require_not_ignored)
            with EditedFileContext(relative_path, self.create_code_editor()) as context:
                original_content = context.get_original_content()
    
                if mode == "literal":
                    regex = re.escape(needle)
                elif mode == "regex":
                    regex = needle
                else:
                    raise ValueError(f"Invalid mode: '{mode}', expected 'literal' or 'regex'.")
    
                regex_flags = re.DOTALL | re.MULTILINE
    
                # create replacement function with validation and backreference handling
                repl_fn = self._create_replacement_function(regex, repl, regex_flags=regex_flags)
    
                # perform replacement
                updated_content, n = re.subn(regex, repl_fn, original_content, flags=regex_flags)
    
                if n == 0:
                    raise ValueError(f"Error: No matches of search expression found in file '{relative_path}'.")
                if not allow_multiple_occurrences and n > 1:
                    raise ValueError(
                        f"Expression matches {n} occurrences in file '{relative_path}'. "
                        "Please revise the expression to be more specific or enable allow_multiple_occurrences if this is expected."
                    )
                context.set_updated_content(updated_content)
    
            return SUCCESS_RESULT
  • The apply method signature and docstring define the input schema, parameters, and expected behavior for the tool.
    def apply(
        self,
        relative_path: str,
        needle: str,
        repl: str,
        mode: Literal["literal", "regex"],
        allow_multiple_occurrences: bool = False,
    ) -> str:
        r"""
        Replaces one or more occurrences of a given pattern in a file with new content.
    
        This is the preferred way to replace content in a file whenever the symbol-level
        tools are not appropriate.
    
        VERY IMPORTANT: The "regex" mode allows very large sections of code to be replaced without fully quoting them!
        Use a regex of the form "beginning.*?end-of-text-to-be-replaced" to be faster and more economical!
        ALWAYS try to use wildcards to avoid specifying the exact content to be replaced,
        especially if it spans several lines. Note that you cannot make mistakes, because if the regex should match
        multiple occurrences while you disabled `allow_multiple_occurrences`, an error will be returned, and you can retry
        with a revised regex.
        Therefore, using regex mode with suitable wildcards is usually the best choice!
    
        :param relative_path: the relative path to the file
        :param needle: the string or regex pattern to search for.
            If `mode` is "literal", this string will be matched exactly.
            If `mode` is "regex", this string will be treated as a regular expression (syntax of Python's `re` module,
            with flags DOTALL and MULTILINE enabled).
        :param repl: the replacement string (verbatim).
            If mode is "regex", the string can contain backreferences to matched groups in the needle regex,
            specified using the syntax $!1, $!2, etc. for groups 1, 2, etc.
        :param mode: either "literal" or "regex", specifying how the `needle` parameter is to be interpreted.
        :param allow_multiple_occurrences: if True, the regex may match multiple occurrences in the file
            and all of them will be replaced.
            If this is set to False and the regex matches multiple occurrences, an error will be returned
            (and you may retry with a revised, more specific regex).
        """
        return self.replace_content(
            relative_path, needle, repl, mode=mode, allow_multiple_occurrences=allow_multiple_occurrences, require_not_ignored=True
        )
  • Static helper method to create a replacement function for re.sub that handles backreferences ($!1 etc.) and validates for ambiguous multi-line matches.
    @staticmethod
    def _create_replacement_function(regex_pattern: str, repl_template: str, regex_flags: int) -> Callable[[re.Match], str]:
        """
        Creates a replacement function that validates for ambiguity and handles backreferences.
    
        :param regex_pattern: The regex pattern being used for matching
        :param repl_template: The replacement template with $!1, $!2, etc. for backreferences
        :param regex_flags: The flags to use when searching (e.g., re.DOTALL | re.MULTILINE)
        :return: A function suitable for use with re.sub() or re.subn()
        """
    
        def validate_and_replace(match: re.Match) -> str:
            matched_text = match.group(0)
    
            # For multi-line match, check if the same pattern matches again within the already-matched text,
            # rendering the match ambiguous. Typical pattern in the code:
            #    <start><other-stuff><start><stuff><end>
            # When matching
            #    <start>.*?<end>
            # this will match the entire span above, while only the suffix may have been intended.
            # (See test case for a practical example.)
            # To detect this, we check if the same pattern matches again within the matched text,
            if "\n" in matched_text and re.search(regex_pattern, matched_text[1:], flags=regex_flags):
                raise ValueError(
                    "Match is ambiguous: the search pattern matches multiple overlapping occurrences. "
                    "Please revise the search pattern to be more specific to avoid ambiguity."
                )
    
            # Handle backreferences: replace $!1, $!2, etc. with actual matched groups
            def expand_backreference(m: re.Match) -> str:
                group_num = int(m.group(1))
                group_value = match.group(group_num)
                return group_value if group_value is not None else m.group(0)
    
            result = re.sub(r"\$!(\d+)", expand_backreference, repl_template)
            return result
    
        return validate_and_replace
  • ToolRegistry singleton automatically discovers and registers all subclasses of Tool in the serena.tools package, including ReplaceContentTool.
    class ToolRegistry:
        def __init__(self) -> None:
            self._tool_dict: dict[str, RegisteredTool] = {}
            for cls in iter_subclasses(Tool):
                if not any(cls.__module__.startswith(pkg) for pkg in tool_packages):
                    continue
                is_optional = issubclass(cls, ToolMarkerOptional)
                name = cls.get_name_from_cls()
                if name in self._tool_dict:
                    raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.")
                self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name)
    
        def get_tool_class_by_name(self, tool_name: str) -> type[Tool]:
            return self._tool_dict[tool_name].tool_class
    
        def get_all_tool_classes(self) -> list[type[Tool]]:
            return list(t.tool_class for t in self._tool_dict.values())
    
        def get_tool_classes_default_enabled(self) -> list[type[Tool]]:
            """
            :return: the list of tool classes that are enabled by default (i.e. non-optional tools).
            """
            return [t.tool_class for t in self._tool_dict.values() if not t.is_optional]
    
        def get_tool_classes_optional(self) -> list[type[Tool]]:
            """
            :return: the list of tool classes that are optional (i.e. disabled by default).
            """
            return [t.tool_class for t in self._tool_dict.values() if t.is_optional]
    
        def get_tool_names_default_enabled(self) -> list[str]:
            """
            :return: the list of tool names that are enabled by default (i.e. non-optional tools).
            """
            return [t.tool_name for t in self._tool_dict.values() if not t.is_optional]
    
        def get_tool_names_optional(self) -> list[str]:
            """
            :return: the list of tool names that are optional (i.e. disabled by default).
            """
            return [t.tool_name for t in self._tool_dict.values() if t.is_optional]
    
        def get_tool_names(self) -> list[str]:
            """
            :return: the list of all tool names.
            """
            return list(self._tool_dict.keys())
    
        def print_tool_overview(
            self, tools: Iterable[type[Tool] | Tool] | None = None, include_optional: bool = False, only_optional: bool = False
        ) -> None:
            """
            Print a summary of the tools. If no tools are passed, a summary of the selection of tools (all, default or only optional) is printed.
            """
            if tools is None:
                if only_optional:
                    tools = self.get_tool_classes_optional()
                elif include_optional:
                    tools = self.get_all_tool_classes()
                else:
                    tools = self.get_tool_classes_default_enabled()
    
            tool_dict: dict[str, type[Tool] | Tool] = {}
            for tool_class in tools:
                tool_dict[tool_class.get_name_from_cls()] = tool_class
            for tool_name in sorted(tool_dict.keys()):
                tool_class = tool_dict[tool_name]
                print(f" * `{tool_name}`: {tool_class.get_tool_description().strip()}")
    
        def is_valid_tool_name(self, tool_name: str) -> bool:
            return tool_name in self._tool_dict
  • Legacy tool name mapping redirects deprecated 'replace_regex' to the replace_content tool name.
    LEGACY_TOOL_NAME_MAPPING = {"replace_regex": ReplaceContentTool.get_name_from_cls()}
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations indicate destructiveHint=true and readOnlyHint=false, which the description aligns with by describing a replacement operation. The description adds valuable behavioral context beyond annotations: it explains error handling when allow_multiple_occurrences is false, provides regex performance optimization tips, and mentions retry capabilities. However, it doesn't cover rate limits or specific permission requirements.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately front-loaded with the core purpose, but contains some redundancy in advocating for regex mode (mentioned multiple times with similar points). The 'VERY IMPORTANT' section could be more streamlined, though all sentences contribute meaningful guidance about tool usage strategies.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (destructive file operation with regex capabilities), the description provides comprehensive context: it explains when to use this versus alternatives, offers detailed regex usage strategies, describes error behavior, and references sibling tools. With annotations covering safety aspects and an output schema presumably handling return values, the description fills all necessary contextual gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the baseline is 3. The description adds meaningful context about parameter usage: it emphasizes regex mode advantages for large sections, explains wildcard strategies to avoid exact content specification, and clarifies the interaction between regex patterns and the allow_multiple_occurrences parameter. This provides practical guidance beyond the schema's technical specifications.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('replaces one or more occurrences of a given pattern in a file with new content') and distinguishes it from sibling tools by mentioning 'symbol-level tools' as alternatives. It explicitly names the resource (file content) and operation (replacement).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool ('preferred way to replace content... whenever the symbol-level tools are not appropriate') and offers detailed advice on regex mode usage versus literal mode. It also references sibling tools like 'replace_symbol_body' as alternatives for symbol-level operations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/oraios/serena'

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