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
    };

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