@flownex-ai/mcp-gpt-image-2 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "mcp-gpt-image-2-plugins",
3
+ "description": "FlowNex AI plugins for Claude Code and Claude Cowork — image generation, presentation imagery, and more.",
4
+ "owner": { "name": "FlowNex AI", "url": "https://flownexai.com" },
5
+ "plugins": [{
6
+ "name": "gpt-image-2",
7
+ "source": { "source": "npm", "package": "@flownex-ai/mcp-gpt-image-2" },
8
+ "description": "Image generation with OpenAI gpt-image-2 via MCP",
9
+ "version": "0.1.0"
10
+ }]
11
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
+ "name": "gpt-image-2",
4
+ "description": "Image generation with OpenAI gpt-image-2 — works in Claude Code and Claude Cowork",
5
+ "version": "0.1.0",
6
+ "author": { "name": "FlowNex AI", "url": "https://flownexai.com" },
7
+ "homepage": "https://github.com/FlowNex-AI/gpt-image-2-mcp",
8
+ "repository": "https://github.com/FlowNex-AI/gpt-image-2-mcp",
9
+ "license": "MIT",
10
+ "keywords": ["openai", "gpt-image-2", "image-generation", "mcp", "cowork", "claude-code"],
11
+ "userConfig": {
12
+ "OPENAI_API_KEY": {
13
+ "type": "string",
14
+ "title": "OpenAI API key",
15
+ "description": "OpenAI API key with gpt-image-2 access. Get one at https://platform.openai.com/api-keys. Your organization must be verified to use the gpt-image family.",
16
+ "sensitive": true,
17
+ "required": true
18
+ },
19
+ "OPENAI_IMAGE_MODEL": {
20
+ "type": "string",
21
+ "title": "Model ID (optional)",
22
+ "description": "Override the model. Leave blank to use gpt-image-2.",
23
+ "default": "gpt-image-2",
24
+ "required": false
25
+ },
26
+ "MCP_GPT_IMAGE_2_OUTPUT_DIR": {
27
+ "type": "directory",
28
+ "title": "Output directory (optional)",
29
+ "description": "Where to save generated images. Defaults to ./generated_imgs in the current working directory.",
30
+ "required": false
31
+ },
32
+ "MCP_GPT_IMAGE_2_INLINE_IMAGE": {
33
+ "type": "string",
34
+ "title": "Return inline image data by default (optional)",
35
+ "description": "Set to \"false\" to skip embedding base64 image data in tool responses (saves context window). Defaults to \"true\".",
36
+ "default": "true",
37
+ "required": false
38
+ }
39
+ }
40
+ }
package/.mcp.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "gpt-image-2": {
4
+ "command": "node",
5
+ "args": ["${CLAUDE_PLUGIN_ROOT}/dist/index.js"],
6
+ "env": {
7
+ "OPENAI_API_KEY": "${user_config.OPENAI_API_KEY}",
8
+ "OPENAI_IMAGE_MODEL": "${user_config.OPENAI_IMAGE_MODEL}",
9
+ "MCP_GPT_IMAGE_2_OUTPUT_DIR": "${user_config.MCP_GPT_IMAGE_2_OUTPUT_DIR}",
10
+ "MCP_GPT_IMAGE_2_INLINE_IMAGE": "${user_config.MCP_GPT_IMAGE_2_INLINE_IMAGE}"
11
+ }
12
+ }
13
+ }
14
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 daveremy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # mcp-gpt-image-2
2
+
3
+ MCP server for **OpenAI's `gpt-image-2`** image generation model. Distributed as a Claude plugin that works in **Claude Code** and **Claude Cowork**.
4
+
5
+ A fork of [daveremy/nano-banana-2-mcp](https://github.com/daveremy/nano-banana-2-mcp) (originally based on [ConechoAI/Nano-Banana-MCP](https://github.com/ConechoAI/Nano-Banana-MCP)), rewritten to target OpenAI's gpt-image-2 instead of Google Gemini.
6
+
7
+ Features:
8
+
9
+ - **gpt-image-2 model** — high-fidelity photorealism, ~99% text accuracy (incl. CJK), strong instruction following
10
+ - **Size control** — official presets (1024x1024, 1536x1024, 1024x1536, 2048x2048, auto) and custom `WxH` (multiples of 16, max edge 3840, ratio ≤ 3:1)
11
+ - **Quality modes** — `low`, `medium`, `high`, `auto`
12
+ - **Output formats** — PNG, JPEG, WebP
13
+ - **Multi-image edits** — up to 16 reference images per call, plus optional mask
14
+ - **Multiple variations** — generate 1–4 images per call (`n`)
15
+ - **File-path-only mode** — no inline base64, fixes context window overflow in Claude Code
16
+ - **Security hardening** — path validation, file size caps, no plaintext API key storage
17
+
18
+ ## Setup
19
+
20
+ ### 1. Get an OpenAI API key
21
+
22
+ Get one from [OpenAI Platform](https://platform.openai.com/api-keys). OpenAI gates the gpt-image family behind organization verification — complete it in the developer console if you hit a 403.
23
+
24
+ ### 2. Install
25
+
26
+ ### A) In Claude Code (recommended)
27
+
28
+ ```bash
29
+ # 1. Add the marketplace (pulls .claude-plugin/marketplace.json from this repo)
30
+ claude plugin marketplace add FlowNex-AI/gpt-image-2-mcp
31
+
32
+ # 2. Install the plugin from that marketplace
33
+ claude plugin install gpt-image-2@mcp-gpt-image-2-plugins
34
+ ```
35
+
36
+ The plugin prompts for your `OPENAI_API_KEY` on install (declared via `userConfig` in `plugin.json`, stored in your system keychain). The bundled MCP server (`dist/index.js`) runs from the cloned repo — no npm publish needed.
37
+
38
+ Alternative: inside Claude Code, run `/plugin` for an interactive picker.
39
+
40
+ ### B) In Claude Cowork
41
+
42
+ Cowork reads the same plugin format:
43
+
44
+ 1. Open Cowork → **Browse plugins** → **Upload custom plugin**, and point it at this repo (or its zipped release). Cowork picks up `.claude-plugin/plugin.json`, the bundled MCP server in `.mcp.json`, and the skills under `skills/`.
45
+ 2. When prompted, paste your `OPENAI_API_KEY` (the same `userConfig` prompt as in Code).
46
+ 3. The `generate-image` and `powerpoint-images` skills become available — try *"make me a hero image for slide 1 of my QBR deck"*.
47
+
48
+ For org-wide MDM deployments, drop this repo into the org-plugins directory documented in the Cowork enterprise admin guide.
49
+
50
+ ### C) Manual MCP config (skip the plugin)
51
+
52
+ If you prefer to wire the MCP server directly into Claude Code, Cowork, or any MCP-compatible client (Cursor, Zed, etc.), add this to your MCP config:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "gpt-image-2": {
58
+ "command": "npx",
59
+ "args": ["-y", "@flownex-ai/mcp-gpt-image-2"],
60
+ "env": {
61
+ "OPENAI_API_KEY": "your-api-key-here"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### D) From source (development)
69
+
70
+ ```bash
71
+ git clone https://github.com/FlowNex-AI/gpt-image-2-mcp.git
72
+ cd gpt-image-2-mcp
73
+ npm install
74
+ npm run build
75
+ ```
76
+
77
+ Then point your MCP config at `dist/index.js`:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "gpt-image-2": {
83
+ "command": "node",
84
+ "args": ["/path/to/gpt-image-2-mcp/dist/index.js"],
85
+ "env": {
86
+ "OPENAI_API_KEY": "your-api-key-here"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### 3. Restart Claude Code
94
+
95
+ The tools will be available after restart.
96
+
97
+ ## Tools
98
+
99
+ ### `generate_image`
100
+
101
+ Generate a new image from a text prompt.
102
+
103
+ | Parameter | Type | Default | Description |
104
+ |-----------|------|---------|-------------|
105
+ | `prompt` | string | (required) | Text prompt for the image |
106
+ | `size` | string | `"1024x1024"` | Preset (`1024x1024`, `1536x1024`, `1024x1536`, `2048x2048`, `auto`) or custom `WxH` |
107
+ | `quality` | string | `"auto"` | `low`, `medium`, `high`, or `auto` |
108
+ | `numberOfImages` | number | `1` | 1–4 |
109
+ | `outputFormat` | string | `"png"` | `png`, `jpeg`, or `webp` |
110
+ | `background` | string | `"auto"` | `auto` or `opaque` (transparent not supported) |
111
+ | `returnInlineImage` | boolean | `true` | If false, return only file path |
112
+
113
+ ### `edit_image`
114
+
115
+ Edit an existing image file. Same parameters as `generate_image`, plus:
116
+
117
+ | Parameter | Type | Description |
118
+ |-----------|------|-------------|
119
+ | `imagePath` | string | (required) Path to the image to edit |
120
+ | `referenceImages` | string[] | Optional additional reference images (up to 15 more, 16 total) |
121
+ | `mask` | string | Optional PNG mask path; transparent pixels mark the editable area |
122
+
123
+ ### `continue_editing`
124
+
125
+ Continue editing the last generated/edited image. Same parameters as `edit_image` minus `imagePath` (uses the last image automatically).
126
+
127
+ ### `get_configuration_status`
128
+
129
+ Check API key, active model, and settings.
130
+
131
+ ### `get_last_image_info`
132
+
133
+ Get path and size of the last generated image.
134
+
135
+ ## Environment Variables
136
+
137
+ | Variable | Default | Description |
138
+ |----------|---------|-------------|
139
+ | `OPENAI_API_KEY` | (required) | OpenAI API key |
140
+ | `OPENAI_IMAGE_MODEL` | `gpt-image-2` | Model ID override (e.g. `gpt-image-2-2026-04-21`, `gpt-image-1`) |
141
+ | `MCP_GPT_IMAGE_2_OUTPUT_DIR` | `./generated_imgs` | Image save directory |
142
+ | `MCP_GPT_IMAGE_2_INLINE_IMAGE` | `true` | Default for `returnInlineImage` |
143
+
144
+ ## Size Constraints
145
+
146
+ When using a custom `WxH` size, gpt-image-2 requires:
147
+
148
+ - Both edges are multiples of **16**
149
+ - Max single edge: **3840px**
150
+ - Total pixels: **655,360 – 8,294,400**
151
+ - Long-edge to short-edge ratio: **≤ 3:1**
152
+
153
+ The server validates these before calling the API.
154
+
155
+ ## Plugin (Claude Code + Cowork)
156
+
157
+ This repo includes a unified Claude plugin that works in both **Claude Code** and **Claude Cowork** (same `.claude-plugin/plugin.json` schema). It ships:
158
+
159
+ - An MCP server (`.mcp.json` declares the `gpt-image-2` server, bundled `dist/index.js`)
160
+ - A `userConfig` block that prompts for `OPENAI_API_KEY` on install (stored in the system keychain, not plain text)
161
+ - Two skills:
162
+ - `generate-image` — best-practice prompting for general image generation
163
+ - `powerpoint-images` — guidance tailored to PowerPoint / Keynote / Google Slides decks (a natural fit for Cowork knowledge workers)
164
+
165
+ See the install instructions above for both Claude Code and Cowork.
166
+
167
+ ## Contributing
168
+
169
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and release process.
170
+
171
+ ## Attribution
172
+
173
+ Based on [daveremy/nano-banana-2-mcp](https://github.com/daveremy/nano-banana-2-mcp), which is based on [ConechoAI/Nano-Banana-MCP](https://github.com/ConechoAI/Nano-Banana-MCP) (MIT License).
174
+
175
+ ## License
176
+
177
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,522 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
5
+ import OpenAI, { toFile } from "openai";
6
+ import fs from "fs/promises";
7
+ import { createReadStream } from "fs";
8
+ import path from "path";
9
+ import { VERSION } from "./version.js";
10
+ // ---------------------------------------------------------------------------
11
+ // Constants
12
+ // ---------------------------------------------------------------------------
13
+ const DEFAULT_MODEL = "gpt-image-2";
14
+ const SIZE_PRESETS = new Set([
15
+ "1024x1024",
16
+ "1536x1024",
17
+ "1024x1536",
18
+ "2048x2048",
19
+ "auto",
20
+ ]);
21
+ const SIZE_PATTERN = /^(\d+)x(\d+)$/;
22
+ const MIN_TOTAL_PIXELS = 655_360;
23
+ const MAX_TOTAL_PIXELS = 8_294_400;
24
+ const MAX_EDGE_PX = 3840;
25
+ const VALID_QUALITY = new Set(["low", "medium", "high", "auto"]);
26
+ const VALID_OUTPUT_FORMATS = new Set(["png", "jpeg", "webp"]);
27
+ const VALID_BACKGROUNDS = new Set(["opaque", "auto"]);
28
+ const ALLOWED_IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".webp"]);
29
+ const MAX_IMAGE_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
30
+ const MAX_NUMBER_OF_IMAGES = 4;
31
+ const MAX_REFERENCE_IMAGES = 16;
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+ function getModelId() {
36
+ return process.env.OPENAI_IMAGE_MODEL || DEFAULT_MODEL;
37
+ }
38
+ function getOutputDir() {
39
+ return process.env.MCP_GPT_IMAGE_2_OUTPUT_DIR || path.join(process.cwd(), "generated_imgs");
40
+ }
41
+ function resolveInlineImage(perCall) {
42
+ if (perCall !== undefined)
43
+ return perCall;
44
+ const env = process.env.MCP_GPT_IMAGE_2_INLINE_IMAGE;
45
+ if (env !== undefined)
46
+ return env === "true";
47
+ return true; // default
48
+ }
49
+ function extensionToMime(ext) {
50
+ if (ext === ".webp")
51
+ return "image/webp";
52
+ if (ext === ".png")
53
+ return "image/png";
54
+ return "image/jpeg";
55
+ }
56
+ function outputFormatToMime(format) {
57
+ if (format === "jpeg")
58
+ return "image/jpeg";
59
+ if (format === "webp")
60
+ return "image/webp";
61
+ return "image/png";
62
+ }
63
+ function randomId() {
64
+ return Math.random().toString(36).slice(2, 8);
65
+ }
66
+ function timestamp() {
67
+ return new Date().toISOString().replace(/[:.]/g, "-");
68
+ }
69
+ function formatBytes(bytes) {
70
+ if (bytes < 1024)
71
+ return `${bytes} B`;
72
+ if (bytes < 1024 * 1024)
73
+ return `${(bytes / 1024).toFixed(1)} KB`;
74
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
75
+ }
76
+ function validateSize(size) {
77
+ if (SIZE_PRESETS.has(size))
78
+ return;
79
+ const match = SIZE_PATTERN.exec(size);
80
+ if (!match) {
81
+ throw new McpError(ErrorCode.InvalidParams, `Invalid size "${size}". Use a preset (${[...SIZE_PRESETS].join(", ")}) or custom WxH like "1280x720".`);
82
+ }
83
+ const w = Number(match[1]);
84
+ const h = Number(match[2]);
85
+ if (w % 16 !== 0 || h % 16 !== 0) {
86
+ throw new McpError(ErrorCode.InvalidParams, `Custom size "${size}" must have both edges as multiples of 16.`);
87
+ }
88
+ if (w > MAX_EDGE_PX || h > MAX_EDGE_PX) {
89
+ throw new McpError(ErrorCode.InvalidParams, `Custom size "${size}" max edge is ${MAX_EDGE_PX}px.`);
90
+ }
91
+ const total = w * h;
92
+ if (total < MIN_TOTAL_PIXELS || total > MAX_TOTAL_PIXELS) {
93
+ throw new McpError(ErrorCode.InvalidParams, `Custom size "${size}" total pixels (${total}) must be between ${MIN_TOTAL_PIXELS} and ${MAX_TOTAL_PIXELS}.`);
94
+ }
95
+ const ratio = Math.max(w, h) / Math.min(w, h);
96
+ if (ratio > 3) {
97
+ throw new McpError(ErrorCode.InvalidParams, `Custom size "${size}" long-edge to short-edge ratio (${ratio.toFixed(2)}) must not exceed 3:1.`);
98
+ }
99
+ }
100
+ async function validateImagePath(filePath) {
101
+ const ext = path.extname(filePath).toLowerCase();
102
+ if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
103
+ throw new McpError(ErrorCode.InvalidParams, `Invalid image extension "${ext}". Allowed: ${[...ALLOWED_IMAGE_EXTENSIONS].join(", ")}`);
104
+ }
105
+ let stat;
106
+ try {
107
+ stat = await fs.stat(filePath);
108
+ }
109
+ catch {
110
+ throw new McpError(ErrorCode.InvalidParams, `File not found: ${filePath}`);
111
+ }
112
+ if (!stat.isFile()) {
113
+ throw new McpError(ErrorCode.InvalidParams, `Not a file: ${filePath}`);
114
+ }
115
+ if (stat.size > MAX_IMAGE_FILE_SIZE) {
116
+ throw new McpError(ErrorCode.InvalidParams, `File too large (${formatBytes(stat.size)}). Max: ${formatBytes(MAX_IMAGE_FILE_SIZE)}`);
117
+ }
118
+ }
119
+ // ---------------------------------------------------------------------------
120
+ // Common image generation parameters (shared schema)
121
+ // ---------------------------------------------------------------------------
122
+ const imageParamProperties = {
123
+ size: {
124
+ type: "string",
125
+ description: "Image dimensions. Presets: \"1024x1024\" (square), \"1536x1024\" (landscape), \"1024x1536\" (portrait), \"2048x2048\" (square hi-res), \"auto\". Custom \"WxH\" also accepted: edges multiples of 16, max edge 3840px, total pixels 655,360–8,294,400, ratio ≤ 3:1.",
126
+ default: "1024x1024",
127
+ },
128
+ quality: {
129
+ type: "string",
130
+ description: "Rendering quality: \"low\", \"medium\", \"high\", or \"auto\". Higher quality increases latency and cost.",
131
+ default: "auto",
132
+ },
133
+ numberOfImages: {
134
+ type: "number",
135
+ description: "Number of images to generate (1–4).",
136
+ default: 1,
137
+ },
138
+ outputFormat: {
139
+ type: "string",
140
+ description: "Output file format: \"png\" (default), \"jpeg\", or \"webp\".",
141
+ default: "png",
142
+ },
143
+ background: {
144
+ type: "string",
145
+ description: "Background handling: \"opaque\" or \"auto\". Transparent backgrounds are not supported by gpt-image-2.",
146
+ default: "auto",
147
+ },
148
+ returnInlineImage: {
149
+ type: "boolean",
150
+ description: "If false, return only file path (no base64). Saves context window space.",
151
+ },
152
+ };
153
+ // ---------------------------------------------------------------------------
154
+ // Server
155
+ // ---------------------------------------------------------------------------
156
+ class GptImage2MCP {
157
+ server;
158
+ openai = null;
159
+ lastImagePath = null;
160
+ constructor() {
161
+ this.server = new Server({ name: "gpt-image-2", version: VERSION }, { capabilities: { tools: {} } });
162
+ this.setupHandlers();
163
+ }
164
+ // -------------------------------------------------------------------------
165
+ // Init
166
+ // -------------------------------------------------------------------------
167
+ initOpenAI() {
168
+ if (this.openai)
169
+ return this.openai;
170
+ const apiKey = process.env.OPENAI_API_KEY;
171
+ if (!apiKey) {
172
+ throw new McpError(ErrorCode.InvalidRequest, "OPENAI_API_KEY environment variable is required. Set it in your MCP server config.");
173
+ }
174
+ this.openai = new OpenAI({ apiKey });
175
+ return this.openai;
176
+ }
177
+ // -------------------------------------------------------------------------
178
+ // Tool definitions
179
+ // -------------------------------------------------------------------------
180
+ setupHandlers() {
181
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
182
+ tools: [
183
+ {
184
+ name: "generate_image",
185
+ description: "Generate a NEW image from text prompt using OpenAI gpt-image-2. Use this ONLY when creating a completely new image, not when modifying an existing one.",
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ prompt: {
190
+ type: "string",
191
+ description: "Text prompt describing the NEW image to create from scratch",
192
+ },
193
+ ...imageParamProperties,
194
+ },
195
+ required: ["prompt"],
196
+ },
197
+ },
198
+ {
199
+ name: "edit_image",
200
+ description: "Edit a SPECIFIC existing image file with OpenAI gpt-image-2, optionally using additional reference images. Use this when you have the exact file path of an image to modify.",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {
204
+ imagePath: {
205
+ type: "string",
206
+ description: "Full file path to the main image file to edit",
207
+ },
208
+ prompt: {
209
+ type: "string",
210
+ description: "Text describing the modifications to make to the existing image",
211
+ },
212
+ referenceImages: {
213
+ type: "array",
214
+ items: { type: "string" },
215
+ description: "Optional array of file paths to additional reference images (up to 15 in addition to the main image, 16 total)",
216
+ },
217
+ mask: {
218
+ type: "string",
219
+ description: "Optional file path to a PNG mask. Transparent pixels indicate the area to edit; must match the main image dimensions.",
220
+ },
221
+ ...imageParamProperties,
222
+ },
223
+ required: ["imagePath", "prompt"],
224
+ },
225
+ },
226
+ {
227
+ name: "continue_editing",
228
+ description: "Continue editing the LAST image that was generated or edited in this session, optionally using additional reference images.",
229
+ inputSchema: {
230
+ type: "object",
231
+ properties: {
232
+ prompt: {
233
+ type: "string",
234
+ description: "Text describing the modifications to the last image",
235
+ },
236
+ referenceImages: {
237
+ type: "array",
238
+ items: { type: "string" },
239
+ description: "Optional array of file paths to additional reference images",
240
+ },
241
+ mask: {
242
+ type: "string",
243
+ description: "Optional file path to a PNG mask matching the last image dimensions",
244
+ },
245
+ ...imageParamProperties,
246
+ },
247
+ required: ["prompt"],
248
+ },
249
+ },
250
+ {
251
+ name: "get_configuration_status",
252
+ description: "Check if OpenAI API key is configured and which model is active",
253
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
254
+ },
255
+ {
256
+ name: "get_last_image_info",
257
+ description: "Get information about the last generated/edited image in this session",
258
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
259
+ },
260
+ ],
261
+ }));
262
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
263
+ try {
264
+ switch (request.params.name) {
265
+ case "generate_image":
266
+ return await this.generateImage(request);
267
+ case "edit_image":
268
+ return await this.editImage(request);
269
+ case "continue_editing":
270
+ return await this.continueEditing(request);
271
+ case "get_configuration_status":
272
+ return this.getConfigurationStatus();
273
+ case "get_last_image_info":
274
+ return await this.getLastImageInfo();
275
+ default:
276
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
277
+ }
278
+ }
279
+ catch (error) {
280
+ if (error instanceof McpError)
281
+ throw error;
282
+ throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
283
+ }
284
+ });
285
+ }
286
+ // -------------------------------------------------------------------------
287
+ // Parameter extraction
288
+ // -------------------------------------------------------------------------
289
+ extractImageParams(args) {
290
+ const size = args.size || "1024x1024";
291
+ const quality = args.quality || "auto";
292
+ const outputFormat = (args.outputFormat || "png").toLowerCase();
293
+ const background = args.background || "auto";
294
+ const raw = Math.round(Number(args.numberOfImages) || 1);
295
+ const numberOfImages = Math.min(Math.max(raw, 1), MAX_NUMBER_OF_IMAGES);
296
+ const returnInlineImage = resolveInlineImage(args.returnInlineImage === undefined ? undefined : Boolean(args.returnInlineImage));
297
+ validateSize(size);
298
+ if (!VALID_QUALITY.has(quality)) {
299
+ throw new McpError(ErrorCode.InvalidParams, `Invalid quality "${quality}". Use: ${[...VALID_QUALITY].join(", ")}`);
300
+ }
301
+ if (!VALID_OUTPUT_FORMATS.has(outputFormat)) {
302
+ throw new McpError(ErrorCode.InvalidParams, `Invalid outputFormat "${outputFormat}". Use: ${[...VALID_OUTPUT_FORMATS].join(", ")}`);
303
+ }
304
+ if (!VALID_BACKGROUNDS.has(background)) {
305
+ throw new McpError(ErrorCode.InvalidParams, `Invalid background "${background}". Use: ${[...VALID_BACKGROUNDS].join(", ")}`);
306
+ }
307
+ return { size, quality, numberOfImages, outputFormat, background, returnInlineImage };
308
+ }
309
+ // -------------------------------------------------------------------------
310
+ // Image saving
311
+ // -------------------------------------------------------------------------
312
+ async ensureOutputDir() {
313
+ const dir = getOutputDir();
314
+ await fs.mkdir(dir, { recursive: true });
315
+ return dir;
316
+ }
317
+ async saveImage(base64Data, mimeType, prefix, suffix) {
318
+ const dir = await this.ensureOutputDir();
319
+ const ext = mimeType === "image/jpeg" ? ".jpg" : mimeType === "image/webp" ? ".webp" : ".png";
320
+ const name = `${prefix}-${timestamp()}-${randomId()}${suffix || ""}${ext}`;
321
+ const filePath = path.join(dir, name);
322
+ const buffer = Buffer.from(base64Data, "base64");
323
+ await fs.writeFile(filePath, buffer);
324
+ return { filePath, fileSize: buffer.length };
325
+ }
326
+ // -------------------------------------------------------------------------
327
+ // Response building
328
+ // -------------------------------------------------------------------------
329
+ buildResponse(savedImages, returnInlineImage) {
330
+ const content = [];
331
+ const lines = savedImages.map((img) => `${img.filePath} (${formatBytes(img.fileSize)})`);
332
+ const target = savedImages.length === 1 ? "this image" : "the first image";
333
+ lines.push(`Use continue_editing to refine ${target}.`);
334
+ content.push({ type: "text", text: lines.join("\n") });
335
+ if (returnInlineImage) {
336
+ for (const img of savedImages) {
337
+ content.push({
338
+ type: "image",
339
+ data: img.base64,
340
+ mimeType: img.mimeType,
341
+ });
342
+ }
343
+ }
344
+ return { content };
345
+ }
346
+ // -------------------------------------------------------------------------
347
+ // Tools: generate_image
348
+ // -------------------------------------------------------------------------
349
+ async generateImage(request) {
350
+ const args = request.params.arguments;
351
+ const prompt = args.prompt;
352
+ if (!prompt)
353
+ throw new McpError(ErrorCode.InvalidParams, "prompt is required");
354
+ const params = this.extractImageParams(args);
355
+ const openai = this.initOpenAI();
356
+ const modelId = getModelId();
357
+ const generateRequest = {
358
+ model: modelId,
359
+ prompt,
360
+ size: params.size,
361
+ quality: params.quality,
362
+ n: params.numberOfImages,
363
+ output_format: params.outputFormat,
364
+ background: params.background,
365
+ };
366
+ const response = await openai.images.generate(generateRequest);
367
+ const allSaved = await this.processResponse(response, "generated", params);
368
+ if (allSaved.length === 0) {
369
+ return { content: [{ type: "text", text: "No image was generated. Try rephrasing your prompt." }] };
370
+ }
371
+ this.lastImagePath = allSaved[0].filePath;
372
+ return this.buildResponse(allSaved, params.returnInlineImage);
373
+ }
374
+ // -------------------------------------------------------------------------
375
+ // Tools: edit_image
376
+ // -------------------------------------------------------------------------
377
+ async editImage(request) {
378
+ const args = request.params.arguments;
379
+ const imagePath = args.imagePath;
380
+ const prompt = args.prompt;
381
+ const referenceImages = args.referenceImages || [];
382
+ const maskPath = args.mask;
383
+ if (!imagePath)
384
+ throw new McpError(ErrorCode.InvalidParams, "imagePath is required");
385
+ if (!prompt)
386
+ throw new McpError(ErrorCode.InvalidParams, "prompt is required");
387
+ const allPaths = [imagePath, ...referenceImages];
388
+ if (allPaths.length > MAX_REFERENCE_IMAGES) {
389
+ throw new McpError(ErrorCode.InvalidParams, `Too many images (${allPaths.length}). Max: ${MAX_REFERENCE_IMAGES} total (main + references).`);
390
+ }
391
+ for (const p of allPaths) {
392
+ await validateImagePath(p);
393
+ }
394
+ if (maskPath)
395
+ await validateImagePath(maskPath);
396
+ const params = this.extractImageParams(args);
397
+ const openai = this.initOpenAI();
398
+ const modelId = getModelId();
399
+ const imageFiles = await Promise.all(allPaths.map(async (p) => {
400
+ const ext = path.extname(p).toLowerCase();
401
+ const mime = extensionToMime(ext);
402
+ return await toFile(createReadStream(p), path.basename(p), { type: mime });
403
+ }));
404
+ const editRequest = {
405
+ model: modelId,
406
+ image: imageFiles,
407
+ prompt,
408
+ size: params.size,
409
+ quality: params.quality,
410
+ n: params.numberOfImages,
411
+ output_format: params.outputFormat,
412
+ background: params.background,
413
+ };
414
+ if (maskPath) {
415
+ const maskMime = extensionToMime(path.extname(maskPath).toLowerCase());
416
+ editRequest.mask = await toFile(createReadStream(maskPath), path.basename(maskPath), {
417
+ type: maskMime,
418
+ });
419
+ }
420
+ const response = await openai.images.edit(editRequest);
421
+ const saved = await this.processResponse(response, "edited", params);
422
+ if (saved.length === 0) {
423
+ return { content: [{ type: "text", text: "No edited image was produced. Try a different prompt." }] };
424
+ }
425
+ this.lastImagePath = saved[0].filePath;
426
+ return this.buildResponse(saved, params.returnInlineImage);
427
+ }
428
+ // -------------------------------------------------------------------------
429
+ // Tools: continue_editing
430
+ // -------------------------------------------------------------------------
431
+ async continueEditing(request) {
432
+ if (!this.lastImagePath) {
433
+ throw new McpError(ErrorCode.InvalidRequest, "No previous image in this session. Use generate_image or edit_image first.");
434
+ }
435
+ const args = request.params.arguments;
436
+ const editArgs = { ...args, imagePath: this.lastImagePath };
437
+ const editRequest = {
438
+ ...request,
439
+ params: { ...request.params, arguments: editArgs },
440
+ };
441
+ return this.editImage(editRequest);
442
+ }
443
+ // -------------------------------------------------------------------------
444
+ // Tools: get_configuration_status
445
+ // -------------------------------------------------------------------------
446
+ getConfigurationStatus() {
447
+ const hasKey = !!process.env.OPENAI_API_KEY;
448
+ const modelId = getModelId();
449
+ const lines = [
450
+ `API key: ${hasKey ? "configured" : "NOT configured — set OPENAI_API_KEY in MCP server env"}`,
451
+ `Model: ${modelId}`,
452
+ `Output dir: ${getOutputDir()}`,
453
+ `Inline images: ${resolveInlineImage(undefined)}`,
454
+ ];
455
+ return { content: [{ type: "text", text: lines.join("\n") }] };
456
+ }
457
+ // -------------------------------------------------------------------------
458
+ // Tools: get_last_image_info
459
+ // -------------------------------------------------------------------------
460
+ async getLastImageInfo() {
461
+ if (!this.lastImagePath) {
462
+ return { content: [{ type: "text", text: "No image generated in this session yet." }] };
463
+ }
464
+ try {
465
+ const stat = await fs.stat(this.lastImagePath);
466
+ return {
467
+ content: [
468
+ {
469
+ type: "text",
470
+ text: `Last image: ${this.lastImagePath}\nSize: ${formatBytes(stat.size)}`,
471
+ },
472
+ ],
473
+ };
474
+ }
475
+ catch {
476
+ return {
477
+ content: [
478
+ {
479
+ type: "text",
480
+ text: `Last image path recorded: ${this.lastImagePath}\n(File may have been moved or deleted)`,
481
+ },
482
+ ],
483
+ };
484
+ }
485
+ }
486
+ // -------------------------------------------------------------------------
487
+ // Process OpenAI response → saved images
488
+ // -------------------------------------------------------------------------
489
+ async processResponse(response, prefix, params) {
490
+ const items = response.data || [];
491
+ const mimeType = outputFormatToMime(params.outputFormat);
492
+ const saved = [];
493
+ for (let i = 0; i < items.length; i++) {
494
+ const item = items[i];
495
+ let base64 = item.b64_json;
496
+ if (!base64 && item.url) {
497
+ const res = await fetch(item.url);
498
+ if (!res.ok) {
499
+ throw new McpError(ErrorCode.InternalError, `Failed to download image from ${item.url}`);
500
+ }
501
+ const buf = Buffer.from(await res.arrayBuffer());
502
+ base64 = buf.toString("base64");
503
+ }
504
+ if (!base64)
505
+ continue;
506
+ const suffix = items.length > 1 ? `-${i + 1}` : "";
507
+ const { filePath, fileSize } = await this.saveImage(base64, mimeType, prefix, suffix);
508
+ saved.push({ filePath, fileSize, base64, mimeType });
509
+ }
510
+ return saved;
511
+ }
512
+ // -------------------------------------------------------------------------
513
+ // Run
514
+ // -------------------------------------------------------------------------
515
+ async run() {
516
+ const transport = new StdioServerTransport();
517
+ await this.server.connect(transport);
518
+ }
519
+ }
520
+ const server = new GptImage2MCP();
521
+ server.run().catch(console.error);
522
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.1.0";
@@ -0,0 +1,2 @@
1
+ export const VERSION = "0.1.0";
2
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@flownex-ai/mcp-gpt-image-2",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for OpenAI's gpt-image-2 image generation with size/quality control, multi-image support, and context-window-safe output",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "mcp-gpt-image-2": "dist/index.js"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "files": [
14
+ "dist/",
15
+ ".claude-plugin/",
16
+ ".mcp.json",
17
+ "skills/",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "prepack": "npm run build",
24
+ "start": "node dist/index.js",
25
+ "dev": "tsx src/index.ts",
26
+ "test": "node --import tsx --test test/*.test.ts",
27
+ "release": "./scripts/release.sh"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.0",
34
+ "openai": "^4.79.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.11.0",
38
+ "tsx": "^4.7.0",
39
+ "typescript": "^5.3.3"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/FlowNex-AI/gpt-image-2-mcp.git"
44
+ },
45
+ "keywords": [
46
+ "mcp",
47
+ "openai",
48
+ "gpt-image-2",
49
+ "image-generation",
50
+ "claude-code"
51
+ ],
52
+ "license": "MIT",
53
+ "author": {
54
+ "name": "FlowNex AI",
55
+ "url": "https://flownexai.com"
56
+ }
57
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: generate-image
3
+ description: Generate or edit images using OpenAI gpt-image-2. Use this skill any time the user asks to create, generate, illustrate, design, mock up, or edit an image — from product shots and diagrams to marketing visuals and slide imagery. Works in Claude Code and Claude Cowork.
4
+ allowed-tools: mcp__gpt-image-2__generate_image, mcp__gpt-image-2__edit_image, mcp__gpt-image-2__continue_editing, mcp__gpt-image-2__get_configuration_status, mcp__gpt-image-2__get_last_image_info
5
+ ---
6
+
7
+ # Image Generation with OpenAI gpt-image-2
8
+
9
+ Use the `generate_image`, `edit_image`, and `continue_editing` MCP tools from the `gpt-image-2` server (powered by OpenAI's `gpt-image-2` model).
10
+
11
+ ## First-Time Setup
12
+
13
+ If you aren't sure the API key is configured, call `get_configuration_status`. If `OPENAI_API_KEY` is missing:
14
+
15
+ - Tell the user the plugin needs an OpenAI API key from https://platform.openai.com/api-keys.
16
+ - If they installed the plugin via marketplace, they can re-run the setup or set the value in their plugin configuration (the manifest exposes `OPENAI_API_KEY` as a `userConfig` field).
17
+ - Note: OpenAI gates the gpt-image family behind organization verification — complete it at https://platform.openai.com/settings/organization/general if you get a 403.
18
+
19
+ ## Prompting Best Practices
20
+
21
+ **Write narrative paragraphs, not comma-separated keyword lists.** gpt-image-2 is autoregressive and follows narrative instructions well.
22
+
23
+ 1. **Start with image type**: "Create an educational diagram showing...", "Generate a photorealistic photograph of...", "Design a flat-style icon depicting..."
24
+ 2. **Text in images works very well** — gpt-image-2 has ~99% text accuracy including CJK. Quote text exactly and describe font/placement.
25
+ 3. **Specify layout explicitly**: side-by-side, top-to-bottom steps, centered with border, etc.
26
+ 4. **Skip quality boosters** like "4k masterpiece", "highly detailed", "award-winning" — they add noise, not quality.
27
+ 5. **Be specific about what you want**, not what you don't want. Positive descriptions work better than negations.
28
+
29
+ ## Parameters
30
+
31
+ - **size** — `"1024x1024"` (default, square), `"1536x1024"` (landscape), `"1024x1536"` (portrait), `"2048x2048"` (square hi-res), `"auto"`, or a custom `"WxH"` (edges multiples of 16, max edge 3840, total pixels 655,360–8,294,400, ratio ≤ 3:1).
32
+ - **quality** — `"low"`, `"medium"`, `"high"`, `"auto"` (default). Higher quality increases latency and cost.
33
+ - **outputFormat** — `"png"` (default, lossless), `"jpeg"` (smaller, faster), `"webp"`.
34
+ - **background** — `"auto"` (default) or `"opaque"`. Transparent backgrounds are not supported by gpt-image-2.
35
+ - **numberOfImages** — `1` (default). Use 2–4 to explore variations.
36
+ - **returnInlineImage** — Consider setting to `false` in Claude Code to avoid context window overflow. The image is still saved to disk and can be viewed by the user.
37
+
38
+ ## Workflow
39
+
40
+ 1. **Generate**: Use `generate_image` with a well-crafted narrative prompt.
41
+ 2. **Review**: Check the saved file path in the response.
42
+ 3. **Refine**: Use `continue_editing` to make adjustments. Be specific about what to change.
43
+ 4. **Iterate**: Each `continue_editing` call builds on the previous result.
44
+
45
+ ## Editing with References
46
+
47
+ `edit_image` and `continue_editing` accept a main image plus optional `referenceImages` (up to 16 total). gpt-image-2 will fuse them per the prompt — useful for product composites, character consistency, or applying a style from one image to another. An optional `mask` (PNG with transparent pixels marking the editable area) can confine changes to a region.
48
+
49
+ ## Style Guidance
50
+
51
+ - **Diagrams**: Specify colors, label positions, arrow directions. Use `quality: "high"` for complex layouts.
52
+ - **Illustrations**: Describe art style (flat, watercolor, line art), mood, and lighting.
53
+ - **Infographics**: Long numbered lists (>8 items) can still be unreliable; consider splitting.
54
+ - **Photos**: Describe camera angle, lighting conditions, depth of field, and subject positioning.
55
+
56
+ ## Context Window Management
57
+
58
+ Base64 image data inlined in tool responses can fill the context window quickly (a 1024x1024 PNG is ~800 KB). Set `returnInlineImage: false` (or set the plugin's `MCP_GPT_IMAGE_2_INLINE_IMAGE` user config to `"false"`) to skip embedding it. The image is still saved to disk — tell the user the file path.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: powerpoint-images
3
+ description: Use when creating images for PowerPoint, Keynote, or Google Slides presentations — hero slides, section dividers, accent illustrations, diagrams, or icon sets. Triggers on mentions of slides, decks, presentations, PowerPoint, Keynote, Google Slides, pitch deck, board deck, all-hands, QBR, town hall, sales deck, or "make me an image for slide X". A natural fit for Claude Cowork (knowledge workers building decks).
4
+ allowed-tools: mcp__gpt-image-2__generate_image, mcp__gpt-image-2__edit_image, mcp__gpt-image-2__continue_editing, mcp__gpt-image-2__get_configuration_status, mcp__gpt-image-2__get_last_image_info
5
+ ---
6
+
7
+ # Slide Images with OpenAI gpt-image-2
8
+
9
+ Use this skill to generate imagery that sits well **inside a slide**, not as a standalone artwork. The constraint is always the same: the image must coexist with title text, body copy, and brand context — it cannot fight them.
10
+
11
+ ## Prerequisite
12
+
13
+ Call `get_configuration_status` once per session if you aren't sure `OPENAI_API_KEY` is set. If missing, ask the user to add it to the MCP server's `env` block.
14
+
15
+ ## Pick a size that matches the slide aspect
16
+
17
+ PowerPoint / Keynote / Google Slides defaults:
18
+
19
+ | Slide aspect | Use case | `size` |
20
+ |---|---|---|
21
+ | **16:9** (default modern decks) | Full-bleed hero, background, section divider | `1536x1024` (≈3:2, crops cleanly to 16:9) or custom `1920x1088` |
22
+ | **4:3** (legacy / corporate) | Full-bleed hero | `1024x1024` cropped, or custom `1440x1088` |
23
+ | Any | Centered accent illustration, mascot, spot art | `1024x1024` |
24
+ | Any | Vertical pull-quote panel, side rail | `1024x1536` |
25
+
26
+ Custom sizes must follow gpt-image-2 constraints (multiples of 16, max edge 3840, ratio ≤ 3:1). The server validates these.
27
+
28
+ Quick rule: **if it fills the slide → landscape; if it sits beside text → square; if it's a side rail → portrait.**
29
+
30
+ ## Style guidance for slides
31
+
32
+ Slide imagery succeeds when it **supports the message at a glance from 3 meters away**. Apply these:
33
+
34
+ 1. **High contrast, low complexity.** Backgrounds should have one focal point and breathing room for overlaid text. Avoid dense textures in the upper-left or center where titles usually land.
35
+ 2. **One dominant color or palette.** Tell gpt-image-2 the exact palette ("muted navy, warm cream, single coral accent") so it doesn't clash with the deck's theme.
36
+ 3. **Negative space on purpose.** For hero images, explicitly ask for negative space on a specific side: *"…with the left third left empty for a title overlay."*
37
+ 4. **Flat / editorial styles over photorealism** for most business decks. Photorealism is great for product shots, hero customer stories, or industry imagery — not for conceptual slides.
38
+ 5. **Consistency across the deck.** When generating multiple slides, reuse the **same style sentence** verbatim in every prompt ("flat illustration, single warm-coral accent color on cream background, soft geometric shapes"). Then vary only the subject. This is the single biggest lever for a coherent deck.
39
+ 6. **No fake logos, no fake brand names.** They'll look amateur and trigger legal review. Describe generic objects instead ("a laptop showing a generic dashboard with bar charts").
40
+
41
+ ## Text inside the image — use sparingly
42
+
43
+ gpt-image-2's text accuracy is high (~99%), but **text on the image competes with text on the slide**. Use it only when intentional:
44
+
45
+ - ✅ A single short label or callout (≤ 25 chars) integrated into the illustration
46
+ - ✅ A stylized number for a section header ("01", "02")
47
+ - ❌ Bullet points (put those in the slide, not the image)
48
+ - ❌ Paragraphs (the slide already has text)
49
+
50
+ When you do request text, quote it exactly and specify the font style: *"the word 'Pipeline' in bold sans-serif, integrated into the illustration as a banner across the top."*
51
+
52
+ ## Workflow
53
+
54
+ 1. **Confirm the slide context first.** Aspect, palette, what text will overlay it, and whether it's hero / accent / icon. If the user didn't say, ask in one line or pick a sensible default and call it out.
55
+ 2. **Build a reusable style sentence** (color palette + art style + medium). Save it in your head for the whole deck.
56
+ 3. **Generate** with `generate_image`. Default to `quality: "high"` for hero slides (worth the latency), `quality: "medium"` for accent illustrations.
57
+ 4. **Refine** with `continue_editing` — typical asks: "move the subject right to leave the left third empty", "warmer color temperature", "remove the small text in the corner".
58
+ 5. **Batch related slides.** When you need 4–8 spot illustrations for the same deck, call `generate_image` once per concept with `numberOfImages: 2` to pick the best of each pair, reusing the exact same style sentence each time.
59
+
60
+ ## Recommended defaults for slides
61
+
62
+ ```
63
+ size: "1536x1024" // 16:9 hero
64
+ quality: "high" // worth it for slide imagery
65
+ outputFormat: "png" // lossless; designers will recompress
66
+ background: "opaque" // gpt-image-2 doesn't do transparency anyway
67
+ returnInlineImage: false // file path is enough; saves context
68
+ ```
69
+
70
+ For spot illustrations / icons that will be placed inside a layout, switch `size` to `"1024x1024"` and `quality` to `"medium"`.
71
+
72
+ ## Cost note
73
+
74
+ `quality: "high"` at large sizes is the expensive setting. For a 30-slide deck, generate **hero slides** at high quality and **accent illustrations** at medium. The visual difference between medium and high on a 1024x1024 spot illustration on a slide is rarely worth the price multiplier.
75
+
76
+ ## Context Window Management
77
+
78
+ Always set `returnInlineImage: false` when generating multiple deck images in one session — base64 of a 1536x1024 PNG can be ~1.5 MB and will fill the context fast. The file path is enough for the user to drop the image into their slide tool. (In Cowork, you can also tell the user the saved path and let them drag the file into the PowerPoint/Keynote/Slides window.)