Skip to main content
Glama

Edit-MCP

file-system-manager.ts9.93 kB
import * as fs from 'fs'; import * as path from 'path'; import * as util from 'util'; import { EventEmitter } from 'events'; // Promisify fs functions const readFile = util.promisify(fs.readFile); const writeFile = util.promisify(fs.writeFile); const appendFile = util.promisify(fs.appendFile); const stat = util.promisify(fs.stat); const unlink = util.promisify(fs.unlink); const mkdir = util.promisify(fs.mkdir); const readdir = util.promisify(fs.readdir); export interface FileStats { size: number; isDirectory: boolean; isFile: boolean; createdAt: Date; modifiedAt: Date; accessedAt: Date; } export interface SearchResult { line: number; column: number; text: string; linesBefore: string[]; linesAfter: string[]; } export interface FileChangeEvent { type: 'create' | 'update' | 'delete'; path: string; } export interface FileWatcher { path: string; close(): void; } export class FileSystemManager extends EventEmitter { private watchers: Map<string, fs.FSWatcher> = new Map(); private backups: Map<string, string> = new Map(); constructor() { super(); } /** * Reads a file and returns its content */ public async readFile(filePath: string, encoding: BufferEncoding = 'utf8'): Promise<string> { try { return await readFile(filePath, { encoding }); } catch (error: any) { throw new Error(`Failed to read file ${filePath}: ${error.message}`); } } /** * Writes content to a file, creating it if it doesn't exist */ public async writeFile(filePath: string, content: string, encoding: BufferEncoding = 'utf8'): Promise<void> { try { // Ensure the directory exists await this.ensureDirectoryExists(path.dirname(filePath)); await writeFile(filePath, content, { encoding }); this.emitChangeEvent('update', filePath); } catch (error: any) { throw new Error(`Failed to write file ${filePath}: ${error.message}`); } } /** * Appends content to a file */ public async appendFile(filePath: string, content: string, encoding: BufferEncoding = 'utf8'): Promise<void> { try { // Ensure the directory exists await this.ensureDirectoryExists(path.dirname(filePath)); await appendFile(filePath, content, { encoding }); this.emitChangeEvent('update', filePath); } catch (error: any) { throw new Error(`Failed to append to file ${filePath}: ${error.message}`); } } /** * Deletes a file */ public async deleteFile(filePath: string): Promise<void> { try { await unlink(filePath); this.emitChangeEvent('delete', filePath); } catch (error: any) { throw new Error(`Failed to delete file ${filePath}: ${error.message}`); } } /** * Gets file statistics */ public async getFileStats(filePath: string): Promise<FileStats> { try { const stats = await stat(filePath); return { size: stats.size, isDirectory: stats.isDirectory(), isFile: stats.isFile(), createdAt: stats.birthtime, modifiedAt: stats.mtime, accessedAt: stats.atime }; } catch (error: any) { throw new Error(`Failed to get file stats for ${filePath}: ${error.message}`); } } /** * Finds occurrences of a pattern in a file */ public async findInFile(filePath: string, pattern: RegExp, contextLines: number = 2): Promise<SearchResult[]> { try { const content = await this.readFile(filePath); const lines = content.split('\n'); const results: SearchResult[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const match = pattern.exec(line); if (match) { const linesBefore = lines.slice(Math.max(0, i - contextLines), i); const linesAfter = lines.slice(i + 1, Math.min(lines.length, i + contextLines + 1)); results.push({ line: i + 1, column: match.index + 1, text: line, linesBefore, linesAfter }); } } return results; } catch (error: any) { throw new Error(`Failed to search in file ${filePath}: ${error.message}`); } } /** * Replaces occurrences of a pattern in a file */ public async replaceInFile(filePath: string, pattern: RegExp, replacement: string): Promise<number> { try { const content = await this.readFile(filePath); const newContent = content.replace(pattern, replacement); const count = (content.match(pattern) || []).length; if (count > 0) { await this.writeFile(filePath, newContent); } return count; } catch (error: any) { throw new Error(`Failed to replace in file ${filePath}: ${error.message}`); } } /** * Creates a backup of a file */ public async createBackup(filePath: string): Promise<string> { try { const content = await this.readFile(filePath); const backupPath = `${filePath}.backup.${Date.now()}`; await this.writeFile(backupPath, content); this.backups.set(filePath, backupPath); return backupPath; } catch (error: any) { throw new Error(`Failed to create backup of ${filePath}: ${error.message}`); } } /** * Restores a backup of a file */ public async restoreBackup(backupPath: string, originalPath: string): Promise<void> { try { const content = await this.readFile(backupPath); await this.writeFile(originalPath, content); } catch (error: any) { throw new Error(`Failed to restore backup ${backupPath} to ${originalPath}: ${error.message}`); } } /** * Reads multiple files at once */ public async batchRead(filePaths: string[]): Promise<Map<string, string>> { const results = new Map<string, string>(); await Promise.all( filePaths.map(async (filePath) => { try { const content = await this.readFile(filePath); results.set(filePath, content); } catch (error) { // Skip files that can't be read } }) ); return results; } /** * Writes to multiple files at once */ public async batchWrite(files: Map<string, string>): Promise<void> { await Promise.all( Array.from(files.entries()).map(async ([filePath, content]) => { await this.writeFile(filePath, content); }) ); } /** * Lists files in a directory */ public async listFiles(dirPath: string, pattern?: string): Promise<string[]> { try { const entries = await readdir(dirPath, { withFileTypes: true }); let files = entries .filter(entry => entry.isFile()) .map(entry => path.join(dirPath, entry.name)); if (pattern) { const regex = new RegExp(pattern); files = files.filter(file => regex.test(file)); } return files; } catch (error: any) { throw new Error(`Failed to list files in ${dirPath}: ${error.message}`); } } /** * Creates a directory if it doesn't exist */ public async createDirectory(dirPath: string): Promise<void> { try { await this.ensureDirectoryExists(dirPath); this.emitChangeEvent('create', dirPath); } catch (error: any) { throw new Error(`Failed to create directory ${dirPath}: ${error.message}`); } } /** * Ensures a directory exists, creating it if necessary */ private async ensureDirectoryExists(dirPath: string): Promise<void> { try { await mkdir(dirPath, { recursive: true }); } catch (error: any) { // Ignore if directory already exists if (error.code !== 'EEXIST') { throw error; } } } /** * Watches a file for changes */ public watchFile(filePath: string, callback: (event: FileChangeEvent) => void): FileWatcher { try { const watcher = fs.watch(filePath, (eventType, filename) => { if (eventType === 'change') { callback({ type: 'update', path: filePath }); } else if (eventType === 'rename') { // Check if the file still exists fs.access(filePath, fs.constants.F_OK, (err) => { if (err) { // File doesn't exist anymore callback({ type: 'delete', path: filePath }); } else { // File was created or renamed callback({ type: 'create', path: filePath }); } }); } }); this.watchers.set(filePath, watcher); return { path: filePath, close: () => this.unwatchFile(filePath) }; } catch (error: any) { throw new Error(`Failed to watch file ${filePath}: ${error.message}`); } } /** * Stops watching a file */ public unwatchFile(filePath: string): void { const watcher = this.watchers.get(filePath); if (watcher) { watcher.close(); this.watchers.delete(filePath); } } /** * Emits a file change event */ private emitChangeEvent(type: 'create' | 'update' | 'delete', path: string): void { this.emit('change', { type, path }); } /** * Cleans up resources */ public dispose(): void { // Close all watchers for (const watcher of this.watchers.values()) { watcher.close(); } this.watchers.clear(); this.backups.clear(); this.removeAllListeners(); } }

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/mixelpixx/microsoft-edit-mcp'

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