Skip to main content
Glama

FileWriteOrEdit

Write or edit files by specifying the percentage of changes needed, using either full content or search/replace blocks based on the modification scope.

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.

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

  • Main handler function for the FileWriteOrEdit tool. Determines whether to perform a full file write or a targeted edit based on the predicted percentage_to_change, delegating to write_file or do_diff_edit accordingly.
    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 FileWriteOrEdit, including fields for file_path, percentage_to_change, text_or_search_replace_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") def model_post_init(self, __context: Any) -> None: self.thread_id = normalize_thread_id(self.thread_id) return super().model_post_init(__context)
  • MCP tool registration for FileWriteOrEdit, providing the JSON schema, name, detailed description, and annotations indicating it's destructive but idempotent.
    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 ), ),
  • Dispatch logic within get_tool_output that invokes the file_writing handler specifically for FileWriteOrEdit tool calls and handles the output and file tracking.
    elif isinstance(arg, FileWriteOrEdit): context.console.print("Calling file writing tool") result, write_edit_paths = file_writing( arg, coding_max_tokens, noncoding_max_tokens, context ) output = result, 0.0 # Add write/edit paths with their ranges to our tracking dictionary for path, ranges in write_edit_paths.items(): if path in file_paths_with_ranges: file_paths_with_ranges[path].extend(ranges) else: file_paths_with_ranges[path] = ranges.copy()
  • MCP server handler for all tool calls, including FileWriteOrEdit. Uses which_tool_name and parse_tool_by_name to instantiate the tool and delegates to get_tool_output.
    async def handle_call_tool( name: str, arguments: dict[str, Any] | None ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: global BASH_STATE if not arguments: raise ValueError("Missing arguments") tool_type = which_tool_name(name) tool_call = parse_tool_by_name(name, arguments) try: assert BASH_STATE output_or_dones, _ = get_tool_output( Context(BASH_STATE, BASH_STATE.console), tool_call, default_enc, 0.0, lambda x, y: ("", 0), 24000, # coding_max_tokens 8000, # noncoding_max_tokens ) except Exception as e: output_or_dones = [f"GOT EXCEPTION while calling tool. Error: {e}"] content: list[types.TextContent | types.ImageContent | types.EmbeddedResource] = [] for output_or_done in output_or_dones: if isinstance(output_or_done, str): if issubclass(tool_type, Initialize): # Prepare the original hardcoded message original_message = """ - Additional important note: as soon as you encounter "The user has chosen to disallow the tool call.", immediately stop doing everything and ask user for the reason. Initialize call done. """ # If custom instructions exist, prepend them to the original message if CUSTOM_INSTRUCTIONS: output_or_done += f"\n{CUSTOM_INSTRUCTIONS}\n{original_message}" else: output_or_done += original_message content.append(types.TextContent(type="text", text=output_or_done)) else: content.append( types.ImageContent( type="image", data=output_or_done.data, mimeType=output_or_done.media_type, ) ) return content

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