Skip to main content
Glama

edit_file_content

Modify text files by replacing specified content with new text, preserving indentation and generating a git-style diff. Use it to preview changes or apply edits directly to files in the workspace.

Instructions

Make line-based edits to a text file in the workspace filesystem. Each edit attempts to replace an exact match of 'oldText' with 'newText'. If no exact match is found, it attempts a line-by-line match ignoring leading/trailing whitespace. Indentation of the first line is preserved, and relative indentation of subsequent lines is attempted. Returns a git-style diff showing the changes made (or previewed if dryRun is true).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dryRunNoIf true, preview changes using git-style diff format without saving.
editsYesAn array of edit operations to apply sequentially.
pathYesThe path of the file to edit (relative to the workspace directory).

Implementation Reference

  • Core handler function that applies sequential edit operations to a file: reads content, performs replacements (exact or line-by-line whitespace-insensitive), preserves indentation, generates unified diff, writes if not dry-run, and formats output.
    async function applyFileEdits(
      filePath: string,
      edits: z.infer<typeof EditOperationSchema>[],
      dryRun = false
    ): Promise<string> {
      const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8'));
      let modifiedContent = content;
    
      for (const edit of edits) {
        const normalizedOld = normalizeLineEndings(edit.oldText);
        const normalizedNew = normalizeLineEndings(edit.newText);
    
        if (modifiedContent.includes(normalizedOld)) {
          modifiedContent = modifiedContent.replace(normalizedOld, normalizedNew);
          continue;
        }
    
        const oldLines = normalizedOld.split('\n');
        const contentLines = modifiedContent.split('\n');
        let matchFound = false;
    
        for (let i = 0; i <= contentLines.length - oldLines.length; i++) {
          const potentialMatch = contentLines.slice(i, i + oldLines.length);
          const isMatch = oldLines.every((oldLine, j) => oldLine.trim() === potentialMatch[j].trim());
    
          if (isMatch) {
            const originalIndent = contentLines[i].match(/^\s*/)?.[0] || '';
            const newLines = normalizedNew.split('\n').map((line, j) => {
              if (j === 0) return originalIndent + line.trimStart();
              const oldIndent = oldLines[j]?.match(/^\s*/)?.[0] || '';
              const newIndent = line.match(/^\s*/)?.[0] || '';
              if (oldIndent && newIndent) {
                const relativeIndent = newIndent.length - oldIndent.length;
                return originalIndent + ' '.repeat(Math.max(0, relativeIndent)) + line.trimStart();
              }
              return line;
            });
    
            contentLines.splice(i, oldLines.length, ...newLines);
            modifiedContent = contentLines.join('\n');
            matchFound = true;
            break;
          }
        }
    
        if (!matchFound) {
          throw new Error(`Could not find exact or whitespace-insensitive match for edit:\n${edit.oldText}`);
        }
      }
    
      const diff = createUnifiedDiff(content, modifiedContent, path.relative(process.cwd(), filePath));
    
      if (!dryRun) {
        await fs.writeFile(filePath, modifiedContent, 'utf-8');
      }
    
      let numBackticks = 3;
      while (diff.includes('`'.repeat(numBackticks))) {
        numBackticks++;
      }
      return `${'`'.repeat(numBackticks)}diff\n${diff}\n${'`'.repeat(numBackticks)}`;
    }
  • Tool dispatch handler in the main switch statement: validates arguments using EditFileArgsSchema, checks edits array, sanitizes path, and invokes applyFileEdits.
    case "edit_file_content": {
      const parsed = EditFileArgsSchema.parse(args);
      if (parsed.edits.length === 0) {
         throw new McpError(ErrorCode.InvalidParams, `'edits' array cannot be empty for ${toolName}.`);
      }
      const validPath = validateWorkspacePath(parsed.path);
      resultText = await applyFileEdits(validPath, parsed.edits, parsed.dryRun);
      break;
    }
  • Zod schemas for EditOperation (oldText, newText) and EditFileArgs (path, edits array, optional dryRun), converted to JSON schema for tool input validation.
    // Schema definitions (adapted from example.ts) - Exported
    export const EditOperationSchema = z.object({
      oldText: z.string().describe('Text to search for - attempts exact match first, then line-by-line whitespace-insensitive match.'),
      newText: z.string().describe('Text to replace with, preserving indentation where possible.')
    });
    
    export const EditFileArgsSchema = z.object({
      path: z.string().describe("The path of the file to edit (relative to the workspace directory)."),
      edits: z.array(EditOperationSchema).describe("An array of edit operations to apply sequentially."),
      dryRun: z.boolean().optional().default(false).describe('If true, preview changes using git-style diff format without saving.')
    });
    
    // Convert Zod schema to JSON schema
    const EditFileJsonSchema = zodToJsonSchema(EditFileArgsSchema);
  • Imports the editFileTool definition and registers it in the allTools array, which is used by the MCP server for tool listing and mapping.
    import { editFileTool } from "./edit_file.js";
    // import { createDirectoryTool } from "./create_directory.js"; // Removed
    import { listDirectoryTool } from "./list_directory.js";
    import { directoryTreeTool } from "./directory_tree.js";
    import { moveFileTool } from "./move_file.js";
    import { searchFilesTool } from "./search_files.js";
    import { getFileInfoTool } from "./get_file_info.js";
    import { executeTerminalCommandTool } from "./execute_terminal_command.js"; // Renamed file and tool variable
    // Import the new combined tools
    import { saveGenerateProjectGuidelinesTool } from "./save_generate_project_guidelines.js";
    import { saveDocSnippetTool } from "./save_doc_snippet.js";
    import { saveTopicExplanationTool } from "./save_topic_explanation.js";
    // Removed old save_query_answer, added new specific ones
    import { saveAnswerQueryDirectTool } from "./save_answer_query_direct.js";
    import { saveAnswerQueryWebsearchTool } from "./save_answer_query_websearch.js";
    
    // Import new research-oriented tools
    import { codeAnalysisWithDocsTool } from "./code_analysis_with_docs.js";
    import { technicalComparisonTool } from "./technical_comparison.js";
    import { architecturePatternRecommendationTool } from "./architecture_pattern_recommendation.js";
    import { dependencyVulnerabilityScanTool } from "./dependency_vulnerability_scan.js";
    import { databaseSchemaAnalyzerTool } from "./database_schema_analyzer.js";
    import { securityBestPracticesAdvisorTool } from "./security_best_practices_advisor.js";
    import { testingStrategyGeneratorTool } from "./testing_strategy_generator.js";
    import { regulatoryComplianceAdvisorTool } from "./regulatory_compliance_advisor.js";
    import { microserviceDesignAssistantTool } from "./microservice_design_assistant.js";
    import { documentationGeneratorTool } from "./documentation_generator.js";
    
    
    export const allTools: ToolDefinition[] = [
        // Query & Generation Tools
        answerQueryWebsearchTool,
        answerQueryDirectTool,
        explainTopicWithDocsTool,
        getDocSnippetsTool,
        generateProjectGuidelinesTool,
        // Filesystem Tools
        readFileTool, // Handles single and multiple files now
        // readMultipleFilesTool, // Merged into readFileTool
        writeFileTool,
        editFileTool,
  • ToolDefinition object with name, description, inputSchema, and buildPrompt for parameter validation (used in generic path, but overridden by filesystem handler).
    export const editFileTool: ToolDefinition = {
        name: "edit_file_content", // Renamed slightly
        description:
          "Make line-based edits to a text file in the workspace filesystem. Each edit attempts to replace " +
          "an exact match of 'oldText' with 'newText'. If no exact match is found, it attempts a " +
          "line-by-line match ignoring leading/trailing whitespace. Indentation of the first line " +
          "is preserved, and relative indentation of subsequent lines is attempted. " +
          "Returns a git-style diff showing the changes made (or previewed if dryRun is true).",
        inputSchema: EditFileJsonSchema as any, // Cast as any if needed
    
        // Minimal buildPrompt as execution logic is separate
        buildPrompt: (args: any, modelId: string) => {
            const parsed = EditFileArgsSchema.safeParse(args);
            if (!parsed.success) {
                throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for edit_file_content: ${parsed.error}`);
            }
            // Add a check for empty edits array
            if (parsed.data.edits.length === 0) {
                 throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for edit_file_content: 'edits' array cannot be empty.`);
            }
            return {
                systemInstructionText: "",
                userQueryText: "",
                useWebSearch: false,
                enableFunctionCalling: false
            };
        },
        // No 'execute' function here
    };
