Skip to main content
Glama

generate_blog_image

Generates AI images for blog posts and social media platforms with preset sizes for Ghost, Medium, Instagram, Twitter, LinkedIn, YouTube, and more.

Instructions

Generate images for blog posts and social media using AI.

Available formats:

  • ghost-banner: Ghost Blog Banner (1200x675)

  • ghost-feature: Ghost Feature Image (HD) (2000x1125)

  • medium-ghost-spooky: Medium Ghost Spooky (2560x1440)

  • medium-banner: Medium Banner (1400x788)

  • substack-header: Substack Header (1456x816)

  • wordpress-featured: WordPress Featured (1200x675)

  • instagram-post: Instagram Post (1080x1080)

  • instagram-story: Instagram Story (1080x1920)

  • instagram-landscape: Instagram Landscape (1080x608)

  • twitter-post: Twitter/X Post (1200x675)

  • twitter-header: Twitter/X Header (1500x500)

  • linkedin-post: LinkedIn Post (1200x628)

  • linkedin-banner: LinkedIn Banner (1584x396)

  • facebook-post: Facebook Post (1200x630)

  • facebook-cover: Facebook Cover (820x312)

  • youtube-thumbnail: YouTube Thumbnail (1280x720)

  • youtube-banner: YouTube Banner (2560x1440)

  • square: Square (1024x1024)

  • square-hd: Square HD (2048x2048)

  • landscape: Landscape (1920x1080)

  • landscape-4k: Landscape 4K (3840x2160)

  • portrait: Portrait (1080x1920)

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" }

IMPORTANT: Always specify an outputPath to save the image to a meaningful location. If omitted, images are saved to a generated-images/ directory in the current working directory with a timestamped filename.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
promptYesDescription of the image to generate
formatNoPlatform preset: ghost-banner, ghost-feature, medium-ghost-spooky, medium-banner, substack-header, wordpress-featured, instagram-post, instagram-story, instagram-landscape, twitter-post, twitter-header, linkedin-post, linkedin-banner, facebook-post, facebook-cover, youtube-thumbnail, youtube-banner, square, square-hd, landscape, landscape-4k, portraitghost-banner
qualityNoQuality levelstandard
styleNoOptional style hint (e.g., 'photorealistic', 'illustration')
titleNoOptional blog post title for context
outputPathNoOptional path to save the image file
providerNoImage generation providergemini

