{"_id":"@bioinfornatics/image-mcp","_rev":"3-dfd39a8bf8177a8989e0eca50d618928","name":"@bioinfornatics/image-mcp","dist-tags":{"latest":"0.3.3"},"versions":{"0.2.1":{"name":"@bioinfornatics/image-mcp","version":"0.2.1","license":"CECILL-2.1","_id":"@bioinfornatics/image-mcp@0.2.1","maintainers":[{"name":"bioinfornatics","email":"jonathan@mercier.paris"}],"homepage":"https://github.com/bioinfornatics/image-mcp#readme","bugs":{"url":"https://github.com/bioinfornatics/image-mcp/issues"},"bin":{"image-mcp":"dist/main.js","gpt-image-mcp":"dist/main.js"},"dist":{"shasum":"eb49fbaec5d7b47c8784830da1ef5c68987237a8","tarball":"https://registry.npmjs.org/@bioinfornatics/image-mcp/-/image-mcp-0.2.1.tgz","fileCount":137,"integrity":"sha512-NXo4nF7i87bkm8Ugqq+27feyR1X69nTdT4CklwOLEJrNhnuOl694e7fOVSNehqaaOhPvXSbaNdTGD5HO/lf/Vg==","signatures":[{"sig":"MEYCIQDR4Ac/hr6jU4W2MMP9jRQq6BKr++sSPu/2jSKg8mLMPwIhAKHF52IFwLZglMwfJZbrVTcOlT6sXHuM3V6xISJEYz+6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":384080},"engines":{"bun":">=1.1.0","node":">=20"},"gitHead":"a7c53e0d266bf7b2dededa3eaf5ad57bd5cc06e9","scripts":{"lint":"eslint 'src/**/*.ts' 'test/**/*.ts'","test":"bun test --coverage","build":"node --max-old-space-size=8192 ./node_modules/typescript/bin/tsc -p tsconfig.build.json","start":"node dist/main.js","format":"prettier --write 'src/**/*.ts' 'test/**/*.ts'","prepack":"bun run build","lint:fix":"eslint 'src/**/*.ts' 'test/**/*.ts' --fix","postbuild":"chmod +x dist/main.js","start:dev":"bun run src/main.ts","start:http":"IMAGE_MCP_TRANSPORT=http bun run src/main.ts","test:watch":"bun test --watch","type-check":"node --max-old-space-size=8192 ./node_modules/typescript/bin/tsc -p tsconfig.build.json --noEmit","auth:doctor":"bun run src/cli/auth-doctor.ts","publish:dry":"npm pack --dry-run","start:stdio":"IMAGE_MCP_TRANSPORT=stdio bun run src/main.ts","quality-gate":"bash bin/quality-gate.sh","secret:store":"bun run src/cli/store-secret.ts","secret:delete":"bun run src/cli/store-secret.ts","test:coverage":"bun test --coverage","version:major":"npm version major --no-git-tag-version","version:minor":"npm version minor --no-git-tag-version","version:patch":"npm version patch --no-git-tag-version","prepublishOnly":"bun test && bun run build"},"_npmUser":{"name":"bioinfornatics","email":"jonathan@mercier.paris"},"overrides":{"lodash":"^4.18.1","multer":"^2.2.0","js-yaml":"^4.3.0","file-type":"^21.3.2","form-data":"^4.0.6","find-my-way":"^9.7.0","brace-expansion":"^1.1.17","@hono/node-server":"^2.1.0"},"repository":{"url":"git+https://github.com/bioinfornatics/image-mcp.git","type":"git"},"_npmVersion":"10.9.7","description":"Provider-neutral MCP server for AI image generation and editing across OpenAI, Microsoft Foundry, OpenRouter, Together AI, and compatible APIs","directories":{},"_nodeVersion":"22.22.2","dependencies":{"joi":"^17.13.3","zod":"^4.2.0","jose":"^6.2.8","rxjs":"^7.8.2","openai":"^4.104.0","fastify":"^5.11.3","winston":"^3.19.0","prom-client":"^15.1.3","@nestjs/core":"^11.1.29","@nestjs/common":"^11.1.29","@nestjs/config":"^3.3.0","@azure/identity":"^4.13.1","class-validator":"^0.14.4","@azure/msal-node":"^5.5.0","@nestjs/terminus":"^11.0.0","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","@nestjs/platform-fastify":"11.1.29","@modelcontextprotocol/node":"^2.0.0","@modelcontextprotocol/server":"^2.0.0","@modelcontextprotocol/fastify":"^2.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"nock":"^13.5.6","eslint":"^8.57.1","prettier":"^3.8.3","supertest":"^7.2.2","typescript":"^5.9.3","@types/node":"^22.19.17","@nestjs/testing":"^11.1.29","@types/supertest":"^6.0.3","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/eslint-plugin":"^7.18.0"},"optionalDependencies":{"keytar":"^7.9.0"},"_npmOperationalInternal":{"tmp":"tmp/image-mcp_0.2.1_1787318199077_0.2054986798757079","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@bioinfornatics/image-mcp","version":"0.3.2","license":"CECILL-2.1","_id":"@bioinfornatics/image-mcp@0.3.2","maintainers":[{"name":"bioinfornatics","email":"jonathan@mercier.paris"}],"homepage":"https://github.com/bioinfornatics/image-mcp#readme","bugs":{"url":"https://github.com/bioinfornatics/image-mcp/issues"},"bin":{"image-mcp":"dist/main.js","gpt-image-mcp":"dist/main.js"},"dist":{"shasum":"1e889ee6773a78e2b4ea94962be07aec9a688705","tarball":"https://registry.npmjs.org/@bioinfornatics/image-mcp/-/image-mcp-0.3.2.tgz","fileCount":142,"integrity":"sha512-HZQ1nd7V7UoeUH6UdqqgLIXCFW0CQR9XDDMkieDXs+bospTwX1MqEXRHuUMgb5h5t/IYyfBnylxJvEwWkGtM5A==","signatures":[{"sig":"MEYCIQCEhdMC9oq0JVVYojXKCHuhbHJtGAh46SulN/Ult5cYawIhAMoLl2spLYZjLCZ/CsIPZq0ymPoGLhSnQ2gJZEThr2rx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":412420},"engines":{"bun":">=1.1.0","node":">=20"},"gitHead":"a56f7de6cb6f2b109dbffd910a209fdc647fd47d","scripts":{"lint":"eslint 'src/**/*.ts' 'test/**/*.ts'","test":"bun test --coverage","build":"bun ./node_modules/typescript/bin/tsc -p tsconfig.build.json","start":"node dist/main.js","format":"prettier --write 'src/**/*.ts' 'test/**/*.ts'","prepack":"bun run build","lint:fix":"eslint 'src/**/*.ts' 'test/**/*.ts' --fix","postbuild":"chmod +x dist/main.js","start:dev":"bun run src/main.ts","start:http":"IMAGE_MCP_TRANSPORT=http bun run src/main.ts","test:watch":"bun test --watch","type-check":"bun ./node_modules/typescript/bin/tsc -p tsconfig.build.json --noEmit","auth:doctor":"bun run src/cli/auth-doctor.ts","publish:dry":"npm pack --dry-run","start:stdio":"IMAGE_MCP_TRANSPORT=stdio bun run src/main.ts","quality-gate":"bash bin/quality-gate.sh","secret:store":"bun run src/cli/store-secret.ts","secret:delete":"bun run src/cli/store-secret.ts","test:coverage":"bun test --coverage","version:major":"npm version major --no-git-tag-version","version:minor":"npm version minor --no-git-tag-version","version:patch":"npm version patch --no-git-tag-version","prepublishOnly":"bun test && bun run build"},"_npmUser":{"name":"bioinfornatics","email":"jonathan@mercier.paris"},"overrides":{"lodash":"^4.18.1","multer":"^2.2.0","js-yaml":"^4.3.0","file-type":"^21.3.2","form-data":"^4.0.6","find-my-way":"^9.7.0","brace-expansion":"^1.1.17","@hono/node-server":"^2.1.0"},"repository":{"url":"git+https://github.com/bioinfornatics/image-mcp.git","type":"git"},"_npmVersion":"10.9.8","description":"Provider-neutral MCP server for AI image generation and editing across OpenAI, Microsoft Foundry, OpenRouter, Together AI, and compatible APIs","directories":{},"_nodeVersion":"22.23.2","dependencies":{"joi":"^17.13.3","zod":"^4.2.0","jose":"^6.2.8","rxjs":"^7.8.2","openai":"^4.104.0","fastify":"^5.11.3","winston":"^3.19.0","prom-client":"^15.1.3","@nestjs/core":"^11.1.29","@nestjs/common":"^11.1.29","@nestjs/config":"^3.3.0","@azure/identity":"^4.13.1","class-validator":"^0.14.4","@azure/msal-node":"^5.5.0","@nestjs/terminus":"^11.0.0","reflect-metadata":"^0.2.2","class-transformer":"^0.5.1","@nestjs/platform-fastify":"11.1.29","@modelcontextprotocol/node":"^2.0.0","@modelcontextprotocol/server":"^2.0.0","@modelcontextprotocol/fastify":"^2.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"nock":"^13.5.6","eslint":"^8.57.1","prettier":"^3.8.3","supertest":"^7.2.2","typescript":"^5.9.3","@types/node":"^22.19.17","@nestjs/testing":"^11.1.29","@types/supertest":"^6.0.3","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/eslint-plugin":"^7.18.0"},"optionalDependencies":{"keytar":"^7.9.0"},"_npmOperationalInternal":{"tmp":"tmp/image-mcp_0.3.2_1787748043507_0.3955196113823247","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@bioinfornatics/image-mcp","version":"0.3.3","description":"Provider-neutral MCP server for AI image generation and editing across OpenAI, Microsoft Foundry, OpenRouter, Together AI, and compatible APIs","license":"CECILL-2.1","repository":{"type":"git","url":"git+https://github.com/bioinfornatics/image-mcp.git"},"publishConfig":{"registry":"https://registry.npmjs.org","access":"public"},"bin":{"image-mcp":"dist/main.js","gpt-image-mcp":"dist/main.js"},"scripts":{"build":"bun ./node_modules/typescript/bin/tsc -p tsconfig.build.json","postbuild":"chmod +x dist/main.js","prepack":"bun run build","start":"node dist/main.js","start:dev":"bun run src/main.ts","start:http":"IMAGE_MCP_TRANSPORT=http bun run src/main.ts","start:stdio":"IMAGE_MCP_TRANSPORT=stdio bun run src/main.ts","quality-gate":"bash bin/quality-gate.sh","test":"bun test --coverage","test:watch":"bun test --watch","test:coverage":"bun test --coverage","lint":"eslint 'src/**/*.ts' 'test/**/*.ts'","lint:fix":"eslint 'src/**/*.ts' 'test/**/*.ts' --fix","type-check":"bun ./node_modules/typescript/bin/tsc -p tsconfig.build.json --noEmit","format":"prettier --write 'src/**/*.ts' 'test/**/*.ts'","secret:store":"bun run src/cli/store-secret.ts","secret:delete":"bun run src/cli/store-secret.ts","auth:doctor":"bun run src/cli/auth-doctor.ts","prepublishOnly":"bun test && bun run build","publish:dry":"npm pack --dry-run","version:patch":"npm version patch --no-git-tag-version","version:minor":"npm version minor --no-git-tag-version","version:major":"npm version major --no-git-tag-version"},"dependencies":{"@azure/identity":"^4.13.1","@azure/msal-node":"^5.5.0","@modelcontextprotocol/fastify":"^2.0.0","@modelcontextprotocol/node":"^2.0.0","@modelcontextprotocol/server":"^2.0.0","@nestjs/common":"^11.1.29","@nestjs/config":"^3.3.0","@nestjs/core":"^11.1.29","@nestjs/platform-fastify":"11.1.29","@nestjs/terminus":"^11.0.0","class-transformer":"^0.5.1","class-validator":"^0.14.4","fastify":"^5.11.3","joi":"^17.13.3","jose":"^6.2.8","openai":"^4.104.0","prom-client":"^15.1.3","reflect-metadata":"^0.2.2","rxjs":"^7.8.2","winston":"^3.19.0","zod":"^4.2.0"},"optionalDependencies":{"keytar":"^7.9.0"},"devDependencies":{"@nestjs/testing":"^11.1.29","@types/node":"^22.19.17","@types/supertest":"^6.0.3","@typescript-eslint/eslint-plugin":"^7.18.0","@typescript-eslint/parser":"^7.18.0","eslint":"^8.57.1","nock":"^13.5.6","prettier":"^3.8.3","supertest":"^7.2.2","typescript":"^5.9.3"},"engines":{"bun":">=1.1.0","node":">=20"},"overrides":{"multer":"^2.2.0","lodash":"^4.18.1","file-type":"^21.3.2","form-data":"^4.0.6","find-my-way":"^9.7.0","js-yaml":"^4.3.0","brace-expansion":"^1.1.17","@hono/node-server":"^2.1.0"},"_id":"@bioinfornatics/image-mcp@0.3.3","gitHead":"726b5a22add4099c5995bbdeb868d8304e5a33d0","bugs":{"url":"https://github.com/bioinfornatics/image-mcp/issues"},"homepage":"https://github.com/bioinfornatics/image-mcp#readme","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-+epDmdKq0SS0xFr/5NIp6jT+CAw0kRtzK9ttkmUfm78VsYONaaKcbi2Le2CWEJV0hCg76OB0xWbJKl8F3WmeCA==","shasum":"a56e438f6923dab5b3acba13befebe8f3cd7f3dc","tarball":"https://registry.npmjs.org/@bioinfornatics/image-mcp/-/image-mcp-0.3.3.tgz","fileCount":142,"unpackedSize":414535,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCEN0GGaidZpQgWdEA68TXjSMH5Oaf1bQjEDYsnn1L5FQIhAO2vq9Ewh2Sq2tJUXm/UqnTN51UBfMcTmRleOz157zut"}]},"_npmUser":{"name":"bioinfornatics","email":"jonathan@mercier.paris"},"directories":{},"maintainers":[{"name":"bioinfornatics","email":"jonathan@mercier.paris"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/image-mcp_0.3.3_1787751249195_0.9574692695784794"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-21T13:16:38.674Z","modified":"2026-08-26T13:34:09.517Z","0.2.1":"2026-08-21T13:16:39.225Z","0.3.2":"2026-08-26T12:40:43.684Z","0.3.3":"2026-08-26T13:34:09.335Z"},"bugs":{"url":"https://github.com/bioinfornatics/image-mcp/issues"},"license":"CECILL-2.1","homepage":"https://github.com/bioinfornatics/image-mcp#readme","repository":{"type":"git","url":"git+https://github.com/bioinfornatics/image-mcp.git"},"description":"Provider-neutral MCP server for AI image generation and editing across OpenAI, Microsoft Foundry, OpenRouter, Together AI, and compatible APIs","maintainers":[{"name":"bioinfornatics","email":"jonathan@mercier.paris"}],"readme":"# image-mcp\n\n> Formerly **gpt-image-mcp**. The npm package is now `@bioinfornatics/image-mcp`; the legacy `gpt-image-mcp` executable remains as a temporary alias. Existing `IMAGE_*` environment variables are unchanged.\n\n> **MCP server** for AI image generation via **OpenAI** and **Azure OpenAI** gpt-image-* models.  \n> Built with **Bun + NestJS** · **Streamable HTTP + stdio** transports · **811 tests, 92 % coverage**\n\n---\n\n## Features\n\n| Capability | Detail |\n|-----------|--------|\n| 🖼️ **image_generate** | Text → image with all gpt-image-* models |\n| ✏️ **image_edit** | Inpainting with optional mask (up to 16 input images) |\n| 🔀 **image_variation** | dall-e-2 variations (dall-e-2 only) |\n| 🔍 **provider_list / validate** | Connectivity check without generating an image |\n| ⚡ **MCP Elicitation** | Interactive parameter refinement (quality, size) |\n| 🧠 **MCP Sampling** | Prompt enhancement via client LLM |\n| 💾 **MCP Roots** | Save images directly to your workspace |\n| 🔒 **Security** | Bearer-token auth, rate limiting, secret masking, path traversal prevention |\n| 📊 **Observability** | Prometheus `/metrics`, `/health/live`, `/health/ready` |\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- [Bun](https://bun.sh) ≥ 1.1 **or** Node.js ≥ 20 **or** Docker\n- An OpenAI API key **or** Azure OpenAI resource\n\n---\n\n### Option A — Zero-install with `bunx` (recommended)\n\n```bash\n# stdio transport (Claude Desktop, Goose, Cursor)\nIMAGE_PROVIDER=openai IMAGE_API_KEY=sk-... IMAGE_MCP_TRANSPORT=stdio bunx @bioinfornatics/image-mcp\n\n# HTTP transport on port 3000 (network-facing: MCP bearer token required)\nIMAGE_PROVIDER=openai IMAGE_API_KEY=sk-... IMAGE_MCP_TRANSPORT=http IMAGE_PORT=3000 \\\n  IMAGE_MCP_API_KEY=replace-with-at-least-16-characters \\\n  bunx @bioinfornatics/image-mcp\n\n# Microsoft Foundry (local stdio; provider inferred from the canonical base URL)\nIMAGE_API_KEY=... bunx @bioinfornatics/image-mcp \\\n  --base-url https://YOUR-RESOURCE.services.ai.azure.com \\\n  --foundry-project-endpoint https://YOUR-RESOURCE.services.ai.azure.com/api/projects/YOUR-PROJECT \\\n  --deployment MAI-Image-2.5 \\\n  --transport stdio\n```\n\n**With `npx` (Node.js users):**\n\n```bash\nIMAGE_PROVIDER=openai IMAGE_API_KEY=sk-... IMAGE_MCP_TRANSPORT=stdio npx @bioinfornatics/image-mcp\n```\n\n### Repeated launches and port reuse\n\n- **Goose and other local MCP hosts should use `stdio`.** Each host launch gets its own pipe-backed process and never binds the shared HTTP port. The packaged command should set `IMAGE_MCP_TRANSPORT=stdio`; the local `bin/start.sh` also defaults to `stdio` when it is omitted.\n- **HTTP is a persistent, multi-client server.** Start it once and configure Goose with a `streamable_http` URI. If the HTTP command is run again with the same host and port, it probes `/health/live`: a compatible `image-mcp` listener is reused and the duplicate process exits successfully. An unrelated listener still produces a port conflict rather than being mistaken for this server.\n\n---\n\n### Option B — Install globally\n\n```bash\nbun add -g @bioinfornatics/image-mcp\nIMAGE_API_KEY=sk-... image-mcp --provider openai --transport stdio\n```\n\n---\n\n### Option C — Clone & run from source\n\n```bash\ngit clone https://github.com/bioinfornatics/image-mcp\ncd image-mcp\nbun install\ncp .env.example .env   # then edit with your keys\n\nbun run start:http     # HTTP on :3000\nbun run start:stdio    # stdio\n```\n\n---\n\n### Option D — Docker\n\n```bash\ndocker build -t image-mcp .\n\ndocker run -p 3000:3000 \\\n  -e IMAGE_PROVIDER=openai \\\n  -e IMAGE_API_KEY=sk-... \\\n  -e IMAGE_MCP_TRANSPORT=http \\\n  -e IMAGE_MCP_API_KEY=replace-with-at-least-16-characters \\\n  image-mcp\n```\n\n---\n\n## Configuration\n\nAzure users can choose among **API key**, **Azure CLI**, and **Microsoft Entra OBO**. Start with the [authentication decision guide](docs/authentication/README.md).\n\n| Variable | Required | Default | Description |\n|----------|----------|---------|-------------|\n| `IMAGE_PROVIDER` | ✅ | — | `openai`, `azure`, `together`, or `custom` |\n| `IMAGE_API_KEY` | Conditional | — | API key for OpenAI/Together and Azure `api_key` mode |\n| `IMAGE_AZURE_AUTH_MODE` | Azure only | inferred from existing key | `api_key`, `azure_cli`, or `on_behalf_of` |\n| `IMAGE_AZURE_TENANT_ID` | ❌ | active CLI tenant | Optional tenant for `azure_cli` |\n| `IMAGE_MCP_AUTH_MODE` | ❌ | transport-sensitive | `none`, `static_bearer`, or `entra`; OBO requires `entra` |\n| `IMAGE_ENTRA_TENANT_ID` | OBO | — | Trusted Entra tenant |\n| `IMAGE_ENTRA_CLIENT_ID` | OBO | — | MCP server app registration ID |\n| `IMAGE_ENTRA_AUDIENCE` | OBO | — | Exact MCP API token audience |\n| `IMAGE_ENTRA_SCOPE` | OBO | `mcp.access` | Required delegated scope |\n| `IMAGE_ENTRA_ALLOWED_CLIENT_IDS` | ❌ | all | Comma-separated allowed OAuth clients |\n| `IMAGE_ENTRA_CLIENT_SECRET` | Current OBO baseline | — | Confidential credential; use a secure secret source |\n| `IMAGE_BASE_URL` | ✅ if `azure`/`custom` | `https://api.openai.com/v1` | Provider endpoint (Azure resource URL or custom OpenAI-compatible URL) |\n| `IMAGE_DEPLOYMENT` | ✅ if `azure` | — | Azure deployment name |\n| `IMAGE_API_VERSION` | ❌ | `2025-04-01-preview` | Azure API version |\n| `IMAGE_MODELS` | ❌ | `custom` | Comma-separated model list (custom provider only) |\n| `IMAGE_DEFAULT_MODEL` | ❌ | `gpt-image-2` | Default model (override per-request via tool param) |\n| `IMAGE_MCP_TRANSPORT` | ❌ | `http` | `http` or `stdio` |\n| `IMAGE_HTTP_HOST` | ❌ | `127.0.0.1` | HTTP bind address |\n| `IMAGE_PORT` | ❌ | `3000` | HTTP listen port |\n| `IMAGE_HTTP_ALLOWED_HOSTS` | ❌ | localhost addresses | Comma-separated hostnames accepted by MCP Fastify DNS-rebinding protection |\n| `IMAGE_HTTP_ALLOWED_ORIGINS` | ❌ | localhost addresses | Comma-separated browser origin hostnames |\n| `IMAGE_MCP_API_KEY` | ❌ | — | Bearer token to protect `/mcp` |\n| `IMAGE_USE_ELICITATION` | ❌ | `true` | Enable MCP Elicitation |\n| `IMAGE_USE_SAMPLING` | ❌ | `true` | Enable MCP Sampling |\n| `IMAGE_MAX_REQUESTS_PER_MINUTE` | ❌ | `60` | Rate limit per client |\n| `IMAGE_OUTPUT_DIR` | ❌ | OS Pictures directory | Override the directory where every generated image is persisted |\n| `IMAGE_LOG_LEVEL` | ❌ | `info` | `debug`/`info`/`warn`/`error` |\n\nEvery generated, edited, or variation image is returned as native MCP image content and persisted automatically. The default directory is the freedesktop `XDG_PICTURES_DIR/image-mcp` on Linux (falling back to `~/Images/image-mcp`), `~/Pictures/image-mcp` on macOS, and `%USERPROFILE%\\\\Pictures\\\\image-mcp` on Windows. `save_to_workspace: true` additionally creates a copy in the MCP workspace.\n\nResponse paths are explicit: `saved_to` is the absolute automatically persisted file, `file_uri` is the canonical `file:` URI for that same file, and `workspace_copy` is present only when `save_to_workspace: true` successfully creates an additional copy under an MCP Root. `IMAGE_WORKSPACE_ALLOWED_ROOTS` uses `:` between POSIX roots and `;` between Windows roots. Only local file roots are supported; UNC/network authorities such as `file://server/share` are rejected.\n\n---\n\n## MCP Client Setup\n\nPrefer CLI arguments for non-secret runtime settings (`--base-url`, `--foundry-project-endpoint`,\n`--deployment`, `--transport`). A canonical `https://<resource>.services.ai.azure.com` base URL\ninfers `--provider azure` when no provider is explicitly configured. The hostname contains the\nFoundry resource/account name—not the Azure resource-group or project name—so the project endpoint\ncannot be derived and remains explicit. Keep only secrets in `env_keys` or `*_FILE` variables. For Azure, `--deployment` already defines the runtime default; do not duplicate\nit with `IMAGE_DEFAULT_MODEL`. API versions are adapter-owned unless a legacy endpoint explicitly\nrequires an override. `IMAGE_LOG_LEVEL` defaults to `info` and may be omitted.\n\n### Claude Desktop\n\n`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) — `%APPDATA%\\Claude\\claude_desktop_config.json` (Windows):\n\n```json\n{\n  \"mcpServers\": {\n    \"image-mcp\": {\n      \"command\": \"bunx\",\n      \"args\": [\"@bioinfornatics/image-mcp\"],\n      \"env\": {\n        \"IMAGE_PROVIDER\": \"openai\",\n        \"IMAGE_API_KEY\": \"sk-...\",\n        \"IMAGE_MCP_TRANSPORT\": \"stdio\",\n        \"IMAGE_LOG_LEVEL\": \"error\"\n      }\n    }\n  }\n}\n```\n\n### Goose\n\nFor a copy-paste `npx` setup on Linux, macOS, or Windows, including secrets, Azure, first-run verification, output paths, and troubleshooting, see **[Use image-mcp with Goose](docs/HOWTO_GOOSE.md)**.\n\nAdd to `~/.config/goose/config.yaml` under `extensions:`:\n\n**OpenAI:**\n```yaml\nextensions:\n  imagemcp:\n    enabled: true\n    type: stdio\n    name: Image MCP\n    description: AI image generation via OpenAI\n    cmd: npx\n    args:\n      - \"--yes\"\n      - \"@bioinfornatics/image-mcp@0.2.0\"\n      - --provider\n      - openai\n      - --transport\n      - stdio\n    env_keys:\n      - IMAGE_API_KEY        # set via `goose configure`; a plain shell `export` alone does not populate Goose's keyring/env_keys\n    timeout: 300\n```\n\n**Azure AI Foundry:**\n```yaml\nextensions:\n  imagemcp:\n    enabled: true\n    type: stdio\n    name: Image MCP\n    description: AI image generation — gpt-image-2 via Azure AI Foundry\n    cmd: npx\n    args:\n      - \"--yes\"\n      - \"@bioinfornatics/image-mcp@0.2.0\"\n      - --provider\n      - azure\n      - --base-url\n      - https://my-resource.services.ai.azure.com\n      - --foundry-project-endpoint\n      - https://my-resource.services.ai.azure.com/api/projects/my-project\n      - --deployment\n      - MAI-Image-2.5\n      - --transport\n      - stdio\n    env_keys:\n      - IMAGE_API_KEY\n    timeout: 300\n```\n\n> See [`examples/goose-config.yaml`](examples/goose-config.yaml) for all options including HTTP transport and local development.\n\n### HTTP / Remote Client\n\n```\nPOST http://localhost:3000/mcp\nAccept: application/json, text/event-stream\nContent-Type: application/json\nAuthorization: Bearer <IMAGE_MCP_API_KEY>   # only if IMAGE_MCP_API_KEY is set\n\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"image_generate\",\"arguments\":{\"prompt\":\"a cat\"}}}\n```\n\n---\n\n## Image routing and observability\n\nAzure supports discovered MAI-Image-2.5 and MAI-Image-2.5-Flash deployments. The configured deployment is the default; discovered exact deployment names or unambiguous model families may override it unless `IMAGE_STRICT_DEPLOYMENT_PINNING=true`.\n\nGeneration JSON includes model and prompt provenance, stage timings, normalized provider usage, and an optional public-price `estimated_cost`. No prices are built in and estimates are never billed cost. Safe deterministic workspace targets are available through `workspace_output_path`; prompt enhancement can be disabled with `disable_prompt_enhancement`.\n\nSee [Image model routing, observability, usage, and cost](docs/IMAGE_OBSERVABILITY.md) for contracts and configuration.\n\n## Available Models\n\n> **As of April 23, 2026** — model landscape has changed significantly. dall-e-3 was retired.\n\n### OpenAI (`IMAGE_PROVIDER=openai`)\n\n| Model | Status | Sizes | Best for |\n|-------|--------|-------|----------|\n| `gpt-image-2` | ✅ **Default · Recommended** | Arbitrary up to 4K (multiples of 16 px, ≤ 3:1 ratio) | Production — best quality, 4K, flexible resolution |\n| `gpt-image-1.5` | ✅ Available | `1024×1024`, `1024×1536`, `1536×1024` | Compatibility / migration |\n| `gpt-image-1-mini` | ✅ Available | `1024×1024`, `1024×1536`, `1536×1024` | High-volume, cost-sensitive pipelines |\n| `gpt-image-1` | ✅ Available | `1024×1024`, `1024×1536`, `1536×1024` | Legacy workflows |\n| `dall-e-2` | ⚠️ Variations only | `256×256`, `512×512`, `1024×1024` | Image variations endpoint only |\n| ~~`dall-e-3`~~ | ⛔ **Retired 2026-03-04** | — | No longer available |\n\n### Azure AI Foundry (`IMAGE_PROVIDER=azure`)\n\n| Model | Status | Notes |\n|-------|--------|-------|\n| `gpt-image-2` | ✅ **Available · No access application needed** | Arbitrary resolution up to 4K. |\n| `gpt-image-1.5` | ⚠️ Limited Access | Apply at [aka.ms/oai/gptimage1.5access](https://aka.ms/oai/gptimage1.5access) |\n| `gpt-image-1-mini` | ⚠️ Limited Access | Apply at [aka.ms/oai/gptimage1access](https://aka.ms/oai/gptimage1access) |\n| `gpt-image-1` | ⚠️ Limited Access | Apply at [aka.ms/oai/gptimage1access](https://aka.ms/oai/gptimage1access) |\n| ~~`dall-e-3`~~ | ⛔ **Retired 2026-03-04** | Existing deployments are non-functional |\n\n> 💡 **Azure users:** `gpt-image-2` is the easiest to start with — it requires no prior access approval today.  \n> A 403 for gpt-image-1.x means you need to register at the links above.\n>\n> ℹ️ IMAGE_DEPLOYMENT selects the Azure default. The same server routes explicit MAI-Image-2.5 and gpt-image-2 requests to their respective adapters. See docs/authentication/TROUBLESHOOTING.md.\n\n---\n\n## gpt-image-2 Highlights\n\n`gpt-image-2` (released **April 21, 2026**) is the flagship model and the recommended default:\n\n| Feature | Detail |\n|---------|--------|\n| **Resolution** | Arbitrary — both edges must be multiples of **16 px**, max edge **3840 px (4K)**, max ratio **3:1**, pixel range **655 360 – 8 294 400** |\n| **Quality** | `low` · `medium` · `high` · `auto` |\n| **Output formats** | `png` (with transparency), `webp` (with transparency + compression), `jpeg` (compression) |\n| **Background** | `transparent` · `opaque` · `auto` |\n| **Images per request** | 1 – 10 (`n` parameter) |\n| **Prompt length** | Up to **32 000 characters** |\n| **Image editing inputs** | Up to **16 images** per edit request |\n| **Streaming** | ✅ `stream: true`, `partial_images: 0–3` |\n| **Variations** | ❌ Not supported (use `dall-e-2` for variations) |\n| **Text rendering** | ✅ Excellent — infographics, banners, labels |\n| **Photorealism** | ✅ Best-in-class — use keyword `photorealistic` in prompt |\n\n**Popular resolution examples for gpt-image-2:**\n\n| Use case | Resolution |\n|----------|-----------|\n| Square (general) | `1024×1024` |\n| Portrait | `1024×1536` |\n| Landscape | `1536×1024` |\n| Widescreen / slides | `1536×864` |\n| 2K / QHD _(reliability limit)_ | `2560×1440` |\n| 4K / UHD _(experimental)_ | `3840×2160` |\n\n---\n\n## Tools Reference\n\n### `image_generate`\n\n```json\n{\n  \"prompt\": \"A serene Japanese garden at dawn, photorealistic\",\n  \"model\": \"gpt-image-2\",\n  \"n\": 1,\n  \"size\": \"1536x1024\",\n  \"quality\": \"high\",\n  \"background\": \"transparent\",\n  \"output_format\": \"webp\",\n  \"output_compression\": 85,\n  \"save_to_workspace\": false,\n  \"response_format\": \"markdown\"\n}\n```\n\n### `image_edit`\n\n```json\n{\n  \"image\": \"<base64-encoded-png>\",\n  \"mask\": \"<base64-encoded-mask>\",\n  \"prompt\": \"Add a red hat to the person\",\n  \"model\": \"gpt-image-2\"\n}\n```\n\n### `image_variation`\n\n```json\n{\n  \"image\": \"<base64-encoded-square-png>\",\n  \"n\": 3,\n  \"size\": \"1024x1024\"\n}\n```\n\n> ⚠️ Variations use `dall-e-2` exclusively. Not supported by Azure OpenAI or any gpt-image-* model.\n\n### `provider_validate`\n\n```json\n{ \"provider\": \"openai\" }\n```\n\n---\n\n## Development\n\n```bash\nbun test                  # 305 tests\nbun test --coverage       # with coverage report (≥ 92 %)\nbun run lint              # ESLint\nbun run type-check        # tsc --noEmit\nbun run build             # compile to dist/\n```\n\n### Project Structure\n\n```\nsrc/\n├── config/          # Joi-validated env config + models.ts (LATEST_MODEL)\n├── mcp/\n│   ├── tools/       # 5 MCP tools + Zod schemas\n│   ├── features/    # Elicitation, Sampling, Roots\n│   └── transport/   # HTTP (stateless) + stdio\n├── providers/       # OpenAI + Azure + Together + Custom adapters\n├── security/        # Auth guard, rate limiter, sanitiser\n└── health/          # /health/* + /metrics\ntest/\n├── unit/            # 240+ unit tests (mocked deps)\n└── integration/     # 65+ integration tests (nock + supertest)\n```\n\n---\n\n## Security\n\n- API keys are **never** logged — masked with `***` in all output\n- Input prompts sanitised (null bytes stripped, bidi control chars removed, length enforced)\n- Path traversal prevented when saving to workspace roots\n- Rate limiting: configurable per-client sliding window\n- Optional bearer-token auth on the `/mcp` endpoint\n- Container runs as **non-root** user (`mcpuser`)\n- Trivy vulnerability scan on every CI build\n- `bun audit` dependency audit on every CI build\n\nSee [`docs/SECURITY.md`](docs/SECURITY.md) for full threat model.\n\n---\n\n## Architecture\n\n```\nMCP Client (Claude / Goose / Cursor)\n      │  JSON-RPC 2.0\n      ▼\n  POST /mcp  (Streamable HTTP)\n  or stdio\n      │\n  ┌───┴────────────────────────────────┐\n  │  NestJS Application                │\n  │  ┌──────────┐  ┌────────────────┐  │\n  │  │ 5 Tools  │  │ MCP Features   │  │\n  │  │ schemas  │  │ Elicitation    │  │\n  │  │ Zod val. │  │ Sampling       │  │\n  │  └────┬─────┘  │ Roots          │  │\n  │       │        └────────────────┘  │\n  │  ┌────▼──────────────────────────┐ │\n  │  │  IImageProvider               │ │\n  │  │  OpenAI · Azure · Together    │ │\n  │  │  Custom OpenAI-compatible     │ │\n  │  └────┬──────────────────────────┘ │\n  └───────┼────────────────────────────┘\n          │  HTTPS\n          ▼\n  OpenAI API / Azure OpenAI\n```\n\nSee [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for C4 diagrams and ADRs.\n\n---\n\n## Contributing\n\n1. Fork → branch → implement with TDD (Red → Green → Refactor)\n2. All PRs must have a failing-test commit before the implementation commit\n3. Coverage must not drop below 90 %\n4. Run `bun run lint && bun run type-check && bun test` before pushing\n\nSee [`docs/TDD_STRATEGY.md`](docs/TDD_STRATEGY.md) for the full TDD workflow.\n\n---\n\n## License\n\n[CeCILL-2.1](./LICENSE) — a French open-source licence compatible with GNU GPL, endorsed by CEA, CNRS, and Inria.  \n© 2026 PhD Jonathan MERCIER\n\n### OpenRouter (Nano Banana 2 or MAI-Image-2.5)\n\nOpenRouter is inferred from `https://openrouter.ai/api/v1`. Configure its key as `IMAGE_API_KEY`:\n\n```bash\nIMAGE_API_KEY=... npx --yes @bioinfornatics/image-mcp \\\n  --base-url https://openrouter.ai/api/v1 \\\n  --model google/gemini-3.1-flash-image \\\n  --transport stdio\n```\n\nSee [`examples/goose-openrouter.yaml`](examples/goose-openrouter.yaml) and the capability table in [`docs/API.md`](docs/API.md#openrouter-image-api).\n\n\n### Security policy notes\n\nHTTP/multi-user workspace saving fails closed unless `IMAGE_WORKSPACE_ALLOWED_ROOTS` is configured; trusted stdio mode may accept client roots without it. Static Azure mode exposes only `IMAGE_DEPLOYMENT`; deployment discovery is required for selectable multi-deployment routing. Prometheus model labels are bounded canonical families and never contain raw requested or deployment names. See `docs/IMAGE_OBSERVABILITY.md` for strict usage and pricing validation, staleness, fallback, aggregation, and rounding policies.\n","readmeFilename":"README.md"}