@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
JavaScript
;
/**
* 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