Implementation Reference

  • The main execution function for the generate_blog_image tool. Validates prompt and output path, resolves the platform preset, calls the provider to generate the image, saves it to disk with embedded metadata, and returns the result with dimensions, file size, and warnings.
    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));
    
      // Determine output path - use provided path or generate a default
      const outputPath = input.outputPath ?? generateDefaultOutputPath(generatedImage.mimeType);
    
      // Build metadata to embed in the image
      const metadata: ImageMetadata = {
        prompt: input.prompt,
        model: generatedImage.model ?? "gemini-2.0-flash-exp",
        provider: input.provider,
        format: input.format,
        style: input.style,
        title: input.title,
        generatedAt: new Date().toISOString(),
      };
    
      // Always save the image to ensure it's never lost
      let savedPath: string;
      try {
        savedPath = await saveImage(
          generatedImage.base64Data,
          outputPath,
          generatedImage.mimeType,
          metadata
        );
      } 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 - image is always saved
      const message = `Image generated and saved to ${savedPath}`;
    
      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,
      };
    }
  • Zod schema defining the input validation for generate_blog_image. Validates prompt (3-4000 chars), format (platform preset enum), quality (standard/high), optional style and title, outputPath, and provider (currently only 'gemini').
    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>;
  • src/index.ts:46-95 (registration)
    Tool registration in the MCP ListTools handler. Registers the tool named 'generate_blog_image' with its description, input schema properties (prompt, format, quality, style, title, outputPath, provider), and required fields.
    tools: [
      {
        name: "generate_blog_image",
        description: getToolDescription(),
        inputSchema: {
          type: "object" as const,
          properties: {
            prompt: {
              type: "string",
              description: "Description of the image to generate",
              minLength: 3,
              maxLength: 4000,
            },
            format: {
              type: "string",
              description: `Platform preset: ${PRESET_KEYS.join(", ")}`,
              enum: PRESET_KEYS,
              default: "ghost-banner",
            },
            quality: {
              type: "string",
              description: "Quality level",
              enum: ["standard", "high"],
              default: "standard",
            },
            style: {
              type: "string",
              description: "Optional style hint (e.g., 'photorealistic', 'illustration')",
              maxLength: 200,
            },
            title: {
              type: "string",
              description: "Optional blog post title for context",
              maxLength: 200,
            },
            outputPath: {
              type: "string",
              description: "Optional path to save the image file",
              maxLength: 500,
            },
            provider: {
              type: "string",
              description: "Image generation provider",
              enum: ["gemini"],
              default: "gemini",
            },
          },
          required: ["prompt"],
        },
      },
  • src/index.ts:121-179 (registration)
    Tool execution handler for generate_blog_image in the CallToolRequestSchema switch. Parses input via Zod schema, calls executeGenerateImage, and formats the response including image data (base64, mimeType) and detail text.
    case "generate_blog_image": {
      // Parse and validate input
      const parseResult = GenerateImageInputSchema.safeParse(args);
      if (!parseResult.success) {
        return {
          content: [
            {
              type: "text" as const,
              text: `Invalid input: ${parseResult.error.errors.map((e) => e.message).join(", ")}`,
            },
          ],
          isError: true,
        };
      }
    
      // Execute generation
      const result = await executeGenerateImage(parseResult.data);
    
      if (!result.success) {
        return {
          content: [
            {
              type: "text" as const,
              text: `Error: ${result.error ?? result.message}`,
            },
          ],
          isError: true,
        };
      }
    
      // Return success with image
      const content: Array<{
        type: "text" | "image";
        text?: string;
        data?: string;
        mimeType?: string;
      }> = [
        {
          type: "text" as const,
          text: result.message,
        },
      ];
    
      // Include image data if available
      if (result.image) {
        content.push({
          type: "image" as const,
          data: result.image.base64Data,
          mimeType: result.image.mimeType,
        });
    
        content.push({
          type: "text" as const,
          text: `\nDetails:\n- Format: ${result.image.format}\n- Dimensions: ${result.image.dimensions.width}x${result.image.dimensions.height}\n- File size: ${result.image.fileSize}${result.image.savedTo ? `\n- Saved to: ${result.image.savedTo}` : ""}`,
        });
      }
    
      return { content };
    }
  • Helper function 'saveImage' called by the handler. Saves base64 image data to disk, handles directory creation, filename generation, and embeds PNG metadata (prompt, model, provider, format, style, title, timestamp) as tEXt chunks.
    export async function saveImage(
      base64Data: string,
      outputPath: string,
      mimeType: string = "image/png",
      metadata?: ImageMetadata
    ): Promise<string> {
      // Resolve to absolute path
      let absolutePath = resolve(outputPath);
    
      // Check if outputPath is an existing directory or ends with a path separator
      const isDirectory =
        outputPath.endsWith("/") ||
        outputPath.endsWith("\\") ||
        (existsSync(absolutePath) && statSync(absolutePath).isDirectory());
    
      if (isDirectory) {
        // Generate a filename and append to the directory path
        const filename = generateFilename("blog-image", mimeType);
        absolutePath = join(absolutePath, filename);
      }
    
      // Ensure the directory exists
      const dir = dirname(absolutePath);
      if (!existsSync(dir)) {
        await mkdir(dir, { recursive: true });
      }
    
      // Add extension if missing
      let finalPath = absolutePath;
      if (!extname(finalPath)) {
        finalPath += getExtensionFromMimeType(mimeType);
      }
    
      // Decode image data
      const rawBuffer = Buffer.from(base64Data, "base64");
    
      // Embed metadata if provided and image is PNG
      const finalBuffer =
        metadata && mimeType === "image/png" ? embedPngMetadata(rawBuffer, metadata) : rawBuffer;
    
      await writeFile(finalPath, finalBuffer);
    
      return finalPath;
    }
    
    /**
     * Calculate approximate file size from base64 data.
     */
    export function estimateFileSize(base64Data: string): number {
      // Base64 is ~4/3 the size of the original binary
      return Math.floor((base64Data.length * 3) / 4);
    }
    
    /**
     * Format file size for display.
     */
    export function formatFileSize(bytes: number): string {
      if (bytes < 1024) return `${bytes} B`;
      if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
      return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
    }
    
    /**
     * Get the default output directory for generated images.
     * Uses IMAGE_OUTPUT_DIR env var if set, otherwise falls back to cwd/generated-images.
     */
    export function getDefaultOutputDir(): string {
      const envDir = process.env.IMAGE_OUTPUT_DIR;
      if (envDir) {
        return resolve(envDir);
      }
      return join(process.cwd(), "generated-images");
    }
    
    /**
     * Generate a default output path for an image.
     * Creates a timestamped filename in the default output directory.
     */
    export function generateDefaultOutputPath(
      mimeType: string = "image/png",
      prefix: string = "blog-image"
    ): string {
      const dir = getDefaultOutputDir();
      const filename = generateFilename(prefix, mimeType);
      return join(dir, filename);
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, description discloses default save behavior and outputPath importance. Does not mention auth, rate limits, or side effects beyond file creation, but cover key behavioral aspects adequately.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-organized with headings, bullet list, examples, and a note. Every section serves a purpose, though the format list could be condensed. Front-loaded with purpose, then details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-param tool with no output schema or annotations, the description explains all inputs, default behaviors, and provides examples. Lacks return value details but that falls on schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, baseline 3. Description adds dimensions to each format, examples for quality/title, and clarifies outputPath purpose. Adds meaningful context beyond schema definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states 'Generate images for blog posts and social media using AI' - a specific verb and resource. Sibling tool 'list_image_formats' lists formats, while this generates images, making the distinction obvious.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Examples show typical use cases and the IMPORTANT note guides outputPath usage. Lacks explicit when-not-to-use or alternative tools, but the sibling count is small and context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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-2-image-generation-mcp'

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