# gemini-image-mcp
A Docker container running a custom Python MCP server that exposes Gemini **image editing** and **image analysis** tools over Streamable HTTP. Built with the MCP Python SDK (`FastMCP`) and the `google-genai` SDK.
## Architecture
```
Client (MCP SDK)
│
│ Streamable HTTP (POST/GET http://host:8000/mcp)
▼
┌────────────────────────────────────────────┐
│ Docker Container (python:3.12-slim) │
│ │
│ server.py (FastMCP) │
│ ├─ image_edit → gemini-3-pro-image │
│ └─ image_analyze → gemini-2.5-pro │
│ │ │
│ │ google-genai SDK │
│ ▼ │
│ Google Gemini API │
└────────────────────────────────────────────┘
```
`server.py` uses FastMCP with Streamable HTTP transport. No intermediary bridge is needed — the Python MCP SDK serves HTTP directly via Uvicorn.
## Tools
| Tool | Model | Description |
|---|---|---|
| `image_edit` | `gemini-3-pro-image-preview` | Edits an existing image based on a text prompt. Accepts base64-encoded image, MIME type, and prompt. Returns the edited image as `ImageContent`. |
| `image_analyze` | `gemini-2.5-pro` | Answers a question about an image. Accepts base64-encoded image, MIME type, and question. Returns a text answer. |
## Prerequisites
- Docker
- A [Google Gemini API key](https://aistudio.google.com/apikey)
- Python 3.11+ and [uv](https://docs.astral.sh/uv/) (for running the tests)
## Quick Start
### Build the image
```bash
docker build -t vocabai/gemini-image-mcp .
```
### Run the container
```bash
docker run --rm --network host -e GEMINI_API_KEY=your_key_here vocabai/gemini-image-mcp
```
The MCP server is now available at `http://localhost:8000/mcp`.
> **Note:** `--network host` is required because Docker's bridge proxy does not handle Server-Sent Events (SSE) properly.
### Connect from any MCP client
```python
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async with streamable_http_client("http://localhost:8000/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print([t.name for t in tools.tools])
```
## File Reference
| File | Purpose |
|---|---|
| `server.py` | MCP server: registers `image_edit` and `image_analyze` tools, calls the Gemini API via `google-genai`, serves Streamable HTTP on port 8000. |
| `Dockerfile` | Builds the container image on `python:3.12-slim`, installs dependencies, copies `server.py`. |
| `test_gemini_mcp.py` | Pytest suite that builds the image, starts a container, connects via the MCP Python SDK, and exercises the full edit-then-analyze workflow. |
| `build.sh` | Builds and pushes the production Docker image (`vocabai/gemini-image-mcp`). |
| `start.sh` | Builds and runs the container with sops-decrypted secrets. |
| `test_data/screenshot.png` | Test image used by the test suite. |
| `pyproject.toml` | Python project metadata and dependencies (managed with `uv`). |
## Test Suite
### Running the tests
```bash
GEMINI_API_KEY=your_key uv run pytest -v
```
Or with sops-encrypted secrets:
```bash
SECRETS_DIR=/path/to/secrets uv run pytest -v
```
The test suite is fully self-contained: it builds the Docker image, starts the container with `--network host`, runs the tests, and tears the container down afterward.
### Tests
#### `test_list_tools`
Connects to the MCP server and verifies that `image_edit` and `image_analyze` tools are exposed.
#### `test_image_edit_and_analyze`
End-to-end round-trip:
1. **Edit** — reads `test_data/screenshot.png`, base64-encodes it, calls `image_edit` with the prompt "add a purple arrow pointing to the Agent name". Validates the returned image with Pillow.
2. **Analyze** — passes the edited image to `image_analyze` asking "Is there a purple arrow pointing to the Agent name?". Asserts the response is affirmative.
### Configuration constants
| Constant | Default | Purpose |
|---|---|---|
| `IMAGE_NAME` | `gemini-mcp-test` | Docker image tag used during the test run. |
| `CONTAINER_NAME` | `gemini-mcp-test-container` | Container name (allows cleanup of stale runs). |
| `HOST_PORT` | `18000` | Port the server listens on during tests. |
| `MCP_URL` | `http://localhost:18000/mcp` | Full MCP endpoint URL. |
| `STARTUP_TIMEOUT` | `120` | Max seconds to wait for the container to accept TCP connections. |
| `MCP_CALL_TIMEOUT` | `180` | Read timeout (seconds) for individual MCP tool calls. |
## Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `GEMINI_API_KEY` | Yes | — | Google Gemini API key. |
| `PORT` | No | `8000` | Port the MCP server listens on. |