Skip to main content
Glama
fileUtils.ts5.97 kB
import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import * as mime from "mime-types"; export interface FileAttachment { filename: string; mimeType: string; data: string; // base64 encoded size: number; } export interface EmailAttachment { attachmentId: string; filename: string; mimeType: string; size: number; } export class FileUtils { private static readonly MAX_FILE_SIZE = 25 * 1024 * 1024; // 25MB limit for Gmail attachments static async readFileAsBase64( filePath: string, customFilename?: string ): Promise<FileAttachment> { try { const normalizedPath = path.resolve(filePath.trim()); if (!fs.existsSync(normalizedPath)) { if (!fs.existsSync(filePath)) { throw new Error(`File not found: ${filePath}`); } return this.readFileAsBase64Internal(filePath, customFilename); } return this.readFileAsBase64Internal(normalizedPath, customFilename); } catch (error) { throw new Error( `Failed to read file ${filePath}: ${ error instanceof Error ? error.message : String(error) }` ); } } private static readFileAsBase64Internal( filePath: string, customFilename?: string ): FileAttachment { try { // Get file stats const stats = fs.statSync(filePath); // Check file size if (stats.size > this.MAX_FILE_SIZE) { throw new Error( `File size (${Math.round( stats.size / 1024 / 1024 )}MB) exceeds Gmail's 25MB attachment limit: ${filePath}` ); } // Read file content const fileBuffer = fs.readFileSync(filePath); const base64Data = fileBuffer.toString("base64"); // Get filename const originalFilename = path.basename(filePath); const filename = customFilename || originalFilename; // Detect MIME type const mimeType = mime.lookup(filePath) || "application/octet-stream"; return { filename, mimeType, data: base64Data, size: stats.size, }; } catch (error) { throw new Error( `Failed to process file ${filePath}: ${ error instanceof Error ? error.message : String(error) }` ); } } static validateFilePath(filePath: string): void { // Basic security check - prevent directory traversal const normalizedPath = path.normalize(filePath); if (normalizedPath.includes("..")) { throw new Error("Invalid file path: directory traversal not allowed"); } if (filePath.trim() === "") { throw new Error("File path cannot be empty"); } } static formatFileSize(bytes: number): string { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; } static getDefaultDownloadPath(): string { const platform = os.platform(); const homeDir = os.homedir(); switch (platform) { case "win32": // Windows: C:\Users\username\Downloads return path.join(homeDir, "Downloads"); case "darwin": // macOS: /Users/username/Downloads return path.join(homeDir, "Downloads"); case "linux": // Linux: /home/username/Downloads (or XDG_DOWNLOAD_DIR if set) const xdgDownload = process.env.XDG_DOWNLOAD_DIR; return xdgDownload || path.join(homeDir, "Downloads"); default: // Fallback for other platforms return path.join(homeDir, "Downloads"); } } static ensureDirectoryExists(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } static async saveBase64File( base64Data: string, filename: string, downloadPath: string ): Promise<string> { try { // Ensure the directory exists this.ensureDirectoryExists(downloadPath); // Create the full file path const filePath = path.join(downloadPath, filename); // Handle file name conflicts let finalFilePath = filePath; let counter = 1; while (fs.existsSync(finalFilePath)) { const extension = path.extname(filename); const baseName = path.basename(filename, extension); finalFilePath = path.join( downloadPath, `${baseName}_${counter}${extension}` ); counter++; } // Convert base64 to buffer and save const buffer = Buffer.from(base64Data, "base64"); fs.writeFileSync(finalFilePath, buffer); return finalFilePath; } catch (error) { throw new Error( `Failed to save file: ${ error instanceof Error ? error.message : String(error) }` ); } } static sanitizeFilename(filename: string): string { // Remove or replace invalid characters for file names return filename.replace(/[<>:"/\\|?*\x00-\x1f]/g, "_"); } static isValidFilePath(filePath: string): boolean { try { const normalizedPath = path.resolve(filePath.trim()); return ( fs.existsSync(normalizedPath) && fs.statSync(normalizedPath).isFile() ); } catch { return false; } } static getFileInfo(filePath: string): { name: string; size: number; mimeType: string; } { try { const normalizedPath = path.resolve(filePath.trim()); const stats = fs.statSync(normalizedPath); const name = path.basename(normalizedPath); const mimeType = mime.lookup(normalizedPath) || "application/octet-stream"; return { name, size: stats.size, mimeType, }; } catch (error) { throw new Error( `Failed to get file info for ${filePath}: ${ error instanceof Error ? error.message : String(error) }` ); } } }

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/vakharwalad23/google-mcp'

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