Skip to main content
Glama

Gemini Flash Image MCP Server

by brunoqgalvao
index.js7.39 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import dotenv from "dotenv"; import { readFile, writeFile } from "fs/promises"; import { resolve } from "path"; dotenv.config(); const GEMINI_API_KEY = process.env.GEMINI_API_KEY; const BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent"; const ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; if (!GEMINI_API_KEY) { console.error("Error: GEMINI_API_KEY environment variable is required"); console.error("Get your API key at: https://aistudio.google.com/apikey"); process.exit(1); } class GeminiImageServer { constructor() { this.server = new Server( { name: "gemini-image-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); this.server.onerror = (error) => console.error("[MCP Error]", error); process.on("SIGINT", async () => { await this.server.close(); process.exit(0); }); } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "generate_image", description: "Generate or edit images using Gemini 2.5 Flash Image (Nano Banana). " + "Supports text-to-image generation, image editing with natural language prompts, " + "and multi-image composition. All generated images include a SynthID watermark.", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "Text prompt describing the image to generate or edits to make", }, input_images: { type: "array", items: { type: "string", }, description: "Optional array of file paths to input images for editing or composition", }, aspect_ratio: { type: "string", enum: ASPECT_RATIOS, description: "Output aspect ratio. Options: " + ASPECT_RATIOS.join(", "), default: "1:1", }, output_path: { type: "string", description: "Path where the generated image will be saved (must end in .png)", default: "output.png", }, image_only: { type: "boolean", description: "If true, requests image-only output without text response", default: false, }, }, required: ["prompt", "output_path"], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name !== "generate_image") { throw new Error(`Unknown tool: ${request.params.name}`); } const { prompt, input_images = [], aspect_ratio = "1:1", output_path = "output.png", image_only = false, } = request.params.arguments; if (!prompt) { throw new Error("prompt is required"); } if (!output_path.endsWith(".png")) { throw new Error("output_path must end with .png"); } if (!ASPECT_RATIOS.includes(aspect_ratio)) { throw new Error(`Invalid aspect_ratio. Must be one of: ${ASPECT_RATIOS.join(", ")}`); } try { // Build request parts const parts = [{ text: prompt }]; // Add input images if provided for (const imagePath of input_images) { const imageData = await this.encodeImage(imagePath); parts.push(imageData); } // Build API request payload const payload = { contents: [{ parts }], generationConfig: { responseModalities: image_only ? ["Image"] : ["Text", "Image"], imageConfig: { aspectRatio: aspect_ratio, }, }, }; // Make API request const url = `${BASE_URL}?key=${GEMINI_API_KEY}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} ${response.statusText}\n${errorText}`); } const result = await response.json(); // Extract and save image let textResponse = null; let imageSaved = false; if (result.candidates) { for (const candidate of result.candidates) { if (candidate.content) { for (const part of candidate.content.parts || []) { if (part.inlineData) { await this.saveImage(part.inlineData, output_path); imageSaved = true; } else if (part.text) { textResponse = part.text; } } } } } if (!imageSaved) { throw new Error("No image was generated in the response"); } // Return success response const responseText = [ `✓ Image generated successfully!`, ` Saved to: ${output_path}`, ` Aspect ratio: ${aspect_ratio}`, textResponse ? ` AI response: ${textResponse}` : null, ] .filter(Boolean) .join("\n"); return { content: [ { type: "text", text: responseText, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating image: ${error.message}`, }, ], isError: true, }; } }); } async encodeImage(imagePath) { const absolutePath = resolve(imagePath); const imageBuffer = await readFile(absolutePath); const base64Data = imageBuffer.toString("base64"); // Determine MIME type from extension const mimeTypes = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp", ".gif": "image/gif", }; const ext = imagePath.toLowerCase().match(/\.\w+$/)?.[0]; const mimeType = mimeTypes[ext] || "image/jpeg"; return { inlineData: { mimeType, data: base64Data, }, }; } async saveImage(inlineData, outputPath) { const absolutePath = resolve(outputPath); const imageBuffer = Buffer.from(inlineData.data, "base64"); await writeFile(absolutePath, imageBuffer); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Gemini Image MCP server running on stdio"); } } const server = new GeminiImageServer(); server.run().catch(console.error);

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/brunoqgalvao/gemini-image-mcp-server'

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