image-gen-mcp
Enables generating images from text prompts using Google Gemini's native image models, with configurable aspect ratios and resolution, returning images inline or as signed URLs for larger outputs.
README
image-gen-mcp
An MCP server that generates images with Google's Gemini native image models ("Nano Banana"), served over Streamable HTTP.
One tool, generate_image. No state anywhere, and no auth of its own —
in production it runs as a backend behind
mcp-oauth-proxy, deployed by
cloudrun-mcp-deployment.
DESIGN.md explains why it is built this way; this file explains how to run it.
Quick start (local, token auth)
uv sync
export GEMINI_API_KEY="…" # from Google AI Studio
export IMAGE_MCP_TOKEN="$(openssl rand -base64 32)"
uv run python -m image_gen_mcp
Point a client at http://127.0.0.1:8080/mcp with
Authorization: Bearer $IMAGE_MCP_TOKEN. For Claude Code:
claude mcp add --transport http image-gen http://127.0.0.1:8080/mcp \
--header "Authorization: Bearer $IMAGE_MCP_TOKEN"
The tool
generate_image(prompt, aspect_ratio="1:1", image_size="1K", model=None)
| Parameter | Values |
|---|---|
prompt |
free text, up to IMAGE_MCP_MAX_PROMPT characters |
aspect_ratio |
1:1 16:9 9:16 4:3 3:4 3:2 2:3 21:9 4:5 5:4 |
image_size |
1K 2K 4K — 2K/4K need a capable model and a bucket |
model |
optional; must be in IMAGE_MCP_ALLOWED_MODELS |
Images at or under IMAGE_MCP_INLINE_MAX_BYTES (default 1.5 MB) come back
inline and render in the chat. Larger ones are uploaded to Cloud Storage and
returned as a signed URL. Structured output always reports the true pixel
dimensions, the model used, and which delivery route was taken.
Model / resolution support
| Model | 1K | 2K | 4K |
|---|---|---|---|
gemini-2.5-flash-image (default) |
✅ | — | — |
gemini-3-pro-image-preview / gemini-3-pro-image |
✅ | ✅ | ✅ |
gemini-3.1-flash-lite-image |
✅ | ✅ | ✅ |
Unsupported combinations are rejected at the tool boundary, in milliseconds, with a message naming what is supported — rather than after a 30-second round trip.
Configuration
Everything comes from the environment. See .env.example for the annotated
list; the essentials:
| Variable | Required | Purpose |
|---|---|---|
GEMINI_API_KEY |
yes | Google AI Studio key |
IMAGE_MCP_TOKEN |
unless proxied | static bearer token |
IMAGE_MCP_MODEL |
default model | |
IMAGE_MCP_ALLOWED_MODELS |
models a caller may select | |
IMAGE_MCP_TRUST_PROXY_HEADERS |
behind proxy | take identity from X-Auth-* |
ALLOWED_EMAILS |
optional narrowing of the proxy's allowlist | |
IMAGE_MCP_GCS_BUCKET |
for 2K/4K | bucket for oversized images |
The server refuses to start rather than run misconfigured: no API key, and no auth boundary at all (neither a static token nor proxy mode) are both startup failures.
Auth
Two shapes, and the server refuses to start in neither.
Behind the proxy (production). Set IMAGE_MCP_TRUST_PROXY_HEADERS=1. The
proxy authenticates the user against Google, enforces its allowlist, strips the
client's Authorization header, and forwards the identity as X-Auth-Email /
X-Auth-Subject / X-Auth-Scope. This server reads those and requires no token
of its own — a request without X-Auth-Email gets a 401.
Trusting headers is safe only because nothing else can reach the process: in the Cloud Run multi-container layout the backend declares no ingress port, so the only things that can open a socket to it are the proxy in the same instance and the startup prober. Never enable this on a routable port.
ALLOWED_EMAILS is optional here and narrows the proxy's allowlist — useful
when the proxy admits a whole domain but image generation should cost money for
only a few people. Unset means "whoever the proxy admitted".
Token (local, Claude Code). Leave IMAGE_MCP_TRUST_PROXY_HEADERS unset and
set IMAGE_MCP_TOKEN. Callers send Authorization: Bearer <token>; the
X-Auth-* headers are ignored entirely, since without the proxy they are just
untrusted request data.
The server does not terminate OAuth and has no /authorize, /token or
/register endpoints. Setting GOOGLE_OAUTH_CLIENT_ID or _SECRET is a
startup error rather than a silent no-op — those belong on the proxy.
Building the image
.github/workflows/build.yml runs the tests, builds the image, and publishes
it to GitHub Container Registry. It does not deploy — deployment is handled
by a separate workflow or repository.
| Event | Test | Build | Push |
|---|---|---|---|
| pull request | ✅ | ✅ | — |
push to main |
✅ | ✅ | latest, sha-<full-sha> |
tag v* |
✅ | ✅ | 1.2.3, 1.2, sha-<full-sha> |
Published as ghcr.io/ramzpat/image-gen-mcp. Nothing needs configuring: the
workflow authenticates with the built-in GITHUB_TOKEN.
Consuming it from a deploy workflow
Deploy by digest, not by tag. A pull-through cache in front of a mutable
tag like :latest will happily serve a previous image; a digest cannot go
stale. Each run prints the digest to its job summary, and the workflow is
callable if you want to build and deploy in one pipeline:
jobs:
build:
uses: ramzpat/image-gen-mcp/.github/workflows/build.yml@main
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "deploying ${{ needs.build.outputs.image }}@${{ needs.build.outputs.digest }}"
From a separate repository, resolve the digest at deploy time instead:
DIGEST=$(docker buildx imagetools inspect \
ghcr.io/ramzpat/image-gen-mcp:latest --format '{{.Manifest.Digest}}')
The GHCR package is private by default. A deploy job in another repository
needs either a PAT with read:packages, or the package set to public under
its GitHub package settings.
Deployment
Deployed by
cloudrun-mcp-deployment
(.github/workflows/deploy-image-gen-mcp.yml), which runs this image as the
backend container of a Cloud Run multi-container service with mcp-oauth-proxy
in front. That repo owns the GCP project, region, allowlist, and secrets; this
one only publishes the image.
What that deployment sets on this container:
PORT / HOST |
8000 / 0.0.0.0 (set by the shared deploy action) |
IMAGE_MCP_TRUST_PROXY_HEADERS |
1 |
GEMINI_API_KEY |
from the service's GitHub Environment secret |
| entrypoint | /app/.venv/bin/python -m image_gen_mcp |
HOST=0.0.0.0 rather than loopback is required, not a leak: Cloud Run's
startup prober runs outside the container's network namespace and cannot reach
a loopback-only socket. Only the container declaring --port (the proxy)
receives ingress, so the backend stays unreachable from outside the instance.
Cloud Storage for 2K/4K
gcloud storage buckets create gs://BUCKET --uniform-bucket-level-access
gcloud storage buckets update gs://BUCKET \
--lifecycle-file=<(echo '{"rule":[{"action":{"type":"Delete"},"condition":{"age":30}}]}')
# The runtime service account signs URLs through the IAM Credentials API,
# because it has no private key file. It needs this role *on itself*:
gcloud iam service-accounts add-iam-policy-binding RUNTIME_SA \
--member="serviceAccount:RUNTIME_SA" --role=roles/iam.serviceAccountTokenCreator
gcloud storage buckets add-iam-policy-binding gs://BUCKET \
--member="serviceAccount:RUNTIME_SA" --role=roles/storage.objectAdmin
Omitting the serviceAccountTokenCreator binding is the most common way signed
URLs ship broken.
Tests
uv run pytest -q
57 tests: startup guards, identity resolution in both shapes (proxy headers, static token, and each ignoring the other's credential), tool validation and delivery, and end-to-end smoke tests driving a real MCP client over real HTTP against uvicorn — in token mode and in the proxied production shape.
Cost
Every allowlisted user draws on one API key. The controls, in order of how much
they actually help: --max-instances, IMAGE_MCP_MAX_CONCURRENCY, a GCP
billing budget alert, and IMAGE_MCP_RATE_PER_HOUR. The rate limit is counted
per instance, so its true ceiling is IMAGE_MCP_RATE_PER_HOUR × --max-instances.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。