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
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | Yes | The prompt describing the storybook scene to generate | |
| fileName | Yes | Base name for the output files (without extension) | |
| artStyle | No | The art style for the image (default: '3d cartoon') |
Implementation Reference
- src/index.ts:349-473 (handler)Core execution logic for the generate_storybook_image tool: parses args, generates story using Gemini, creates enhanced image prompt, generates and processes image stream, saves image/PNG, story/TXT, HTML preview, opens in browser, returns paths and success message.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'); }
- src/index.ts:319-337 (schema)Input schema defining parameters for generate_storybook_image: required prompt and fileName, optional artStyle enum.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"] }
- src/index.ts:316-338 (registration)Tool specification registered in ListToolsRequestSchema handler, including name, description, and input schema.{ 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"] } }
- src/index.ts:270-309 (helper)Helper function generates children's story text using Gemini 'gemini-1.5-pro' model from input 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)})`; } }
- src/index.ts:142-192 (helper)Cross-platform helper to save image buffer to 'storybook-images' dir on desktop (if SAVE_TO_DESKTOP=true) or locally, with fallback.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 }; } }