image2-mcp
Provides image generation and editing capabilities through any OpenAI-compatible API, including text-to-image, image editing/fusion with optional masks, and model/connectivity checks.
Click on "Deploy 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., "@image2-mcpCreate a photorealistic golden retriever in a wizard hat and save it to my desktop"
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.
image2-mcp
An MCP Server that wraps an OpenAI-compatible image generation API, giving any MCP client (Claude Desktop / Cursor / Cline / WorkBuddy …) native image-generation tools.
Dependencies are deliberately minimal — only @modelcontextprotocol/sdk and zod at
runtime, using Node's built-in fetch, with an 80-line hand-rolled .env parser.
This repository is not affiliated with any API relay service. No relay is bundled or assumed — you must point it at a service you are authorized to use via
IMAGE2_BASE_URL.
Table of Contents
Related MCP server: GPT Image MCP
1. What this server wraps
An OpenAI-compatible image API — you supply the endpoint via IMAGE2_BASE_URL.
Its main job is turning the API response into a real image file on disk and handing back an
absolute path. That is the advantage over calling the API with raw curl: the client gets a
local file it can immediately read and display.
1.1 Endpoints used
All three are standard OpenAI shapes, so they are the same across compatible relays:
Endpoint | Method | Content-Type | Purpose |
| POST |
| Text-to-image |
| POST |
| Image edit / multi-image fusion |
| GET | — | Connectivity & model list probe |
1.2 Response shape
{
"created": 1789630481,
"data": [{ "b64_json": "iVBORw0KGgoAAAANSUhEUg..." }],
"size": "1024x1024",
"quality": "high",
"output_format": "png",
"usage": { "output_tokens": 2058, "total_tokens": 2065 }
}⚠️ Two possible response shapes. An entry in
data[]carries eitherb64_jsonor a temporaryurl— which one you get depends on the request (in testing,output_format: "jpeg"produced aurl, while the default returnedb64_json). Both are handled: base64 is decoded locally, aurlis downloaded (with retries).
1.3 Model names depend on your relay
This server assumes no particular model names. Which models a relay exposes is up to that relay — the same capability may be named completely differently from one service to another.
Find out what yours offers:
check_image_serviceIt queries {base}/models and lists everything the relay advertises, highlighting the
image-related entries. Then pass whatever ID actually exists on your relay as the model
parameter.
⚠️ The table below is an example, not a standard. These are the model IDs that happened to work on the author's relay in 2026-09. Your relay will very likely use different names.
Model ID (example) | Alias | Status | Notes |
|
| ✅ OK | Default |
|
| ✅ OK | Variant, different style |
|
| ✅ OK | Variant, different style |
|
| ✅ OK | Higher-detail variant |
Which model gets used
Setting | Effect |
| Used for that one call. Any model ID works — aliases are optional. |
| Used when |
| Define your own shorthands, e.g. |
The built-in aliases (base / pro / hq / flare / sunburst) are pure convenience and
carry no meaning outside the author's relay. You can ignore them and pass explicit model IDs,
or replace them via IMAGE2_MODEL_ALIASES — no code changes needed.
2. The 3 exposed tools
generate_image — text-to-image
Generates an image from a description and writes it to disk, returning an absolute path.
Parameter | Required | Description |
| ✅ | Image description; more detail is better |
| A model ID your relay actually serves — see 1.3 | |
|
| |
|
| |
| Count, 1–4 | |
|
| |
|
| |
| Output directory, default | |
| Filename prefix |
edit_image — image edit / img2img
Generates a new image from existing ones; accepts multiple reference images for fusion and an optional mask for inpainting.
Parameter | Required | Description |
| ✅ | What to change, e.g. "replace the background with a snowy sunset" |
| ✅ | Array of local input paths, 1–8 images |
| Optional mask (PNG; transparent area = region to repaint) | |
others | Same as |
check_image_service — connectivity self-check
Probes relay reachability, key validity, timeout and output-dir settings, and lists the image models the server advertises. Run this first when troubleshooting.
3. Install & configure
Requirements
Node.js ≥ 20 (uses built-in
fetch/FormData/Blob; see.nvmrc)An OpenAI-compatible relay endpoint and API key
Install
git clone https://github.com/sailor20/image2-mcp.git
cd image2-mcp
npm installConfigure
cp .env.example .envThen edit .env and fill in both required values:
# Your relay's root URL — there is no built-in default
IMAGE2_BASE_URL=https://your-relay.example.com/v1
IMAGE2_API_KEY=sk-your-keyAlternatively pass them via your MCP client's env field
(see section 5) so no .env is written.
If either value is missing, the tools return an error that names the missing variable — the server does not guess an endpoint.
.envis gitignored and will never be committed. Never put a real key in any file that gets committed.
4. Usage
Just describe what you want
Once the tools are live you don't need to write JSON — natural language works:
Generate an image: a cyberpunk city at night, neon reflecting off rain-slicked streetsMake a 16:9 tech product poster for an AI coding assistant, dark backgroundFor fine control, state the parameters inline:
Generate 4 forest illustrations in Ghibli style, square, high qualitySwitching models
Run check_image_service first if you don't know what your relay offers — model IDs are
relay-specific (see 1.3).
A. Per call
Just name the model, either as a raw ID or (if you defined one) an alias:
Use my-relay-turbo to generate 4 forest illustrations in Ghibli style, squareAliases are entirely optional. Define your own in .env — no code changes needed:
# 别名=模型ID,逗号分隔;设置后完全取代内置别名
IMAGE2_MODEL_ALIASES=fast=my-relay-turbo,cheap=my-relay-liteThe built-in aliases, for reference, are only meaningful on the author's relay:
Alias | Resolves to |
|
|
|
|
|
|
|
|
B. Change the default (persistent)
Set it to a model your relay actually serves:
IMAGE2_DEFAULT_MODEL=my-relay-turboYou must restart the MCP connection (toggle the server off/on, or restart the client).
C. Set it in the MCP client's env (highest priority)
"env": {
"IMAGE2_DEFAULT_MODEL": "my-relay-turbo"
}Priority: MCP client
env>.envfile.config.jsonly fills from the file when the variable isn't already set. If editing.envseems to have no effect, check here first.
Parameter reference
Parameter | Values | Default | Notes |
|
|
| see table above |
|
|
| server may adjust |
|
|
| affects latency and tokens |
| 1–4 | 1 | more images = slower and pricier |
|
|
|
|
|
|
| see section 9 |
| path |
| relative paths resolve against the server root |
| filename prefix | auto | handy for finding results later |
Image editing
edit_image takes local file paths:
Change the wall in ~/pics/room.png to dark green, keep the furniture as-isMulti-image fusion (1–8 references):
Using the style of ~/pics/style1.png and the subject of ~/pics/subject.png,
generate a new illustrationInpainting requires a mask PNG (transparent area = region to repaint):
Use ~/pics/mask.png as the mask and replace the masked area with a catWhere output goes
By default into output/ under the project, named like {prompt-words}-{timestamp}.png.
The returned absolute path can be read, displayed, or edited further. If a name is already
taken, a numeric suffix is appended (name-2.png, name-3.png, …) so nothing is overwritten.
The extension reflects the actual file bytes (see section 9, item 2). If the real format differs from what you requested, the returned text says so explicitly.
5. Connecting to an MCP client
5.1 The config, in JSON
Merge this into your client's mcpServers:
{
"mcpServers": {
"image2": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/image2-mcp/src/index.js"],
"env": {
"IMAGE2_BASE_URL": "https://your-relay.example.com/v1",
"IMAGE2_API_KEY": "sk-your-key",
"IMAGE2_OUTPUT_DIR": "/absolute/path/to/image2-mcp/output",
"IMAGE2_TIMEOUT_MS": "300000"
},
"timeout": 600000
}
}
}Where the config lives:
Client | Config file |
Claude Desktop |
|
Cursor |
|
WorkBuddy |
|
Others | see your client's MCP / stdio docs |
Windows: prefer an absolute path to
nodeforcommand, and use either double backslashes (C:\\path\\to\\...) or forward slashes (C:/path/to/...) inargs.
5.2 Adding it from the WorkBuddy UI
The MCP page's Add MCP server dialog has three tabs — Quick / Manual / JSON (快速 / 手动 / JSON). They all produce the same server entry; they differ only in how much you type. Pick whichever suits you.
Tab 1 — Quick (快速)
One box: paste a command, a URL, or a JSON blob, and the transport is detected automatically. For a local stdio server like this one, paste the command that starts it:
node /absolute/path/to/image2-mcp/src/index.jsThat's the whole thing — no environment variables needed in the dialog, provided you have
already filled in .env. The server resolves .env relative to its own script location, not
the working directory, so it finds the file even when the client launches it from elsewhere.
Switch to another tab if you need to pass env vars through the dialog instead of .env.
Tab 2 — Manual (手动)
Fill in three fields, then expand Advanced options (高级选项) if you want to set environment variables or a working directory.
Field | Value |
Name (名称) |
|
Transport (传输方式) |
|
Command (命令) |
|
Tab 3 — JSON
Paste the configuration exactly as shown in 5.1. This tab accepts
either form — with or without the outer mcpServers wrapper:
{ "image2": { "type": "stdio", "command": "node", "args": ["..."] } }{ "mcpServers": { "image2": { "type": "stdio", "command": "node", "args": ["..."] } } }After adding: the server appears in the MCP list. On clients that require it, trust or enable it there before the tools become available.
Windows tip: use forward slashes in paths (
D:/tools/image2-mcp/src/index.js) or escape the backslashes (D:\\tools\\...). Ifnodeis not on the client'sPATH, use the full path to thenodeexecutable as the command instead.
5.3 Three things to watch
Raise the timeout. A single image takes 40–180 s; most clients default to 60 s and will cut it off. Increase both the client-side
timeoutand the server-sideIMAGE2_TIMEOUT_MS(e.g. 300000–600000).Config changes often don't hot-reload. Restart the client, or trust/enable the new server in the connector/MCP settings page.
Never commit your key. Prefer
.env(gitignored), or the client's local config file, which should itself stay out of version control.
6. Environment variables
Variable | Default | Description |
| — | Required. Relay root, without |
| — | Required. |
|
| Output dir; relative paths resolve against server root |
|
| Per-request timeout (ms); max 2147483647 |
|
| Model used when |
| — | Your own shorthands, |
|
| Default size |
|
| Default quality |
|
| multipart field name for |
| — | Path to an extra |
There is no built-in default relay.
IMAGE2_BASE_URLhas no fallback, on purpose: the server should never silently target a third-party service you did not choose. If either required variable is missing, tool calls return an error naming exactly which ones.
Load priority: process env (i.e. the MCP client's env) > IMAGE2_ENV_FILE > project .env.
Variables already present in the environment are never overwritten by the file.
7. Development
Everyday commands
Command | What it does |
| Full test suite via Node's built-in test runner |
| ESLint over the repository |
| ESLint with automatic fixes |
| Format everything with Prettier |
| Verify formatting without writing (what CI runs) |
| Real end-to-end request — costs credits, not run in CI |
Tests
The suite lives in test/ and runs on the built-in
node:test runner — no test framework dependency.
npm test # everything
node --test test/edge-cases.test.js # a single fileIt is fully offline: it never contacts a real API and never spends credits.
test/protocol.test.js spawns the real server over stdio and checks the handshake, the tool
list, input validation, and the error paths for a missing API key and unreadable input files.
test/edge-cases.test.js covers format sniffing, filename sanitization, and atomic writes.
Smoke test
Hits a live endpoint and generates one image, so it is run manually by maintainers:
npm run smoke # default prompt
node scripts/smoke.js a cat wearing sunglasses # custom prompt
node scripts/smoke.js --models # probe only, no generationContinuous integration
.github/workflows/ci.yml runs lint, format check, and tests on Node 20 and 22, plus one
Windows job (filename handling is platform-sensitive). It also runs npm pack --dry-run to
keep the published file list honest.
Contributions are welcome — see CONTRIBUTING.md.
8. Switching to another relay
This implementation is not tied to any specific relay. As long as the target service is
OpenAI-compatible, just change IMAGE2_BASE_URL and IMAGE2_API_KEY. Four things need
attention:
Model names — there is no universal default, and every relay names its models differently. Run
check_image_serviceto list what yours offers, then setIMAGE2_DEFAULT_MODELto one of them (and add your ownIMAGE2_MODEL_ALIASESif you want shorthands). See 1.3.b64_jsonvsurl— both are already handled inpersistImages().Support for
size/quality/output_format— unsupported params are either ignored or rejected by the server; error messages are passed through verbatim.The
editsfield name — the standard isimageplus optionalmask. If your relay differs, just change theIMAGE2_EDIT_FIELDenv var — no code changes needed.
9. Known upstream behaviors & pitfalls
All items below were empirically verified, not assumed.
1. size may be adjusted by the server
Request size: 1024x1024 and you may get 1254×1254 back. Some upstream implementations apply
their own sizing policy; the size field in the response is the real value.
This is expected, not a bug.
2. output_format: "jpeg" may still return PNG bytes
Verified: when requesting output_format: "jpeg", the response echoes
output_format: "jpeg", but the decoded data[].b64_json bytes are actually PNG.
How this server handles it: the on-disk extension is derived from the magic bytes
(sniffImageExt()), ignoring both the request parameter and the response field. So you'll
get photo.png, not photo.jpg. This is intentional — it prevents producing files whose
extension contradicts their contents, which image hosts and CDNs with MIME sniffing reject.
Because of this, the tool result reports two separate fields rather than one ambiguous one:
Field | Meaning |
| the format actually written to disk (from the bytes) |
| what you asked for, or |
When the two differ, the human-readable part of the result also says so explicitly.
Every entry in files[] carries its own format as well.
3. image vs image[] route to different upstream backends
The relay is sensitive to the multipart field name:
Field name |
|
|
|
|
|
|
|
|
|
|
|
So mixing the two field names makes 1-image and multi-image calls hit different implementations, changing both output style and response shape.
How this server handles it: it uses one field name for all images (default image),
overridable via IMAGE2_EDIT_FIELD. If your relay requires image[], set
IMAGE2_EDIT_FIELD=image[].
4. Multi-image requests may omit size / quality
When passing multiple reference images, the upstream response may lack size and quality;
those fields will be null in the result.
5. url responses can be slow to connect, and the image is already paid for
When the response uses a url, the file lives on a CDN whose connection time varies widely —
in testing, between roughly 0.5 s and beyond 10 s. Node's fetch applies its own 10-second
connect timeout (UND_ERR_CONNECT_TIMEOUT), which fires before IMAGE2_TIMEOUT_MS and
cannot be raised through that variable.
This matters because by the time the download starts, the image has already been generated and billed. A single transient network hiccup would otherwise mean paying for an image and receiving nothing.
How this server handles it: downloads are retried up to 3 times with a short backoff, and
4xx responses (a genuinely dead link) are not retried. Failure messages include the underlying
cause rather than a bare fetch failed, so the reason is visible when retries are exhausted.
10. Project layout
image2-mcp/
├── src/
│ ├── index.js # MCP server: tool definitions, validation, startup
│ ├── relay-client.js # HTTP client + format sniffing + safe writes
│ └── config.js # zero-dependency .env parser
├── test/
│ ├── protocol.test.js # spawns the server; handshake, tools, error paths
│ └── edge-cases.test.js # format sniffing, filenames, atomic writes
├── scripts/
│ └── smoke.js # manual end-to-end check (costs credits)
├── .github/workflows/ci.yml # lint + format check + tests + pack check
├── eslint.config.js # ESLint flat config
├── .prettierrc.json # formatting rules
├── .editorconfig # editor whitespace rules
├── .nvmrc # pinned Node major version
├── .env.example # config template (.env is gitignored)
└── output/ # generated images (gitignored)Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md. For security matters, please follow SECURITY.md instead of opening a public issue.
License
Related MCP Connectors
Generate AI images and videos from any compatible MCP client.
OCR, transcription, file extraction, and image generation for AI agents via MCP.
Generate images with any major model — one API key, one prepaid balance, one MCP.
Focused MCP server for OpenAI image/audio generation (v2.0.0). Wraps endpoints via HAPI CLI.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides tools for generating and editing images using OpenAI's gpt-image-1 model via an MCP interface, enabling AI assistants to create and modify images based on text prompts.15Apache 2.0
- AlicenseNot gradedqualityBmaintenanceGenerates and edits images using OpenAI image models via MCP tools.MIT
- AlicenseAqualityBmaintenanceGenerates and edits images using OpenAI GPT Image or Google Gemini models, saving every result to disk and returning local file paths so AI assistants can continue working with the images. It enables prompt-based image creation, editing, inpainting, multi-image composition, and model listing through MCP tools.116 npm1Apache 2.0
- FlicenseAqualityCmaintenanceEnables generating and editing images through OpenAI image models, with configurable output as saved files or returned URLs.2-