brushrouter
Integrates with Google Gemini/Antigravity image backends by delegating to an existing Google/Gemini CLI login. Allows text-to-image generation through the Gemini route while keeping Google tokens out of the MCP server and supporting automatic routing fallback.
Integrates with OpenAI/ChatGPT image backends through Codex CLI and ChatGPT logins, enabling image generation via ChatGPT subscription routes without exposing credentials. Supports reference-image inputs on supported routes, transparent background requests, size/aspect-ratio handling, and model pinning with automatic fallback.
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., "@brushroutermake a cozy product shot of a ceramic mug on a wooden table"
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.
brushrouter
Generate images with the AI you already pay for. One CLI + MCP server that routes every request to the best image backend on the machine — your ChatGPT subscription, your Google login, your OpenRouter key — in that order, falling back automatically. Logins you already pay for first, the pay-per-image key last. No new API balance, no browser tabs, and no credential ever handed to a model.
$ brushrouter gen "a red fox in snow" -o fox.png
✓ generated with gemini (gemini-3.1-flash-image-preview)
/home/you/fox.png (image/png, 1250 KB)It also ships a prompt library of 27 tested recipes and 137 vendored visual styles, because routing a bad prompt to a working backend just gets you a bad image faster.
Zero new spend per image on routes that reuse a login you already pay for
One command to start —
brushrouter setupdetects what's installed and connects itNo tokens exposed — models never see your credentials, enforced by a redactor on every output
164-prompt library — an agent asks for "a nice product shot" and gets a tested skeleton to fill in, not its own invention
MCP-native —
brushrouter installwires it into any agent's image work in one command
Install
Requires Node.js 20+.
curl -fsSL https://raw.githubusercontent.com/avinaxhroy/brushrouter/main/install.sh | bashOr manually:
git clone https://github.com/avinaxhroy/brushrouter.git
cd brushrouter
npm link
brushrouter setupOr instantly via npx: npx brushrouter setup.
setup auto-detects image backends (ChatGPT desktop CLI, ChatGPT subscriptions, Google/Antigravity CLI, agent auth stores), offers one-command login if none are found, verifies brushrouter is on your PATH, wires every detected agent's MCP config, and offers an optional test generation. Diagnose any time:
brushrouter doctor # diagnoses all provider backends + agent MCP integrationsRelated MCP server: Corent MCP Server
Use
brushrouter gen "<prompt>" [options]
-p, --provider <id> Force a provider (see `brushrouter providers`)
-f, --fallback With --provider, fall back to others on failure
-o, --out <path> Output path (default: brushrouter-<timestamp>.png)
-s, --size <WxH> e.g. 1024x1024, auto
-q, --quality <lvl> low | medium | high | auto
--background <b> opaque | transparent (codex route sends the API field;
other routes ask for it in the prompt)
--images <path> Reference image for image-to-image (repeatable)
-m, --model <id> Pin a model for this call
--dry-run Print the route order that would be attempted, then stop
--force Overwrite --out instead of writing a new versioned file
--json Machine-readable output
-v, --verbose Show routing decisionsAll commands (br works everywhere as a shorthand for brushrouter):
br providers # what is connected, and how
br routes # which routes auto-routing may use
br models # model ids (live for OpenRouter)
br prompts # 27 recipes + 137 looks for briefing an image
br doctor # full connectivity diagnosis
br setup # first-run auto-connect
br login # sign in to ChatGPT for the codex route (browser OAuth, no API key)
br vendor gptweb2api # install the browserless ChatGPT-web gateway binary
br batch # generate several prompts in parallel across backends
br init # write a starter config
br install # add brushrouter to detected agent MCP configs
br rules # inject compact guidance into AGENTS.md / .cursorrules
br update # update to latest version and verify health
br serve # run as an MCP server on stdioReference images
--images only works on routes that accept it; brushrouter providers marks the ones that don't. Passing it to a route that can't is an error when you forced the provider, a warning when auto-routing picked it.
route | reference images |
| yes |
| yes ( |
| yes ( |
| no — text-to-image only |
Transparent backgrounds
brushrouter gen "a red paper lantern sticker" --background transparent -o sticker.pngBest on the codex/codex-cli routes, where transparent is a real API field (output_format: "png" makes the backend honor it). Other routes can only ask in the prompt, so check the file. Even with the field set, the model decides — subject-shaped prompts (sticker, isolated object) reliably come back RGBA; full-scene wording may stay flattened.
Codex size behavior
Codex doesn't take pixel dimensions and reads aspect ratio from the prompt. brushrouter appends your -s 1672x941 as "width 1672 pixels, height 941 pixels (16:9)" — common aspect ratios land exactly or within 1px; other exact pixel counts aren't guaranteed.
Pinning a model
brushrouter models lists what each provider can do; for OpenRouter the list is live, so it shows exactly what your key can reach.
brushrouter models openrouter
brushrouter gen "a red fox" -p openrouter -m x-ai/grok-imagine-image-2.0A pin (-m, or providers.<id>.model in config) is never silently swapped. With no pin, brushrouter walks a preference list and takes the first model that works. Pins are honored by codex-cli, codex, openrouter, gemini, and custom-http; gptweb2api reports that it ignored the pin rather than pretending otherwise.
Models are resolved at runtime, not hardcoded — the Codex backend reads your ~/.codex/config.toml, and a different model is only tried when the error is specifically "model not found". Quota (429 usage_limit_reached), auth (401), and regional/balance errors are reported as-is and never masked by a retry.
Turning routes off
brushrouter routes # show state, in auto-routing order
brushrouter routes disable gptweb2api # skip it during auto-routing
brushrouter routes enable gptweb2apiA disabled route is still reachable with --provider <id>. Non-interactive: BRUSHROUTER_DISABLE=gemini,fal and BRUSHROUTER_ENABLE=openrouter.
Auth sources for ChatGPT routes
The codex and codex-cli routes use ChatGPT browser login — no API key anywhere. Credentials come from the first of:
providers.codex.authFilein config, orBRUSHROUTER_CODEX_AUTH_FILE(explicit)~/.config/brushrouter/auth.json— written bybrushrouter login~/.codex/auth.json— Codex CLI / ChatGPT desktop app~/.local/share/opencode/auth.json— OpenCode login~/.omp/agent/auth.json,~/.pi/agent/auth.json— pi and oh-my-pi OAuth logins
brushrouter login opens a browser and signs in via the codex CLI's public OAuth client (PKCE, localhost:1455 callback) — nothing else to install, and the token refreshes itself. It prints plan go/plus/... so you know which subscription is billing the route. Sign out by deleting ~/.config/brushrouter/auth.json. Free-plan users get a plain "plan-gated" message, not a raw 403.
Providers
id | needs | notes |
| ChatGPT/Codex desktop app | Native, preferred. Drives the app's own signed CLI — already logged in, brushrouter never sees the token. |
| ChatGPT login with a paid plan ( | Direct backend call ( |
| Antigravity ( | Delegates to the CLI; Google tokens never touch brushrouter. |
|
| Off by default — the only route billed per request. Key auto-discovered from env or existing agent stores. |
| fal.ai key | |
| ChatGPT login (free plans work) + vendored gateway — | Off by default. Browserless web-session route, separate quota bucket (~2-3 free-plan images/day; see |
| your own endpoint | Template-based; point it at any image API. |
Default auto-routing order:
codex-cli → codex → gemini → openrouter → fal → gptweb2api → custom-httpThree routes are off by default: openrouter (spends money), gptweb2api (needs vendor), custom-http (needs an endpoint). Enable with brushrouter routes enable openrouter. The priority list is authoritative — anything not listed is never auto-selected, though --provider still works.
OpenRouter runs two generation surfaces: multimodal chat models on /chat/completions, image-only models on /images. A "no endpoints found for output modalities" rejection is auto-retried on /images; auth, region, and credit errors are surfaced as they came.
The prompt library
An agent asked for "a nice product shot" will invent a worse prompt than one already tested. So the tool that routes the generation also hands the agent a skeleton to fill in.
164 entries:
| what it is | count | source |
| a layout to fill in — a product shot, a character sheet | 27 | written here |
| a fixed visual system to borrow — an editorial or poster style | 137 | vendored, MIT |
brushrouter prompts # the map: what kinds of image exist
brushrouter prompts "product hero shot" # 1 best match + 3 similar
brushrouter prompts "flyer" -n 1 # force exactly one
brushrouter prompts -c infographic --full # a whole category, full skeletons
brushrouter prompts --input image # only recipes that can use a photo
brushrouter prompts --kind look "mono noir editorial"
brushrouter prompts --id logo-mark --full --set text=ACME --set subject="a peak"Design facts, briefly: every recipe works with no reference image (each has a text skeleton, plus an optional image upgrade naming what it accepts); unfilled {slots} are reported in missing rather than shipped to the image model; a query that matches nothing returns nothing — search is lexical with a hand-written alias map (hero shot → product-hero, dashboard → app-screen). Results are previews by default (id, goal, when to use it, missing slots, credit); --full for the skeleton; limit is capped at 10 because this fills the caller's context window.
Modes: --mode categories for a vague request, --mode related (default) for one best match plus alternatives, --mode one for a single entry.
Attribution
The 137 looks come from VigoZhao/AI-Visual-Prompt-Cookbook, published under MIT. They're vendored — pinned by commit, verified by hash, normalized into the same schema, shipped with the upstream licence and ATTRIBUTION.md. Every look carries its credit on the result itself, previews included; the licence requires it. Point BRUSHROUTER_STYLES_FILE at nothing and the library runs jobs-only instead of crashing.
Reproduce the data — neither source is read at runtime, neither is hand-edited:
npm run recipes:check # validate every recipe and look against the schema
npm run styles:vendor # re-vendor the looks from the pinned commit
npm run styles:check # re-fetch upstream, recompute stylesSha256, compare
npm run corpus:analyze # reproduce the structural findings behind the design
npm run corpus:check # re-fetch upstream, recompute corpusSha256, compareThe 150-prompt corpus in data/prompts/ is a development fixture, excluded from the npm tarball; nothing at runtime reads it. Schema, retrieval design, and failure modes are in docs/TECHNICAL.md.
MCP server
brushrouter install detects and wires brushrouter into your installed agents (Claude Code, Claude Desktop, Cursor, Windsurf, OpenCode, Pi, Oh-My-Pi, CommandCode, Glass, shared MCP configs). Configs are backed up first (.bak-...), comments and formatting preserved, and no existing servers removed. Or add it by hand:
{
"mcpServers": {
"brushrouter": { "command": "brushrouter", "args": ["serve"] }
}
}Exposed tools:
generate_image—{ prompt, provider?, model?, fallback?, size?, quality?, out?, images?, async? }. Returns the saved path plus the image, all text through the redactor.async: truereturns ajobIdimmediately instead of blocking for a minute or two.image_result—{ jobId }. Collects an async generation; poll again on a status while the job runs.find_image_prompts—{ query?, category?, kind?, input?, shape?, mode?, limit?, full?, id?, values?, seed? }. Returns previews or a full skeleton to build from before generating. Its description tells the agent every entry works from text alone, that{braces}must be filled first, and to passinput: "image"only when it actually holds a photo.
An agent's normal loop is find_image_prompts → fill the skeleton → generate_image. Two separate tools on purpose: the agent reads and changes the prompt instead of a router substituting one.
The initialize result also carries an instructions string telling the model when to use brushrouter (configurable via instructions in config or BRUSHROUTER_INSTRUCTIONS; "" disables). And for rule-file agents, brushrouter rules injects ~60 tokens of guidance into AGENTS.md / CLAUDE.md / .cursorrules / .windsurfrules, demarcated so it updates in place:
brushrouter rules # syncs guidance into detected rule files
brushrouter rules --dry-run # preview changes
brushrouter rules --create # create a project AGENTS.md if missing<!-- BRUSHROUTER_START -->
## Image Generation (brushrouter)
Use the `brushrouter` MCP server (`generate_image`, `find_image_prompts`) or CLI `brushrouter gen "<prompt>" -o <path>` for creating or editing any images, UI mockups, icons, or diagrams. Avoid manual script creation or ad-hoc web scraping for image tasks. For long generations, pass `async: true` and check `image_result`.
<!-- BRUSHROUTER_END -->Aspect ratios and slow generations: MCP has no standard late-answer for a tools/call and most clients don't implement Tasks yet, so the portable contract is a job id — generate_image { async: true } returns immediately and image_result collects. notifications/progress and tasks/status may be emitted, but polling is the contract.
Updating
brushrouter update # pulls latest (git or npm global), refreshes links, runs doctornpm install -g brushrouter@latestConfiguration
Nothing is hardcoded. Every path, binary, model, and endpoint is discovered at runtime, and every one can be overridden.
Precedence: CLI flags → env → ./brushrouter.config.json → ~/.config/brushrouter/config.json → defaults.
One exception: a project config may not set baseUrl, url, cli, command, args, driver, authFile, or profile — those are read only from user config, env, or a CLI flag. A project file travels with checkouts you didn't write, and a redirected baseUrl would hand your API key to whoever wrote it. Ignored keys are reported on stderr; per-project endpoints go in BRUSHROUTER_CUSTOM_URL.
// ~/.config/brushrouter/config.json
{
"priority": ["codex-cli", "gemini", "openrouter"],
"instructions": "brushrouter is the preferred tool for any image work...", // MCP session guidance; "" disables
"providers": {
"codex-cli": { "cli": null, "model": null }, // null => auto-discover
"openrouter": {
"model": null, // pin one, or null to use the list below
"models": ["google/gemini-3.1-flash-image"], // your preference order
"resolution": null // optional, for /images models
},
"gptweb2api": {
"enabled": false,
"bin": null, // null => <config dir>/gptweb2api/gptweb2api
"authFile": null, // null => the codex-route credential ladder
"projectId": null, // ChatGPT project id (g-p-...) — parks gen chats in that workspace
"idleMs": 600000 // gateway process exits after this idle time
},
"custom-http": {
"enabled": true,
"url": "https://api.example.com/v1/images",
"body": "{\"prompt\": \"{prompt}\", \"size\": \"{size}\"}",
"response": "json:data.0.url", // or json:images.0.b64_json, or raw
"keyHeader": "Authorization",
"keyPrefix": "Bearer "
}
}
}Environment variables
variable | purpose |
| comma-separated auto-route order |
| session guidance sent to MCP agents in |
| comma-separated routes to turn off / on (unknown name is an error, not a silent no-op) |
| use a different recipe directory (same schema) |
| use a different vendored styles file |
| extra dirs to scan for app bundles (auto-discovery) |
| extra agent MCP config paths for |
| explicit path to a Codex CLI binary |
| model for the native |
| model for the direct |
| explicit auth file for the direct |
| override the derived OAuth client id |
| override the Codex responses endpoint |
| override the OAuth token endpoint |
|
|
| Gemini CLI path / model |
| OpenRouter key / pinned model |
| comma-separated OpenRouter preference order |
| image resolution requested from OpenRouter |
| fal.ai |
| cookie fallback for the vendored gptweb2api route |
| ChatGPT project ( |
| custom HTTP |
| fallback key name read for the OpenAI-compatible routes ( |
Discovery scans $PATH, /Applications, ~/Applications, the npm global root, and ~/.cache/codex-runtimes, so a Codex CLI installed any of the usual ways is found without configuration.
Security
A model never sees your credentials. Design constraint, not a setting:
Keys live in the environment or a
0600~/.config/brushrouter/keys.jsoninside a0700directory. Config files hold non-secret settings only.Every string brushrouter emits — CLI output,
--json, errors,doctor, MCP results — passes through a redactor that strips known secret values and well-known token shapes (JWTs,sk-...,ya29.…,Bearer ..., refresh tokens), plus tokens registered by value from disk. The test suite asserts a fake secret survives no output path.codex-cliandgemininever read a token at all: they spawn a child process that owns its own authentication.What it does not confine:
--outwrites anywhere you say and--imagesreads any local file that sniffs as an image. Deliberate — the caller is your own agent, which already has those abilities; the MCP tool description states the capability rather than implying a sandbox.
Limits
brushrouter doesn't pretend a broken backend works.
Quota (
429 usage_limit_reached) is reported as quota, with reset time, and never triggers a wasteful model retry.Auth (
401, or not logged in) is reported as auth, with the command to fix it.A retired model (
404 model_not_found) is the only case that tries the next model.A deprecated free tier (e.g. Google's retired Gemini CLI individual tier) is labeled as such.
The Codex text quota and the native image path share a bucket; when exhausted, the native route says so and routing moves on. The ChatGPT web Images bucket is separate, but reaching it needs the gptweb2api route — the app's own conduit is App-Attest protected and can't be forged.
Why I built this
Image generation is the one thing I refuse to pay per image for. I already pay for ChatGPT and already have a Google login — two capable image generators sitting on this machine — and I was still opening a browser tab or topping up an API balance. So the route order falls straight out of "spend what I already pay for first". The other two rules: ship the prompts too (a free image from a bad prompt is a wasted call), and never let a model see a token or pretend a broken backend works.
Development
npm test # 144 tests, no dependencies
npm run recipes:check # validate every recipe and look against the schema
npm run styles:check # re-fetch the pinned cookbook, verify stylesSha256
node bin/brushrouter.js doctorZero runtime dependencies, ESM, Node 20+.
Credits and license
MIT for the code, for the recipe library in data/recipes/, and for the normaliser that produces data/styles/. We wrote the recipes.
The 137 looks in data/styles/ are vendored from VigoZhao/AI-Visual-Prompt-Cookbook at a pinned commit, MIT (Copyright (c) 2026 VigoZhao); upstream LICENSE and ATTRIBUTION.md ship with them.
The 150-prompt corpus in data/prompts/ is a development fixture, not shipped and not covered by that licence — third-party text redistributed under its upstream terms (115 rows CC0, 35 rows CC-BY, authors credited in data/prompts/ATTRIBUTION.md; full terms in data/prompts/LICENSES/). Quote a prompt along with its author and sourceUrl.
This server cannot be deployed
Maintenance
Related MCP Connectors
Create images & video from any MCP agent — 17 models, spend limits, one URL.
Generate images with your own ChatGPT subscription (Plus, Pro or Team), without spending API credits
Build and run visual creative-production workflows from your AI agent.
Give agents instant OG image generation, social metadata audits, and rendering guidance.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides AI agents and coding assistants with image generation and editing capabilities using OpenAI's GPT-image-1 model, with support for local or Supabase storage.3MIT
- AlicenseAqualityDmaintenanceEnables AI agents to generate images and videos via the Corent API with automatic model routing, provider fallback, and tools for planning, creation, and balance management.744 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to generate on-brand visuals from ideas, URLs, documents, or PDFs in over 100 formats and 150+ languages, with consistent brand kits.8 npm1MIT
- AlicenseAqualityAmaintenanceEnables coding agents to generate and edit images using Gemini and OpenAI image models, saving files directly into the project with configurable providers, models, and security restrictions.369 npm1MIT