generate_meme
Create custom memes with text overlays using pre-configured templates. Fill specific text placeholders for each meme type to generate shareable images.
Instructions
Generate a meme with custom text overlays.
Each meme type has specific named text placeholders that must be filled. Use the 'get_meme_info' tool to see available memes and their placeholder requirements.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| meme_name | Yes | The type of meme to generate | |
| texts | Yes | Dictionary like {"placeholder_name": "Your text here"}. To skip a placeholder, use an empty string explicitly. |
Implementation Reference
- app/server.py:96-156 (handler)Primary handler for the 'generate_meme' tool, registered via @mcp.tool(). Includes input schema validation using Pydantic Annotated and Field, validation of meme configs and placeholders, calls helper to generate image, and returns success/error dict with output path.@mcp.tool() async def generate_meme( meme_name: Annotated[str, Field(description="The type of meme to generate")], texts: Annotated[ dict[str, str], Field( description=( 'Dictionary like {"placeholder_name": "Your text here"}. ' "To skip a placeholder, use an empty string explicitly." ) ), ], ) -> dict: """ Generate a meme with custom text overlays. Each meme type has specific named text placeholders that must be filled. Use the 'get_meme_info' tool to see available memes and their placeholder requirements. """ try: meme_configs = get_meme_configs() if meme_name not in meme_configs: return { "status": "error", "message": f"Unknown meme type: {meme_name}", "available_memes": list(meme_configs.keys()), } config = meme_configs[meme_name] expected_keys = set(config.placeholders.keys()) provided_keys = set(texts.keys()) if provided_keys != expected_keys: missing = expected_keys - provided_keys extra = provided_keys - expected_keys error_parts = [] if missing: error_parts.append(f"missing: {', '.join(missing)}") if extra: error_parts.append(f"unexpected: {', '.join(extra)}") msg = f"Meme '{meme_name}' placeholder mismatch. {'; '.join(error_parts)}." if missing: msg += " To skip a placeholder, use an empty string explicitly." raise ValueError(msg) saved_path = generate_meme_image(meme_name, texts, get_output_dir()) return { "status": "success", "message": "Meme generated successfully", "output_path": str(saved_path.resolve()), "meme_type": meme_name, "texts_used": texts, } except Exception as e: return { "status": "error", "message": f"Error generating meme: {str(e)}", "meme_type": meme_name, }
- app/server.py:47-93 (helper)Helper function that performs the actual meme image generation using Pillow (PIL): loads template, wraps and draws text on placeholders with fonts and strokes, saves to output directory.def generate_meme_image( meme_type: str, texts: dict[str, str], output_dir: Path ) -> Path: """Generate a meme image with the given texts.""" config = get_meme_configs()[meme_type] template_path = get_templates_dir() / config.template_file if not template_path.exists(): raise FileNotFoundError(f"Template not found: {config.template_file}") img = Image.open(template_path) draw = ImageDraw.Draw(img) for name, text in texts.items(): placeholder = config.placeholders[name] try: font = ImageFont.truetype("Impact", placeholder.font_size) except OSError: try: font = ImageFont.truetype(str(get_fallback_font_path()), placeholder.font_size) except OSError: font = ImageFont.load_default(placeholder.font_size) lines = wrap_text(draw, text, font, placeholder.max_width) anchor_map = {"left": "la", "center": "ma", "right": "ra"} anchor = anchor_map[placeholder.align] y_offset = placeholder.y for line in lines: bbox = draw.textbbox((0, 0), line, font=font) line_height = bbox[3] - bbox[1] draw.text( (placeholder.x, y_offset), line, font=font, fill=placeholder.fill, stroke_width=placeholder.stroke_width, stroke_fill=placeholder.stroke_fill, anchor=anchor, ) y_offset += line_height + int(placeholder.font_size * 0.2) output_path = output_dir / f"{int(time.time())}_{meme_type}.jpg" img.save(output_path, "JPEG") return output_path
- app/server.py:18-43 (helper)Supporting utility for wrapping meme text to fit within specified pixel width using font bbox calculations.def wrap_text( draw: ImageDraw.ImageDraw, text: str, font: ImageFont.FreeTypeFont | ImageFont.ImageFont, max_width: int, ) -> list[str]: """Wrap text to fit within max_width pixels.""" lines: list[str] = [] current_line: list[str] = [] for word in text.upper().split(): current_line.append(word) test_line = " ".join(current_line) bbox = draw.textbbox((0, 0), test_line, font=font) if bbox[2] - bbox[0] > max_width: if len(current_line) > 1: current_line.pop() lines.append(" ".join(current_line)) current_line = [word] else: lines.append(test_line) current_line = [] if current_line: lines.append(" ".join(current_line))