Skip to main content
Glama

asset_generate_og_image

Create 1200×630 Open Graph images using Satori typography rendering without diffusion. Choose from templates like centered_hero or product_card, add optional background images, and render server-side without an API key.

Instructions

Render a 1200×630 OG image via Satori template (deterministic typography, no diffusion). Default mode=api renders server-side without any API key. external_prompt_only is only meaningful when with_background_image is set. inline_svg is not supported (web-font loading + precise text layout beyond LLM reach).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
titleYes
modeNo
subtitleNo
templateNocentered_hero
brand_bundleNo
with_background_imageNo
background_briefNo
output_dirNo

Implementation Reference

  • The main handler function `generateOgImage` for the asset_generate_og_image tool. It handles three modes: external_prompt_only (returns a paste plan for diffusion-based background), inline_svg (throws error as unsupported), and api (default) which renders a Satori template with optional diffusion background image and saves PNG+SVG output.
    import { resolve } from "node:path";
    import { mkdirSync, writeFileSync } from "node:fs";
    import { renderOg } from "../pipeline/og-render.js";
    import { generate } from "../providers/index.js";
    import { tier0 } from "../pipeline/validate.js";
    import { CONFIG } from "../config.js";
    import { hashBundle } from "../brand.js";
    import { resolveMode, buildExternalPromptPlan } from "./mode-runtime.js";
    import { resolvePasteTargets } from "../paste-targets.js";
    import type { GenerateOgImageInputT } from "../schemas.js";
    import type { AssetGenerationResult, AssetSpec } from "../types.js";
    
    /**
     * Tool: asset_generate_og_image
     *
     * OG cards are fundamentally a typography-layout problem, not a diffusion
     * problem — research angle: Satori + @resvg/resvg-js is the production
     * pattern. So the mode matrix is different here:
     *   - api: render server-side with Satori template. Zero external key
     *     needed for the template itself (font loading is local). If the user
     *     asks for a diffusion-generated background hero, that sub-call needs
     *     a Flux / OpenAI / similar key.
     *   - external_prompt_only: only meaningful when with_background_image is
     *     set — the user pastes the hero-background prompt into Flux/etc.
     *   - inline_svg: not offered (web-font loading + text layout beyond
     *     practical LLM reach).
     */
    export async function generateOgImage(
      input: GenerateOgImageInputT
    ): Promise<AssetGenerationResult> {
      // If the caller explicitly chose external_prompt_only (likely because
      // they want hero-background generation but have no Flux key), return the
      // paste plan for the background brief.
      if (input.mode === "external_prompt_only") {
        if (!input.with_background_image || !input.background_brief) {
          throw new Error(
            "mode=external_prompt_only on asset_generate_og_image requires with_background_image=true and a background_brief (that's the only part of the OG pipeline that goes through diffusion; the template itself renders server-side)."
          );
        }
        const bgSpec: AssetSpec = {
          asset_type: "og_image",
          brief: input.background_brief,
          rewritten_prompt: `${input.background_brief}. Cinematic, no text, no UI, subtle, dark, leaves generous space in the center-left for a headline.`,
          target_model: "flux-pro",
          fallback_models: ["gpt-image-1", "imagen-4"],
          params: {},
          postprocess: [],
          safe_zone: null,
          dimensions: { width: 1200, height: 630 },
          transparency_required: false,
          vector_required: false,
          text_content: null,
          modes_available: ["external_prompt_only"],
          paste_targets: resolvePasteTargets("flux-pro").primary_targets,
          warnings: []
        };
        return buildExternalPromptPlan("og_image", input.background_brief, bgSpec);
      }
    
      // Block inline_svg explicitly with a clear reason.
      if (input.mode === "inline_svg") {
        throw new Error(
          "mode=inline_svg is not supported for asset_generate_og_image. " +
            "OG images require web-font loading and precise text layout; Satori-based api mode handles this server-side. " +
            "Use mode=api (default) or mode=external_prompt_only with a diffusion background."
        );
      }
    
      // api mode — Satori template, optional diffusion background.
      // modes.ts asserts api is always listed for og_image, but validate availability
      // for the diffusion sub-call when requested.
      if (input.mode === "api" || input.mode === undefined) {
        if (input.with_background_image && input.background_brief) {
          // resolveMode will throw if flux / openai / imagen aren't available.
          resolveMode("api", "hero", "flux-pro", ["gpt-image-1", "imagen-4"]);
        }
      }
    
      const outDir = input.output_dir ?? resolve(CONFIG.outputDir, `og-${Date.now()}`);
      mkdirSync(outDir, { recursive: true });
    
      let backgroundImage: Buffer | undefined;
      if (input.with_background_image && input.background_brief) {
        const bg = await generate("flux-pro", {
          prompt: `${input.background_brief}. Cinematic, no text, no UI, subtle, dark, leaves generous space in the center-left for a headline.`,
          width: 1200,
          height: 630,
          seed: 0,
          transparency: false,
          output_format: "png"
        });
        backgroundImage = bg.image;
      }
    
      const result = await renderOg({
        title: input.title,
        ...(input.subtitle && { subtitle: input.subtitle }),
        template: input.template,
        ...(input.brand_bundle?.palette && { palette: input.brand_bundle.palette }),
        ...(input.brand_bundle?.logo_mark && { logoSvg: input.brand_bundle.logo_mark }),
        ...(input.brand_bundle?.typography?.primary && {
          fontFamily: input.brand_bundle.typography.primary
        }),
        ...(backgroundImage && { backgroundImage })
      });
    
      const pngPath = resolve(outDir, "og.png");
      const svgPath = resolve(outDir, "og.svg");
      if (result.png.length > 0) writeFileSync(pngPath, result.png);
      writeFileSync(svgPath, result.svg);
    
      const validation = await tier0({
        image: result.png.length > 0 ? result.png : Buffer.from(result.svg),
        asset_type: "og_image",
        expected_width: 1200,
        expected_height: 630,
        transparency_required: false,
        ...(input.brand_bundle && { brand_bundle: input.brand_bundle })
      });
    
      return {
        mode: "api",
        asset_type: "og_image",
        brief: `${input.template}: ${input.title}`,
        brand_bundle_hash: hashBundle(input.brand_bundle ?? null),
        variants: [
          ...(result.png.length > 0
            ? [{ path: pngPath, format: "png", width: 1200, height: 630, bytes: result.png.length }]
            : []),
          { path: svgPath, format: "svg", bytes: result.svg.length }
        ],
        provenance: { model: "satori+resvg", seed: 0, prompt_hash: "", params_hash: "" },
        validations: validation,
        warnings: [...result.warnings, ...validation.warnings]
      };
    }
  • Zod schema `GenerateOgImageInput` defining input validation: title (required), mode (optional), subtitle (optional), template (enum defaulting to 'centered_hero'), brand_bundle (optional), with_background_image (default false), background_brief (optional), output_dir (optional).
    export const GenerateOgImageInput = z.object({
      title: z.string().min(1),
      mode: ModeSchema.optional(),
      subtitle: z.string().optional(),
      template: z
        .enum(["centered_hero", "left_title", "minimal", "quote", "product_card"])
        .default("centered_hero"),
      brand_bundle: BrandBundleSchema.optional(),
      with_background_image: z.boolean().default(false),
      background_brief: z.string().optional(),
      output_dir: z.string().optional()
    });
  • Type alias `GenerateOgImageInputT` inferred from the Zod schema, used to type the handler's input parameter.
    export type GenerateOgImageInputT = z.infer<typeof GenerateOgImageInput>;
  • Registration of the tool in the MCP server: tool name 'asset_generate_og_image' with description and inputSchema (JSON schema for title, mode, subtitle, template, brand_bundle, with_background_image, background_brief, output_dir).
      name: "asset_generate_og_image",
      description:
        "Render a 1200×630 OG image via Satori template (deterministic typography, no diffusion). Default mode=api renders server-side without any API key. external_prompt_only is only meaningful when with_background_image is set. inline_svg is not supported (web-font loading + precise text layout beyond LLM reach).",
      inputSchema: {
        type: "object",
        properties: {
          title: { type: "string" },
          mode: {
            type: "string",
            enum: ["inline_svg", "external_prompt_only", "api"]
          },
          subtitle: { type: "string" },
          template: {
            type: "string",
            enum: ["centered_hero", "left_title", "minimal", "quote", "product_card"],
            default: "centered_hero"
          },
          brand_bundle: { type: "object" },
          with_background_image: { type: "boolean", default: false },
          background_brief: { type: "string" },
          output_dir: { type: "string" }
        },
        required: ["title"]
      },
      annotations: { openWorldHint: true }
    },
  • Dispatch/handler case in the server's CallToolRequest handler: routes 'asset_generate_og_image' to `generateOgImage(GenerateOgImageInput.parse(args ?? {}))`.
    case "asset_generate_og_image":
      result = await generateOgImage(GenerateOgImageInput.parse(args ?? {}));
      break;
