Skip to main content
Glama

Filesystem MCP Server

by QuantGeekDev
EditFileTool.ts4.24 kB
import { MCPTool } from "mcp-framework"; import { z } from "zod"; import fs from "fs/promises"; import { createTwoFilesPatch } from 'diff'; interface EditOperation { oldText: string; newText: string; } interface EditFileInput { path: string; edits: EditOperation[]; dryRun: boolean; } class EditFileTool extends MCPTool<EditFileInput> { 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."; schema = { path: { type: z.string(), description: "Path to the file to edit", }, edits: { type: z.array(z.object({ oldText: z.string().describe('Text to search for - must match exactly'), newText: z.string().describe('Text to replace with') })), description: "Array of edit operations to perform", }, dryRun: { type: z.boolean(), description: "Preview changes using git-style diff format", default: false, }, }; private normalizeLineEndings(text: string): string { return text.replace(/\r\n/g, '\n'); } private createUnifiedDiff(originalContent: string, newContent: string, filepath: string = 'file'): string { const normalizedOriginal = this.normalizeLineEndings(originalContent); const normalizedNew = this.normalizeLineEndings(newContent); return createTwoFilesPatch( filepath, filepath, normalizedOriginal, normalizedNew, 'original', 'modified' ); } async execute(input: EditFileInput) { try { const content = this.normalizeLineEndings(await fs.readFile(input.path, 'utf-8')); let modifiedContent = content; for (const edit of input.edits) { const normalizedOld = this.normalizeLineEndings(edit.oldText); const normalizedNew = this.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) => { const contentLine = potentialMatch[j]; return oldLine.trim() === contentLine.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 match for edit:\n${edit.oldText}`); } } const diff = this.createUnifiedDiff(content, modifiedContent, input.path); let numBackticks = 3; while (diff.includes('`'.repeat(numBackticks))) { numBackticks++; } const formattedDiff = `${'`'.repeat(numBackticks)}diff\n${diff}${'`'.repeat(numBackticks)}\n\n`; if (!input.dryRun) { await fs.writeFile(input.path, modifiedContent, 'utf-8'); } return formattedDiff; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to edit file: ${errorMessage}`); } } } export default EditFileTool;

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

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