UNPKG

@graisol/gpt-image-mcp

Version:

A Model Context Protocol (MCP) server for OpenAI GPT-Image-1 image generation and editing

274 lines 10.5 kB
"use strict"; /** * Image Manager for handling image storage, caching, and metadata */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImageManager = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); class ImageManager { config; historyFile; storageDir; constructor(config) { this.config = config; this.storageDir = path_1.default.resolve(config.image_storage_path); this.historyFile = path_1.default.join(this.storageDir, 'generation_history.json'); // Initialize storage asynchronously - methods will handle missing files gracefully this.initializeStorage().catch(err => console.error('Storage initialization warning:', err.message)); } /** * Initialize storage directory and history file */ async initializeStorage() { try { await promises_1.default.mkdir(this.storageDir, { recursive: true }); // Create history file if it doesn't exist try { await promises_1.default.access(this.historyFile); } catch { const initialHistory = { images: [], total_count: 0, last_updated: Date.now(), }; await promises_1.default.writeFile(this.historyFile, JSON.stringify(initialHistory, null, 2)); } } catch (error) { console.error('Error initializing storage:', error); } } /** * Store image metadata in the history */ async storeImageMetadata(metadata) { try { const history = await this.loadHistory(); // Add new image to the beginning of the array history.images.unshift(metadata); history.total_count++; history.last_updated = Date.now(); // Maintain maximum number of stored images if (history.images.length > this.config.max_stored_images) { history.images = history.images.slice(0, this.config.max_stored_images); } await this.saveHistory(history); // Save individual image metadata file const imageFile = path_1.default.join(this.storageDir, `${metadata.id}.json`); await promises_1.default.writeFile(imageFile, JSON.stringify(metadata, null, 2)); console.log(`Stored metadata for image ${metadata.id}`); } catch (error) { console.error('Error storing image metadata:', error); throw new Error(`Failed to store image metadata: ${error}`); } } /** * Get information about a specific image */ async getImageInfo(imageId) { try { const imageFile = path_1.default.join(this.storageDir, `${imageId}.json`); const data = await promises_1.default.readFile(imageFile, 'utf-8'); const metadata = JSON.parse(data); // Remove base64 data to avoid MCP token limits return { ...metadata, b64_json: undefined, url: metadata.url || `Image saved locally with ID: ${metadata.id}` }; } catch (error) { console.error(`Error getting image info for ${imageId}:`, error); return null; } } /** * List recent image generations with optional filtering */ async listGenerations(options = {}) { try { const history = await this.loadHistory(); let filteredImages = history.images; // Apply text filter if provided if (options.filter) { const filterLower = options.filter.toLowerCase(); filteredImages = history.images.filter(img => img.prompt.toLowerCase().includes(filterLower) || (img.revised_prompt && img.revised_prompt.toLowerCase().includes(filterLower))); } // Apply pagination const offset = options.offset || 0; const limit = options.limit || 10; const paginatedImages = filteredImages.slice(offset, offset + limit); // Remove base64 data from images to avoid MCP token limits const sanitizedImages = paginatedImages.map(img => ({ ...img, b64_json: undefined, // Remove base64 data url: img.url || `Image saved locally with ID: ${img.id}` })); return { images: sanitizedImages, total_count: filteredImages.length, last_updated: history.last_updated, }; } catch (error) { console.error('Error listing generations:', error); throw new Error(`Failed to list generations: ${error}`); } } /** * Save image data to local storage (for base64 responses or URLs) */ async saveImageData(imageId, data, format) { try { if (format === 'base64') { const imageBuffer = Buffer.from(data, 'base64'); const imagePath = path_1.default.join(this.storageDir, `${imageId}.png`); await promises_1.default.writeFile(imagePath, imageBuffer); return imagePath; } else if (format === 'url') { // Download the image from URL and save locally const response = await fetch(data); if (!response.ok) { throw new Error(`Failed to download image: ${response.statusText}`); } const imageBuffer = await response.arrayBuffer(); const imagePath = path_1.default.join(this.storageDir, `${imageId}.png`); await promises_1.default.writeFile(imagePath, Buffer.from(imageBuffer)); return imagePath; } else { return data; } } catch (error) { console.error('Error saving image data:', error); throw new Error(`Failed to save image data: ${error}`); } } /** * Clean up old images and metadata */ async cleanupOldImages() { try { const history = await this.loadHistory(); const cutoffTime = Date.now() - (30 * 24 * 60 * 60 * 1000); // 30 days ago const recentImages = history.images.filter(img => img.created > cutoffTime); const removedImages = history.images.filter(img => img.created <= cutoffTime); // Remove old image files for (const img of removedImages) { try { await promises_1.default.unlink(path_1.default.join(this.storageDir, `${img.id}.json`)); await promises_1.default.unlink(path_1.default.join(this.storageDir, `${img.id}.png`)).catch(() => { }); } catch (error) { console.error(`Error removing old image ${img.id}:`, error); } } // Update history history.images = recentImages; history.total_count = recentImages.length; history.last_updated = Date.now(); await this.saveHistory(history); console.log(`Cleaned up ${removedImages.length} old images`); } catch (error) { console.error('Error during cleanup:', error); } } /** * Get storage statistics */ async getStorageStats() { try { const history = await this.loadHistory(); let totalSize = 0; const files = await promises_1.default.readdir(this.storageDir); for (const file of files) { const filePath = path_1.default.join(this.storageDir, file); const stats = await promises_1.default.stat(filePath); totalSize += stats.size; } const timestamps = history.images.map(img => img.created); return { total_images: history.images.length, storage_size: totalSize, oldest_image: timestamps.length > 0 ? Math.min(...timestamps) : 0, newest_image: timestamps.length > 0 ? Math.max(...timestamps) : 0, }; } catch (error) { console.error('Error getting storage stats:', error); return { total_images: 0, storage_size: 0, oldest_image: 0, newest_image: 0, }; } } /** * Load generation history from file */ async loadHistory() { try { const data = await promises_1.default.readFile(this.historyFile, 'utf-8'); return JSON.parse(data); } catch (error) { console.error('Error loading history:', error); return { images: [], total_count: 0, last_updated: Date.now(), }; } } /** * Save generation history to file */ async saveHistory(history) { try { await promises_1.default.writeFile(this.historyFile, JSON.stringify(history, null, 2)); } catch (error) { console.error('Error saving history:', error); throw new Error(`Failed to save history: ${error}`); } } /** * Validate image data (basic validation) */ validateImageData(data, format) { try { if (format === 'base64') { // Basic base64 validation const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/; return base64Regex.test(data) && data.length > 0; } else { // Basic URL validation try { new URL(data); return true; } catch { return false; } } } catch (error) { console.error('Error validating image data:', error); return false; } } } exports.ImageManager = ImageManager; //# sourceMappingURL=image-manager.js.map