Skip to main content
Glama

mcp-image-gen

This is a small Node.js MCP server that gives an AI coding agent (Claude Code or any MCP client) one tool, generate_image.

I generate a lot of images from Claude Code sessions, and I run ComfyUI with Qwen-Image 2.1 on my own GPU. Cloud image quotas run out, and when the one I relied on did, my image work stopped. So this server tries the OpenAI Images API first (when it's configured) and falls back automatically to Qwen-Image 2.1 on a local ComfyUI server when the cloud is rate-limited or unavailable.

No npm dependencies, just Node.js built-ins (fetch, FormData, node:test).

Sample output: Qwen-Image 2.1 on a local ComfyUI, 1024x1024, 35 s end to end

Sample output of the local route, requested over MCP stdio with the prompt "a small red fox sitting in a snowy forest, soft morning light" (Qwen-Image 2.1 on ComfyUI, 1024x1024, 35 s).

How it works

flowchart LR
  C["MCP client (e.g. Claude Code)"] <-->|"stdio, JSON-RPC 2.0"| S["server.mjs"]
  S --> V["validate input"]
  V --> G{"backend"}
  G -->|"auto / openai"| O["OpenAI Images API<br/>/images/generations, /images/edits"]
  O -->|"429: pause cloud 15 min<br/>network, 401/403/404, 5xx"| Q
  O -->|"400 refusal: error, no fallback"| E["tool error"]
  G -->|"comfyui, or cloud skipped"| Q["ComfyUI: Qwen-Image 2.1<br/>/upload/image, /prompt, /history, /view"]
  O --> F["PNG files + file:/// links"]
  Q --> F

Related MCP server: 2Xapi.com GPT-image MCP Server

Quickstart

You need Node.js 20.11+. The local backend also needs a ComfyUI server with the Qwen-Image 2.1 nodes (TextEncodeQwenImage21, QwenImage21Cache) and the model files; the cloud backend needs an OpenAI API key.

git clone <this repo> && cd mcp-image-gen
npm test

# register with Claude Code (the key is read from your environment, not stored in the MCP config)
export OPENAI_API_KEY=...            # optional
claude mcp add image-gen -e COMFYUI_URL=http://127.0.0.1:8188 -- node /path/to/mcp-image-gen/src/server.mjs

Everything is configured through environment variables (see .env.example). These are the main ones:

Variable

Default

Meaning

OPENAI_API_KEY

(none)

Turns on the cloud backend. Without it, auto goes straight to ComfyUI.

OPENAI_IMAGE_MODEL

gpt-image-1

The model sent to the Images API. OpenAI's guide now lists newer GPT Image models, so set this to a current one.

COMFYUI_URL

http://127.0.0.1:8188

The ComfyUI server.

QWEN_UNET, QWEN_CLIP, QWEN_VAE

the Qwen-Image 2.1 file names

Model files in your ComfyUI install.

IMAGE_OUTPUT_DIR

~/Pictures/mcp-image-gen

Where images go when the call doesn't name a path.

The tool

generate_image { prompt, output?, references?, format?, quality?, variants?, backend? }

  • references: up to 10 image paths (PNG/JPEG/WebP, max 20 MB each) to keep a character, place or style. OpenAI gets them as image[] parts of /images/edits; ComfyUI gets them as uploads wired into the Qwen-Image 2.1 encoder.

  • format: portrait (default; 1024x1536 on OpenAI, 768x1376 on ComfyUI), square, landscape.

  • backend: auto (default), openai or comfyui.

  • The result names the backend, the real pixel size read from the PNG header, the time, and the saved paths with a file:/// link for each, plus a note when it fell back.

Tests and CI

npm test     # node --test: 23 tests, offline

The tests start local fake servers (a fake OpenAI and a fake ComfyUI, built on node:http), and every HTTP call goes to them, so there are no real API calls. test/server.test.mjs runs the real server process over stdio, end to end. .github/workflows/ci.yml runs the suite on Ubuntu and Windows with Node 20 and 22.

Security

These are the security rules I set for the server:

  • Transport is stdio only. The server opens no network port, talks only to the MCP client that started it, and makes outbound requests only to the configured OpenAI and ComfyUI URLs.

  • Inputs are validated before any request goes out: the prompt is required and at most 4,000 characters; format, quality and backend are enums; variants is an integer from 1 to 4; there are at most 10 references, each an existing regular file with an image extension and at most 20 MB; output must be a non-empty path without NUL bytes. The schema rejects unknown arguments (additionalProperties: false).

  • No secrets are logged, because nothing is logged: stdout carries only protocol messages. The API key is read from the environment, sent only in the Authorization header, and redacted from any upstream error text before it reaches the client (some APIs echo the key back in auth errors; a test checks the redaction).

  • Refusals are never routed around silently. A cloud refusal (HTTP 400, e.g. a content policy rejection) comes back as an error. It isn't retried on the local model unless the caller explicitly asks for backend: "comfyui".

  • The server writes images wherever the client asks (output). It trusts its MCP client, like any local tool with file access.

Design decisions

  • Classify cloud errors, then decide. rate-limited (429, quota) -> fall back and skip the cloud for 15 minutes, so the next calls don't wait on a known failure; unavailable (network, 401/403/404, 5xx) -> fall back for this call; refused -> error.

  • The local workflow is built in code. buildWorkflow generates the ComfyUI graph (API format). That way references are padded to the encoder's three inputs (repeating the last one), extra references get their own nodes, and an explicit EmptyLatentImage fixes the canvas size (without it, the canvas follows the first reference). One job per variant, with consecutive seeds.

  • Real size in the result. The reported size is read from the saved PNG header, not from the request.

  • The tool stays visible. _meta["anthropic/alwaysLoad"] keeps it in the model's base tool list, so the model calls it instead of answering "I created the image" without calling anything.

  • Zero dependencies, so the server starts instantly and has no supply-chain surface beyond Node.js.

Limitations

  • I haven't verified the OpenAI request/response shape against the live API in this version (see below), and the default model name may need changing for your account.

  • The ComfyUI backend needs the Qwen-Image 2.1 nodes and models; other models need another workflow. It polls /history every 2 seconds instead of using the WebSocket API.

  • No streaming or partial images, and no cancellation of a running ComfyUI job when the client disconnects.

  • Variants on ComfyUI run one after another.

How I built it

I built this with AI coding agents (Claude Code), which wrote most of the code. My part was the architecture (the backend order, the error classes and when to fall back, the security rules), the review gates and the verification.

I don't take an agent's "done" at face value, so this is what I checked, and how:

  • This repository: npm test runs 23 offline tests and they pass (fake OpenAI and ComfyUI servers; the stdio server is tested as a real child process). I also checked each critical rule by injecting a deliberate bug and confirming that a test fails: no fallback on refusal, key redaction, the rate-limit pause, reference padding, consecutive seeds, and the 401 classification.

  • The private predecessor of this server is the version I use every day (same tool, same fallback rules, a different cloud route). I verified it with a stdio test client, no model involved: real cloud text-to-image and reference-guided generations, a real local Qwen-Image 2.1 generation (21 s), and a fake cloud endpoint answering 429, after which the server switched to Qwen and the next request skipped the cloud. Then a real Claude Code session found the tool on its own and returned the file:/// link. That's also how I found out the alwaysLoad flag was necessary. The ComfyUI workflow graph (nodes, parameters, the EmptyLatentImage canvas fix, padding references to three) comes from my production setup.

  • Local route, verified on a real server (29.10.2026): I started this server as a stdio child process with no OpenAI key, and it answered initialize, tools/list and a generate_image call. With auto, it fell back to ComfyUI with the note "OpenAI not configured", and Qwen-Image 2.1 returned a 1024x1024 PNG in 35 s (the sample above).

  • Cloud route, not run yet: in this repo the predecessor's cloud route is replaced by the official OpenAI Images API, which has never been called with a real OpenAI account. Only the mocked tests cover it. I checked the request shape against OpenAI's image-generation guide on 29.10.2026 (repeated image[] parts, the three sizes, data[].b64_json). My assumptions about the OpenAI API: POST /v1/images/generations (JSON) and POST /v1/images/edits (multipart, repeated image[] parts), fields model, prompt, n, size (1024x1536, 1024x1024, 1536x1024), optional quality (low/medium/high), response data[].b64_json, errors as { error: { message, code } }, default model gpt-image-1.

  • CI: GitHub Actions runs npm test on Ubuntu and Windows with Node 20 and 22. The first run passed on 29.10.2026.

License

MIT, see LICENSE.

Available Tools

1 tool
generate_imageA

Generates a PNG image and saves it to disk. Tries the OpenAI Images API first (when OPENAI_API_KEY is set); when it is rate-limited or unavailable, falls back automatically to Qwen-Image 2.1 on the local ComfyUI server. Returns the file paths and the backend used.

NameRequiredDescriptionDefault
formatNoportrait (default): 1024x1536 on OpenAI, 768x1376 on ComfyUI. square: 1:1. landscape: the portrait sizes rotated.
outputNoFolder or .png file to save to. Default: the image folder, in a subfolder per day.
promptYesWhat the image must contain. English works best.
backendNoauto (default): OpenAI, then ComfyUI when OpenAI is rate-limited or unavailable. openai / comfyui: only that backend.
qualityNoOpenAI only. Default: the model default.
variantsNoHow many variants (default 1).
referencesNoPaths to reference images (character, place, style); with them the image is made by editing.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses backend fallback ordering, the OPENAI_API_KEY prerequisite, rate-limit/unavailability triggers, and that results are written to disk. It doesn't cover what happens when both backends fail, or any cost/latency tradeoffs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, front-loaded with the action and output, followed by routing behavior and return shape. No filler; each sentence adds information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a multi-backend generation tool with no output schema, the description usefully states the return value ('file paths and the backend used') and the fallback behavior. The only gap is failure behavior when both backends are unavailable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all seven parameters including enums and limits are already documented. The description only echoes the backend fallback already stated in the 'backend' field and adds no new parameter-level meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Generates a PNG image and saves it to disk') plus the multi-backend routing, so an agent knows exactly what the call produces. No sibling tools exist, so no differentiation is needed.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the default backend-selection logic (OpenAI first, ComfyUI on rate-limit/unavailability), which is useful context, but it never says when to call this tool versus not calling it, nor when an agent should override the 'auto' default.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev1.0.0
    • First observedgenerate_image

TDQS

A4/5.0

Scored across 1 tool

Disambiguation5/5

With a single tool there is no possibility of misselection or overlap. An agent's only choice is whether to call it at all, and the description makes the trigger conditions (generate a PNG from a prompt, with automatic backend fallback) unambiguous.

Naming Consistency5/5

The single name follows a clean verb_noun snake_case convention (generate_image) that would scale predictably if more tools were added. No style mixing is possible with one tool.

Tool Count3/5

One tool is thin even for a narrowly scoped server; the rubric treats 1-2 tools as borderline. It earns its place, but the surface feels underbuilt for a server whose apparent remit is image generation and management.

Completeness3/5

Generation is well covered, including a fallback backend, but there is no way to list, inspect, delete, or re-run previously generated images, and no way to check backend/API status. These are notable gaps an agent would hit when managing outputs, though core generation works.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers