Skip to main content
Glama
gabrielmaialva33

MCP Filesystem Server

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

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