Skip to main content
Glama
generate-image.ts7.01 kB
/** * Generate blog image tool definition and handler. */ import { z } from "zod"; import { PLATFORM_PRESETS, PRESET_KEYS, type PlatformPreset } from "../config/presets.js"; import { createProvider, type GeneratedImage, type ProviderName } from "../providers/index.js"; import { estimateFileSize, formatFileSize, saveImage } from "../utils/image.js"; import { safeErrorMessage, validateOutputPath, validatePrompt } from "../utils/security.js"; /** * Zod schema for tool input validation. */ export const GenerateImageInputSchema = z.object({ prompt: z .string() .min(3, "Prompt must be at least 3 characters") .max(4000, "Prompt must be less than 4000 characters") .describe("Description of the image to generate"), format: z .enum(PRESET_KEYS as [string, ...string[]]) .default("ghost-banner") .describe("Platform preset (e.g., 'ghost-banner', 'instagram-post', 'twitter-post')"), quality: z .enum(["standard", "high"]) .default("standard") .describe("Quality level: 'standard' (faster) or 'high' (better quality, uses Pro model)"), style: z .string() .max(200) .optional() .describe("Optional style hint (e.g., 'photorealistic', 'illustration', 'minimalist')"), title: z.string().max(200).optional().describe("Optional blog post title for context"), outputPath: z.string().max(500).optional().describe("Optional path to save the image file"), provider: z.enum(["gemini"]).default("gemini").describe("Image generation provider to use"), }); export type GenerateImageInput = z.infer<typeof GenerateImageInputSchema>; export interface GenerateImageResult { success: boolean; message: string; image?: { base64Data: string; mimeType: string; format: string; dimensions: { /** Actual width of generated image */ width: number; /** Actual height of generated image */ height: number; /** Requested width (may differ from actual) */ requestedWidth?: number; /** Requested height (may differ from actual) */ requestedHeight?: number; }; fileSize: string; savedTo?: string; }; preset?: PlatformPreset; /** Warning about aspect ratio conversion or other issues */ warning?: string; error?: string; } /** * Execute the generate_blog_image tool. */ export async function executeGenerateImage( input: GenerateImageInput ): Promise<GenerateImageResult> { // Validate prompt const promptValidation = validatePrompt(input.prompt); if (!promptValidation.valid) { return { success: false, message: "Invalid prompt", error: promptValidation.error, }; } // Validate output path if provided if (input.outputPath) { const pathValidation = validateOutputPath(input.outputPath); if (!pathValidation.valid) { return { success: false, message: "Invalid output path", error: pathValidation.error, }; } } // Get preset configuration const preset = PLATFORM_PRESETS[input.format]; if (!preset) { return { success: false, message: "Invalid format", error: `Unknown format: ${input.format}. Available: ${PRESET_KEYS.join(", ")}`, }; } // Create provider const provider = createProvider(input.provider as ProviderName); if (!provider.isConfigured()) { return { success: false, message: "Provider not configured", error: `${input.provider} provider requires API key. Set GOOGLE_API_KEY environment variable.`, }; } // Build enhanced prompt with title context let enhancedPrompt = promptValidation.sanitized!; if (input.title) { enhancedPrompt = `For a blog post titled "${input.title}": ${enhancedPrompt}`; } // Generate image let generatedImage: GeneratedImage; try { generatedImage = await provider.generateImage({ prompt: enhancedPrompt, aspectRatio: preset.aspectRatio, width: preset.width, height: preset.height, quality: input.quality, style: input.style, }); } catch (error) { return { success: false, message: "Image generation failed", error: safeErrorMessage(error), }; } // Calculate file size const fileSize = formatFileSize(estimateFileSize(generatedImage.base64Data)); // Save to file if path provided let savedPath: string | undefined; if (input.outputPath) { try { savedPath = await saveImage( generatedImage.base64Data, input.outputPath, generatedImage.mimeType ); } catch (error) { return { success: false, message: "Failed to save image", error: safeErrorMessage(error), }; } } // Determine actual dimensions (from provider or estimate from preset) const actualWidth = generatedImage.width ?? preset.width; const actualHeight = generatedImage.height ?? preset.height; // Build warning message if aspect ratio is not natively supported let warning: string | undefined; if (!preset.nativeAspectRatio) { warning = `Note: This preset's aspect ratio (${preset.aspectRatio}) is not natively supported by Gemini. ` + `Image was generated at ${preset.geminiAspectRatio} aspect ratio. ` + `You may need to crop the image to fit ${preset.width}x${preset.height}.`; } else if (actualWidth !== preset.width || actualHeight !== preset.height) { warning = `Image was generated at ${actualWidth}x${actualHeight}, which differs from the preset's ${preset.width}x${preset.height}. ` + `You may need to resize the image.`; } // Build descriptive message let message: string; if (savedPath) { message = `Image generated and saved to ${savedPath}`; } else { message = `Image generated successfully (${preset.name}, actual: ${actualWidth}x${actualHeight})`; } return { success: true, message, image: { base64Data: generatedImage.base64Data, mimeType: generatedImage.mimeType, format: input.format, dimensions: { width: actualWidth, height: actualHeight, requestedWidth: preset.width, requestedHeight: preset.height, }, fileSize, savedTo: savedPath, }, preset, warning, }; } /** * Get tool description for MCP registration. */ export function getToolDescription(): string { const presetList = Object.entries(PLATFORM_PRESETS) .map(([key, preset]) => ` - ${key}: ${preset.name} (${preset.width}x${preset.height})`) .join("\n"); return `Generate images for blog posts and social media using AI. Available formats: ${presetList} Examples: - Generate a Ghost blog banner: { "prompt": "A serene mountain landscape at sunset", "format": "ghost-banner" } - High quality Instagram post: { "prompt": "Minimalist coffee cup on marble", "format": "instagram-post", "quality": "high" } - YouTube thumbnail with title: { "prompt": "Exciting tech reveal", "format": "youtube-thumbnail", "title": "New iPhone 17 Review" }`; }

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/12-days-of-shipmas-2025/day-1-image-generation-mcp'

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