OpenRouter Custom Image Generation MCP Server
by nguyen1oc
README.md
# š¼ļø MCP Image Generation Server for LibreChat
A custom [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides **AI image generation** capabilities to [LibreChat](https://github.com/danny-avila/LibreChat) via [OpenRouter](https://openrouter.ai/).
> **Model**: Locked to `google/gemini-2.5-flash-image` for all requests. To change the model, modify the source code in `tools.js`.
---
## š Project Structure
```
mcp-image-gen-custom/
āāā index.js # Express app, SSE transport, MCP server + tool registration
āāā tools.js # Tool handlers: get_user_images, generate_image, rate limiting
āāā openrouter.js # OpenRouter API communication (chat completions with image modality)
āāā files.js # MongoDB file record lookup + local file stream reader
āāā db.js # MongoDB connection pool manager
āāā Dockerfile # Container build definition
āāā package.json
āāā example/
āāā docker-compose.override.example.yml # Docker service config example
āāā librechat.example.yaml # librechat.yaml MCP config example
āāā system_instructions.example.md # Agent system prompt template
```
---
## š ļø Tools Provided
### 1. `get_user_images`
Retrieves images uploaded by the current user in LibreChat, returning index aliases and `file_id`s for use as reference images.
| Argument | Type | Required | Description |
|---|---|---|---|
| `limit` | number | No | Max images to return. Default: `10` |
**Example response:**
```
Found 2 uploaded image(s):
1. [INDEX_1] file_id: "cab88c4e-aecb-4812-9fff-6c85aede6362" ā mycar.png
2. [INDEX_2] file_id: "f6752381-3a6d-4b69-a92b-5555a9521069" ā cat.png
Use EITHER the file_id OR the index number (e.g., '1', '2' or 'INDEX_1', 'INDEX_2') as the reference_image_url when calling generate_image.
```
---
### 2. `generate_image`
Generates an image from a text prompt. Supports **Text-to-Image** and **Image-to-Image** (editing) via reference images.
| Argument | Type | Required | Description |
|---|---|---|---|
| `prompt` | string | ā
| Text description of the image to generate |
| `reference_image_url` | string | No | Index alias (`"1"`, `"INDEX_1"`) or local `file_id` of a single reference image |
| `reference_image_urls` | string[] | No | List of index aliases or `file_id`s for multiple reference images |
| `aspect_ratio` | string | No | Output image dimensions. See options below |
**Aspect ratio options:**
| Value | Shape | Common use |
|---|---|---|
| `1:1` | Square | Profile pictures, feed posts |
| `16:9` | Wide landscape | Banners, cover photos |
| `9:16` | Tall portrait | Stories, Reels, TikTok |
| `4:5` | Portrait | Feed posts (portrait) |
> **Note:** `aspect_ratio` only controls image dimensions ā it does **not** affect image style or content.
> **Note:** Do **NOT** pass Google/OpenAI temporary `file-xxxx` IDs into `reference_image_url`. These are not stored in the local database. Always use `get_user_images` first to get valid index aliases or local `file_id`s.
---
## š Per-User Rate Limiting
The server enforces configurable usage limits per user to prevent spam. Limits are tracked in MongoDB (`mcp_image_gen_usage` collection).
| Env Variable | Default | Description |
|---|---|---|
| `IMAGE_GEN_DAILY_LIMIT` | `3` | Max images per user per day (reset at midnight local time) |
| `IMAGE_GEN_COOLDOWN_SEC` | `30` | Min seconds between generations per user |
**To reset limits manually (all users):**
```bash
docker exec chat-mongodb mongosh LibreChat --eval "db.mcp_image_gen_usage.deleteMany({})"
```
**To reset limits for a specific user:**
```bash
docker exec chat-mongodb mongosh LibreChat --eval "db.mcp_image_gen_usage.deleteMany({ userId: ObjectId('<userId>') })"
```
**To view current usage:**
```bash
docker exec chat-mongodb mongosh LibreChat --eval "db.mcp_image_gen_usage.aggregate([{ \$group: { _id: '\$userId', count: { \$sum: 1 } } }]).forEach(r => print(r._id, '->', r.count))"
```
---
## āļø How Index Resolution Works
The LLM agent only sees image bytes in chat history ā it does not know the database `file_id`. This server resolves short index aliases (e.g., `"1"` or `"INDEX_1"`) to the correct local `file_id` automatically.
```mermaid
sequenceDiagram
actor User
participant Agent as LLM Agent
participant MCP as MCP Server
participant DB as MongoDB
User->>Agent: "Edit this image" (uploads car.png)
Agent->>MCP: get_user_images()
MCP->>DB: Query user's latest images
DB-->>MCP: [car.png ā file_id: "abc-123"]
MCP-->>Agent: "1. [INDEX_1] file_id: 'abc-123' ā car.png"
Agent->>MCP: generate_image(prompt="make it red", reference_image_url="INDEX_1")
Note over MCP: Resolves "INDEX_1" ā "abc-123"
MCP->>DB: Fetch file "abc-123" from disk
MCP-->>User: Returns edited image ā
```
---
## š Installation & Deployment
### Prerequisites
- LibreChat running via Docker Compose
- An [OpenRouter](https://openrouter.ai/) API key
---
### Step 1: Place this folder
Clone or copy this `mcp-image-gen-custom/` directory into the root of your LibreChat repository:
```
LibreChat/
āāā mcp-image-gen-custom/ ā here
āāā api/
āāā docker-compose.yml
āāā ...
```
---
### Step 2: Configure Environment Variables
Add the following to your `.env` file at the LibreChat root:
```env
# Required
OPENROUTER_KEY=your_openrouter_api_key_here
# Optional ā image generation rate limits
IMAGE_GEN_DAILY_LIMIT=3
IMAGE_GEN_COOLDOWN_SEC=30
```
---
### Step 3: Add the MCP service to Docker Compose
Add the following to your `docker-compose.override.yml` (create it if it doesn't exist):
```yaml
services:
api:
volumes:
- type: bind
source: ./librechat.yaml
target: /app/librechat.yaml
mcp-image-gen:
build: ./mcp-image-gen-custom
restart: always
environment:
NODE_ENV: ${NODE_ENV:-production}
PORT: 3013
OPENROUTER_KEY: ${OPENROUTER_KEY}
OPENROUTER_API_KEY: ${OPENROUTER_KEY}
MONGO_URI: mongodb://mongodb:27017/LibreChat
IMAGE_GEN_DAILY_LIMIT: ${IMAGE_GEN_DAILY_LIMIT:-3}
IMAGE_GEN_COOLDOWN_SEC: ${IMAGE_GEN_COOLDOWN_SEC:-30}
volumes:
- ./uploads:/app/uploads # shared with LibreChat api container
- ./images:/app/images # shared with LibreChat api container
expose:
- "3013"
networks:
- default
```
> See `example/docker-compose.override.example.yml` for a complete example.
---
### Step 4: Register the MCP server in `librechat.yaml`
Append the following to your `librechat.yaml`:
```yaml
mcpSettings:
allowedDomains:
- 'mcp-image-gen'
allowedAddresses:
- 'mcp-image-gen:3013'
mcpServers:
openrouter-imager:
type: sse
url: http://mcp-image-gen:3013/sse
headers:
x-user-id: '{{LIBRECHAT_USER_ID}}'
```
> See `example/librechat.example.yaml` for a complete example.
---
### Step 5: Build and start
```bash
# Build and start all services
docker compose up -d --build
# Restart the LibreChat API to reload librechat.yaml
docker compose restart api
```
---
### Step 6: Configure the Agent in LibreChat UI
1. Go to **LibreChat ā Agents** and create a new agent (or edit an existing one).
2. Enable the **`openrouter-imager`** MCP tool.
3. Paste the system prompt from `example/system_instructions.example.md` into the **Instructions** field.
---
## š§ Changing the Model
The model is hardcoded to `google/gemini-2.5-flash-image` in [`tools.js`](./tools.js):
```js
const selectedModel = "google/gemini-2.5-flash-image";
```
To use a different model, edit this line directly and rebuild the container:
```bash
docker compose up -d --build mcp-image-gen
```
---
## š Useful Commands
```bash
# View live MCP server logs
docker logs librechat-mcp-image-gen-1 -f
# Rebuild and restart MCP server only
docker compose up -d --build mcp-image-gen
# Reset all image generation usage limits
docker exec chat-mongodb mongosh LibreChat --eval "db.mcp_image_gen_usage.deleteMany({})"
```
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues