Skip to main content
Glama

generate_orly_cover

Create custom O'Reilly-style parody book covers with personalized titles, subtitles, authors, and visual themes. The tool generates high-resolution images displayed directly in chat, offering options for guide text placement, image codes, and scalable resolutions.

Instructions

Generate an O'RLY? book cover image.

This tool creates a parody book cover in the style of O'Reilly books with custom title, subtitle, author, and styling options. The generated image will be displayed directly in the chat. Args: title (str): The main title for the book cover subtitle (str): The subtitle text (appears at the top) author (str): The author name (appears at the bottom right) image_code (str, optional): Image code 1-40 for the animal/object on the cover. Defaults to random. theme (str, optional): Color theme 0-16. Defaults to random. guide_text_placement (str, optional): Where to place "guide" text - 'top_left', 'top_right', 'bottom_left', 'bottom_right'. Defaults to 'bottom_right'. guide_text (str, optional): The guide text to display. Defaults to 'The Definitive Guide' As often as possible, try not to just use "The Definitive Guide" but something more creative. scale (float, optional): Scale factor for image resolution. 1.0 = 500x700px, 2.0 = 1000x1400px, 3.0 = 1500x2100px (default). Higher values create larger, higher resolution images. Returns: Image: The generated O'RLY? book cover image that will be displayed in chat.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
authorNoAnonymous
guide_textNoThe Definitive Guide
guide_text_placementNobottom_right
image_codeNo
scaleNo
subtitleNo
themeNo
titleYes

