gemini-mcp
This MCP server provides a suite of tools for media generation and management using Google Gemini models. You can:
List available image generation models and see the active default.
Generate images from text prompts with control over size, aspect ratio, seed, model, and search grounding, and reference images via URLs, Files API, local paths, or base64.
Edit or compose images by applying text-driven edits or merging multiple images.
Create consistent image sets with a master image plus variations or themed scenes.
Iteratively refine images through multi-turn editing with the Interactions API, chaining edits and recovering from errors.
Generate short videos from text, images, or existing videos using the Omni model, with async support.
Compose music clips from text prompts (mood, genre, instruments) with Lyria models, outputting MP3 or WAV.
Run long generations asynchronously and poll for results to avoid timeouts.
Upload, list, and delete files in the Gemini Files API for reusable references (48h expiry).
Sign media URLs (for hosted connectors) to refresh expired links.
Use idempotency keys to safely retry timed-out calls.
Write output to disk or receive it inline as base64.
All generation tools support async mode, multiple input methods, and progress heartbeats to keep connections alive.
Provides tools for interacting with Google Gemini's media generation API, enabling image generation/editing, video generation, music generation, and file management through the Gemini platform.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gemini-mcpgenerate an image of a cat wearing a hat"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
gemini-mcp
MCP server for Google Gemini media generation. Exposes eleven tools to Claude over stdio: list available models, generate/edit/compose images, generate a consistent set of images from a master prompt, multi-turn image refinement (Interactions API), video generation (omni), music generation (Lyria), an async result poll for long generations, and Files API upload/list/delete for reusable image references. Output is written to disk by default (path returned) or returned inline as base64. Built on the Gemini v1beta API (generativelanguage.googleapis.com) using the Nano Banana / Nano Banana Pro (images), omni (video), and Lyria (music) model families.
Developed and maintained by AI (Claude Code).
Environment Variables
Variable | Required | Description |
| Yes | Your Google Gemini API key (aistudio.google.com/apikey) |
| No | Override the default image model (default: |
| No | Default directory for generated images (default: current working directory) |
| No | Directory to resolve bare input-image filenames against (so |
| No | Upstream request timeout in ms (default: |
| No | Progress-notification cadence in ms while a generation runs (default: |
| No | How long to wait out interactions-store lag when a chained call 404s (default: |
Long generations and client timeouts
4K / Pro-model generations can outrun an MCP host's own tools/call timeout (error -32001).
The server sends notifications/progress heartbeats so hosts that reset their timeout on
progress wait it out. If the host still gives up, the server-side generation usually completes
anyway: the image is written to the output dir, gemini_interact also writes an
<image>.json sidecar recording the interaction_id, and continue_last: true resumes the
interaction the lost response belonged to.
When a chained call 404s
A 404 on a request carrying previous_interaction_id is not proof the chain expired. The
only 404 body observed live is generic — "Requested entity was not found." — and never names
which entity. An unknown or renamed model id, and an expired Files API files/… uri
(~48h TTL), return exactly the same thing. So the server no longer asserts a cause it can't
establish: the upstream text is surfaced verbatim, and gemini_interact runs an experiment to
find out which it was.
Most often the id isn't missing at all — it just isn't visible yet. The interactions store is
eventually consistent, and a freshly created id can 404 while the same id resolves fine minutes
later; heavy turns (4K, Pro, thinking_level: high) are the likeliest to hit it, which is
exactly the turn you most want to chain from. So a chained 404 is retried with exponential
backoff for up to 120s (GEMINI_CHAIN_RETRY_MS) before anything is declared broken. The
404 generates nothing and isn't billed, so the wait costs only time.
After that budget is spent, the tool looks up that id's sidecar, re-attaches the image it produced, and re-issues the request without the chain:
The re-issue succeeds → the chain really was the problem, and you get your image anyway, reported as
chain_recovered: { expired_interaction_id, reanchored_on }. The 404'd attempt generates nothing, so this costs the one generation you'd have paid for re-anchoring manually.The re-issue 404s too → the interaction id was never the cause. You get told exactly that, with the upstream text, and pointed at the model id and any
files/…uri instead of being sent to chase an interaction that was fine all along.No sidecar matches the dead id → the original error, rather than a guess. Re-anchoring on the wrong picture would silently corrupt the edit.
Separately, continue_last no longer dies with the server process: with no in-memory id it
resumes from the newest <image>.json sidecar in the output dir and reports
continued_from_sidecar: true. That case was never an expired chain at all — the interaction
was alive upstream the whole time; only our memory of its id was gone.
For hosts whose timeout can't be tamed (e.g. Claude Desktop, a fixed ~30s cap that ignores progress), two guards make re-issuing safe and unnecessary:
async: truereturns ajob_idimmediately instead of the image, so the call can't time out at all; pollgemini_get_resultwith thejob_iduntil it'sdone.idempotency_keymakes a repeat call idempotent — a retry with the same key returns the recorded result (reused: true) instead of billing a second generation. (Even without a key, two identical in-flight calls are deduplicated automatically.)
Related MCP server: mmxomni
Tools
Tool | Description |
| List available Gemini image models and the current default |
| Generate image(s) from a text prompt |
| One-off edits or multi-image composition with a text instruction (for a series of edits, use |
| Generate a master image plus N consistent images referencing it |
| Preferred tool for iterative refinement: multi-turn generation/editing via the Interactions API — chain the returned |
| Generate a short video (text→video, image→video, or |
| Generate music from a text prompt via a Lyria model — |
| Fetch an async generation started with |
| Upload an image (or video/audio) to the Gemini Files API once — from a |
| List the files currently uploaded under this API key, with MIME types and expiry times |
| Delete an uploaded file before its ~48h expiry (confirm-gated) |
| (hosted connector only) Mint a fresh signed URL for generated media from its |
Seeing your images (hosted connector)
On the hosted connector there is no filesystem, so a generated image has to come back as something you can open. It does: every result includes a URL, with no configuration.
{
"images": ["https://connector.example.com/media/gen/2026-07-29/ab12cd34-a-cat.png?exp=…&sig=…"],
"media": [{ "url": "https://…", "r2_key": "gen/2026-07-29/ab12cd34-a-cat.png",
"expires_at": "2026-07-31T12:00:00.000Z",
"curl_hint": "curl -sS -o a-cat.png \"https://…\"" }]
}Those links need no auth header — the signature is in the URL — so they work in a browser, in
curl, and in a chat message. They expire (48h by default) and the objects behind them are
swept on a retention schedule. The r2_key is the durable handle for that window:
gemini_sign_media(hosted connector only) mints a fresh signed URL from anr2_key, so an expired link never forces you to re-generate — and re-pay for — the image.gemini_upload_filewithr2_keyturns media the connector generated into a Files API reference (the connector reads its own bucket — no HTTP round trip, no signature needed), so a generated image can become the reference image for the next generation in one cheap call.Idempotent replays (
idempotency_key) re-mint the URLs inside the recorded result before returning it, so a reused result never carries a dead link.
Why this matters: MCP's inline image content blocks (inline: true) are visible to the
assistant but many chat clients never render them to the user, and the assistant cannot
extract bytes back out of its own context to save them elsewhere. A generation could bill
successfully and be invisible. A URL is the portable answer; inline remains available, but
it is no longer the only way to receive media.
Three ways to serve the bytes
setup | URL shape | |
Worker route (default) | none — works out of the box |
|
r2.dev public bucket | enable public access on the bucket, set |
|
Custom domain (best) | attach a domain to the bucket in Cloudflare, set |
|
The built-in Worker route is the zero-config path: the connector streams the object out of R2 itself. The other two are worth setting up if you want plain, non-expiring URLs or want media traffic served by R2 rather than by the Worker — r2.dev is easiest but rate-limited and fine for personal use; a custom domain is the best of the three.
Auth on /media is a signed, expiring URL rather than an unlisted key. Random keys would
be simpler, but they never expire and never revoke: anything that ever logged or forwarded the
link keeps working forever. A signature scopes access to one object with a deadline, and
rotating MEDIA_URL_SECRET invalidates every outstanding link at once. The tradeoff is that
links are long and cannot be shortened by hand.
For assistants relaying a result
Show the user the URL. If your sandbox has network egress to the connector host, fetching it
and attaching the bytes as a file gives the nicest result; otherwise present the link itself.
Whether a given client renders  markdown inline varies by client — a bare URL is the
safe form, and a markdown link is a reasonable enhancement where you know it renders.
Retention
Variable | Default | Effect |
|
| Objects older than this are deleted by a daily cron (both the current |
| generated | HMAC key for |
| unset | Serve from R2 directly instead of the Worker route |
Signed-URL lifetime is clamped to MEDIA_TTL_DAYS, so a link never outlives the object it
points at.
Sending reference images without burning context
Every tool that takes a reference image — gemini_image_generate, gemini_image_edit,
gemini_image_set, gemini_interact, plus gemini_video_generate (reference stills) and
gemini_music_generate — accepts them four ways. Only one of them costs model context:
Parameter | Where the bytes travel | Context cost |
| the server downloads the https URL | none |
| a | none |
| read off local disk (stdio builds only) | none |
| through the tool-call JSON | ~14k tokens per JPEG |
images_base64 is the fallback of last resort. It costs roughly 14k tokens per modest photo,
and it is silently corrupted whenever the file read that produced it was truncated — the
payload still looks like base64, so the failure surfaces as a bad generation rather than an
error. Prefer any of the other three.
images_url — the server fetches it
{ "prompt": "make it look like winter", "images_url": ["https://example.com/photo.jpg"] }Fetches are restricted to public https:// URLs — private, loopback and link-local hosts are
refused (IPv6 literals are parsed, so [::ffff:7f00:1] is caught as loopback), every redirect
hop is revalidated, and each hop is bounded by a timeout. The response must be
Content-Type: image/* and is capped at 15MB, enforced while streaming rather than trusted
from Content-Length. A failure names the offending URL. Anything over 6MB is uploaded to the
Files API and referenced by uri instead of inlined, since generateContent caps a whole
request near 20MB.
images_file_uris — upload once, reference many times
// 1. upload
{ "tool": "gemini_upload_file", "url": "https://example.com/photo.jpg" }
// → { "file_uri": "files/abc123", "mime_type": "image/jpeg", "expires": "..." }
// 2. reference it, as many times as you like
{ "prompt": "make it winter", "images_file_uris": ["files/abc123"] }
{ "prompt": "make it sunrise", "images_file_uris": ["files/abc123"] }Uploads are retained ~48h; after that the reference stops resolving (as a generic 404 —
see the chained-404 section above). gemini_image_set fetches or resolves such a reference
once and passes it to the master and every scene call.
On stdio builds, a local images path that gets referenced more than once in a session is
uploaded to the Files API automatically (keyed on path + mtime + size), so repeated edits of
the same photo stop re-sending the bytes. Editing the file invalidates the cached upload.
POST /upload — raw bytes, no base64 at all (hosted connector)
The hosted connector exposes an HTTP upload endpoint behind the same OAuth token as
/mcp. This is the intended path for an agent with a shell: disk file → curl → file_uri →
tool call, with the image never entering the conversation.
curl -X POST https://connector.gemini.nullnet.app/upload \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
# → {"file_uri":"files/abc123","mime_type":"image/jpeg","expires":"...","...":"..."}Then pass "images_file_uris": ["files/abc123"] to any image tool. The body is streamed
straight to the Files API (nothing is buffered), so Content-Length is required — curl sets it
automatically with --data-binary @file. Accepted content types are image/*, video/* and
audio/*; an optional ?name= or X-Filename header sets the display name.
Quick Start
{
"mcpServers": {
"gemini": {
"command": "npx",
"args": ["-y", "@chrischall/gemini-mcp"],
"env": {
"GEMINI_API_KEY": "your-api-key-here"
}
}
}
}See SKILL.md for full usage documentation.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityAmaintenanceProduction-grade MCP server for image and video understanding and generation across Gemini, OpenAI, and Grok.Last updated54MIT
- Alicense-qualityBmaintenanceModel Context Protocol server exposing MiniMax's image, speech, music, and video generation APIs as MCP tools for use with any MCP-aware host.Last updated3,604MIT
- Flicense-qualityDmaintenanceWraps Google Gemini's image generation API as an MCP server, enabling text-to-image, image editing, and grounded search workflows from any MCP client.Last updated
- AlicenseAqualityDmaintenanceMCP server for generating and editing images using Google Gemini API, with support for multi-turn iterative refinement.Last updated31MIT
Related MCP Connectors
MCP server for Google Veo AI video generation
MCP server for Hailuo (MiniMax) AI video generation
MCP server for Grok Imagine AI video generation
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/chrischall/gemini-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server