Skip to main content
Glama
enhanced-file-output-manager.ts14.2 kB
/** * Enhanced File Output Manager for MCP Color Server * Specialized for visualization file handling with dual background support */ import { fileOutputManager, FileMetadata } from './file-output-manager'; import { VisualizationResult, PNGVisualizationResult, FileVisualizationResult, } from '../types/index'; import { logger } from './logger'; export interface VisualizationSaveOptions { description?: string; customName?: string; subdirectory?: string; toolName: string; parameters?: Record<string, unknown>; } export interface DualPNGSaveOptions extends VisualizationSaveOptions { dimensions: [number, number]; resolution: number; colorSpace?: string; } export class EnhancedFileOutputManager { private static instance: EnhancedFileOutputManager; private constructor() { // Private constructor for singleton } public static getInstance(): EnhancedFileOutputManager { if (!EnhancedFileOutputManager.instance) { EnhancedFileOutputManager.instance = new EnhancedFileOutputManager(); } return EnhancedFileOutputManager.instance; } public async initialize(): Promise<void> { await fileOutputManager.initialize(); } /** * Save HTML visualization file and return file-based result */ public async saveHTMLVisualization( htmlContent: string, options: VisualizationSaveOptions ): Promise<VisualizationResult> { try { const saveOptions: { description?: string; customName?: string; subdirectory?: string; } = { description: options.description || `${options.toolName} HTML visualization`, }; if (options.customName) { saveOptions.customName = options.customName; } if (options.subdirectory) { saveOptions.subdirectory = options.subdirectory; } const metadata = await fileOutputManager.saveFile( htmlContent, 'html', saveOptions ); const fileResult: FileVisualizationResult = { file_path: metadata.path, filename: metadata.filename, size: metadata.size, created_at: metadata.createdAt.toISOString(), type: 'html', description: metadata.description, }; logger.info('HTML visualization saved', { tool: options.toolName, path: metadata.path, size: metadata.size, }); return { html_file: fileResult, }; } catch (error) { logger.error('Failed to save HTML visualization', { tool: options.toolName, error: error as Error, }); throw error; } } /** * Save dual PNG files (light and dark backgrounds) and return file-based result */ public async saveDualPNGVisualization( lightBuffer: Buffer, darkBuffer: Buffer, options: DualPNGSaveOptions ): Promise<VisualizationResult> { // Check if we should create actual files in test environment const isTestEnvironment = process.env['NODE_ENV'] === 'test' || process.env['JEST_WORKER_ID'] !== undefined || typeof jest !== 'undefined'; const shouldCreateFiles = !isTestEnvironment || process.env['TEST_ENABLE_FILE_OUTPUT'] === 'true'; if (isTestEnvironment && !shouldCreateFiles) { // Return the first buffer as base64 for backward compatibility with tests return { png_base64: lightBuffer.toString('base64'), }; } try { // Generate base filename for both variants const baseDescription = options.description || `${options.toolName} PNG visualization`; // Save light background variant const lightSaveOptions: { description?: string; customName?: string; subdirectory?: string; } = { description: `${baseDescription} (light background)`, }; if (options.customName) { lightSaveOptions.customName = `${options.customName}-light`; } if (options.subdirectory) { lightSaveOptions.subdirectory = options.subdirectory; } const lightMetadata = await fileOutputManager.saveFile( lightBuffer, 'png', lightSaveOptions ); // Save dark background variant const darkSaveOptions: { description?: string; customName?: string; subdirectory?: string; } = { description: `${baseDescription} (dark background)`, }; if (options.customName) { darkSaveOptions.customName = `${options.customName}-dark`; } if (options.subdirectory) { darkSaveOptions.subdirectory = options.subdirectory; } const darkMetadata = await fileOutputManager.saveFile( darkBuffer, 'png', darkSaveOptions ); const lightResult: FileVisualizationResult = { file_path: lightMetadata.path, filename: lightMetadata.filename, size: lightMetadata.size, created_at: lightMetadata.createdAt.toISOString(), type: 'png', description: lightMetadata.description, }; const darkResult: FileVisualizationResult = { file_path: darkMetadata.path, filename: darkMetadata.filename, size: darkMetadata.size, created_at: darkMetadata.createdAt.toISOString(), type: 'png', description: darkMetadata.description, }; logger.info('Dual PNG visualization saved', { tool: options.toolName, lightPath: lightMetadata.path, darkPath: darkMetadata.path, lightSize: lightMetadata.size, darkSize: darkMetadata.size, dimensions: options.dimensions, resolution: options.resolution, }); return { png_files: [lightResult, darkResult], }; } catch (error) { logger.error('Failed to save dual PNG visualization', { tool: options.toolName, error: error as Error, }); throw error; } } /** * Save single PNG file and return file-based result */ public async savePNGVisualization( buffer: Buffer, options: VisualizationSaveOptions ): Promise<VisualizationResult> { // Check if we should create actual files in test environment const isTestEnvironment = process.env['NODE_ENV'] === 'test' || process.env['JEST_WORKER_ID'] !== undefined || typeof jest !== 'undefined'; const shouldCreateFiles = !isTestEnvironment || process.env['TEST_ENABLE_FILE_OUTPUT'] === 'true'; if (isTestEnvironment && !shouldCreateFiles) { return { png_base64: buffer.toString('base64'), }; } try { const saveOptions: { description?: string; customName?: string; subdirectory?: string; } = { description: options.description || `${options.toolName} PNG visualization`, }; if (options.customName) { saveOptions.customName = options.customName; } if (options.subdirectory) { saveOptions.subdirectory = options.subdirectory; } const metadata = await fileOutputManager.saveFile( buffer, 'png', saveOptions ); const fileResult: FileVisualizationResult = { file_path: metadata.path, filename: metadata.filename, size: metadata.size, created_at: metadata.createdAt.toISOString(), type: 'png', description: metadata.description, }; logger.info('PNG visualization saved', { tool: options.toolName, path: metadata.path, size: metadata.size, }); return { png_files: [fileResult], }; } catch (error) { logger.error('Failed to save PNG visualization', { tool: options.toolName, error: error as Error, }); throw error; } } /** * Save SVG visualization file and return file-based result */ public async saveSVGVisualization( svgContent: string, options: VisualizationSaveOptions ): Promise<VisualizationResult> { try { const saveOptions: { description?: string; customName?: string; subdirectory?: string; } = { description: options.description || `${options.toolName} SVG visualization`, }; if (options.customName) { saveOptions.customName = options.customName; } if (options.subdirectory) { saveOptions.subdirectory = options.subdirectory; } const metadata = await fileOutputManager.saveFile( svgContent, 'svg', saveOptions ); const fileResult: FileVisualizationResult = { file_path: metadata.path, filename: metadata.filename, size: metadata.size, created_at: metadata.createdAt.toISOString(), type: 'svg', description: metadata.description, }; logger.info('SVG visualization saved', { tool: options.toolName, path: metadata.path, size: metadata.size, }); return { svg_file: fileResult, }; } catch (error) { logger.error('Failed to save SVG visualization', { tool: options.toolName, error: error as Error, }); throw error; } } /** * Save combined HTML and PNG visualizations */ public async saveCombinedVisualization( htmlContent: string, pngBuffer: Buffer, options: VisualizationSaveOptions ): Promise<VisualizationResult> { try { const [htmlResult, pngResult] = await Promise.all([ this.saveHTMLVisualization(htmlContent, options), this.savePNGVisualization(pngBuffer, options), ]); const result: VisualizationResult = {}; if (htmlResult.html_file) { result.html_file = htmlResult.html_file; } if (pngResult.png_files) { result.png_files = pngResult.png_files; } if (pngResult.png_base64) { result.png_base64 = pngResult.png_base64; } return result; } catch (error) { logger.error('Failed to save combined visualization', { tool: options.toolName, error: error as Error, }); throw error; } } /** * Save complete visualization set (HTML + dual PNG) */ public async saveCompleteVisualization( htmlContent: string, lightPngBuffer: Buffer, darkPngBuffer: Buffer, options: DualPNGSaveOptions ): Promise<VisualizationResult> { try { const [htmlResult, pngResult] = await Promise.all([ this.saveHTMLVisualization(htmlContent, options), this.saveDualPNGVisualization(lightPngBuffer, darkPngBuffer, options), ]); const result: VisualizationResult = {}; if (htmlResult.html_file) { result.html_file = htmlResult.html_file; } if (pngResult.png_files) { result.png_files = pngResult.png_files; } if (pngResult.png_base64) { result.png_base64 = pngResult.png_base64; } return result; } catch (error) { logger.error('Failed to save complete visualization', { tool: options.toolName, error: error as Error, }); throw error; } } /** * Create a structured PNG result with metadata */ public createPNGVisualizationResult( lightFile: FileVisualizationResult, darkFile: FileVisualizationResult, options: DualPNGSaveOptions ): PNGVisualizationResult { return { light_background: lightFile, dark_background: darkFile, metadata: { dimensions: options.dimensions, resolution: options.resolution, color_space: options.colorSpace || 'sRGB', total_size: lightFile.size + darkFile.size, }, }; } /** * Get visualization directory statistics */ public async getVisualizationStats() { return await fileOutputManager.getDirectoryStats(); } /** * Clean up old visualization files */ public async cleanupVisualizations(force = false) { return await fileOutputManager.cleanup(force); } /** * Delete a specific visualization file */ public async deleteVisualization(filePath: string): Promise<void> { await fileOutputManager.deleteFile(filePath); } /** * Get information about a visualization file */ public async getVisualizationInfo( filePath: string ): Promise<FileMetadata | null> { return await fileOutputManager.getFileInfo(filePath); } /** * Validate that a file path is a valid visualization file */ public async validateVisualizationFile(filePath: string): Promise<boolean> { try { const info = await this.getVisualizationInfo(filePath); if (!info) return false; // Check if it's a supported visualization type const supportedTypes = ['html', 'png', 'svg', 'css', 'json']; return supportedTypes.includes(info.type); } catch (error) { logger.debug('File validation failed', { filePath, error: error as Error, }); return false; } } /** * Generate a preview URL for a visualization file (if web server is available) */ public generatePreviewURL( fileResult: FileVisualizationResult, baseURL?: string ): string | undefined { if (!baseURL) return undefined; // Generate a relative path from the visualization directory const relativePath = fileResult.filename; return `${baseURL.replace(/\/$/, '')}/visualizations/${relativePath}`; } /** * Create a visualization manifest for tracking */ public createVisualizationManifest( result: VisualizationResult, options: VisualizationSaveOptions ): Record<string, unknown> { return { tool: options.toolName, parameters: options.parameters || {}, created_at: new Date().toISOString(), files: { ...(result.html_file && { html: result.html_file }), ...(result.png_files && { png: result.png_files }), ...(result.svg_file && { svg: result.svg_file }), }, description: options.description, }; } } // Export singleton instance export const enhancedFileOutputManager = EnhancedFileOutputManager.getInstance();

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/keyurgolani/ColorMcp'

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