Behavior5/5

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

Adds substantial behavioral context beyond annotations: deterministic typography, no diffusion, server-side without API key, unsupported inline_svg mode. Informs agent about tool limitations and rendering behavior.

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

Conciseness5/5

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

Three sentences, front-loaded with purpose, no redundant information. Each sentence earns its place.

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

Completeness2/5

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

Despite good purpose and transparency, the description omits details for most parameters (6 of 8 undocumented), and no output schema or return value description. Given 8 parameters and nested objects, the description is incomplete for full parameter understanding.

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

Parameters3/5

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

Adds meaning for mode and with_background_image (inline_svg unsupported, external_prompt_only conditional). However, 0% schema coverage means other parameters (title, subtitle, template, brand_bundle, background_brief, output_dir) are left undescribed. Partial compensation but not comprehensive.

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?

The description clearly states the output ('Render a 1200×630 OG image'), method ('via Satori template'), and key characteristics ('deterministic typography, no diffusion'). Distinguishes from diffusion-based generation tools among siblings.

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?

Provides guidance on mode usage: api is default and server-side, inline_svg is not supported, external_prompt_only is conditional on with_background_image. Lacks explicit comparison to sibling asset generation tools but is clear enough for its specific purpose.

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/MohamedAbdallah-14/prompt-to-asset'

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