Skip to main content
Glama
file-utils.ts16.7 kB
import fs from 'fs-extra'; import path from 'path'; import yaml from 'js-yaml'; import { z } from 'zod'; import logger from '../../../logger.js'; import { UnifiedSecurityEngine, createDefaultSecurityConfig } from '../core/unified-security-engine.js'; /** * File operation result */ export interface FileOperationResult<T = unknown> { success: boolean; data?: T; error?: string; metadata?: { filePath: string; operation: string; timestamp: Date; size?: number; loadTime?: number; fromCache?: boolean; }; } /** * File system utilities for the Vibe Task Manager */ export class FileUtils { private static readonly MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB private static readonly ALLOWED_EXTENSIONS = ['.json', '.yaml', '.yml', '.md', '.txt']; private static securityEngine: UnifiedSecurityEngine | null = null; /** * Get or initialize the security engine */ private static async getSecurityEngine(): Promise<UnifiedSecurityEngine> { if (!this.securityEngine) { const config = createDefaultSecurityConfig(); this.securityEngine = UnifiedSecurityEngine.getInstance(config); await this.securityEngine.initialize(); } return this.securityEngine; } /** * Safely read a file with validation */ static async readFile(filePath: string): Promise<FileOperationResult<string>> { try { // Validate file path with security checks const validationResult = await this.validateFilePath(filePath); if (!validationResult.valid) { return { success: false, error: `Invalid file path: ${validationResult.error}`, metadata: { filePath, operation: 'read', timestamp: new Date() } }; } // Check if file exists if (!await fs.pathExists(filePath)) { return { success: false, error: 'File does not exist', metadata: { filePath, operation: 'read', timestamp: new Date() } }; } // Check file size const stats = await fs.stat(filePath); if (stats.size > this.MAX_FILE_SIZE) { return { success: false, error: `File too large: ${stats.size} bytes (max: ${this.MAX_FILE_SIZE})`, metadata: { filePath, operation: 'read', timestamp: new Date(), size: stats.size } }; } // Read file content const content = await fs.readFile(filePath, 'utf-8'); logger.debug({ filePath, size: stats.size }, 'File read successfully'); return { success: true, data: content, metadata: { filePath, operation: 'read', timestamp: new Date(), size: stats.size } }; } catch (error) { logger.error({ err: error, filePath }, 'Failed to read file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'read', timestamp: new Date() } }; } } /** * Safely write a file with validation and atomic operations */ static async writeFile(filePath: string, content: string): Promise<FileOperationResult<void>> { try { // Validate file path with security checks for write operation const validationResult = await this.validateFilePath(filePath, 'write'); if (!validationResult.valid) { return { success: false, error: `Invalid file path: ${validationResult.error}`, metadata: { filePath, operation: 'write', timestamp: new Date() } }; } // Ensure directory exists const dirPath = path.dirname(filePath); await fs.ensureDir(dirPath); // Use atomic write operation to prevent corruption const tempFilePath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substr(2, 9)}`; try { // Write to temporary file first await fs.writeFile(tempFilePath, content, 'utf-8'); // Atomically rename temporary file to target file await fs.rename(tempFilePath, filePath); } catch (writeError) { // Clean up temporary file if it exists try { await fs.remove(tempFilePath); } catch { // Ignore cleanup errors } throw writeError; } const stats = await fs.stat(filePath); logger.debug({ filePath, size: stats.size }, 'File written successfully (atomic)'); return { success: true, metadata: { filePath, operation: 'write', timestamp: new Date(), size: stats.size } }; } catch (error) { logger.error({ err: error, filePath }, 'Failed to write file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'write', timestamp: new Date() } }; } } /** * Read and parse YAML file with schema validation */ static async readYamlFile<T>( filePath: string, schema?: z.ZodSchema<T> ): Promise<FileOperationResult<T>> { try { const readResult = await this.readFile(filePath); if (!readResult.success) { return readResult as FileOperationResult<T>; } // Parse YAML content const parsedData = yaml.load(readResult.data!) as T; // Validate with schema if provided if (schema) { const validationResult = schema.safeParse(parsedData); if (!validationResult.success) { return { success: false, error: `YAML validation failed: ${validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`, metadata: { filePath, operation: 'read_yaml', timestamp: new Date() } }; } return { success: true, data: validationResult.data, metadata: { filePath, operation: 'read_yaml', timestamp: new Date(), size: readResult.metadata?.size } }; } return { success: true, data: parsedData, metadata: { filePath, operation: 'read_yaml', timestamp: new Date(), size: readResult.metadata?.size } }; } catch (error) { logger.error({ err: error, filePath }, 'Failed to read YAML file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'read_yaml', timestamp: new Date() } }; } } /** * Write data to YAML file */ static async writeYamlFile<T>( filePath: string, data: T, schema?: z.ZodSchema<T> ): Promise<FileOperationResult<void>> { try { // Validate with schema if provided if (schema) { const validationResult = schema.safeParse(data); if (!validationResult.success) { return { success: false, error: `Data validation failed: ${validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`, metadata: { filePath, operation: 'write_yaml', timestamp: new Date() } }; } } // Convert to YAML const yamlContent = yaml.dump(data, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: true }); // Write file return await this.writeFile(filePath, yamlContent); } catch (error) { logger.error({ err: error, filePath }, 'Failed to write YAML file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'write_yaml', timestamp: new Date() } }; } } /** * Read and parse JSON file with schema validation */ static async readJsonFile<T>( filePath: string, schema?: z.ZodSchema<T> ): Promise<FileOperationResult<T>> { try { const readResult = await this.readFile(filePath); if (!readResult.success) { return readResult as FileOperationResult<T>; } // Parse JSON content const parsedData = JSON.parse(readResult.data!) as T; // Validate with schema if provided if (schema) { const validationResult = schema.safeParse(parsedData); if (!validationResult.success) { return { success: false, error: `JSON validation failed: ${validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`, metadata: { filePath, operation: 'read_json', timestamp: new Date() } }; } return { success: true, data: validationResult.data, metadata: { filePath, operation: 'read_json', timestamp: new Date(), size: readResult.metadata?.size } }; } return { success: true, data: parsedData, metadata: { filePath, operation: 'read_json', timestamp: new Date(), size: readResult.metadata?.size } }; } catch (error) { logger.error({ err: error, filePath }, 'Failed to read JSON file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'read_json', timestamp: new Date() } }; } } /** * Write data to JSON file */ static async writeJsonFile<T>( filePath: string, data: T, schema?: z.ZodSchema<T> ): Promise<FileOperationResult<void>> { try { // Validate with schema if provided if (schema) { const validationResult = schema.safeParse(data); if (!validationResult.success) { return { success: false, error: `Data validation failed: ${validationResult.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`, metadata: { filePath, operation: 'write_json', timestamp: new Date() } }; } } // Convert to JSON const jsonContent = JSON.stringify(data, null, 2); // Write file return await this.writeFile(filePath, jsonContent); } catch (error) { logger.error({ err: error, filePath }, 'Failed to write JSON file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'write_json', timestamp: new Date() } }; } } /** * Validate file path for security using unified security engine */ private static async validateFilePath(filePath: string, operation: 'read' | 'write' = 'read'): Promise<{ valid: boolean; error?: string }> { try { const securityEngine = await this.getSecurityEngine(); // For write operations, we need to validate the directory path and allow non-existent files if (operation === 'write') { const dirPath = path.dirname(filePath); const fileName = path.basename(filePath); // Validate directory path exists and is accessible const dirValidationResult = await securityEngine.validatePath(dirPath, 'write'); if (!dirValidationResult.success || !dirValidationResult.data?.isValid) { // Try to validate the full path with write operation const fullPathResult = await securityEngine.validatePath(filePath, 'write'); if (!fullPathResult.success) { return { valid: false, error: fullPathResult.error.message || 'Path validation failed' }; } if (!fullPathResult.data.isValid) { return { valid: false, error: fullPathResult.data.error || 'Path validation failed' }; } } // Validate filename doesn't contain dangerous characters const dangerousChars = /[<>"|?*]/; const controlChars = new RegExp('[' + String.fromCharCode(0) + '-' + String.fromCharCode(31) + ']'); if (dangerousChars.test(fileName) || controlChars.test(fileName)) { return { valid: false, error: 'Filename contains dangerous characters' }; } } else { // For read operations, use standard validation const validationResult = await securityEngine.validatePath(filePath, 'read'); if (!validationResult.success) { return { valid: false, error: validationResult.error.message || 'Path validation failed' }; } if (!validationResult.data.isValid) { return { valid: false, error: validationResult.data.error || 'Path validation failed' }; } } // Additional file extension check for FileUtils const ext = path.extname(filePath).toLowerCase(); if (ext && !this.ALLOWED_EXTENSIONS.includes(ext)) { return { valid: false, error: `File extension ${ext} not allowed. Allowed extensions: ${this.ALLOWED_EXTENSIONS.join(', ')}` }; } return { valid: true }; } catch (error) { logger.error({ err: error, filePath, operation }, 'File path validation failed with exception'); return { valid: false, error: `Path validation error: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Ensure directory exists */ static async ensureDirectory(dirPath: string): Promise<FileOperationResult<void>> { try { await fs.ensureDir(dirPath); return { success: true, metadata: { filePath: dirPath, operation: 'ensure_directory', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, dirPath }, 'Failed to ensure directory'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: dirPath, operation: 'ensure_directory', timestamp: new Date() } }; } } /** * Check if file exists */ static async fileExists(filePath: string): Promise<boolean> { try { return await fs.pathExists(filePath); } catch { return false; } } /** * Delete file safely */ static async deleteFile(filePath: string): Promise<FileOperationResult<void>> { try { // Validate file path with security checks for read operation (file must exist to delete) const validationResult = await this.validateFilePath(filePath, 'read'); if (!validationResult.valid) { return { success: false, error: `Invalid file path: ${validationResult.error}`, metadata: { filePath, operation: 'delete', timestamp: new Date() } }; } // Check if file exists if (!await fs.pathExists(filePath)) { return { success: true, // File doesn't exist, consider it deleted metadata: { filePath, operation: 'delete', timestamp: new Date() } }; } // Delete file await fs.remove(filePath); logger.debug({ filePath }, 'File deleted successfully'); return { success: true, metadata: { filePath, operation: 'delete', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, filePath }, 'Failed to delete file'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath, operation: 'delete', timestamp: new Date() } }; } } }

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/freshtechbro/vibe-coder-mcp'

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