Skip to main content
Glama
falahgs

MCP Storybook Image Generator

by falahgs

generate_storybook_image

Generate 3D cartoon storybook illustrations with matching stories from text prompts. Supports multiple art styles including watercolor, pixel art, hand drawn, and claymation.

Instructions

Generates a 3D style cartoon image with a children's story based on the given prompt

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
promptYesThe prompt describing the storybook scene to generate
fileNameYesBase name for the output files (without extension)
artStyleNoThe art style for the image (default: '3d cartoon')

Implementation Reference

  • src/index.ts:313-341 (registration)
    Registration of the 'generate_storybook_image' tool, including name, description, and input schema in the ListToolsRequestSchema handler.
      return {
        tools: [
          // Image with story generation tool
          {
            name: "generate_storybook_image",
            description: "Generates a 3D style cartoon image with a children's story based on the given prompt",
            inputSchema: {
              type: "object",
              properties: {
                prompt: {
                  type: "string",
                  description: "The prompt describing the storybook scene to generate"
                },
                fileName: {
                  type: "string",
                  description: "Base name for the output files (without extension)"
                },
                artStyle: {
                  type: "string",
                  description: "The art style for the image (default: '3d cartoon')",
                  enum: ["3d cartoon", "watercolor", "pixel art", "hand drawn", "claymation"]
                }
              },
              required: ["prompt", "fileName"]
            }
          }
        ]
      };
    });
  • Input schema definition for the generate_storybook_image tool.
    inputSchema: {
      type: "object",
      properties: {
        prompt: {
          type: "string",
          description: "The prompt describing the storybook scene to generate"
        },
        fileName: {
          type: "string",
          description: "Base name for the output files (without extension)"
        },
        artStyle: {
          type: "string",
          description: "The art style for the image (default: '3d cartoon')",
          enum: ["3d cartoon", "watercolor", "pixel art", "hand drawn", "claymation"]
        }
      },
      required: ["prompt", "fileName"]
    }
  • Core handler execution logic for the generate_storybook_image tool within the CallToolRequestSchema handler. Generates story, image using Google Gemini AI, saves files (image, story, HTML preview), and optionally opens in browser.
    if (toolName === "generate_storybook_image") {
      const { prompt, fileName, artStyle = "3d cartoon" } = args;
      
      // Generate the story first
      const story = await generateStory(prompt);
      
      // Create story filename
      const storyFileName = `${fileName.replace(/\.[^/.]+$/, '')}_story.txt`;
      const { savedPath: storyPath } = await saveStoryWithProperPath(story, storyFileName);
      
      // Add art style to the prompt
      const imagePrompt = `Generate a ${artStyle} style image for a children's storybook with this scene: ${prompt}. 
      The image should be colorful, playful, and child-friendly. Use bright colors, appealing characters, 
      and a fun, engaging style that appeals to children.`;
      
      const contents = [
        {
          role: 'user',
          parts: [
            {
              text: imagePrompt,
            },
          ],
        },
      ];
    
      try {
        const response = await genAI.models.generateContentStream({
          model: imageModel,
          config: imageGenConfig,
          contents,
        });
    
        for await (const chunk of response) {
          if (!chunk.candidates || !chunk.candidates[0].content || !chunk.candidates[0].content.parts) {
            continue;
          }
          if (chunk.candidates[0].content.parts[0].inlineData) {
            const inlineData = chunk.candidates[0].content.parts[0].inlineData;
            const buffer = Buffer.from(inlineData.data || '', 'base64');
            
            // Create an output filename with timestamp for uniqueness
            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
            const outputFileName = fileName.endsWith('.png') 
              ? fileName 
              : `${fileName}_${timestamp}.png`;
            
            // Find appropriate save location
            const { savedPath } = await saveImageWithProperPath(buffer, outputFileName);
            
            // Create HTML preview that includes the story
            const htmlContent = `
            <!DOCTYPE html>
            <html>
            <head>
              <title>Storybook Preview</title>
              <style>
                body { font-family: Arial, sans-serif; margin: 20px; background-color: #f9f9f9; }
                .container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
                .image-container { text-align: center; margin: 20px 0; }
                img { max-width: 100%; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
                .prompt { margin: 10px 0; color: #666; font-style: italic; }
                .story { margin: 20px 0; line-height: 1.6; white-space: pre-line; }
                .path { font-family: monospace; margin: 10px 0; font-size: 12px; color: #888; }
                h1 { color: #4a4a4a; text-align: center; }
                h2 { color: #5a5a5a; }
              </style>
            </head>
            <body>
              <div class="container">
                <h1>Storybook Image</h1>
                <div class="prompt">Prompt: ${prompt}</div>
                <div class="image-container">
                  <img src="file://${savedPath}" alt="Generated storybook image">
                </div>
                <h2>The Story</h2>
                <div class="story">${story}</div>
                <div class="path">Image saved to: ${savedPath}</div>
                <div class="path">Story saved to: ${storyPath}</div>
              </div>
            </body>
            </html>
            `;
    
            // Create and save HTML file
            const htmlFileName = `${outputFileName.replace('.png', '')}_preview.html`;
            const htmlPath = path.join(path.dirname(savedPath), htmlFileName);
            
            // Ensure directory exists before writing
            ensureDirectoryExists(path.dirname(htmlPath));
            fs.writeFileSync(htmlPath, htmlContent, 'utf8');
    
            // Try to open in browser
            try {
              await openInBrowser(htmlPath);
            } catch (error) {
              console.warn('Could not open browser automatically:', error);
            }
    
            return {
              toolResult: {
                success: true,
                imagePath: savedPath,
                storyPath: storyPath,
                htmlPath: htmlPath,
                content: [
                  {
                    type: "text",
                    text: `Storybook generated successfully!\nImage saved to: ${savedPath}\nStory saved to: ${storyPath}\nPreview HTML: ${htmlPath}`
                  }
                ],
                message: "Storybook image and story generated and saved"
              }
            };
          }
        }
        
        throw new McpError(ErrorCode.InternalError, "No image data received from the API");
      } catch (error) {
        console.error('Error generating image:', error);
        if (error instanceof Error) {
          throw new McpError(ErrorCode.InternalError, `Failed to generate image: ${error.message}`);
        }
        throw new McpError(ErrorCode.InternalError, 'An unknown error occurred');
      }
  • Helper function to generate the children's story text using Gemini AI based on the prompt.
    async function generateStory(prompt: string): Promise<string> {
      try {
        const storyPrompt = `Write a short children's story based on the following prompt: "${prompt}". 
        The story should be engaging, appropriate for young children, have a clear beginning, middle, and end, 
        and convey a positive message or lesson. Keep it under 500 words.`;
        
        const contents = [
          {
            role: 'user',
            parts: [
              {
                text: storyPrompt,
              },
            ],
          },
        ];
    
        // Using the same model, but for text content
        const response = await genAI.models.generateContentStream({
          model: storyModel,
          contents
        });
        
        // Collect all text chunks
        let storyText = '';
        for await (const chunk of response) {
          if (chunk.candidates && chunk.candidates[0].content && chunk.candidates[0].content.parts) {
            const part = chunk.candidates[0].content.parts[0];
            if (typeof part.text === 'string') {
              storyText += part.text;
            }
          }
        }
        
        return storyText || "Once upon a time... (Story generation failed, but the image has been created)";
      } catch (error) {
        console.error('Error generating story:', error);
        return `Once upon a time... (Story generation failed: ${error instanceof Error ? error.message : String(error)})`;
      }
    }
  • Helper function to save the generated image buffer to desktop or local directory with proper path handling.
    async function saveImageWithProperPath(buffer: Buffer, fileName: string): Promise<{savedPath: string}> {
      try {
        // Check if SAVE_TO_DESKTOP is true
        if (process.env.SAVE_TO_DESKTOP === "true") {
          // Desktop saving logic
          const desktopSaveDir = path.join(getDesktopPath(), 'storybook-images');
          
          debugLog(`Saving to desktop directory: ${desktopSaveDir}`);
          debugLog(`Platform: ${os.platform()}`);
          
          // Ensure save directory exists
          ensureDirectoryExists(desktopSaveDir);
          
          // Create full path and normalize for OS
          const outputPath = path.normalize(path.join(desktopSaveDir, fileName));
          
          // Save the file
          fs.writeFileSync(outputPath, buffer);
          debugLog(`Image saved successfully to: ${outputPath}`);
          
          return { savedPath: outputPath };
        } else {
          // Save locally in the server directory
          const serverDir = process.cwd();
          const localSaveDir = path.join(serverDir, 'storybook-images');
          
          debugLog(`Saving to server directory: ${localSaveDir}`);
          
          // Ensure output directory exists
          ensureDirectoryExists(localSaveDir);
          
          // Create full path and normalize for OS
          const outputPath = path.normalize(path.join(localSaveDir, fileName));
          
          // Save the file
          fs.writeFileSync(outputPath, buffer);
          debugLog(`Image saved successfully to server path: ${outputPath}`);
          
          return { savedPath: outputPath };
        }
      } catch (error) {
        console.error('Error saving image:', error);
        // Fallback to output directory
        const fallbackDir = path.join(process.cwd(), 'output');
        ensureDirectoryExists(fallbackDir);
        const fallbackPath = path.join(fallbackDir, fileName);
        fs.writeFileSync(fallbackPath, buffer);
        debugLog(`Fallback save to: ${fallbackPath}`);
        return { savedPath: fallbackPath };
      }
    }
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions the output (a 3D cartoon image with a story) but lacks details on permissions, rate limits, file formats, or error handling. For a generative tool with zero annotation coverage, this is a significant gap in transparency.

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?

The description is a single, efficient sentence that directly states the tool's function without unnecessary words. It is appropriately sized and front-loaded, making it easy to understand quickly.

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?

Given the complexity of a generative tool with no annotations and no output schema, the description is incomplete. It does not explain the return values (e.g., image format, story text), error conditions, or operational limits, leaving critical gaps for agent usage.

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?

Schema description coverage is 100%, so the schema fully documents all three parameters (prompt, fileName, artStyle). The description adds no additional parameter semantics beyond what the schema provides, such as examples or constraints, meeting the baseline for high coverage.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Generates a 3D style cartoon image with a children's story based on the given prompt.' It specifies the action (generates), resource (image with story), and style (3D cartoon). However, with no sibling tools, it cannot demonstrate differentiation from alternatives, preventing a perfect score.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives, prerequisites, or exclusions. It only states what the tool does without context for its application, leaving the agent to infer usage scenarios.

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/falahgs/MCP-Storybook-Image-Generator'

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