Behavior4/5

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

With no annotations provided, the description carries full burden and does well by disclosing key behavioral traits: the two-stage matching algorithm (exact then whitespace-insensitive), indentation preservation rules, and the return format (git-style diff). It also clarifies the dryRun behavior. Missing details include error handling for non-existent files or permission issues.

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

Conciseness5/5

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

The description is efficiently structured in three sentences: first states purpose, second explains the editing algorithm, third covers return behavior and dryRun. Every sentence adds essential information with zero redundant content, making it easy to parse.

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

Completeness4/5

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

For a mutation tool with no annotations and no output schema, the description provides substantial context: behavioral algorithm, return format, and dryRun preview. It adequately covers the core functionality. Minor gaps include lack of error scenarios or side effects, but overall it's sufficiently complete for an agent to use correctly.

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

Parameters3/5

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

Schema description coverage is 100%, providing clear documentation for all parameters. The description adds some semantic context about how 'oldText' and 'newText' are used in the matching algorithm and indentation preservation, but doesn't significantly enhance understanding beyond what the schema already explains. Baseline 3 is appropriate given high schema coverage.

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 specific action ('Make line-based edits to a text file'), identifies the resource ('workspace filesystem'), and distinguishes from siblings like 'write_file_content' (which presumably creates/overwrites entire files) and 'read_file_content' (which only reads). The phrase 'line-based edits' provides precise differentiation.

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

Usage Guidelines4/5

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

The description implies usage context through 'line-based edits' and 'text file', suggesting this is for modifying existing files rather than creating new ones. However, it doesn't explicitly state when to use this versus 'write_file_content' (for full file replacement) or other editing alternatives, nor does it mention prerequisites like file existence.

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

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/shariqriazz/vertex-ai-mcp-server'

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