Implementation Reference

  • The MCP tool handler function 'generate_orly_cover' that performs input validation, sets random defaults for image_code and theme if not provided, calls the underlying 'generate_image' helper, and returns an MCP Image object for chat display.
    def generate_orly_cover( title: str, subtitle: str = "", author: str = "Anonymous", image_code: str = None, theme: str = None, guide_text_placement: str = "bottom_right", guide_text: str = "The Definitive Guide", scale: float = 3.0 ) -> Image: if not title.strip(): raise ValueError("Title cannot be empty.") try: # Set defaults if not provided if image_code is None: import random import time # Seed the random number generator with current time to improve randomness random.seed(time.time()) image_code = str(random.randrange(1, 41)) else: # Validate image_code is in range 1-40 try: img_num = int(image_code) if not (1 <= img_num <= 40): raise ValueError(f"Image code must be between 1 and 40, got {image_code}") image_code = str(img_num) except ValueError as ve: if "Image code must be between" in str(ve): raise ve raise ValueError(f"Image code must be a number between 1 and 40, got '{image_code}'") if theme is None: import random theme = str(random.randrange(0, 17)) else: # Validate theme is in range 0-16 try: theme_num = int(theme) if not (0 <= theme_num <= 16): raise ValueError(f"Theme must be between 0 and 16, got {theme}") theme = str(theme_num) except ValueError as ve: if "Theme must be between" in str(ve): raise ve raise ValueError(f"Theme must be a number between 0 and 16, got '{theme}'") # Validate guide_text_placement valid_placements = ['top_left', 'top_right', 'bottom_left', 'bottom_right'] if guide_text_placement not in valid_placements: raise ValueError(f"guide_text_placement must be one of {valid_placements}, got '{guide_text_placement}'") # Validate scale if scale <= 0: raise ValueError(f"Scale must be greater than 0, got {scale}") if scale > 10: raise ValueError(f"Scale must be 10 or less to avoid excessive memory usage, got {scale}") # Generate the image image_path = generate_image( title=title, topText=subtitle, author=author, image_code=image_code, theme=theme, guide_text_placement=guide_text_placement, guide_text=guide_text, scale=scale ) # Return the image using the Image helper class for direct display return Image(path=image_path) except Exception as e: raise RuntimeError(f"Error generating book cover: {str(e)}") from e
  • The @mcp.tool decorator registers the 'generate_orly_cover' tool with the FastMCP server, providing the tool description and parameter schema.
    @mcp.tool( description="""Generate an O'RLY? book cover image. This tool creates a parody book cover in the style of O'Reilly books with custom title, subtitle, author, and styling options. The generated image will be displayed directly in the chat. Args: title (str): The main title for the book cover subtitle (str): The subtitle text (appears at the top) author (str): The author name (appears at the bottom right) image_code (str, optional): Image code 1-40 for the animal/object on the cover. Defaults to random. theme (str, optional): Color theme 0-16. Defaults to random. guide_text_placement (str, optional): Where to place "guide" text - 'top_left', 'top_right', 'bottom_left', 'bottom_right'. Defaults to 'bottom_right'. guide_text (str, optional): The guide text to display. Defaults to 'The Definitive Guide' As often as possible, try not to just use "The Definitive Guide" but something more creative. scale (float, optional): Scale factor for image resolution. 1.0 = 500x700px, 2.0 = 1000x1400px, 3.0 = 1500x2100px (default). Higher values create larger, higher resolution images. Returns: Image: The generated O'RLY? book cover image that will be displayed in chat. """ )
  • Core helper function that generates the O'RLY book cover image using Pillow (PIL), including caching, theme colors, font rendering, text clamping, unicode sanitization, and overlaying the image asset. Called by the tool handler.
    def generate_image(title, topText, author, image_code, theme, guide_text_placement='bottom_right', guide_text='The Definitive Guide', scale=1.0): cache_string = title + "_" + topText + "_" + author + "_" + image_code + "_" + theme + "_" + guide_text_placement + "_" + guide_text + "_" + str(scale) cached = get(cache_string) if cached: print("Cache hit") try: final_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), ('%s.png'%datetime.datetime.now()))) width = int(500 * scale) height = int(700 * scale) im = Image.frombytes('RGBA', (width, height), cached) im.save(final_path) im.close() return final_path except Exception as e: print(str(e)) else: print("Cache miss") themeColors = { "0" : (85,19,93,255), "1" : (113,112,110,255), "2" : (128,27,42,255), "3" : (184,7,33,255), "4" : (101,22,28,255), "5" : (80,61,189,255), "6" : (225,17,5,255), "7" : (6,123,176,255), "8" : (247,181,0,255), "9" : (0,15,118,255), "10" : (168,0,155,255), "11" : (0,132,69,255), "12" : (0,153,157,255), "13" : (1,66,132,255), "14" : (177,0,52,255), "15" : (55,142,25,255), "16" : (133,152,0,255), } themeColor = themeColors[theme] # Base dimensions: 500x700, scaled by the scale parameter width = int(500 * scale) height = int(700 * scale) im = Image.new('RGBA', (width, height), "white") font_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'fonts', 'Garamond Light.ttf')) font_path_helv = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'fonts', 'HelveticaNeue-Medium.otf')) font_path_helv_bold = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'fonts', 'Helvetica Bold.ttf')) font_path_italic = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'fonts', 'Garamond LightItalic.ttf')) # Font sizes scaled by the scale parameter topFont = ImageFont.truetype(font_path_italic, int(20 * scale)) subtitleFont = ImageFont.truetype(font_path_italic, int(34 * scale)) authorFont = ImageFont.truetype(font_path_italic, int(24 * scale)) titleFont = ImageFont.truetype(font_path, int(62 * scale)) oriellyFont = ImageFont.truetype(font_path_helv, int(28 * scale)) questionMarkFont = ImageFont.truetype(font_path_helv_bold, int(16 * scale)) dr = ImageDraw.Draw(im) dr.rectangle(((int(20*scale),0),(width-int(20*scale),int(10*scale))), fill=themeColor) topText = sanitzie_unicode(topText, font_path_italic) textWidth, textHeight = get_text_size(dr, topText, topFont) textPositionX = (width/2) - (textWidth/2) dr.text((textPositionX,int(10*scale)), topText, fill='black', font=topFont) author = sanitzie_unicode(author, font_path_italic) textWidth, textHeight = get_text_size(dr, author, authorFont) textPositionX = width - textWidth - int(20*scale) textPositionY = height - textHeight - int(20*scale) dr.text((textPositionX,textPositionY), author, fill='black', font=authorFont) oreillyText = "O RLY" textWidth, textHeight = get_text_size(dr, oreillyText, oriellyFont) textPositionX = int(20*scale) textPositionY = height - textHeight - int(20*scale) dr.text((textPositionX,textPositionY), oreillyText, fill='black', font=oriellyFont) oreillyText = "?" textPositionX = textPositionX + textWidth dr.text((textPositionX,textPositionY-1), oreillyText, fill=themeColor, font=questionMarkFont) titleFont, newTitle = clamp_title_text(sanitzie_unicode(title, font_path), width-int(80*scale), scale) if newTitle == None: raise ValueError('Title too long') textWidth, textHeight = get_text_size(dr, newTitle, titleFont, multiline=True) title_box_y = int(400 * scale) dr.rectangle([(int(20*scale),title_box_y),(width-int(20*scale),title_box_y + textHeight + int(40*scale))], fill=themeColor) subtitle = sanitzie_unicode(guide_text, font_path_italic) if guide_text_placement == 'top_left': textWidth, textHeight = get_text_size(dr, subtitle, subtitleFont) textPositionX = int(20*scale) textPositionY = title_box_y - textHeight - int(2*scale) elif guide_text_placement == 'top_right': textWidth, textHeight = get_text_size(dr, subtitle, subtitleFont) textPositionX = width - int(20*scale) - textWidth textPositionY = title_box_y - textHeight - int(2*scale) elif guide_text_placement == 'bottom_left': textPositionY = title_box_y + textHeight + int(40*scale) textWidth, textHeight = get_text_size(dr, subtitle, subtitleFont) textPositionX = int(20*scale) else:#bottom_right is default textPositionY = title_box_y + textHeight + int(40*scale) textWidth, textHeight = get_text_size(dr, subtitle, subtitleFont) textPositionX = width - int(20*scale) - textWidth dr.text((textPositionX,textPositionY), subtitle, fill='black', font=subtitleFont) dr.multiline_text((int(40*scale),title_box_y + int(20*scale)), newTitle, fill='white', font=titleFont) cover_image_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'images', ('%s.png'%image_code))) coverImage = Image.open(cover_image_path).convert('RGBA') # Scale the animal image by the scale parameter original_size = coverImage.size scaled_size = (int(original_size[0] * scale), int(original_size[1] * scale)) coverImage = coverImage.resize(scaled_size, Image.LANCZOS) offset = (int(80*scale),int(40*scale)) im.paste(coverImage, offset, coverImage) final_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), ('%s.png'%datetime.datetime.now()))) im.save(final_path) set(cache_string, im.tobytes()) im.close() return final_path

Other Tools

Related 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/princefishthrower/orly-mcp'

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