Skip to main content
Glama

FileWriteOrEdit

Edit or write files by specifying the percentage of changes needed. Use search/replace blocks for minor edits or provide full content for >50% changes. Supports precise, multi-location edits in a single call.

Instructions

  • Writes or edits a file based on the percentage of changes.

  • Use absolute path only (~ allowed).

  • First write down percentage of lines that need to be replaced in the file (between 0-100) in percentage_to_change

  • percentage_to_change should be low if mostly new code is to be added. It should be high if a lot of things are to be replaced.

  • If percentage_to_change > 50, provide full file content in text_or_search_replace_blocks

  • If percentage_to_change <= 50, text_or_search_replace_blocks should be search/replace blocks.

Instructions for editing files.

Example

Input file

import numpy as np from impls import impl1, impl2 def hello(): "print a greeting" print("hello") def call_hello(): "call hello" hello() print("Called") impl1() hello() impl2()

Edit format on the input file

<<<<<<< SEARCH from impls import impl1, impl2 ======= from impls import impl1, impl2 from hello import hello as hello_renamed >>>>>>> REPLACE <<<<<<< SEARCH def hello(): "print a greeting" print("hello") ======= >>>>>>> REPLACE <<<<<<< SEARCH def call_hello(): "call hello" hello() ======= def call_hello_renamed(): "call hello renamed" hello_renamed() >>>>>>> REPLACE <<<<<<< SEARCH impl1() hello() impl2() ======= impl1() hello_renamed() impl2() >>>>>>> REPLACE

SEARCH/REPLACE block Rules:

  • Every "SEARCH" section must EXACTLY MATCH the existing file content, character for character, including all comments, docstrings, whitespaces, etc.

  • Use multiple search/replace blocks in a single FileWriteOrEdit tool call to edit in a single file in multiple places from top to bottom (separate calls are slower).

  • Including multiple unique SEARCH/REPLACE blocks if needed.

  • Include enough and only enough lines in each SEARCH section to uniquely match each set of lines that need to change.

  • Keep SEARCH/REPLACE blocks concise.

  • Break large SEARCH/REPLACE blocks into a series of smaller blocks that each change a small portion of the file.

  • Include just the changing lines, and a few surrounding lines (0-3 lines) if needed for uniqueness.

  • Other than for uniqueness, avoid including those lines which do not change in search (and replace) blocks. Target 0-3 non trivial extra lines per block.

  • Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathYes#1: absolute file path
percentage_to_changeYes#2: predict this percentage, calculated as number of existing lines that will have some diff divided by total existing lines.
text_or_search_replace_blocksYes#3: content/edit blocks. Must be after #2 in the tool xml
thread_idYes#4: thread_id

