Skip to main content
Glama

Gemini Image MCP Server

by devexpert-io
imageService.ts5.23 kB
import { writeFile, mkdir } from 'fs/promises'; import { join, resolve, extname } from 'path'; import { existsSync } from 'fs'; import sharp from 'sharp'; import { ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { ensureMcpError, invalidParams } from '../utils/errors.js'; export interface ImageData { base64: string; mimeType: string; } export interface SaveImageOptions { outputPath?: string; description?: string; watermarkPath?: string; watermarkPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; } export class ImageService { async saveImage(imageData: ImageData, options: SaveImageOptions = {}): Promise<string> { const { outputPath, description, watermarkPath, watermarkPosition } = options; // Determine file extension based on mimeType const extension = imageData.mimeType === 'image/png' ? '.png' : imageData.mimeType === 'image/jpeg' ? '.jpg' : imageData.mimeType === 'image/webp' ? '.webp' : '.png'; // Generate filename based on description or timestamp const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const safeDescription = description ? description.replace(/[^a-zA-Z0-9\s]/g, '').replace(/\s+/g, '_').slice(0, 50) : 'image'; const filename = `${safeDescription}_${timestamp}${extension}`; // Determine final path const finalPath = this.resolvePath(outputPath, filename); // Convert base64 to buffer let buffer: Buffer; try { buffer = Buffer.from(imageData.base64, 'base64'); } catch (error) { throw invalidParams('Generated image data was not valid base64', { cause: error instanceof Error ? error.message : String(error), }); } let resolvedWatermarkPath: string | undefined; if (watermarkPath) { resolvedWatermarkPath = resolve(watermarkPath); if (!existsSync(resolvedWatermarkPath)) { throw invalidParams(`Watermark image not found: ${resolvedWatermarkPath}`, { watermarkPath: resolvedWatermarkPath, }); } try { buffer = Buffer.from( await this.addWatermark(buffer, resolvedWatermarkPath, watermarkPosition) ); } catch (error) { throw ensureMcpError(error, ErrorCode.InternalError, 'Failed to apply watermark', { watermarkPath: resolvedWatermarkPath, }); } } try { await this.ensureDirectoryExists(join(finalPath, '..')); await writeFile(finalPath, buffer); } catch (error) { throw ensureMcpError(error, ErrorCode.InternalError, 'Failed to save generated image to disk', { outputPath: outputPath ? resolve(outputPath) : undefined, finalPath, }); } return finalPath; } private resolvePath(outputPath?: string, filename?: string): string { if (!outputPath) { // Save to current directory return resolve(process.cwd(), filename!); } const resolvedPath = resolve(outputPath); // Check if it's a directory or a file if (outputPath.endsWith('/') || (!extname(outputPath) && existsSync(resolvedPath))) { // It's a directory return join(resolvedPath, filename!); } else { // It's a specific file path return resolvedPath; } } private async addWatermark( imageBuffer: Buffer, watermarkPath: string, position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' = 'bottom-right' ): Promise<Buffer> { const resolvedWatermarkPath = resolve(watermarkPath); // Get image dimensions const imageInfo = await sharp(imageBuffer).metadata(); const imageWidth = imageInfo.width || 1024; const imageHeight = imageInfo.height || 1024; // Calculate watermark size (25% of image width, maintaining aspect ratio) const watermarkSize = Math.floor(imageWidth * 0.25); // Resize watermark and get actual dimensions const processedWatermark = await sharp(resolvedWatermarkPath) .resize(watermarkSize, watermarkSize, { fit: 'inside', withoutEnlargement: true }) .toBuffer(); // Get actual dimensions of the processed watermark const watermarkInfo = await sharp(processedWatermark).metadata(); const watermarkWidth = watermarkInfo.width || watermarkSize; const watermarkHeight = watermarkInfo.height || watermarkSize; // Add watermark in selected corner with consistent padding const padding = Math.floor(imageWidth * 0.03); // 3% of image width for consistent spacing let left = padding; let top = padding; const isRight = position.endsWith('right'); const isBottom = position.startsWith('bottom'); if (isRight) { left = imageWidth - watermarkWidth - padding; } if (isBottom) { top = imageHeight - watermarkHeight - padding; } return await sharp(imageBuffer) .composite([ { input: processedWatermark, left, top, blend: 'over' } ]) .toBuffer(); } private async ensureDirectoryExists(dirPath: string): Promise<void> { if (!existsSync(dirPath)) { await mkdir(dirPath, { recursive: true }); } } }

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/devexpert-io/gemini-image-mcp-server'

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