gemini_generate_image
Generate images with Google Gemini AI while maintaining consistent styles across sessions using reference images and aspect ratio controls.
Instructions
Generate images using Gemini's image generation capabilities. Supports session-based image consistency for maintaining style/character across multiple generations.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | Yes | Description of the image to generate | |
| aspect_ratio | No | Aspect ratio for the generated image. Overrides session setting if provided. | |
| output_path | No | Optional path where to save the generated image. If not provided, saves to ~/Documents/nanobanana_generated/ | |
| conversation_id | No | Session ID for maintaining image history and consistency across generations | |
| use_image_history | No | If true, includes previous generated images from this session for style/character consistency | |
| reference_images | No | Array of file paths to reference images for style/character consistency | |
| enable_google_search | No | Enable Google Search for real-world reference grounding |
Implementation Reference
- src/index.ts:561-722 (handler)The primary handler for the 'gemini_generate_image' MCP tool, which orchestrates image generation by gathering context, preparing reference images, calling the Gemini API, and saving the resulting file.
case "gemini_generate_image": { const { prompt, aspect_ratio, output_path, conversation_id = "default", use_image_history = false, reference_images = [], } = args as any; try { // 대화 컨텍스트 가져오기/생성 const context = getOrCreateContext(conversation_id); // Validate directly passed aspect_ratio if (aspect_ratio && !VALID_ASPECT_RATIOS.includes(aspect_ratio as AspectRatio)) { return { content: [{ type: "text", text: `Invalid aspect ratio: ${aspect_ratio}. Valid: ${VALID_ASPECT_RATIOS.join(", ")}`, }], isError: true, }; } // Priority: direct param > session setting const effectiveAspectRatio = aspect_ratio ?? context.aspectRatio; // aspectRatio 필수 체크 (둘 다 없으면 에러) if (effectiveAspectRatio === null) { return { content: [{ type: "text", text: `Error: Aspect ratio not specified. Either pass aspect_ratio parameter or call set_aspect_ratio first.\nValid ratios: ${VALID_ASPECT_RATIOS.join(", ")}`, }], isError: true, }; } // contents 구성: 참조 이미지 + 히스토리 이미지 + 프롬프트 const parts: GeminiImageRequestPart[] = []; const failedReferenceImages: Array<{ path: string; reason: string }> = []; // 1. 수동 지정 참조 이미지 추가 if (reference_images && reference_images.length > 0) { for (const imgPath of reference_images) { try { let resolvedPath = imgPath; if (!path.isAbsolute(resolvedPath)) { resolvedPath = path.join(process.cwd(), resolvedPath); } const base64 = await imageToBase64(resolvedPath); parts.push({ inlineData: { mimeType: "image/png", data: base64, }, }); } catch (error) { failedReferenceImages.push({ path: imgPath, reason: error instanceof Error ? error.message : String(error), }); } } } // 2. 히스토리 이미지 추가 (일관성 유지용) if (use_image_history && context.imageHistory.length > 0) { const recentImages = context.imageHistory.slice(-MAX_REFERENCE_IMAGES); for (const img of recentImages) { parts.push({ inlineData: { mimeType: img.mimeType, data: img.base64Data, }, }); } } // 3. 프롬프트 추가 (히스토리 이미지가 있으면 일관성 유지 지시 추가) let finalPrompt = prompt; if (use_image_history && context.imageHistory.length > 0) { finalPrompt = `${prompt}\n\nIMPORTANT: Maintain visual consistency with the provided reference images (same style, character appearance, color palette).`; } parts.push({ text: finalPrompt }); // REST API 직접 호출 (세션 모델 우선, 없으면 환경 변수 기본값) const effectiveModel = context.selectedModel ?? IMAGE_MODEL; const apiResponse = await callGeminiImageAPI(parts, effectiveAspectRatio, effectiveModel); if (apiResponse.error) { return { content: [{ type: "text", text: `Image generation failed: ${apiResponse.error}\n${apiResponse.textResponse}`, }], isError: true, }; } if (!apiResponse.imageData) { return { content: [{ type: "text", text: `Image generation failed.\nPrompt: "${prompt}"\n` + (apiResponse.textResponse ? `Model response: ${apiResponse.textResponse}` : 'No image returned from model'), }], isError: true, }; } // Determine output path - always ensure PNG extension let finalPath = output_path; if (!finalPath) { const homeDir = os.homedir(); const tempDir = path.join(homeDir, 'Documents', 'nanobanana_generated'); await fs.mkdir(tempDir, { recursive: true }); const filename = `generated_${Date.now()}.png`; finalPath = path.join(tempDir, filename); } else { if (!path.isAbsolute(finalPath)) { finalPath = path.join(process.cwd(), finalPath); } if (!finalPath.toLowerCase().endsWith('.png')) { finalPath = finalPath.replace(/\.[^/.]+$/, '') + '.png'; } } // Save image const buffer = Buffer.from(apiResponse.imageData, 'base64'); await saveImageFromBuffer(buffer, finalPath); // 생성된 이미지를 히스토리에 저장 addImageToHistory(context, { id: generateImageId(), filePath: finalPath, base64Data: apiResponse.imageData, mimeType: "image/png", prompt: prompt, timestamp: Date.now(), type: "generated", }); let successText = `Image generated successfully!\n` + `Prompt: "${prompt}"\n` + `Saved to: ${finalPath}\n` + `Session: ${conversation_id} (history: ${context.imageHistory.length} images)`; if (failedReferenceImages.length > 0) { successText += `\n\nWarning: ${failedReferenceImages.length} reference image(s) could not be loaded:\n`; successText += failedReferenceImages.map(f => ` - ${f.path}: ${f.reason}`).join('\n'); } if (apiResponse.textResponse) { successText += `\n\nModel response: ${apiResponse.textResponse}`; } return { content: [ ...(RETURN_PATH_ONLY ? [] : [{ type: "image", data: apiResponse.imageData, mimeType: "image/png" }]), { type: "text", text: successText },