Skip to main content
Glama

edit_file

Modify text files by replacing exact line sequences with new content, returning a git-style diff. Works within predefined directories for secure, controlled file edits.

Instructions

Make line-based edits to a text file. Each edit replaces exact line sequences with new content. Returns a git-style diff showing the changes made. Only works within allowed directories.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dryRunNoPreview changes using git-style diff format
editsYesList of edit operations to perform
pathYesPath to the file to edit

Implementation Reference

  • Main handler function that performs the file editing logic: reads file, applies sequential text replacements preserving indentation, generates unified diff, and writes changes unless dryRun.
    export async function editFile( args: z.infer<typeof EditFileArgsSchema>, config: Config ): Promise<string> { const endMetric = metrics.startOperation('edit_file') try { const validPath = await validatePath(args.path, config) // Read the original content const content = await fs.readFile(validPath, 'utf-8') let modifiedContent = content // Track whether any edit was applied let appliedAnyEdit = false // Apply each edit for (const edit of args.edits) { const contentLines = modifiedContent.split('\n') let matchFound = false // Normalize line endings const normalizedOld = edit.oldText.replace(/\r\n/g, '\n') const normalizedNew = edit.newText.replace(/\r\n/g, '\n') const oldLines = normalizedOld.split('\n') // Validate edit if (oldLines.length === 0) { throw new InvalidArgumentsError('edit_file', 'Edit operation contains empty oldText') } // Find and replace the text for (let i = 0; i <= contentLines.length - oldLines.length; i++) { const potentialMatch = contentLines.slice(i, i + oldLines.length).join('\n') if (potentialMatch === normalizedOld) { // Preserve indentation 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 appliedAnyEdit = true break } } if (!matchFound) { throw new Error(`Could not find exact match for edit:\n${edit.oldText}`) } } // If no edits were applied, return early if (!appliedAnyEdit) { return 'No changes made - all edit patterns were empty or not found' } // Generate diff const diff = createUnifiedDiff(content, modifiedContent, validPath) // Write file if not a dry run if (!args.dryRun) { await fs.writeFile(validPath, modifiedContent, 'utf-8') await logger.debug(`Successfully edited file: ${validPath}`) } endMetric() return diff } catch (error) { metrics.recordError('edit_file') throw error }
  • Zod schemas defining EditOperation (oldText/newText) and EditFileArgsSchema (path, edits array, dryRun option) for input validation.
    export const EditOperation = z.object({ oldText: z.string().describe('Text to search for - must match exactly'), newText: z.string().describe('Text to replace with'), }) /** * Schema for edit_file arguments */ export const EditFileArgsSchema = z.object({ path: z.string().describe('Path to the file to edit'), edits: z.array(EditOperation).describe('List of edit operations to perform'), dryRun: z.boolean().default(false).describe('Preview changes using git-style diff format'), })
  • src/index.ts:263-269 (registration)
    Tool registration in list_tools response: defines name, description, and converts EditFileArgsSchema to JSON schema for MCP protocol.
    name: 'edit_file', description: 'Make line-based edits to a text file. Each edit replaces exact line sequences ' + 'with new content. Returns a git-style diff showing the changes made. ' + 'Only works within allowed directories.', inputSchema: zodToJsonSchema(EditFileArgsSchema) as ToolInput, },
  • MCP call_tool dispatcher case: parses arguments with schema, calls editFile handler, returns result as text content.
    case 'edit_file': { const parsed = EditFileArgsSchema.safeParse(a) if (!parsed.success) { throw new FileSystemError(`Invalid arguments for ${name}`, 'INVALID_ARGS', undefined, { errors: parsed.error.format(), }) } const result = await editFile(parsed.data, config) endMetric() return { content: [{ type: 'text', text: result }], } }
  • Helper function to generate git-style unified diff between original and modified content, wrapped in markdown code block.
    function createUnifiedDiff( originalContent: string, modifiedContent: string, filePath: string ): string { const diff = createTwoFilesPatch( filePath, filePath, originalContent, modifiedContent, 'Original', 'Modified' ) // Find enough backticks to safely wrap the diff let numBackticks = 3 while (diff.includes('`'.repeat(numBackticks))) { numBackticks++ } return `${'`'.repeat(numBackticks)}diff\n${diff}${'`'.repeat(numBackticks)}\n\n` }

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/gabrielmaialva33/mcp-filesystem'

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