openai-image-mcp
# openai-image-mcp
A local, stdio-transport [MCP](https://modelcontextprotocol.io) server that lets Claude Code and
Claude Desktop generate and edit images with OpenAI's Images API (`gpt-image-2` by default).
Finished images are written into the current project (default `./assets/generated`) so they can be
used directly as site assets, and every tool call returns a small JPEG preview so the calling model
can inspect the result and iterate. Images only; no video.
## Tools
| Tool | Purpose |
|---|---|
| `generate_image` | New image(s) from a text prompt. |
| `edit_image` | New image guided by 1–8 reference images (consistent style, modifications, optional mask). |
| `list_generated_images` | Files in the output directory with dimensions, size and the prompt from each `.json` sidecar. |
Each generated image gets a `<name>.json` sidecar next to it holding the prompt, model, requested and
final size, quality, background, output format, reference paths (for edits) and an ISO timestamp.
Files are never overwritten: a taken name gets `-2`, `-3`, … appended.
## Prerequisites
- Python ≥ 3.11 and, ideally, [uv](https://docs.astral.sh/uv/).
- An OpenAI API key with access to the GPT Image models.
- **Organization verification.** OpenAI gates GPT Image models behind
[API Organization Verification](https://platform.openai.com/settings/organization/general).
If your organization has not completed it, calls fail with a 403 and this server reports
"Your OpenAI organization is not verified for gpt-image-2 …". Verification can take a few minutes
to propagate after it is approved.
## Install
With uv (recommended):
```bash
git clone https://github.com/mostmark/openai-image-mcp.git
cd openai-image-mcp
uv sync
uv run openai-image-mcp # starts the server on stdio; Ctrl-C to stop
```
With plain pip:
```bash
python -m venv .venv && source .venv/bin/activate
pip install -e .
python -m openai_image_mcp
```
## Environment variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `OPENAI_API_KEY` | yes | — | API key. The server exits at startup with a clear message if it is missing. |
| `OPENAI_BASE_URL` | no | OpenAI default | Route calls through an OpenAI-compatible gateway. |
| `OPENAI_IMAGE_MODEL` | no | `gpt-image-2` | Model for both generation and edits. |
| `IMAGE_OUTPUT_DIR` | no | `./assets/generated` | Where images are written, relative to the server's working directory. Created on first write. |
| `IMAGE_TIMEOUT_SECONDS` | no | `180` | Per-request timeout. High-quality generations can take over a minute. |
See `.env.example`. All logging goes to stderr; stdout is reserved for the MCP protocol.
## Register with Claude Code
The server writes images relative to its working directory, and Claude Code starts MCP servers in the
project directory, so a user-scoped registration puts assets into whichever project you are working in.
Use `uv run --project` (not `--directory`) so the working directory stays your project:
```bash
claude mcp add openai-image --scope user \
-e OPENAI_API_KEY=sk-... \
-- uv run --project /absolute/path/to/openai-image-mcp openai-image-mcp
```
Equivalent project-scoped setup in the project's `.mcp.json` (commit it, keep the key out of it by
using an environment reference):
```json
{
"mcpServers": {
"openai-image": {
"command": "uv",
"args": ["run", "--project", "/absolute/path/to/openai-image-mcp", "openai-image-mcp"],
"env": {
"OPENAI_API_KEY": "${OPENAI_API_KEY}",
"IMAGE_OUTPUT_DIR": "./public/images/generated"
}
}
}
}
```
Verify with `claude mcp list`, then ask Claude Code to "generate a hero image for the landing page".
## Register with Claude Desktop
Add to `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`,
Windows: `%APPDATA%\Claude\claude_desktop_config.json`). Claude Desktop does not start servers inside a
project directory, so set `IMAGE_OUTPUT_DIR` to an absolute path:
```json
{
"mcpServers": {
"openai-image": {
"command": "uv",
"args": ["run", "--project", "/absolute/path/to/openai-image-mcp", "openai-image-mcp"],
"env": {
"OPENAI_API_KEY": "sk-...",
"IMAGE_OUTPUT_DIR": "/absolute/path/to/your/project/assets/generated"
}
}
}
}
```
If Desktop cannot find `uv`, use its absolute path (`which uv`) as `command`.
## Getting started: example prompts
Once the server is registered, you talk to Claude in plain language and it picks the tool and
parameters. Everything below is something you can type into Claude Code (inside your project) or
Claude Desktop. Generated files land in `IMAGE_OUTPUT_DIR`, and Claude sees a preview of each result,
so you can follow up with "make it warmer" or "try a version without text".
### First image
> Generate a hero image for the landing page: a lighthouse on a rocky coast at dawn, soft pastel sky,
> flat vector illustration, no text. Save it as `hero-lighthouse`.
Claude calls `generate_image` with `size: "hero"`, `filename: "hero-lighthouse"`, and reports the path,
for example `assets/generated/hero-lighthouse.png`, plus a preview.
### Quick drafts, then a final render
> Give me three low-quality draft ideas for a blog card about database migrations, landscape format.
`generate_image` with `n: 3`, `quality: "low"`, `size: "landscape"`. Pick one, then:
> I like the second one. Regenerate it at high quality with the same prompt and call it
> `blog-migrations`.
### Icons and logos with transparent backgrounds
> Create a transparent PNG app icon of a paper airplane, single flat blue shape, centered, 512x512.
`generate_image` with `background: "transparent"`, `output_format: "png"`, `size: "512x512"`.
Transparent output needs `png` or `webp`; Claude gets a clear error if it asks for `jpeg`.
> Make a webp banner for the newsletter header, 1400x500, our mascot waving, warm colours.
`generate_image` with `output_format: "webp"` and `size: "1400x500"`. Dimensions are rounded to
multiples of 16, and the result reports the size actually produced (here `1408x496`). Anything wider
than 3:1 (or taller than 1:3) is rejected with a message explaining the limit.
### A consistent set of assets
> First create a brand board: a style sheet with a navy, amber and off-white palette, flat vector
> illustration style, soft shadows, rounded geometry, no text. Landscape, high quality, filename
> `brand-board`.
Then, for every further asset:
> Using `assets/generated/brand-board.png` as the reference, create a feature illustration of a
> developer at a desk with two monitors, landscape, filename `feature-workspace`.
> Same reference: a transparent PNG icon of a cloud with an upload arrow, square, filename
> `icon-upload`.
Each of these is an `edit_image` call with `reference_paths: ["assets/generated/brand-board.png"]`.
The section below shows how to make this the default via `CLAUDE.md`.
### Editing an existing image
> Take `public/images/team-photo.jpg` and turn it into a flat illustration in the same composition,
> keep the number of people and their poses.
> Using `assets/generated/hero-lighthouse.png` as reference, produce a night-time version with a lit
> lamp and a starry sky, same framing.
> Combine `assets/logo.png` and `assets/generated/hero-lighthouse.png`: place the logo bottom-right on
> the hero as a small watermark.
All three are `edit_image` calls; the last one passes both files in `reference_paths`. With a mask:
> Using `assets/generated/hero-lighthouse.png` and the mask `assets/masks/sky.png`, replace the sky
> with a dramatic thunderstorm and leave the rest untouched.
Masking is prompt-guided rather than pixel-exact, so keep the description of the change in the prompt.
### Picking up where you left off
> What images have already been generated in this project?
`list_generated_images` returns each file's path, dimensions, size in KB, and the prompt from its
sidecar, so a new session can reuse a brand board or earlier asset without regenerating it.
### Wiring the result into a site
> Generate an Open Graph image for the pricing page, 1200x630, and add it to the page's `<meta>` tags.
Claude generates the file, then edits your HTML or framework config to reference the returned path.
Because the file is already in your project tree, no copying step is needed.
## Size presets
| Preset | Pixels | Typical use |
|---|---|---|
| `square` | 1024x1024 | Icons, avatars, social tiles (default) |
| `landscape` | 1536x1024 | Blog and card images |
| `portrait` | 1024x1536 | Posters, mobile screens |
| `hero` | 1920x1088 | Full-width hero sections |
| `banner` | 1536x512 | Headers, email banners |
| `auto` | model decides | When the prompt implies a shape |
Explicit `WIDTHxHEIGHT` is also accepted. Each dimension is rounded to the nearest multiple of 16
(minimum 256); the aspect ratio must be within 1:3 to 3:1 and neither side may exceed 3840. The tool
result reports the size actually produced. `background="transparent"` requires `png` or `webp` output.
## Consistent assets
To keep one look across a whole set of assets, generate a brand board first and then pass it as a
reference for every subsequent image. A suggested snippet for a project's `CLAUDE.md`:
```markdown
## Image assets
- Images are generated with the `openai-image` MCP server into `assets/generated/`.
- Run `list_generated_images` first; if `assets/generated/brand-board.png` exists, reuse it.
- If it does not exist, create it once with `generate_image`
(filename `brand-board`, size `landscape`, quality `high`): a style sheet showing our palette
(#0B3D91 navy, #F2A900 amber, off-white), flat vector illustration style, soft shadows,
rounded geometry, no text.
- Create every other asset with `edit_image`, passing
`reference_paths: ["assets/generated/brand-board.png"]` and describing the new subject
in the prompt ("In the style of the reference: …"). Use `background: transparent`
with `png` for icons and logos.
- Inspect the returned preview; if it is off-brand, adjust the prompt and regenerate rather
than editing the file by hand.
```
## Errors
Common OpenAI failures are translated into short, actionable messages: missing organization
verification, safety-system (content policy) rejections, rate limits and quota, and timeouts. The
OpenAI request ID is included when available. Transient errors are retried by the OpenAI SDK's default
policy; the server adds no retry loop of its own.
## Development
```bash
uv sync # installs dev dependencies (pytest)
uv run pytest # unit tests + a stdio round-trip; no network access
uv run python scripts/smoke.py # one real low-quality generation; needs OPENAI_API_KEY
```
Layout: `src/openai_image_mcp/server.py` (tool definitions), `client.py` (OpenAI calls and error
translation), `images.py` (size handling, file naming, sidecars, previews).
TDQS
Scored across 3 tools
The three tools have clearly distinct purposes: generate_image creates new images from text only, edit_image modifies or creates images guided by reference images, and list_generated_images discovers previously created assets. No overlap or ambiguity exists between them.
All tool names follow a consistent verb_noun snake_case pattern: generate_image, edit_image, list_generated_images. The verbs clearly indicate the action and the nouns consistently refer to images.
Three tools is well-scoped for an image generation server: create, edit, and list. Each tool earns its place and there is no bloat or missing core functionality.
The tool set covers the full lifecycle for this domain: generating new images, editing existing ones (including masking and style references), and listing previously generated assets for reuse. No critical dead end or missing operation is apparent.