Skip to main content
Glama
index.ts19.9 kB
/** * Nano Banana MCP Server * * MCP server for Google's Nano Banana Pro (Gemini 3 Pro Image) and * Nano Banana (Gemini 2.5 Flash Image) models for AI image generation. * * Features: * - Text-to-image generation * - Image editing with text prompts * - Multi-image composition (up to 14 reference images) * - Various aspect ratios and resolutions up to 4K * - Google Search grounding for real-time information * * Authentication: * Set the GEMINI_API_KEY environment variable with your API key from * https://aistudio.google.com/apikey */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import express from "express"; import { MODELS, ModelInfo, AspectRatio, Resolution } from "./types.js"; import { GenerateImageInputSchema, EditImageInputSchema, ComposeImagesInputSchema, ListModelsInputSchema, type GenerateImageInput, type EditImageInput, type ComposeImagesInput, type ListModelsInput, } from "./schemas.js"; import { generateImage, editImage, editImageWithReferences, getApiKey, } from "./services/gemini-client.js"; // Model information for list_models tool const MODEL_INFO: ModelInfo[] = [ { id: MODELS.NANO_BANANA_PRO, name: "Nano Banana Pro (Gemini 3 Pro Image)", description: "State-of-the-art image generation and editing model. Optimized for professional asset production with advanced reasoning, high-fidelity text rendering, and up to 4K resolution.", features: [ "High-resolution output (1K, 2K, 4K)", "Advanced text rendering (legible, stylized text)", "Google Search grounding for real-time data", "Up to 14 reference images for composition", "Thinking mode for complex prompts", "Multi-turn conversation for iterative refinement", ], maxResolution: "4K", }, { id: MODELS.NANO_BANANA, name: "Nano Banana (Gemini 2.5 Flash Image)", description: "Fast, low-latency image generation model. Great for quick experimentation, iteration, and high-volume generation.", features: [ "Fast generation speed", "Low latency", "Good for batch processing", "Text-to-image generation", "Image editing", "Multi-turn conversation", ], maxResolution: "1K", }, ]; // Initialize MCP Server const server = new McpServer({ name: "nanobanana-mcp-server", version: "1.0.0", }); // ============================================================================ // Tool: nanobanana_generate_image // ============================================================================ server.registerTool( "nanobanana_generate_image", { title: "Generate Image with Nano Banana", description: `Generate high-quality images from text descriptions using Google's Nano Banana models. This tool creates images from natural language prompts. For best results, be descriptive about: - Subject and composition - Style (photorealistic, illustration, painting, etc.) - Lighting and atmosphere - Colors and mood - Camera angle and lens (for photorealistic images) Args: - prompt (string, required): Text description of the image to generate - model (string): Model to use. Options: * 'gemini-3-pro-image-preview' (Nano Banana Pro) - Best quality, 4K, text rendering * 'gemini-2.5-flash-image' (Nano Banana) - Fast generation Default: Nano Banana Pro - aspect_ratio (string): Image aspect ratio. Options: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 - resolution (string): Image resolution (Pro only). Options: 1K, 2K, 4K - use_google_search (boolean): Enable real-time information grounding (Pro only) Returns: - success (boolean): Whether generation succeeded - imageData (string): Base64-encoded image data - mimeType (string): Image MIME type (usually image/png) - text (string): Any accompanying text from the model - error (string): Error message if generation failed Examples: - "A photorealistic portrait of an astronaut on Mars at sunset" - "Kawaii-style sticker of a happy corgi with a transparent background" - "Minimalist logo for 'TechStart' in blue and white, modern sans-serif font" Error Handling: - Returns error if GEMINI_API_KEY is not set - Returns error if API rate limit exceeded (try again later) - Returns error if content policy violated`, inputSchema: GenerateImageInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, }, async (params: GenerateImageInput) => { try { // Validate API key is available getApiKey(); const result = await generateImage({ prompt: params.prompt, model: params.model, aspectRatio: params.aspect_ratio as AspectRatio | undefined, resolution: params.resolution as Resolution | undefined, useGoogleSearch: params.use_google_search, }); const output = { success: true, model: params.model || MODELS.NANO_BANANA_PRO, prompt: params.prompt, imageData: result.imageData, mimeType: result.mimeType, text: result.text, }; // Format text response let textContent = `Image generated successfully!\n`; textContent += `**Model:** ${output.model}\n`; textContent += `**Prompt:** ${params.prompt.substring(0, 100)}${params.prompt.length > 100 ? "..." : ""}`; if (result.text) { textContent += `\n\n**Model Response:**\n${result.text}`; } // Return image in content array so Claude.ai can display it const content: Array< | { type: "text"; text: string } | { type: "image"; data: string; mimeType: string } > = []; if (result.imageData && result.mimeType) { content.push({ type: "image" as const, data: result.imageData, mimeType: result.mimeType, }); } content.push({ type: "text" as const, text: textContent }); return { content, structuredContent: output, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; const output = { success: false, model: params.model || MODELS.NANO_BANANA_PRO, prompt: params.prompt, error: errorMessage, }; return { content: [ { type: "text", text: `❌ Image generation failed\n\n**Error:** ${errorMessage}\n\n**Troubleshooting:**\n- Ensure GEMINI_API_KEY environment variable is set\n- Check if your prompt complies with content policies\n- Try again if rate limited`, }, ], structuredContent: output, }; } } ); // ============================================================================ // Tool: nanobanana_edit_image // ============================================================================ server.registerTool( "nanobanana_edit_image", { title: "Edit Image with Nano Banana", description: `Edit an existing image using text prompts with Google's Nano Banana models. Provide an image and describe your desired changes. The model will: - Add, remove, or modify elements - Change style, lighting, or colors - Adjust composition - Apply filters or effects The model maintains the original image's style and context while applying changes. Args: - prompt (string, required): Description of the edit to make - image_base64 (string, required): Base64-encoded image data (no data URI prefix) - image_mime_type (string, required): MIME type of the image (e.g., 'image/png', 'image/jpeg') - model (string): Model to use. Default: Nano Banana Pro - aspect_ratio (string): Output aspect ratio. Options: 1:1, 2:3, 3:2, etc. - resolution (string): Output resolution (Pro only). Options: 1K, 2K, 4K Returns: - success (boolean): Whether editing succeeded - imageData (string): Base64-encoded edited image - mimeType (string): Image MIME type - text (string): Any accompanying text from the model - error (string): Error message if editing failed Examples: - "Add a small wizard hat on the cat's head" - "Change the background to a sunset beach" - "Make this image look like a Van Gogh painting" - "Remove the person in the background" Error Handling: - Returns error if image data is invalid - Returns error if MIME type is unsupported - Returns error if content policy violated`, inputSchema: EditImageInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, }, async (params: EditImageInput) => { try { getApiKey(); const result = await editImage({ prompt: params.prompt, imageBase64: params.image_base64, imageMimeType: params.image_mime_type, model: params.model, aspectRatio: params.aspect_ratio as AspectRatio | undefined, resolution: params.resolution as Resolution | undefined, }); const output = { success: true, model: params.model || MODELS.NANO_BANANA_PRO, prompt: params.prompt, imageData: result.imageData, mimeType: result.mimeType, text: result.text, }; let textContent = `Image edited successfully!\n`; textContent += `**Model:** ${output.model}\n`; textContent += `**Edit:** ${params.prompt.substring(0, 100)}${params.prompt.length > 100 ? "..." : ""}`; if (result.text) { textContent += `\n\n**Model Response:**\n${result.text}`; } // Return image in content array so Claude.ai can display it const content: Array< | { type: "text"; text: string } | { type: "image"; data: string; mimeType: string } > = []; if (result.imageData && result.mimeType) { content.push({ type: "image" as const, data: result.imageData, mimeType: result.mimeType, }); } content.push({ type: "text" as const, text: textContent }); return { content, structuredContent: output, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; const output = { success: false, model: params.model || MODELS.NANO_BANANA_PRO, prompt: params.prompt, error: errorMessage, }; return { content: [ { type: "text", text: `❌ Image editing failed\n\n**Error:** ${errorMessage}`, }, ], structuredContent: output, }; } } ); // ============================================================================ // Tool: nanobanana_compose_images // ============================================================================ server.registerTool( "nanobanana_compose_images", { title: "Compose Multiple Images with Nano Banana Pro", description: `Compose new images using multiple reference images with Nano Banana Pro. Use up to 14 reference images to: - Create group compositions - Transfer styles between images - Maintain character consistency across scenes - Combine objects from different images Limits: - Up to 6 images of objects for high-fidelity inclusion - Up to 5 images of humans for character consistency - Total maximum: 14 images Args: - prompt (string, required): Description of how to compose the images - images (array, required): Array of image objects with: * base64 (string): Base64-encoded image data * mime_type (string): Image MIME type - model (string): Must be Nano Banana Pro (gemini-3-pro-image-preview) - aspect_ratio (string): Output aspect ratio - resolution (string): Output resolution (1K, 2K, 4K) Returns: - success (boolean): Whether composition succeeded - imageData (string): Base64-encoded composed image - mimeType (string): Image MIME type - text (string): Any accompanying text - error (string): Error message if failed Examples: - "Create a group photo of these 5 people at a beach" - "Apply the style of the first image to the subject in the second" - "Combine these product images into a catalog layout"`, inputSchema: ComposeImagesInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, }, async (params: ComposeImagesInput) => { try { getApiKey(); const images = params.images.map((img) => ({ base64: img.base64, mimeType: img.mime_type, })); const result = await editImageWithReferences({ prompt: params.prompt, images, model: params.model, aspectRatio: params.aspect_ratio as AspectRatio | undefined, resolution: params.resolution as Resolution | undefined, }); const output = { success: true, model: MODELS.NANO_BANANA_PRO, prompt: params.prompt, imageCount: params.images.length, imageData: result.imageData, mimeType: result.mimeType, text: result.text, }; let textContent = `Images composed successfully!\n`; textContent += `**Model:** ${MODELS.NANO_BANANA_PRO}\n`; textContent += `**Input Images:** ${params.images.length}\n`; textContent += `**Prompt:** ${params.prompt.substring(0, 100)}${params.prompt.length > 100 ? "..." : ""}`; if (result.text) { textContent += `\n\n**Model Response:**\n${result.text}`; } // Return image in content array so Claude.ai can display it const content: Array< | { type: "text"; text: string } | { type: "image"; data: string; mimeType: string } > = []; if (result.imageData && result.mimeType) { content.push({ type: "image" as const, data: result.imageData, mimeType: result.mimeType, }); } content.push({ type: "text" as const, text: textContent }); return { content, structuredContent: output, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [ { type: "text", text: `❌ Image composition failed\n\n**Error:** ${errorMessage}`, }, ], structuredContent: { success: false, model: MODELS.NANO_BANANA_PRO, prompt: params.prompt, error: errorMessage, }, }; } } ); // ============================================================================ // Tool: nanobanana_list_models // ============================================================================ server.registerTool( "nanobanana_list_models", { title: "List Nano Banana Models", description: `List available Nano Banana image generation models and their capabilities. Returns information about: - Model IDs for API calls - Model names and descriptions - Features and capabilities - Maximum resolution supported Args: - response_format (string): Output format. Options: 'markdown' (default), 'json' Returns: - models: Array of model information objects with id, name, description, features, maxResolution`, inputSchema: ListModelsInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, async (params: ListModelsInput) => { const output = { models: MODEL_INFO }; if (params.response_format === "json") { return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } // Markdown format let markdown = "# Nano Banana Image Generation Models\n\n"; for (const model of MODEL_INFO) { markdown += `## ${model.name}\n\n`; markdown += `**Model ID:** \`${model.id}\`\n\n`; markdown += `${model.description}\n\n`; markdown += `**Max Resolution:** ${model.maxResolution}\n\n`; markdown += `**Features:**\n`; for (const feature of model.features) { markdown += `- ${feature}\n`; } markdown += "\n---\n\n"; } markdown += "## Quick Reference\n\n"; markdown += "| Model | Best For | Max Res |\n"; markdown += "|-------|----------|--------|\n"; markdown += "| Nano Banana Pro | Professional assets, text in images, 4K output | 4K |\n"; markdown += "| Nano Banana | Fast iteration, batch processing | 1K |\n"; return { content: [{ type: "text", text: markdown }], structuredContent: output, }; } ); // ============================================================================ // Server Transport Setup // ============================================================================ /** * Run server with stdio transport (for local/CLI use) */ async function runStdio(): Promise<void> { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Nano Banana MCP Server running on stdio"); } /** * Validate auth token from Authorization header or query parameter */ function validateAuthToken(authHeader: string | undefined, queryToken: string | undefined): boolean { const expectedToken = process.env.MCP_AUTH_TOKEN; // If no token is configured, allow all requests (for backwards compatibility) if (!expectedToken) { console.warn("WARNING: MCP_AUTH_TOKEN not set - server is unauthenticated!"); return true; } // Check query parameter first (for Claude.ai which doesn't support custom headers) if (queryToken && queryToken === expectedToken) { return true; } // Fall back to Authorization header if (authHeader) { const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader; return token === expectedToken; } return false; } /** * Run server with HTTP transport (for remote/web use) */ async function runHTTP(): Promise<void> { const app = express(); app.use(express.json({ limit: "50mb" })); // Large limit for base64 images // Health check endpoint (no auth required) app.get("/health", (_req, res) => { res.json({ status: "ok", server: "nanobanana-mcp-server", version: "1.0.0" }); }); // MCP endpoint (auth required) app.post("/mcp", async (req, res) => { // Validate auth token (from query param or header) const queryToken = req.query.token as string | undefined; if (!validateAuthToken(req.headers.authorization, queryToken)) { res.status(401).json({ error: "Unauthorized - invalid or missing token" }); return; } const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, }); res.on("close", () => transport.close()); await server.connect(transport); await transport.handleRequest(req, res, req.body); }); const port = parseInt(process.env.PORT || "3000"); app.listen(port, () => { console.error(`Nano Banana MCP Server running on http://localhost:${port}/mcp`); }); } // ============================================================================ // Main Entry Point // ============================================================================ const transport = process.env.TRANSPORT || "stdio"; if (transport === "http") { runHTTP().catch((error) => { console.error("Server error:", error); process.exit(1); }); } else { runStdio().catch((error) => { console.error("Server error:", error); process.exit(1); }); }

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/mikeroussell/nano-banana-mcp'

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