#
tokens: 17029/50000 25/25 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── config
│   │   └── index.ts
│   ├── index.ts
│   ├── resources
│   │   ├── imageList.ts
│   │   ├── index.ts
│   │   ├── predictionList.ts
│   │   └── svgList.ts
│   ├── server
│   │   └── index.ts
│   ├── services
│   │   └── replicate.ts
│   ├── tools
│   │   ├── createPrediction.ts
│   │   ├── generateImage.ts
│   │   ├── generateImageVariants.ts
│   │   ├── generateMultipleImages.ts
│   │   ├── generateSVG.ts
│   │   ├── getPrediction.ts
│   │   ├── index.ts
│   │   └── predictionList.ts
│   ├── types
│   │   └── index.ts
│   └── utils
│       ├── error.ts
│       └── image.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | /node_modules
 2 | /build
 3 | /dist
 4 | /coverage
 5 | /logs
 6 | /tmp
 7 | .env
 8 | .cursor
 9 | .cursorignore
10 | .cursorrules
11 | .cursorconfig
12 | .cursorignorerules
13 | .cursorignoreconfig
14 | .npmrc
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/awkoy-replicate-flux-mcp-badge.png)](https://mseep.ai/app/awkoy-replicate-flux-mcp)
  2 | 
  3 | # Replicate Flux MCP
  4 | 
  5 | ![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-blue)
  6 | ![License](https://img.shields.io/badge/license-MIT-green)
  7 | ![TypeScript](https://img.shields.io/badge/TypeScript-4.9+-blue)
  8 | ![Model Context Protocol](https://img.shields.io/badge/MCP-Enabled-purple)
  9 | [![smithery badge](https://smithery.ai/badge/@awkoy/replicate-flux-mcp)](https://smithery.ai/server/@awkoy/replicate-flux-mcp)
 10 | ![NPM Downloads](https://img.shields.io/npm/dw/replicate-flux-mcp)
 11 | ![Stars](https://img.shields.io/github/stars/awkoy/replicate-flux-mcp)
 12 | 
 13 | <a href="https://glama.ai/mcp/servers/ss8n1knen8">
 14 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/ss8n1knen8/badge" />
 15 | </a>
 16 | 
 17 | **Replicate Flux MCP** is an advanced Model Context Protocol (MCP) server that empowers AI assistants to generate high-quality images and vector graphics. Leveraging [Black Forest Labs' Flux Schnell model](https://replicate.com/black-forest-labs/flux-schnell) for raster images and [Recraft's V3 SVG model](https://replicate.com/recraft-ai/recraft-v3-svg) for vector graphics via the Replicate API.
 18 | 
 19 | ## 📑 Table of Contents
 20 | 
 21 | - [Getting Started & Integration](#-getting-started--integration)
 22 |   - [Setup Process](#setup-process)
 23 |   - [Cursor Integration](#cursor-integration)
 24 |   - [Claude Desktop Integration](#claude-desktop-integration)
 25 |   - [Smithery Integration](#smithery-integration)
 26 |   - [Glama.ai Integration](#glamaai-integration)
 27 | - [Features](#-features)
 28 | - [Documentation](#-documentation)
 29 |   - [Available Tools](#available-tools)
 30 |   - [Available Resources](#available-resources)
 31 | - [Development](#-development)
 32 | - [Technical Details](#-technical-details)
 33 | - [Troubleshooting](#-troubleshooting)
 34 | - [Contributing](#-contributing)
 35 | - [License](#-license)
 36 | - [Resources](#-resources)
 37 | - [Examples](#-examples)
 38 | 
 39 | ## 🚀 Getting Started & Integration
 40 | 
 41 | ### Setup Process
 42 | 
 43 | 1. **Obtain a Replicate API Token**
 44 |    - Sign up at [Replicate](https://replicate.com/)
 45 |    - Create an API token in your account settings
 46 | 
 47 | 2. **Choose Your Integration Method**
 48 |    - Follow one of the integration options below based on your preferred MCP client
 49 | 
 50 | 3. **Ask Your AI Assistant to Generate an Image**
 51 |    - Simply ask naturally: "Can you generate an image of a serene mountain landscape at sunset?"
 52 |    - Or be more specific: "Please create an image showing a peaceful mountain scene with a lake reflecting the sunset colors in the foreground"
 53 | 
 54 | 4. **Explore Advanced Features**
 55 |    - Try different parameter settings for customized results
 56 |    - Experiment with SVG generation using `generate_svg`
 57 |    - Use batch image generation or variant generation features
 58 | 
 59 | ### Cursor Integration
 60 | 
 61 | #### Method 1: Using mcp.json
 62 | 
 63 | 1. Create or edit the `.cursor/mcp.json` file in your project directory:
 64 | 
 65 | ```json
 66 | {
 67 |   "mcpServers": {
 68 |     "replicate-flux-mcp": {
 69 |       "command": "env REPLICATE_API_TOKEN=YOUR_TOKEN npx",
 70 |       "args": ["-y", "replicate-flux-mcp"]
 71 |     }
 72 |   }
 73 | }
 74 | ```
 75 | 
 76 | 2. Replace `YOUR_TOKEN` with your actual Replicate API token
 77 | 3. Restart Cursor to apply the changes
 78 | 
 79 | #### Method 2: Manual Mode
 80 | 
 81 | 1. Open Cursor and go to Settings
 82 | 2. Navigate to the "MCP" or "Model Context Protocol" section
 83 | 3. Click "Add Server" or equivalent
 84 | 4. Enter the following command in the appropriate field:
 85 | 
 86 | ```
 87 | env REPLICATE_API_TOKEN=YOUR_TOKEN npx -y replicate-flux-mcp
 88 | ```
 89 | 
 90 | 5. Replace `YOUR_TOKEN` with your actual Replicate API token
 91 | 6. Save the settings and restart Cursor if necessary
 92 | 
 93 | ### Claude Desktop Integration
 94 | 
 95 | 1. Create or edit the `mcp.json` file in your configuration directory:
 96 | 
 97 | ```json
 98 | {
 99 |   "mcpServers": {
100 |     "replicate-flux-mcp": {
101 |       "command": "npx",
102 |       "args": ["-y", "replicate-flux-mcp"],
103 |       "env": {
104 |         "REPLICATE_API_TOKEN": "YOUR TOKEN"
105 |       }
106 |     }
107 |   }
108 | }
109 | ```
110 | 
111 | 2. Replace `YOUR_TOKEN` with your actual Replicate API token
112 | 3. Restart Claude Desktop to apply the changes
113 | 
114 | ### Smithery Integration
115 | 
116 | This MCP server is available as a hosted service on Smithery, allowing you to use it without setting up your own server.
117 | 
118 | 1. Visit [Smithery](https://smithery.ai/) and create an account if you don't have one
119 | 2. Navigate to the [Replicate Flux MCP server page](https://smithery.ai/server/@awkoy/replicate-flux-mcp)
120 | 3. Click "Add to Workspace" to add the server to your Smithery workspace
121 | 4. Configure your MCP client (Cursor, Claude Desktop, etc.) to use your Smithery workspace URL
122 | 
123 | For more information on using Smithery with your MCP clients, visit the [Smithery documentation](https://smithery.ai/docs).
124 | 
125 | ### Glama.ai Integration
126 | 
127 | This MCP server is also available as a hosted service on Glama.ai, providing another option to use it without local setup.
128 | 
129 | 1. Visit [Glama.ai](https://glama.ai/) and create an account if you don't have one
130 | 2. Go to the [Replicate Flux MCP server page](https://glama.ai/mcp/servers/ss8n1knen8)
131 | 3. Click "Install Server" to add the server to your workspace
132 | 4. Configure your MCP client to use your Glama.ai workspace
133 | 
134 | For more information, visit the [Glama.ai MCP servers documentation](https://glama.ai/mcp/servers).
135 | 
136 | ## 🌟 Features
137 | 
138 | - **🖼️ High-Quality Image Generation** - Create stunning images using Flux Schnell, a state-of-the-art AI model
139 | - **🎨 Vector Graphics Support** - Generate professional SVG vector graphics with Recraft V3 SVG model
140 | - **🤖 AI Assistant Integration** - Seamlessly enable AI assistants like Claude to generate visual content
141 | - **🎛️ Advanced Customization** - Fine-tune generation with controls for aspect ratio, quality, resolution, and more
142 | - **🔌 Universal MCP Compatibility** - Works with all MCP clients including Cursor, Claude Desktop, Cline, and Zed
143 | - **🔒 Secure Local Processing** - All requests are processed locally for enhanced privacy and security
144 | - **🔍 Comprehensive History Management** - Track, view, and retrieve your complete generation history
145 | - **📊 Batch Processing** - Generate multiple images from different prompts in a single request
146 | - **🔄 Variant Exploration** - Create and compare multiple interpretations of the same concept
147 | - **✏️ Prompt Engineering** - Fine-tune image variations with specialized prompt modifications
148 | 
149 | ## 📚 Documentation
150 | 
151 | ### Available Tools
152 | 
153 | #### `generate_image`
154 | 
155 | Generates an image based on a text prompt using the Flux Schnell model.
156 | 
157 | ```typescript
158 | {
159 |   prompt: string;                // Required: Text description of the image to generate
160 |   seed?: number;                 // Optional: Random seed for reproducible generation
161 |   go_fast?: boolean;             // Optional: Run faster predictions with optimized model (default: true)
162 |   megapixels?: "1" | "0.25";     // Optional: Image resolution (default: "1")
163 |   num_outputs?: number;          // Optional: Number of images to generate (1-4) (default: 1)
164 |   aspect_ratio?: string;         // Optional: Aspect ratio (e.g., "16:9", "4:3") (default: "1:1")
165 |   output_format?: string;        // Optional: Output format ("webp", "jpg", "png") (default: "webp")
166 |   output_quality?: number;       // Optional: Image quality (0-100) (default: 80)
167 |   num_inference_steps?: number;  // Optional: Number of denoising steps (1-4) (default: 4)
168 |   disable_safety_checker?: boolean; // Optional: Disable safety filter (default: false)
169 | }
170 | ```
171 | 
172 | #### `generate_multiple_images`
173 | 
174 | Generates multiple images based on an array of prompts using the Flux Schnell model.
175 | 
176 | ```typescript
177 | {
178 |   prompts: string[];             // Required: Array of text descriptions for images to generate (1-10 prompts)
179 |   seed?: number;                 // Optional: Random seed for reproducible generation
180 |   go_fast?: boolean;             // Optional: Run faster predictions with optimized model (default: true)
181 |   megapixels?: "1" | "0.25";     // Optional: Image resolution (default: "1")
182 |   aspect_ratio?: string;         // Optional: Aspect ratio (e.g., "16:9", "4:3") (default: "1:1")
183 |   output_format?: string;        // Optional: Output format ("webp", "jpg", "png") (default: "webp")
184 |   output_quality?: number;       // Optional: Image quality (0-100) (default: 80)
185 |   num_inference_steps?: number;  // Optional: Number of denoising steps (1-4) (default: 4)
186 |   disable_safety_checker?: boolean; // Optional: Disable safety filter (default: false)
187 | }
188 | ```
189 | 
190 | #### `generate_image_variants`
191 | 
192 | Generates multiple variants of the same image from a single prompt.
193 | 
194 | ```typescript
195 | {
196 |   prompt: string;                // Required: Text description for the image to generate variants of
197 |   num_variants: number;          // Required: Number of image variants to generate (2-10, default: 4)
198 |   prompt_variations?: string[];  // Optional: List of prompt modifiers to apply to variants (e.g., ["in watercolor style", "in oil painting style"])
199 |   variation_mode?: "append" | "replace"; // Optional: How to apply variations - 'append' adds to base prompt, 'replace' uses variations directly (default: "append")
200 |   seed?: number;                 // Optional: Base random seed. Each variant will use seed+variant_index
201 |   go_fast?: boolean;             // Optional: Run faster predictions with optimized model (default: true)
202 |   megapixels?: "1" | "0.25";     // Optional: Image resolution (default: "1")
203 |   aspect_ratio?: string;         // Optional: Aspect ratio (e.g., "16:9", "4:3") (default: "1:1")
204 |   output_format?: string;        // Optional: Output format ("webp", "jpg", "png") (default: "webp")
205 |   output_quality?: number;       // Optional: Image quality (0-100) (default: 80)
206 |   num_inference_steps?: number;  // Optional: Number of denoising steps (1-4) (default: 4)
207 |   disable_safety_checker?: boolean; // Optional: Disable safety filter (default: false)
208 | }
209 | ```
210 | 
211 | #### `generate_svg`
212 | 
213 | Generates an SVG vector image based on a text prompt using the Recraft V3 SVG model.
214 | 
215 | ```typescript
216 | {
217 |   prompt: string;                // Required: Text description of the SVG to generate
218 |   size?: string;                 // Optional: Size of the generated SVG (default: "1024x1024")
219 |   style?: string;                // Optional: Style of the generated image (default: "any")
220 |                                 // Options: "any", "engraving", "line_art", "line_circuit", "linocut"
221 | }
222 | ```
223 | 
224 | #### `prediction_list`
225 | 
226 | Retrieves a list of your recent predictions from Replicate.
227 | 
228 | ```typescript
229 | {
230 |   limit?: number;  // Optional: Maximum number of predictions to return (1-100) (default: 50)
231 | }
232 | ```
233 | 
234 | #### `get_prediction`
235 | 
236 | Gets detailed information about a specific prediction.
237 | 
238 | ```typescript
239 | {
240 |   predictionId: string;  // Required: ID of the prediction to retrieve
241 | }
242 | ```
243 | 
244 | ### Available Resources
245 | 
246 | #### `imagelist`
247 | 
248 | Browse your history of generated images created with the Flux Schnell model.
249 | 
250 | #### `svglist`
251 | 
252 | Browse your history of generated SVG images created with the Recraft V3 SVG model.
253 | 
254 | #### `predictionlist`
255 | 
256 | Browse all your Replicate predictions history.
257 | 
258 | ## 💻 Development
259 | 
260 | 1. Clone the repository:
261 | 
262 | ```bash
263 | git clone https://github.com/awkoy/replicate-flux-mcp.git
264 | cd replicate-flux-mcp
265 | ```
266 | 
267 | 2. Install dependencies:
268 | 
269 | ```bash
270 | npm install
271 | ```
272 | 
273 | 3. Start development mode:
274 | 
275 | ```bash
276 | npm run dev
277 | ```
278 | 
279 | 4. Build the project:
280 | 
281 | ```bash
282 | npm run build
283 | ```
284 | 
285 | 5. Connect to Client:
286 | 
287 | ```json
288 | {
289 |   "mcpServers": {
290 |     "image-generation-mcp": {
291 |       "command": "npx",
292 |       "args": [
293 |         "/Users/{USERNAME}/{PATH_TO}/replicate-flux-mcp/build/index.js"
294 |       ],
295 |       "env": {
296 |         "REPLICATE_API_TOKEN": "YOUR REPLICATE API TOKEN"
297 |       }
298 |     }
299 |   }
300 | }
301 | ```
302 | 
303 | ## ⚙️ Technical Details
304 | 
305 | ### Stack
306 | 
307 | - **Model Context Protocol SDK** - Core MCP functionality for tool and resource management
308 | - **Replicate API** - Provides access to state-of-the-art AI image generation models
309 | - **TypeScript** - Ensures type safety and leverages modern JavaScript features
310 | - **Zod** - Implements runtime type validation for robust API interactions
311 | 
312 | ### Configuration
313 | 
314 | The server can be configured by modifying the `CONFIG` object in `src/config/index.ts`:
315 | 
316 | ```javascript
317 | const CONFIG = {
318 |   serverName: "replicate-flux-mcp",
319 |   serverVersion: "0.1.2",
320 |   imageModelId: "black-forest-labs/flux-schnell",
321 |   svgModelId: "recraft-ai/recraft-v3-svg",
322 |   pollingAttempts: 25,
323 |   pollingInterval: 2000, // ms
324 | };
325 | ```
326 | 
327 | ## 🔍 Troubleshooting
328 | 
329 | ### Common Issues
330 | 
331 | #### Authentication Error
332 | - Ensure your `REPLICATE_API_TOKEN` is correctly set in the environment
333 | - Verify your token is valid by testing it with the Replicate API directly
334 | 
335 | #### Safety Filter Triggered
336 | - The model has a built-in safety filter that may block certain prompts
337 | - Try modifying your prompt to avoid potentially problematic content
338 | 
339 | #### Timeout Error
340 | - For larger images or busy servers, you might need to increase `pollingAttempts` or `pollingInterval` in the configuration
341 | - Default settings should work for most use cases
342 | 
343 | ## 🤝 Contributing
344 | 
345 | Contributions are welcome! Please follow these steps to contribute:
346 | 
347 | 1. Fork the repository
348 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
349 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
350 | 4. Push to the branch (`git push origin feature/amazing-feature`)
351 | 5. Open a Pull Request
352 | 
353 | For feature requests or bug reports, please create a GitHub issue. If you like this project, consider starring the repository!
354 | 
355 | ## 📄 License
356 | 
357 | This project is licensed under the MIT License - see the LICENSE file for details.
358 | 
359 | ## 🔗 Resources
360 | 
361 | - [Model Context Protocol Documentation](https://modelcontextprotocol.io)
362 | - [Replicate API Documentation](https://replicate.com/docs)
363 | - [Flux Schnell Model](https://replicate.com/black-forest-labs/flux-schnell)
364 | - [Recraft V3 SVG Model](https://replicate.com/recraft-ai/recraft-v3-svg)
365 | - [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
366 | - [Smithery Documentation](https://smithery.ai/docs)
367 | - [Glama.ai MCP Servers](https://glama.ai/mcp/servers)
368 | 
369 | ## 🎨 Examples
370 | 
371 | ![Demo](https://github.com/user-attachments/assets/ad6db606-ae3a-48db-a1cc-e1f88847769e)
372 | 
373 | | Multiple Prompts | Prompt Variants |
374 | |-----------------|-----------------|
375 | | ![Multiple prompts example: "A serene mountain lake at sunset", "A bustling city street at night", "A peaceful garden in spring"](https://github.com/user-attachments/assets/e5ac56d2-bfbb-4f33-938c-a3d7bffeee60) | ![Variants example: Base prompt "A majestic castle" with modifiers "in watercolor style", "as an oil painting", "with gothic architecture"](https://github.com/user-attachments/assets/8ebe5992-4803-4bf3-a82a-251135b0698a) |
376 | 
377 | Here are some examples of how to use the tools:
378 | 
379 | ### Batch Image Generation with `generate_multiple_images`
380 | 
381 | Create multiple distinct images at once with different prompts:
382 | 
383 | ```json
384 | {
385 |   "prompts": [
386 |     "A red sports car on a mountain road", 
387 |     "A blue sports car on a beach", 
388 |     "A vintage sports car in a city street"
389 |   ]
390 | }
391 | ```
392 | 
393 | ### Image Variants with `generate_image_variants`
394 | 
395 | Create different interpretations of the same concept using seeds:
396 | 
397 | ```json
398 | {
399 |   "prompt": "A futuristic city skyline at night",
400 |   "num_variants": 4,
401 |   "seed": 42
402 | }
403 | ```
404 | 
405 | Or explore style variations with prompt modifiers:
406 | 
407 | ```json
408 | {
409 |   "prompt": "A character portrait",
410 |   "prompt_variations": [
411 |     "in anime style", 
412 |     "in watercolor style", 
413 |     "in oil painting style", 
414 |     "as a 3D render"
415 |   ]
416 | }
417 | ```
418 | 
419 | ---
420 | 
421 | Made with ❤️ by Yaroslav Boiko
422 | 
423 | 
```

--------------------------------------------------------------------------------
/src/utils/error.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
2 | 
3 | export function handleError(error: unknown): never {
4 |   if (error instanceof Error) {
5 |     throw new McpError(ErrorCode.InternalError, error.message);
6 |   }
7 |   throw new McpError(ErrorCode.InternalError, String(error));
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Configuration
 2 | export const CONFIG = {
 3 |   serverName: "replicate-flux-mcp",
 4 |   serverVersion: "0.1.2",
 5 |   imageModelId: "black-forest-labs/flux-schnell" as `${string}/${string}`,
 6 |   svgModelId: "recraft-ai/recraft-v3-svg" as `${string}/${string}`,
 7 |   pollingAttempts: 25,
 8 |   pollingInterval: 2000, // ms
 9 | };
10 | 
```

--------------------------------------------------------------------------------
/src/resources/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { registerImageListResource } from "./imageList.js";
 2 | import { registerPreditionListResource } from "./predictionList.js";
 3 | import { registerSvgListResource } from "./svgList.js";
 4 | 
 5 | export const registerAllResources = () => {
 6 |   registerImageListResource();
 7 |   registerPreditionListResource();
 8 |   registerSvgListResource();
 9 | };
10 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | FROM node:22.12-alpine AS builder
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | COPY package*.json ./
 6 | COPY tsconfig.json ./
 7 | COPY src/ ./src/
 8 | 
 9 | RUN --mount=type=cache,target=/root/.npm npm install
10 | RUN npm run build
11 | 
12 | FROM node:22.12-alpine AS release
13 | 
14 | WORKDIR /app
15 | 
16 | COPY --from=builder /app/build /app/build
17 | COPY --from=builder /app/package.json ./
18 | COPY --from=builder /app/package-lock.json ./
19 | 
20 | ENV NODE_ENV=production
21 | 
22 | RUN npm ci --ignore-scripts --omit-dev
23 | 
24 | CMD ["node", "build/index.js"]
25 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/deployments
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - replicateApiToken
10 |     properties:
11 |       replicateApiToken:
12 |         type: string
13 |         description: The API key for the Replicate API.
14 |   commandFunction:
15 |     # A function that produces the CLI command to start the MCP on stdio.
16 |     |-
17 |     config=>({command:'node',args:['build/index.js'],env:{REPLICATE_API_TOKEN:config.replicateApiToken}})
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | import { registerAllResources } from "./resources/index.js";
 3 | import { startServer } from "./server/index.js";
 4 | import { registerAllTools } from "./tools/index.js";
 5 | 
 6 | registerAllTools();
 7 | registerAllResources();
 8 | 
 9 | async function main() {
10 |   try {
11 |     await startServer();
12 |   } catch (error) {
13 |     console.error(
14 |       "Unhandled server error:",
15 |       error instanceof Error ? error.message : String(error)
16 |     );
17 |     process.exit(1);
18 |   }
19 | }
20 | 
21 | main().catch((error: unknown) => {
22 |   console.error(
23 |     "Unhandled server error:",
24 |     error instanceof Error ? error.message : String(error)
25 |   );
26 |   process.exit(1);
27 | });
28 | 
```

--------------------------------------------------------------------------------
/src/tools/getPrediction.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { replicate } from "../services/replicate.js";
 2 | import { handleError } from "../utils/error.js";
 3 | import { GetPredictionParams } from "../types/index.js";
 4 | import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
 5 | 
 6 | export const registerGetPredictionTool = async ({
 7 |   predictionId,
 8 | }: GetPredictionParams): Promise<CallToolResult> => {
 9 |   try {
10 |     const prediction = await replicate.predictions.get(predictionId);
11 | 
12 |     return {
13 |       content: [
14 |         {
15 |           type: "text",
16 |           text: JSON.stringify(prediction, null, 2),
17 |         },
18 |       ],
19 |     };
20 |   } catch (error) {
21 |     handleError(error);
22 |   }
23 | };
24 | 
```

--------------------------------------------------------------------------------
/src/services/replicate.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Replicate from "replicate";
 2 | import { CONFIG } from "../config/index.js";
 3 | 
 4 | export function getReplicateApiToken(): string {
 5 |   const token = process.env.REPLICATE_API_TOKEN;
 6 |   if (!token) {
 7 |     console.error(
 8 |       "Error: REPLICATE_API_TOKEN environment variable is required"
 9 |     );
10 |     process.exit(1);
11 |   }
12 |   return token;
13 | }
14 | 
15 | export const replicate = new Replicate({
16 |   auth: getReplicateApiToken(),
17 | });
18 | 
19 | export async function pollForCompletion(predictionId: string) {
20 |   for (let i = 0; i < CONFIG.pollingAttempts; i++) {
21 |     const latest = await replicate.predictions.get(predictionId);
22 |     if (latest.status !== "starting" && latest.status !== "processing") {
23 |       return latest;
24 |     }
25 |     await new Promise((resolve) => setTimeout(resolve, CONFIG.pollingInterval));
26 |   }
27 |   return null;
28 | }
29 | 
```

--------------------------------------------------------------------------------
/src/tools/createPrediction.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { CreatePredictionParams } from "../types/index.js";
 2 | import { pollForCompletion, replicate } from "../services/replicate.js";
 3 | import { handleError } from "../utils/error.js";
 4 | import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
 5 | import { CONFIG } from "../config/index.js";
 6 | 
 7 | export const registerCreatePredictionTool = async (
 8 |   input: CreatePredictionParams
 9 | ): Promise<CallToolResult> => {
10 |   try {
11 |     const prediction = await replicate.predictions.create({
12 |       model: CONFIG.imageModelId,
13 |       input,
14 |     });
15 | 
16 |     await replicate.predictions.get(prediction.id);
17 |     const completed = await pollForCompletion(prediction.id);
18 | 
19 |     return {
20 |       content: [
21 |         {
22 |           type: "text",
23 |           text: JSON.stringify(completed || "Processing timed out", null, 2),
24 |         },
25 |       ],
26 |     };
27 |   } catch (error) {
28 |     handleError(error);
29 |   }
30 | };
31 | 
```

--------------------------------------------------------------------------------
/src/server/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 3 | import { CONFIG } from "../config/index.js";
 4 | 
 5 | export const server = new McpServer(
 6 |   {
 7 |     name: CONFIG.serverName,
 8 |     version: CONFIG.serverVersion,
 9 |   },
10 |   {
11 |     capabilities: {
12 |       resources: {},
13 |       tools: {},
14 |     },
15 |     instructions: `
16 |     MCP server for the Replicate models.
17 |     It is used to generate images and SVGs from text prompts.
18 |     `,
19 |   }
20 | );
21 | 
22 | export async function startServer() {
23 |   try {
24 |     const transport = new StdioServerTransport();
25 |     await server.connect(transport);
26 |     console.error(
27 |       `${CONFIG.serverName} v${CONFIG.serverVersion} running on stdio`
28 |     );
29 |   } catch (error) {
30 |     console.error(
31 |       "Server initialization error:",
32 |       error instanceof Error ? error.message : String(error)
33 |     );
34 |     process.exit(1);
35 |   }
36 | }
37 | 
```

--------------------------------------------------------------------------------
/src/utils/image.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { FileOutput } from "replicate";
 2 | 
 3 | export async function outputToBase64(output: FileOutput) {
 4 |   const blob = await output.blob();
 5 |   const buffer = Buffer.from(await blob.arrayBuffer());
 6 |   return buffer.toString("base64");
 7 | }
 8 | 
 9 | export async function urlToSvg(url: string) {
10 |   try {
11 |     const data = await fetch(url, {
12 |       headers: {
13 |         Authorization: `Bearer ${process.env.REPLICATE_API_TOKEN}`,
14 |       },
15 |     });
16 | 
17 |     const text = await data.text();
18 | 
19 |     return text;
20 |   } catch (error) {
21 |     throw new Error("Error fetching svg");
22 |   }
23 | }
24 | 
25 | export async function urlToBase64(url: string) {
26 |   try {
27 |     const data = await fetch(url, {
28 |       headers: {
29 |         Authorization: `Bearer ${process.env.REPLICATE_API_TOKEN}`,
30 |       },
31 |     });
32 | 
33 |     const blob = await data.blob();
34 | 
35 |     let buffer = Buffer.from(await blob.arrayBuffer());
36 |     return buffer.toString("base64");
37 |   } catch (error) {
38 |     throw new Error("Error fetching image");
39 |   }
40 | }
41 | 
```

--------------------------------------------------------------------------------
/src/tools/predictionList.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { PredictionListParams } from "../types/index.js";
 2 | import { replicate } from "../services/replicate.js";
 3 | import { handleError } from "../utils/error.js";
 4 | import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
 5 | 
 6 | export const registerPredictionListTool = async ({
 7 |   limit,
 8 | }: PredictionListParams): Promise<CallToolResult> => {
 9 |   try {
10 |     const predictions = [];
11 |     for await (const page of replicate.paginate(replicate.predictions.list)) {
12 |       predictions.push(...page);
13 |       if (predictions.length >= limit) {
14 |         break;
15 |       }
16 |     }
17 | 
18 |     const limitedPredictions = predictions.slice(0, limit);
19 |     const totalPages = Math.ceil(predictions.length / limit);
20 | 
21 |     return {
22 |       content: [
23 |         {
24 |           type: "text",
25 |           text: `Found ${limitedPredictions.length} predictions (showing ${limitedPredictions.length} of ${predictions.length} total, page 1 of ${totalPages})`,
26 |         },
27 |         {
28 |           type: "text",
29 |           text: JSON.stringify(limitedPredictions, null, 2),
30 |         },
31 |       ],
32 |     };
33 |   } catch (error) {
34 |     handleError(error);
35 |   }
36 | };
37 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "replicate-flux-mcp",
 3 |   "version": "0.1.2",
 4 |   "type": "module",
 5 |   "bin": {
 6 |     "replicate-flux-mcp": "build/index.js"
 7 |   },
 8 |   "scripts": {
 9 |     "build": "tsc && shx chmod +x build/*.js",
10 |     "prepare": "npm run build",
11 |     "watch": "tsc --watch",
12 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js -e REPLICATE_API_TOKEN=YOUR_REPLICATE_API_TOKEN"
13 |   },
14 |   "homepage": "https://github.com/awkoy/replicate-flux-mcp",
15 |   "keywords": [
16 |     "replicate",
17 |     "flux",
18 |     "mcp",
19 |     "flux-schnell",
20 |     "flux-schnell-mcp",
21 |     "modelcontextprotocol",
22 |     "image-generation",
23 |     "ai"
24 |   ],
25 |   "author": "Yaroslav Boiko <[email protected]>",
26 |   "license": "MIT",
27 |   "description": "MCP for Replicate Flux Model",
28 |   "files": [
29 |     "build"
30 |   ],
31 |   "dependencies": {
32 |     "@modelcontextprotocol/sdk": "^1.7.0",
33 |     "replicate": "^1.0.1",
34 |     "zod": "^3.24.2"
35 |   },
36 |   "devDependencies": {
37 |     "@types/node": "^22.13.10",
38 |     "shx": "^0.3.4",
39 |     "typescript": "^5.8.2"
40 |   },
41 |   "engines": {
42 |     "node": ">=18"
43 |   },
44 |   "repository": {
45 |     "type": "git",
46 |     "url": "git+https://github.com/awkoy/replicate-flux-mcp.git"
47 |   }
48 | }
49 | 
```

--------------------------------------------------------------------------------
/src/tools/generateSVG.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { SvgGenerationParams } from "../types/index.js";
 2 | import { replicate } from "../services/replicate.js";
 3 | import { handleError } from "../utils/error.js";
 4 | import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
 5 | import { urlToSvg } from "../utils/image.js";
 6 | import { CONFIG } from "../config/index.js";
 7 | import { FileOutput } from "replicate";
 8 | 
 9 | export const registerGenerateSvgTool = async (
10 |   input: SvgGenerationParams
11 | ): Promise<CallToolResult> => {
12 |   try {
13 |     const output = (await replicate.run(CONFIG.svgModelId, {
14 |       input,
15 |     })) as FileOutput;
16 | 
17 |     const svgUrl = output.url() as unknown as string;
18 |     if (!svgUrl) {
19 |       throw new Error("Failed to generate SVG URL");
20 |     }
21 | 
22 |     try {
23 |       const svg = await urlToSvg(svgUrl);
24 | 
25 |       return {
26 |         content: [
27 |           {
28 |             type: "text",
29 |             text: `This is a generated SVG url: ${svgUrl}`,
30 |           },
31 |           {
32 |             type: "text",
33 |             text: svg,
34 |           },
35 |         ],
36 |       };
37 |     } catch (error) {
38 |       return {
39 |         content: [
40 |           {
41 |             type: "text",
42 |             text: `This is a generated SVG url: ${svgUrl}`,
43 |           },
44 |         ],
45 |       };
46 |     }
47 |   } catch (error) {
48 |     return handleError(error);
49 |   }
50 | };
51 | 
```

--------------------------------------------------------------------------------
/src/resources/predictionList.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { server } from "../server/index.js";
 2 | import {
 3 |   ListResourcesCallback,
 4 |   ResourceTemplate,
 5 | } from "@modelcontextprotocol/sdk/server/mcp.js";
 6 | import { replicate } from "../services/replicate.js";
 7 | import { Prediction } from "replicate";
 8 | 
 9 | export const registerPreditionListResource = () => {
10 |   const list: ListResourcesCallback = async () => {
11 |     try {
12 |       const predictions: Prediction[] = [];
13 |       for await (const page of replicate.paginate(replicate.predictions.list)) {
14 |         predictions.push(...page);
15 |       }
16 | 
17 |       return {
18 |         resources: predictions.map((prediction) => ({
19 |           uri: `predictions://${prediction.id}`,
20 |           name: `Prediction ${prediction.id}`,
21 |           mimeType: "application/json",
22 |         })),
23 |         nextCursor: undefined,
24 |       };
25 |     } catch (error) {
26 |       console.error("Error listing predictions:", error);
27 |       return {
28 |         resources: [],
29 |         nextCursor: undefined,
30 |       };
31 |     }
32 |   };
33 | 
34 |   server.resource(
35 |     "predictions",
36 |     new ResourceTemplate("predictions://{id}", {
37 |       list,
38 |     }),
39 |     async (uri, { id }) => {
40 |       const prediction = await replicate.predictions.get(id as string);
41 | 
42 |       return {
43 |         contents: [
44 |           {
45 |             name: "prediction",
46 |             uri: uri.href,
47 |             text: JSON.stringify(prediction),
48 |             mimeType: "application/json",
49 |           },
50 |         ],
51 |       };
52 |     }
53 |   );
54 | };
55 | 
```

--------------------------------------------------------------------------------
/src/tools/generateImage.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ImageGenerationParams } from "../types/index.js";
 2 | import { replicate } from "../services/replicate.js";
 3 | import { handleError } from "../utils/error.js";
 4 | import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
 5 | import { FileOutput } from "replicate";
 6 | import { outputToBase64 } from "../utils/image.js";
 7 | import { CONFIG } from "../config/index.js";
 8 | 
 9 | export const registerGenerateImageTool = async (
10 |   input: ImageGenerationParams
11 | ): Promise<CallToolResult> => {
12 |   const { support_image_mcp_response_type, ...predictionInput } = input;
13 |   try {
14 |     const [output] = (await replicate.run(CONFIG.imageModelId, {
15 |       input: predictionInput,
16 |     })) as [FileOutput];
17 |     const imageUrl = output.url() as unknown as string;
18 | 
19 |     if (support_image_mcp_response_type) {
20 |       const imageBase64 = await outputToBase64(output);
21 |       return {
22 |         content: [
23 |           {
24 |             type: "text",
25 |             text: `This is a generated image link: ${imageUrl}`,
26 |           },
27 |           {
28 |             type: "image",
29 |             data: imageBase64,
30 |             mimeType: "image/png",
31 |           },
32 |           {
33 |             type: "text",
34 |             text: `The image above is generated by the Flux model and prompt: ${input.prompt}`,
35 |           },
36 |         ],
37 |       };
38 |     }
39 | 
40 |     return {
41 |       content: [
42 |         {
43 |           type: "text",
44 |           text: `This is a generated image link: ${imageUrl}`,
45 |         },
46 |         {
47 |           type: "text",
48 |           text: `The image above is generated by the Flux model and prompt: ${input.prompt}`,
49 |         },
50 |       ],
51 |     };
52 |   } catch (error) {
53 |     handleError(error);
54 |   }
55 | };
56 | 
```

--------------------------------------------------------------------------------
/src/resources/svgList.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { server } from "../server/index.js";
 2 | import {
 3 |   ListResourcesCallback,
 4 |   ResourceTemplate,
 5 | } from "@modelcontextprotocol/sdk/server/mcp.js";
 6 | import { replicate } from "../services/replicate.js";
 7 | import { urlToSvg } from "../utils/image.js";
 8 | import { Prediction } from "replicate";
 9 | import { CONFIG } from "../config/index.js";
10 | 
11 | export const registerSvgListResource = () => {
12 |   const list: ListResourcesCallback = async () => {
13 |     try {
14 |       const predictions: Prediction[] = [];
15 |       for await (const page of replicate.paginate(replicate.predictions.list)) {
16 |         predictions.push(...page);
17 |       }
18 | 
19 |       return {
20 |         resources: predictions
21 |           .filter((prediction) => prediction.model === CONFIG.svgModelId)
22 |           .map((prediction) => ({
23 |             uri: `svglist://${prediction.id}`,
24 |             name: `SVG ${prediction.id}`,
25 |             mimeType: "application/json",
26 |             description: `Generated image by ${prediction.model} with id ${prediction.id}`,
27 |           })),
28 |         nextCursor: undefined,
29 |       };
30 |     } catch (error) {
31 |       console.error("Error listing predictions:", error);
32 |       return {
33 |         resources: [],
34 |         nextCursor: undefined,
35 |       };
36 |     }
37 |   };
38 | 
39 |   server.resource(
40 |     "svglist",
41 |     new ResourceTemplate("svglist://{id}", {
42 |       list,
43 |     }),
44 |     async (uri, { id }) => {
45 |       const prediction = await replicate.predictions.get(id as string);
46 | 
47 |       if (!prediction.output) {
48 |         return {
49 |           contents: [
50 |             {
51 |               name: "Not Found!",
52 |               uri: uri.href,
53 |               text: `Data has been removed by Replicate automatically after an hour, by default. You have to save your own copies before it is removed.`,
54 |               mimeType: "text/plain",
55 |             },
56 |           ],
57 |         };
58 |       }
59 | 
60 |       const svg = await urlToSvg(prediction.output);
61 | 
62 |       return {
63 |         contents: [
64 |           {
65 |             name: "svglist",
66 |             uri: uri.href,
67 |             text: svg,
68 |             mimeType: "image/svg+xml",
69 |           },
70 |         ],
71 |       };
72 |     }
73 |   );
74 | };
75 | 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import {
 2 |   getPredictionSchema,
 3 |   svgGenerationSchema,
 4 |   multiImageGenerationSchema,
 5 |   imageVariantsGenerationSchema,
 6 | } from "../types/index.js";
 7 | import { predictionListSchema } from "../types/index.js";
 8 | 
 9 | import { server } from "../server/index.js";
10 | import { imageGenerationSchema } from "../types/index.js";
11 | import { registerGetPredictionTool } from "./getPrediction.js";
12 | import { registerPredictionListTool } from "./predictionList.js";
13 | import { registerGenerateImageTool } from "./generateImage.js";
14 | import { createPredictionSchema } from "../types/index.js";
15 | import { registerCreatePredictionTool } from "./createPrediction.js";
16 | import { registerGenerateSvgTool } from "./generateSVG.js";
17 | import { registerGenerateMultipleImagesTool } from "./generateMultipleImages.js";
18 | import { registerGenerateImageVariantsTool } from "./generateImageVariants.js";
19 | 
20 | export const registerAllTools = () => {
21 |   server.tool(
22 |     "generate_image",
23 |     "Generate an image from a text prompt using Flux Schnell model",
24 |     imageGenerationSchema,
25 |     registerGenerateImageTool
26 |   );
27 |   server.tool(
28 |     "generate_multiple_images",
29 |     "Generate multiple images from an array of prompts using Flux Schnell model",
30 |     multiImageGenerationSchema,
31 |     registerGenerateMultipleImagesTool
32 |   );
33 |   server.tool(
34 |     "generate_image_variants",
35 |     "Generate multiple variants of the same image from a single prompt",
36 |     imageVariantsGenerationSchema,
37 |     registerGenerateImageVariantsTool
38 |   );
39 |   server.tool(
40 |     "generate_svg",
41 |     "Generate an SVG from a text prompt using Recraft model",
42 |     svgGenerationSchema,
43 |     registerGenerateSvgTool
44 |   );
45 |   server.tool(
46 |     "get_prediction",
47 |     "Get details of a specific prediction by ID",
48 |     getPredictionSchema,
49 |     registerGetPredictionTool
50 |   );
51 |   server.tool(
52 |     "create_prediction",
53 |     "Generate an prediction from a text prompt using Flux Schnell model",
54 |     createPredictionSchema,
55 |     registerCreatePredictionTool
56 |   );
57 |   server.tool(
58 |     "prediction_list",
59 |     "Get a list of recent predictions from Replicate",
60 |     predictionListSchema,
61 |     registerPredictionListTool
62 |   );
63 | };
64 | 
```

--------------------------------------------------------------------------------
/src/resources/imageList.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { server } from "../server/index.js";
 2 | import {
 3 |   ListResourcesCallback,
 4 |   ResourceTemplate,
 5 | } from "@modelcontextprotocol/sdk/server/mcp.js";
 6 | import { replicate } from "../services/replicate.js";
 7 | import { urlToBase64 } from "../utils/image.js";
 8 | import { Prediction } from "replicate";
 9 | import { CONFIG } from "../config/index.js";
10 | 
11 | export const registerImageListResource = () => {
12 |   const list: ListResourcesCallback = async () => {
13 |     try {
14 |       const predictions: Prediction[] = [];
15 |       for await (const page of replicate.paginate(replicate.predictions.list)) {
16 |         predictions.push(...page);
17 |       }
18 | 
19 |       return {
20 |         resources: predictions
21 |           .filter(
22 |             (prediction) =>
23 |               prediction.output?.length &&
24 |               prediction.model === CONFIG.imageModelId
25 |           )
26 |           .map((prediction) => ({
27 |             uri: `images://${prediction.id}`,
28 |             name: `Image ${prediction.id}`,
29 |             mimeType: "application/json",
30 |             description: `Generated image by ${prediction.model} with id ${prediction.id}`,
31 |           })),
32 |         nextCursor: undefined,
33 |       };
34 |     } catch (error) {
35 |       console.error("Error listing predictions:", error);
36 |       return {
37 |         resources: [],
38 |         nextCursor: undefined,
39 |       };
40 |     }
41 |   };
42 | 
43 |   server.resource(
44 |     "images",
45 |     new ResourceTemplate("images://{id}", {
46 |       list,
47 |     }),
48 |     async (uri, { id }) => {
49 |       const prediction = await replicate.predictions.get(id as string);
50 | 
51 |       if (!prediction.output?.length) {
52 |         return {
53 |           contents: [
54 |             {
55 |               name: "Not Found!",
56 |               uri: uri.href,
57 |               text: `Data has been removed by Replicate automatically after an hour, by default. You have to save your own copies before it is removed.`,
58 |               mimeType: "text/plain",
59 |             },
60 |           ],
61 |         };
62 |       }
63 | 
64 |       const imageBase64 = await urlToBase64(prediction.output[0]);
65 | 
66 |       return {
67 |         contents: [
68 |           {
69 |             name: "image",
70 |             uri: uri.href,
71 |             blob: imageBase64,
72 |             mimeType: "image/png",
73 |           },
74 |         ],
75 |       };
76 |     }
77 |   );
78 | };
79 | 
```

--------------------------------------------------------------------------------
/src/tools/generateMultipleImages.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { MultiImageGenerationParams } from "../types/index.js";
 2 | import { replicate } from "../services/replicate.js";
 3 | import { handleError } from "../utils/error.js";
 4 | import {
 5 |   CallToolResult,
 6 |   TextContent,
 7 |   ImageContent,
 8 | } from "@modelcontextprotocol/sdk/types.js";
 9 | import { FileOutput } from "replicate";
10 | import { outputToBase64 } from "../utils/image.js";
11 | import { CONFIG } from "../config/index.js";
12 | 
13 | export const registerGenerateMultipleImagesTool = async (
14 |   input: MultiImageGenerationParams
15 | ): Promise<CallToolResult> => {
16 |   const { prompts, support_image_mcp_response_type, ...commonParams } = input;
17 |   try {
18 |     // Process all prompts in parallel
19 |     const generationPromises = prompts.map(async (prompt) => {
20 |       const [output] = (await replicate.run(CONFIG.imageModelId, {
21 |         input: {
22 |           prompt,
23 |           ...commonParams,
24 |         },
25 |       })) as [FileOutput];
26 | 
27 |       const imageUrl = output.url() as unknown as string;
28 | 
29 |       if (support_image_mcp_response_type) {
30 |         const imageBase64 = await outputToBase64(output);
31 |         return {
32 |           prompt,
33 |           imageUrl,
34 |           imageBase64,
35 |         };
36 |       }
37 | 
38 |       return {
39 |         prompt,
40 |         imageUrl,
41 |       };
42 |     });
43 | 
44 |     // Wait for all image generation to complete
45 |     const results = await Promise.all(generationPromises);
46 | 
47 |     // Build response content
48 |     const responseContent: (TextContent | ImageContent)[] = [];
49 | 
50 |     // Add intro text
51 |     responseContent.push({
52 |       type: "text",
53 |       text: `Generated ${results.length} images based on your prompts:`,
54 |     } as TextContent);
55 | 
56 |     // Add each image with its prompt
57 |     for (const result of results) {
58 |       responseContent.push({
59 |         type: "text",
60 |         text: `\n\nPrompt: "${result.prompt}"\nImage URL: ${result.imageUrl}`,
61 |       } as TextContent);
62 | 
63 |       if (support_image_mcp_response_type && result.imageBase64) {
64 |         responseContent.push({
65 |           type: "image",
66 |           data: result.imageBase64,
67 |           mimeType: `image/${
68 |             input.output_format === "jpg" ? "jpeg" : input.output_format
69 |           }`,
70 |         } as ImageContent);
71 |       }
72 |     }
73 | 
74 |     return {
75 |       content: responseContent,
76 |     };
77 |   } catch (error) {
78 |     handleError(error);
79 |   }
80 | };
81 | 
```

--------------------------------------------------------------------------------
/src/tools/generateImageVariants.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ImageVariantsGenerationParams } from "../types/index.js";
  2 | import { replicate } from "../services/replicate.js";
  3 | import { handleError } from "../utils/error.js";
  4 | import {
  5 |   CallToolResult,
  6 |   TextContent,
  7 |   ImageContent,
  8 | } from "@modelcontextprotocol/sdk/types.js";
  9 | import { FileOutput } from "replicate";
 10 | import { outputToBase64 } from "../utils/image.js";
 11 | import { CONFIG } from "../config/index.js";
 12 | 
 13 | type ImageVariantResult = {
 14 |   variantIndex: number;
 15 |   imageUrl: string;
 16 |   imageBase64?: string;
 17 |   usedPrompt: string;
 18 | };
 19 | 
 20 | export const registerGenerateImageVariantsTool = async (
 21 |   input: ImageVariantsGenerationParams
 22 | ): Promise<CallToolResult> => {
 23 |   const {
 24 |     prompt,
 25 |     num_variants,
 26 |     seed,
 27 |     support_image_mcp_response_type,
 28 |     prompt_variations,
 29 |     variation_mode,
 30 |     ...commonParams
 31 |   } = input;
 32 | 
 33 |   try {
 34 |     let effectiveVariants = num_variants;
 35 |     let usingPromptVariations = false;
 36 | 
 37 |     // Decide if we're using prompt variations
 38 |     if (prompt_variations && prompt_variations.length > 0) {
 39 |       usingPromptVariations = true;
 40 |       // If using prompt variations, number of variants is limited by available variations
 41 |       effectiveVariants = Math.min(num_variants, prompt_variations.length);
 42 |     }
 43 | 
 44 |     // Process all variants in parallel
 45 |     const generationPromises = Array.from(
 46 |       { length: effectiveVariants },
 47 |       (_, index) => {
 48 |         // If seed is provided, create deterministic variants by adding the index
 49 |         const variantSeed = seed !== undefined ? seed + index : undefined;
 50 | 
 51 |         // Determine which prompt to use for this variant
 52 |         let variantPrompt = prompt;
 53 |         if (usingPromptVariations) {
 54 |           const variation = prompt_variations![index];
 55 |           if (variation_mode === "append") {
 56 |             variantPrompt = `${prompt} ${variation}`;
 57 |           } else {
 58 |             // 'replace' mode
 59 |             variantPrompt = variation;
 60 |           }
 61 |         }
 62 | 
 63 |         return replicate
 64 |           .run(CONFIG.imageModelId, {
 65 |             input: {
 66 |               prompt: variantPrompt,
 67 |               seed: variantSeed,
 68 |               ...commonParams,
 69 |             },
 70 |           })
 71 |           .then((outputs) => {
 72 |             const [output] = outputs as [FileOutput];
 73 |             const imageUrl = output.url() as unknown as string;
 74 | 
 75 |             if (support_image_mcp_response_type) {
 76 |               return outputToBase64(output).then((imageBase64) => ({
 77 |                 variantIndex: index + 1,
 78 |                 imageUrl,
 79 |                 imageBase64,
 80 |                 usedPrompt: variantPrompt,
 81 |               }));
 82 |             }
 83 | 
 84 |             return {
 85 |               variantIndex: index + 1,
 86 |               imageUrl,
 87 |               usedPrompt: variantPrompt,
 88 |             };
 89 |           });
 90 |       }
 91 |     );
 92 | 
 93 |     // Wait for all variant generation to complete
 94 |     const results = (await Promise.all(
 95 |       generationPromises
 96 |     )) as ImageVariantResult[];
 97 | 
 98 |     // Build response content
 99 |     const responseContent: (TextContent | ImageContent)[] = [];
100 | 
101 |     // Add intro text - different based on whether we're using prompt variations
102 |     if (usingPromptVariations) {
103 |       responseContent.push({
104 |         type: "text",
105 |         text: `Generated ${results.length} variants of "${prompt}" using custom prompt variations (${variation_mode} mode)`,
106 |       } as TextContent);
107 |     } else {
108 |       responseContent.push({
109 |         type: "text",
110 |         text: `Generated ${results.length} variants of: "${prompt}" using seed variations`,
111 |       } as TextContent);
112 |     }
113 | 
114 |     // Add each variant with its index and prompt info
115 |     for (const result of results) {
116 |       // Build an appropriate description based on variant type
117 |       let variantDescription = `Variant #${result.variantIndex}`;
118 | 
119 |       if (usingPromptVariations) {
120 |         variantDescription += `\nPrompt: "${result.usedPrompt}"`;
121 |       } else if (seed !== undefined) {
122 |         variantDescription += ` (seed: ${seed + (result.variantIndex - 1)})`;
123 |       }
124 | 
125 |       variantDescription += `\nImage URL: ${result.imageUrl}`;
126 | 
127 |       responseContent.push({
128 |         type: "text",
129 |         text: `\n\n${variantDescription}`,
130 |       } as TextContent);
131 | 
132 |       if (support_image_mcp_response_type && result.imageBase64) {
133 |         responseContent.push({
134 |           type: "image",
135 |           data: result.imageBase64,
136 |           mimeType: `image/${
137 |             input.output_format === "jpg" ? "jpeg" : input.output_format
138 |           }`,
139 |         } as ImageContent);
140 |       }
141 |     }
142 | 
143 |     return {
144 |       content: responseContent,
145 |     };
146 |   } catch (error) {
147 |     handleError(error);
148 |   }
149 | };
150 | 
```

--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | 
  3 | export const createPredictionSchema = {
  4 |   prompt: z.string().min(1).describe("Prompt for generated image"),
  5 |   seed: z
  6 |     .number()
  7 |     .int()
  8 |     .optional()
  9 |     .describe("Random seed. Set for reproducible generation"),
 10 |   go_fast: z
 11 |     .boolean()
 12 |     .default(true)
 13 |     .describe(
 14 |       "Run faster predictions with model optimized for speed (currently fp8 quantized); disable to run in original bf16"
 15 |     ),
 16 |   megapixels: z
 17 |     .enum(["1", "0.25"])
 18 |     .default("1")
 19 |     .describe("Approximate number of megapixels for generated image"),
 20 |   num_outputs: z
 21 |     .number()
 22 |     .int()
 23 |     .min(1)
 24 |     .max(4)
 25 |     .default(1)
 26 |     .describe("Number of outputs to generate"),
 27 |   aspect_ratio: z
 28 |     .enum([
 29 |       "1:1",
 30 |       "16:9",
 31 |       "21:9",
 32 |       "3:2",
 33 |       "2:3",
 34 |       "4:5",
 35 |       "5:4",
 36 |       "3:4",
 37 |       "4:3",
 38 |       "9:16",
 39 |       "9:21",
 40 |     ])
 41 |     .default("1:1")
 42 |     .describe("Aspect ratio for the generated image"),
 43 |   output_format: z
 44 |     .enum(["webp", "jpg", "png"])
 45 |     .default("webp")
 46 |     .describe("Format of the output images"),
 47 |   output_quality: z
 48 |     .number()
 49 |     .int()
 50 |     .min(0)
 51 |     .max(100)
 52 |     .default(80)
 53 |     .describe(
 54 |       "Quality when saving the output images, from 0 to 100. 100 is best quality, 0 is lowest quality. Not relevant for .png outputs"
 55 |     ),
 56 |   num_inference_steps: z
 57 |     .number()
 58 |     .int()
 59 |     .min(1)
 60 |     .max(4)
 61 |     .default(4)
 62 |     .describe(
 63 |       "Number of denoising steps. 4 is recommended, and lower number of steps produce lower quality outputs, faster."
 64 |     ),
 65 |   disable_safety_checker: z
 66 |     .boolean()
 67 |     .default(false)
 68 |     .describe("Disable safety checker for generated images."),
 69 | };
 70 | const createPredictionObjectSchema = z.object(createPredictionSchema);
 71 | export type CreatePredictionParams = z.infer<
 72 |   typeof createPredictionObjectSchema
 73 | >;
 74 | 
 75 | export const imageGenerationSchema = {
 76 |   ...createPredictionSchema,
 77 |   support_image_mcp_response_type: z
 78 |     .boolean()
 79 |     .default(true)
 80 |     .describe(
 81 |       "Disable if the image type is not supported in the response, if it's Cursor app for example"
 82 |     ),
 83 | };
 84 | const imageGenerationObjectSchema = z.object(imageGenerationSchema);
 85 | export type ImageGenerationParams = z.infer<typeof imageGenerationObjectSchema>;
 86 | 
 87 | export const svgGenerationSchema = {
 88 |   prompt: z.string().min(1).describe("Prompt for generated SVG"),
 89 |   size: z
 90 |     .enum([
 91 |       "1024x1024",
 92 |       "1365x1024",
 93 |       "1024x1365",
 94 |       "1536x1024",
 95 |       "1024x1536",
 96 |       "1820x1024",
 97 |       "1024x1820",
 98 |       "1024x2048",
 99 |       "2048x1024",
100 |       "1434x1024",
101 |       "1024x1434",
102 |       "1024x1280",
103 |       "1280x1024",
104 |       "1024x1707",
105 |       "1707x1024",
106 |     ])
107 |     .default("1024x1024")
108 |     .describe("Size of the generated SVG"),
109 |   style: z
110 |     .enum(["any", "engraving", "line_art", "line_circuit", "linocut"])
111 |     .default("any")
112 |     .describe("Style of the generated image."),
113 | };
114 | const svgGenerationObjectSchema = z.object(svgGenerationSchema);
115 | export type SvgGenerationParams = z.infer<typeof svgGenerationObjectSchema>;
116 | 
117 | export const predictionListSchema = {
118 |   limit: z
119 |     .number()
120 |     .int()
121 |     .min(1)
122 |     .max(100)
123 |     .default(50)
124 |     .describe("Maximum number of predictions to return"),
125 | };
126 | const predictionListObjectSchema = z.object(predictionListSchema);
127 | export type PredictionListParams = z.infer<typeof predictionListObjectSchema>;
128 | 
129 | export const getPredictionSchema = {
130 |   predictionId: z.string().min(1).describe("ID of the prediction to retrieve"),
131 | };
132 | const getPredictionObjectSchema = z.object(getPredictionSchema);
133 | export type GetPredictionParams = z.infer<typeof getPredictionObjectSchema>;
134 | 
135 | export const multiImageGenerationSchema = {
136 |   prompts: z
137 |     .array(z.string().min(1))
138 |     .min(1)
139 |     .max(10)
140 |     .describe("Array of text descriptions for the images to generate"),
141 |   seed: z
142 |     .number()
143 |     .int()
144 |     .optional()
145 |     .describe("Random seed. Set for reproducible generation"),
146 |   go_fast: z
147 |     .boolean()
148 |     .default(true)
149 |     .describe(
150 |       "Run faster predictions with model optimized for speed (currently fp8 quantized); disable to run in original bf16"
151 |     ),
152 |   megapixels: z
153 |     .enum(["1", "0.25"])
154 |     .default("1")
155 |     .describe("Approximate number of megapixels for generated image"),
156 |   aspect_ratio: z
157 |     .enum([
158 |       "1:1",
159 |       "16:9",
160 |       "21:9",
161 |       "3:2",
162 |       "2:3",
163 |       "4:5",
164 |       "5:4",
165 |       "3:4",
166 |       "4:3",
167 |       "9:16",
168 |       "9:21",
169 |     ])
170 |     .default("1:1")
171 |     .describe("Aspect ratio for the generated image"),
172 |   output_format: z
173 |     .enum(["webp", "jpg", "png"])
174 |     .default("webp")
175 |     .describe("Format of the output images"),
176 |   output_quality: z
177 |     .number()
178 |     .int()
179 |     .min(0)
180 |     .max(100)
181 |     .default(80)
182 |     .describe(
183 |       "Quality when saving the output images, from 0 to 100. 100 is best quality, 0 is lowest quality. Not relevant for .png outputs"
184 |     ),
185 |   num_inference_steps: z
186 |     .number()
187 |     .int()
188 |     .min(1)
189 |     .max(4)
190 |     .default(4)
191 |     .describe(
192 |       "Number of denoising steps. 4 is recommended, and lower number of steps produce lower quality outputs, faster."
193 |     ),
194 |   disable_safety_checker: z
195 |     .boolean()
196 |     .default(false)
197 |     .describe("Disable safety checker for generated images."),
198 |   support_image_mcp_response_type: z
199 |     .boolean()
200 |     .default(true)
201 |     .describe(
202 |       "Disable if the image type is not supported in the response, if it's Cursor app for example"
203 |     ),
204 | };
205 | const multiImageGenerationObjectSchema = z.object(multiImageGenerationSchema);
206 | export type MultiImageGenerationParams = z.infer<
207 |   typeof multiImageGenerationObjectSchema
208 | >;
209 | 
210 | export const imageVariantsGenerationSchema = {
211 |   prompt: z
212 |     .string()
213 |     .min(1)
214 |     .describe("Text description for the image to generate variants of"),
215 |   num_variants: z
216 |     .number()
217 |     .int()
218 |     .min(2)
219 |     .max(10)
220 |     .default(4)
221 |     .describe("Number of image variants to generate (2-10)"),
222 |   prompt_variations: z
223 |     .array(z.string())
224 |     .optional()
225 |     .describe(
226 |       "Optional list of prompt modifiers to apply to variants (e.g., ['in watercolor style', 'in oil painting style']). If provided, these will be used instead of random seeds."
227 |     ),
228 |   variation_mode: z
229 |     .enum(["append", "replace"])
230 |     .default("append")
231 |     .describe(
232 |       "How to apply prompt variations: 'append' adds to the base prompt, 'replace' uses variations as standalone prompts"
233 |     ),
234 |   seed: z
235 |     .number()
236 |     .int()
237 |     .optional()
238 |     .describe(
239 |       "Base random seed. Each variant will use seed+variant_index for reproducibility"
240 |     ),
241 |   go_fast: z
242 |     .boolean()
243 |     .default(true)
244 |     .describe(
245 |       "Run faster predictions with model optimized for speed (currently fp8 quantized); disable to run in original bf16"
246 |     ),
247 |   megapixels: z
248 |     .enum(["1", "0.25"])
249 |     .default("1")
250 |     .describe("Approximate number of megapixels for generated image"),
251 |   aspect_ratio: z
252 |     .enum([
253 |       "1:1",
254 |       "16:9",
255 |       "21:9",
256 |       "3:2",
257 |       "2:3",
258 |       "4:5",
259 |       "5:4",
260 |       "3:4",
261 |       "4:3",
262 |       "9:16",
263 |       "9:21",
264 |     ])
265 |     .default("1:1")
266 |     .describe("Aspect ratio for the generated image"),
267 |   output_format: z
268 |     .enum(["webp", "jpg", "png"])
269 |     .default("webp")
270 |     .describe("Format of the output images"),
271 |   output_quality: z
272 |     .number()
273 |     .int()
274 |     .min(0)
275 |     .max(100)
276 |     .default(80)
277 |     .describe(
278 |       "Quality when saving the output images, from 0 to 100. 100 is best quality, 0 is lowest quality. Not relevant for .png outputs"
279 |     ),
280 |   num_inference_steps: z
281 |     .number()
282 |     .int()
283 |     .min(1)
284 |     .max(4)
285 |     .default(4)
286 |     .describe(
287 |       "Number of denoising steps. 4 is recommended, and lower number of steps produce lower quality outputs, faster."
288 |     ),
289 |   disable_safety_checker: z
290 |     .boolean()
291 |     .default(false)
292 |     .describe("Disable safety checker for generated images."),
293 |   support_image_mcp_response_type: z
294 |     .boolean()
295 |     .default(true)
296 |     .describe("Support image MCP response type on client side"),
297 | };
298 | const imageVariantsGenerationObjectSchema = z.object(
299 |   imageVariantsGenerationSchema
300 | );
301 | export type ImageVariantsGenerationParams = z.infer<
302 |   typeof imageVariantsGenerationObjectSchema
303 | >;
304 | 
```