Implementation Reference

  • Primary handler function that implements FileWriteOrEdit logic: decides between full file write (if low changes or new file) or search-replace edit (if moderate changes), dispatching to `write_file` or `do_diff_edit`.
    def file_writing( file_writing_args: FileWriteOrEdit, coding_max_tokens: Optional[int], noncoding_max_tokens: Optional[int], context: Context, ) -> tuple[ str, dict[str, list[tuple[int, int]]] ]: # Updated to return message and file paths with line ranges """ Write or edit a file based on percentage of changes. If percentage_changed > 50%, treat content as direct file content. Otherwise, treat content as search/replace blocks. """ # Check if the thread_id matches current if file_writing_args.thread_id != context.bash_state.current_thread_id: # Try to load state from the thread_id if not context.bash_state.load_state_from_thread_id( file_writing_args.thread_id ): return ( f"Error: No saved bash state found for thread_id {file_writing_args.thread_id}. Please re-initialize to get a new id or use correct id.", {}, ) # Expand the path before checking if it's absolute path_ = expand_user(file_writing_args.file_path) if not os.path.isabs(path_): return ( f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}", {}, # Return empty dict instead of empty list for type consistency ) # If file doesn't exist, always use direct file_content mode content = file_writing_args.text_or_search_replace_blocks if not _is_edit(content, file_writing_args.percentage_to_change): # Use direct content mode (same as WriteIfEmpty) result, paths = write_file( WriteIfEmpty( file_path=path_, file_content=file_writing_args.text_or_search_replace_blocks, ), True, coding_max_tokens, noncoding_max_tokens, context, ) return result, paths else: # File exists and percentage <= 50, use search/replace mode result, paths = do_diff_edit( FileEdit( file_path=path_, file_edit_using_search_replace_blocks=file_writing_args.text_or_search_replace_blocks, ), coding_max_tokens, noncoding_max_tokens, context, ) return result, paths
  • Pydantic BaseModel defining the input schema for the FileWriteOrEdit tool, including fields for file path, predicted change percentage, content/edit blocks, and thread ID.
    class FileWriteOrEdit(BaseModel): # Naming should be in sorted order otherwise it gets changed in LLM backend. file_path: str = Field(description="#1: absolute file path") percentage_to_change: int = Field( description="#2: predict this percentage, calculated as number of existing lines that will have some diff divided by total existing lines." ) text_or_search_replace_blocks: str = Field( description="#3: content/edit blocks. Must be after #2 in the tool xml" ) thread_id: str = Field(description="#4: thread_id")
  • MCP Tool registration definition including schema (from FileWriteOrEdit.model_json_schema()), name, description with usage instructions, and annotations.
    Tool( inputSchema=remove_titles_from_schema(FileWriteOrEdit.model_json_schema()), name="FileWriteOrEdit", description=""" - Writes or edits a file based on the percentage of changes. - Use absolute path only (~ allowed). - First write down percentage of lines that need to be replaced in the file (between 0-100) in percentage_to_change - percentage_to_change should be low if mostly new code is to be added. It should be high if a lot of things are to be replaced. - If percentage_to_change > 50, provide full file content in text_or_search_replace_blocks - If percentage_to_change <= 50, text_or_search_replace_blocks should be search/replace blocks. """ + diffinstructions, annotations=ToolAnnotations( destructiveHint=True, idempotentHint=True, openWorldHint=False ), ),
  • MCP server endpoint that lists all tools, including FileWriteOrEdit via returning TOOL_PROMPTS.
    @server.list_tools() # type: ignore async def handle_list_tools() -> list[types.Tool]: """ List available tools. Each tool specifies its arguments using JSON Schema validation. """ return TOOL_PROMPTS
  • Helper function that parses search/replace blocks from the tool input string and applies edits using diff_edit logic, returning updated file content and comments.
    def search_replace_edit( lines: list[str], original_content: str, logger: Callable[[str], object] ) -> tuple[str, str]: if not lines: raise SearchReplaceSyntaxError("Error: No input to search replace edit") original_lines = original_content.split("\n") n_lines = len(lines) i = 0 search_replace_blocks = list[tuple[list[str], list[str]]]() while i < n_lines: if SEARCH_MARKER.match(lines[i]): line_num = i + 1 search_block = [] i += 1 while i < n_lines and not DIVIDER_MARKER.match(lines[i]): if SEARCH_MARKER.match(lines[i]) or REPLACE_MARKER.match(lines[i]): raise SearchReplaceSyntaxError( f"Line {i + 1}: Found stray marker in SEARCH block: {lines[i]}" ) search_block.append(lines[i]) i += 1 if i >= n_lines: raise SearchReplaceSyntaxError( f"Line {line_num}: Unclosed SEARCH block - missing ======= marker" ) if not search_block: raise SearchReplaceSyntaxError( f"Line {line_num}: SEARCH block cannot be empty" ) i += 1 replace_block = [] while i < n_lines and not REPLACE_MARKER.match(lines[i]): if SEARCH_MARKER.match(lines[i]) or DIVIDER_MARKER.match(lines[i]): raise SearchReplaceSyntaxError( f"Line {i + 1}: Found stray marker in REPLACE block: {lines[i]}" ) replace_block.append(lines[i]) i += 1 if i >= n_lines: raise SearchReplaceSyntaxError( f"Line {line_num}: Unclosed block - missing REPLACE marker" ) i += 1 for line in search_block: logger("> " + line) logger("=======") for line in replace_block: logger("< " + line) logger("\n\n\n\n") search_replace_blocks.append((search_block, replace_block)) else: if REPLACE_MARKER.match(lines[i]) or DIVIDER_MARKER.match(lines[i]): raise SearchReplaceSyntaxError( f"Line {i + 1}: Found stray marker outside block: {lines[i]}" ) i += 1 if not search_replace_blocks: raise SearchReplaceSyntaxError( "No valid search replace blocks found, ensure your SEARCH/REPLACE blocks are formatted correctly" ) edited_content, comments_ = edit_with_individual_fallback( original_lines, search_replace_blocks ) edited_file = "\n".join(edited_content) if not comments_: comments = "Edited successfully" else: comments = ( "Edited successfully. However, following warnings were generated while matching search blocks.\n" + "\n".join(comments_) ) return edited_file, comments

Other Tools

Related 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/rusiaaman/wcgw'

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