imageforge-mcp 0.3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Image2Gen MCP contributors
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,317 @@
1
+ # ImageForge MCP
2
+
3
+ English | [中文](#中文说明)
4
+
5
+ A lightweight TypeScript MCP server for generating and editing images with `gpt-image-2` through the OpenAI Images API.
6
+
7
+ - Text-to-image requests use `POST /v1/images/generations`.
8
+ - Image editing and reference-image generation use multipart `POST /v1/images/edits`.
9
+ - Generated images are returned as native MCP `image` content blocks and can optionally be saved locally.
10
+
11
+ ## Features
12
+
13
+ - Model: `gpt-image-2`
14
+ - MCP tools: `generate_image` and `edit_image`
15
+ - Text-to-image generation
16
+ - Image generation guided by one or more reference images
17
+ - Editing one or more existing images
18
+ - Local absolute paths and HTTP(S) URLs as image inputs
19
+ - PNG, JPEG, and WebP input and output
20
+ - Up to 16 input images, with a 50 MB limit per image
21
+ - Optional local output path with automatic parent-directory creation
22
+ - Safe URL validation against loopback, link-local, and private network targets
23
+ - stdio transport
24
+
25
+ ## OpenAI-compatible CPA and gateway support
26
+
27
+ ImageForge MCP works with OpenAI and with CPA, relay, or proxy services that implement a compatible OpenAI Images API. This includes deployments based on projects such as **New API**, **CLI Proxy API**, and similar OpenAI-compatible gateways.
28
+
29
+ Compatibility depends on the gateway implementation rather than its product name:
30
+
31
+ - Text-to-image requires `POST /v1/images/generations`.
32
+ - Reference-image generation and editing require multipart `POST /v1/images/edits` with `image[]` file forwarding.
33
+ - Responses must include Base64 image data in `data[0].b64_json`.
34
+ - The configured model name must accept `gpt-image-2`.
35
+
36
+ A gateway that only implements `/v1/images/generations` can be used for text-to-image generation, but not for reference-image generation or editing.
37
+
38
+ ## Requirements
39
+
40
+ - Node.js 22 or later
41
+ - An OpenAI API key or a token issued by a compatible CPA/gateway
42
+
43
+ ## Install and build
44
+
45
+ ```bash
46
+ npm install
47
+ npm run build
48
+ ```
49
+
50
+ ## MCP client configuration
51
+
52
+ Inject credentials through the MCP client environment:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "imageforge": {
58
+ "command": "node",
59
+ "args": ["/absolute/path/to/ImageForgeMCP/dist/index.js"],
60
+ "env": {
61
+ "OPENAI_BASE_URL": "https://your-openai-compatible-gateway.example/v1",
62
+ "OPENAI_API_KEY": "your-token",
63
+ "OPENAI_IMAGE_MODEL": "gpt-image-2"
64
+ }
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ `OPENAI_BASE_URL` defaults to `https://api.openai.com/v1`. Set it to the `/v1` root of your New API, CLI Proxy API, or other OpenAI-compatible gateway. ImageForge MCP appends `/images/generations` or `/images/edits` as required.
71
+
72
+ Do not commit real API keys to Git or write them into shared configuration files.
73
+
74
+ ## Codex local development configuration
75
+
76
+ Create a project-scoped `.codex/config.toml` using paths that match your machine:
77
+
78
+ ```toml
79
+ [mcp_servers.imageforge_dev]
80
+ command = "/absolute/path/to/node"
81
+ args = ["/absolute/path/to/ImageForgeMCP/dist/index.js"]
82
+ cwd = "/absolute/path/to/ImageForgeMCP"
83
+ env_vars = ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_IMAGE_MODEL"]
84
+ startup_timeout_sec = 10
85
+ tool_timeout_sec = 300
86
+ enabled = true
87
+ required = false
88
+ ```
89
+
90
+ Export the environment variables before starting Codex:
91
+
92
+ ```bash
93
+ export OPENAI_API_KEY="your-gateway-token"
94
+ export OPENAI_BASE_URL="https://your-openai-compatible-gateway.example/v1"
95
+ export OPENAI_IMAGE_MODEL="gpt-image-2"
96
+
97
+ cd /absolute/path/to/ImageForgeMCP
98
+ codex app
99
+ ```
100
+
101
+ Codex loads project-scoped `.codex/config.toml` only for trusted projects. After adding or changing the MCP configuration, restart Codex or open a new task, then use `/mcp verbose` to confirm that `imageforge_dev` exposes:
102
+
103
+ - `generate_image`
104
+ - `edit_image`
105
+
106
+ After changing the TypeScript source, rebuild `dist/index.js` and restart the task using the MCP server:
107
+
108
+ ```bash
109
+ npm run build
110
+ ```
111
+
112
+ See the [official Codex MCP documentation](https://developers.openai.com/codex/mcp) for all configuration options.
113
+
114
+ ## `generate_image`
115
+
116
+ | Parameter | Required | Default | Description |
117
+ | --- | --- | --- | --- |
118
+ | `prompt` | Yes | - | Image description |
119
+ | `model` | No | Environment or `gpt-image-2` | Only `gpt-image-2` is accepted |
120
+ | `base_url` | No | `OPENAI_BASE_URL` | Per-call API base URL override |
121
+ | `api_key` | No | `OPENAI_API_KEY` | Per-call credential override; environment variables are preferred |
122
+ | `size` | No | `1024x1024` | Output size requested from the provider |
123
+ | `quality` | No | `auto` | `auto`, `low`, `medium`, or `high` |
124
+ | `output_format` | No | `png` | `png`, `jpeg`, or `webp` |
125
+ | `output_path` | No | - | Local save path; relative paths resolve from the MCP working directory |
126
+ | `reference_images` | No | - | Reference images as local absolute paths or HTTP(S) URLs |
127
+
128
+ Without `reference_images`, the tool uses `/images/generations`. With reference images, it uploads them as multipart `image[]` files to `/images/edits`.
129
+
130
+ ## `edit_image`
131
+
132
+ `edit_image` uses the same model, API, output, and local-save parameters as `generate_image`, but requires `input_images`:
133
+
134
+ | Parameter | Required | Description |
135
+ | --- | --- | --- |
136
+ | `prompt` | Yes | Editing instructions |
137
+ | `input_images` | Yes | 1–16 local absolute paths or HTTP(S) URLs |
138
+
139
+ Local images are read directly. URL images are downloaded and validated before upload. URLs resolving to loopback, link-local, or private addresses are rejected.
140
+
141
+ Configuration precedence is: tool arguments, environment variables, then built-in defaults.
142
+
143
+ When `output_path` is supplied, ImageForge MCP returns the native MCP image content block, saves the decoded image, and includes the final absolute path in a text content block. Missing parent directories are created automatically. Existing files are not overwritten.
144
+
145
+ Some OpenAI-compatible gateways may return dimensions different from the requested `size`; the actual returned file dimensions are authoritative.
146
+
147
+ ## Verification
148
+
149
+ ```bash
150
+ npm test
151
+ npm run check
152
+ ```
153
+
154
+ Tests use a local HTTP mock. They do not call a real image API or incur generation charges.
155
+
156
+ ## License
157
+
158
+ [MIT](./LICENSE)
159
+
160
+ ---
161
+
162
+ # 中文说明
163
+
164
+ ImageForge MCP 是一个轻量的 TypeScript MCP 图片生成与编辑服务,通过 OpenAI Images API 调用 `gpt-image-2`。
165
+
166
+ - 纯文本生图调用 `POST /v1/images/generations`。
167
+ - 图片编辑和参考图生图调用 multipart `POST /v1/images/edits`。
168
+ - 生成结果以原生 MCP `image` 内容块返回,也可以同时保存到本地。
169
+
170
+ ## 功能
171
+
172
+ - 模型:`gpt-image-2`
173
+ - MCP 工具:`generate_image`、`edit_image`
174
+ - 支持纯文本生图
175
+ - 支持一张或多张参考图引导生图
176
+ - 支持一张或多张图片编辑
177
+ - 输入图片支持本地绝对路径和 HTTP(S) URL
178
+ - 输入与输出支持 PNG、JPEG、WebP
179
+ - 最多 16 张输入图片,每张不超过 50 MB
180
+ - 支持通过 `output_path` 保存到本地,并自动创建父目录
181
+ - URL 安全校验,拒绝访问环回、链路本地和私有网络地址
182
+ - stdio Transport
183
+
184
+ ## OpenAI 兼容 CPA 与网关
185
+
186
+ ImageForge MCP 不仅支持 OpenAI 官方接口,也支持实现了 OpenAI Images API 兼容协议的 CPA、中转或代理服务,包括基于 **New API**、**CLI Proxy API** 等项目部署的 OpenAI 兼容网关。
187
+
188
+ 是否兼容取决于网关实现的接口能力,而不是产品名称:
189
+
190
+ - 纯文本生图需要实现 `POST /v1/images/generations`。
191
+ - 参考图生图和图片编辑需要实现 multipart `POST /v1/images/edits`,并正确转发 `image[]` 文件。
192
+ - 响应需要在 `data[0].b64_json` 中返回 Base64 图片数据。
193
+ - 网关需要接受 `gpt-image-2` 模型名。
194
+
195
+ 如果网关只实现了 `/v1/images/generations`,仍可用于纯文本生图,但不能使用参考图生图和图片编辑。
196
+
197
+ ## 环境要求
198
+
199
+ - Node.js 22 或更高版本
200
+ - OpenAI API Key,或 OpenAI 兼容 CPA/网关签发的令牌
201
+
202
+ ## 安装与构建
203
+
204
+ ```bash
205
+ npm install
206
+ npm run build
207
+ ```
208
+
209
+ ## MCP 客户端配置
210
+
211
+ 推荐通过 MCP 客户端环境变量注入密钥:
212
+
213
+ ```json
214
+ {
215
+ "mcpServers": {
216
+ "imageforge": {
217
+ "command": "node",
218
+ "args": ["/absolute/path/to/ImageForgeMCP/dist/index.js"],
219
+ "env": {
220
+ "OPENAI_BASE_URL": "https://你的-OpenAI-兼容网关域名/v1",
221
+ "OPENAI_API_KEY": "你的令牌",
222
+ "OPENAI_IMAGE_MODEL": "gpt-image-2"
223
+ }
224
+ }
225
+ }
226
+ }
227
+ ```
228
+
229
+ `OPENAI_BASE_URL` 默认是 `https://api.openai.com/v1`。使用 New API、CLI Proxy API 或其他 OpenAI 兼容网关时,应填写到 `/v1` 为止;服务会根据请求追加 `/images/generations` 或 `/images/edits`。
230
+
231
+ 不要把真实 API Key 写入 Git 或其他共享配置文件。
232
+
233
+ ## Codex 本地开发配置
234
+
235
+ 根据本机路径创建项目级 `.codex/config.toml`:
236
+
237
+ ```toml
238
+ [mcp_servers.imageforge_dev]
239
+ command = "/你的/node/绝对路径"
240
+ args = ["/你的/ImageForgeMCP/绝对路径/dist/index.js"]
241
+ cwd = "/你的/ImageForgeMCP/绝对路径"
242
+ env_vars = ["OPENAI_API_KEY", "OPENAI_BASE_URL", "OPENAI_IMAGE_MODEL"]
243
+ startup_timeout_sec = 10
244
+ tool_timeout_sec = 300
245
+ enabled = true
246
+ required = false
247
+ ```
248
+
249
+ 启动 Codex 前设置环境变量:
250
+
251
+ ```bash
252
+ export OPENAI_API_KEY="你的网关令牌"
253
+ export OPENAI_BASE_URL="https://你的-OpenAI-兼容网关域名/v1"
254
+ export OPENAI_IMAGE_MODEL="gpt-image-2"
255
+
256
+ cd /你的/ImageForgeMCP/绝对路径
257
+ codex app
258
+ ```
259
+
260
+ Codex 只会加载已信任项目中的 `.codex/config.toml`。新增或修改 MCP 配置后,需要重新启动 Codex 或新建任务,再使用 `/mcp verbose` 确认 `imageforge_dev` 提供以下工具:
261
+
262
+ - `generate_image`
263
+ - `edit_image`
264
+
265
+ 修改 TypeScript 源码后,需要重新构建并重启使用该 MCP 的任务:
266
+
267
+ ```bash
268
+ npm run build
269
+ ```
270
+
271
+ 完整配置说明见 [Codex MCP 官方文档](https://developers.openai.com/codex/mcp)。
272
+
273
+ ## `generate_image`
274
+
275
+ | 参数 | 必填 | 默认值 | 说明 |
276
+ | --- | --- | --- | --- |
277
+ | `prompt` | 是 | - | 图片描述 |
278
+ | `model` | 否 | 环境变量或 `gpt-image-2` | 仅接受 `gpt-image-2` |
279
+ | `base_url` | 否 | `OPENAI_BASE_URL` | 单次调用覆盖 API 地址 |
280
+ | `api_key` | 否 | `OPENAI_API_KEY` | 单次调用覆盖密钥;推荐使用环境变量 |
281
+ | `size` | 否 | `1024x1024` | 请求提供商输出的图片尺寸 |
282
+ | `quality` | 否 | `auto` | `auto`、`low`、`medium`、`high` |
283
+ | `output_format` | 否 | `png` | `png`、`jpeg`、`webp` |
284
+ | `output_path` | 否 | - | 本地保存路径;相对路径按 MCP 工作目录解析 |
285
+ | `reference_images` | 否 | - | 参考图数组,支持本地绝对路径和 HTTP(S) URL |
286
+
287
+ 不传 `reference_images` 时调用 `/images/generations`;传入参考图时,将图片作为 multipart `image[]` 文件上传到 `/images/edits`。
288
+
289
+ ## `edit_image`
290
+
291
+ `edit_image` 与 `generate_image` 使用相同的模型、API、输出和本地保存参数,但 `input_images` 必填:
292
+
293
+ | 参数 | 必填 | 说明 |
294
+ | --- | --- | --- |
295
+ | `prompt` | 是 | 编辑指令 |
296
+ | `input_images` | 是 | 1–16 张输入图片,支持本地绝对路径或 HTTP(S) URL |
297
+
298
+ 本地图片会直接读取;URL 图片会经过下载和安全校验后再上传。解析到环回、链路本地或私有地址的 URL 会被拒绝。
299
+
300
+ 配置优先级为:工具参数、环境变量、内置默认值。
301
+
302
+ 传入 `output_path` 时,服务会在返回原生 MCP 图片内容块的同时保存文件,并在文本内容块中返回最终绝对路径。父目录不存在时会自动创建;已有文件不会被覆盖。
303
+
304
+ 部分 OpenAI 兼容网关可能不会严格遵循请求中的 `size`,应以实际返回文件的尺寸为准。
305
+
306
+ ## 验证
307
+
308
+ ```bash
309
+ npm test
310
+ npm run check
311
+ ```
312
+
313
+ 测试使用本地 HTTP mock,不会调用真实图片 API,也不会产生生图费用。
314
+
315
+ ## 许可证
316
+
317
+ [MIT](./LICENSE)
@@ -0,0 +1,10 @@
1
+ export declare const MAX_INPUT_IMAGES = 16;
2
+ export declare const MAX_IMAGE_BYTES: number;
3
+ export interface LoadedImage {
4
+ data: Buffer;
5
+ fileName: string;
6
+ mimeType: "image/png" | "image/jpeg" | "image/webp";
7
+ }
8
+ export declare function loadRemoteImage(source: string, fetchImpl?: typeof fetch): Promise<LoadedImage>;
9
+ export declare function loadLocalImage(source: string): Promise<LoadedImage>;
10
+ export declare function loadImages(sources: string[]): Promise<LoadedImage[]>;
@@ -0,0 +1,134 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { isIP } from "node:net";
3
+ import { lookup } from "node:dns/promises";
4
+ import path from "node:path";
5
+ export const MAX_INPUT_IMAGES = 16;
6
+ export const MAX_IMAGE_BYTES = 50 * 1024 * 1024;
7
+ function detectMimeType(data) {
8
+ if (data.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
9
+ return "image/png";
10
+ }
11
+ if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {
12
+ return "image/jpeg";
13
+ }
14
+ if (data.subarray(0, 4).toString("ascii") === "RIFF" && data.subarray(8, 12).toString("ascii") === "WEBP") {
15
+ return "image/webp";
16
+ }
17
+ return undefined;
18
+ }
19
+ function extensionFor(mimeType) {
20
+ return mimeType === "image/jpeg" ? ".jpg" : mimeType === "image/webp" ? ".webp" : ".png";
21
+ }
22
+ function validateImage(data, fileName) {
23
+ if (data.length === 0) {
24
+ throw new Error(`Input image '${fileName}' is empty.`);
25
+ }
26
+ if (data.length > MAX_IMAGE_BYTES) {
27
+ throw new Error(`Input image '${fileName}' exceeds the 50 MB limit.`);
28
+ }
29
+ const mimeType = detectMimeType(data);
30
+ if (!mimeType) {
31
+ throw new Error(`Input image '${fileName}' must be PNG, JPEG, or WebP.`);
32
+ }
33
+ const baseName = path.basename(fileName, path.extname(fileName)) || "image";
34
+ return { data, mimeType, fileName: `${baseName}${extensionFor(mimeType)}` };
35
+ }
36
+ function isPrivateAddress(address) {
37
+ if (address === "::1" || address === "::" || address.startsWith("fe80:") || address.startsWith("fc") || address.startsWith("fd")) {
38
+ return true;
39
+ }
40
+ if (address.startsWith("::ffff:")) {
41
+ return isPrivateAddress(address.slice(7));
42
+ }
43
+ const parts = address.split(".").map(Number);
44
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part)))
45
+ return false;
46
+ return (parts[0] === 10 ||
47
+ parts[0] === 127 ||
48
+ (parts[0] === 169 && parts[1] === 254) ||
49
+ (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
50
+ (parts[0] === 192 && parts[1] === 168) ||
51
+ parts[0] === 0);
52
+ }
53
+ async function assertPublicUrl(url) {
54
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
55
+ throw new Error("Image URLs must use HTTP or HTTPS.");
56
+ }
57
+ if (url.username || url.password) {
58
+ throw new Error("Image URLs must not contain credentials.");
59
+ }
60
+ const addresses = isIP(url.hostname)
61
+ ? [{ address: url.hostname }]
62
+ : await lookup(url.hostname, { all: true, verbatim: true });
63
+ if (addresses.length === 0 || addresses.some(({ address }) => isPrivateAddress(address))) {
64
+ throw new Error(`Image URL host '${url.hostname}' resolves to a private or local address.`);
65
+ }
66
+ }
67
+ async function readLimitedResponse(response) {
68
+ const declaredLength = Number(response.headers.get("content-length"));
69
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_IMAGE_BYTES) {
70
+ throw new Error("Remote input image exceeds the 50 MB limit.");
71
+ }
72
+ if (!response.body)
73
+ throw new Error("Remote input image returned an empty response body.");
74
+ const reader = response.body.getReader();
75
+ const chunks = [];
76
+ let total = 0;
77
+ while (true) {
78
+ const { done, value } = await reader.read();
79
+ if (done)
80
+ break;
81
+ total += value.byteLength;
82
+ if (total > MAX_IMAGE_BYTES) {
83
+ await reader.cancel();
84
+ throw new Error("Remote input image exceeds the 50 MB limit.");
85
+ }
86
+ chunks.push(value);
87
+ }
88
+ return Buffer.concat(chunks, total);
89
+ }
90
+ export async function loadRemoteImage(source, fetchImpl = fetch) {
91
+ let current = new URL(source);
92
+ for (let redirects = 0; redirects <= 3; redirects += 1) {
93
+ await assertPublicUrl(current);
94
+ const response = await fetchImpl(current, { redirect: "manual", signal: AbortSignal.timeout(30_000) });
95
+ if (response.status >= 300 && response.status < 400) {
96
+ const location = response.headers.get("location");
97
+ if (!location || redirects === 3)
98
+ throw new Error("Remote input image has too many redirects.");
99
+ current = new URL(location, current);
100
+ continue;
101
+ }
102
+ if (!response.ok) {
103
+ throw new Error(`Failed to download input image (${response.status} ${response.statusText}).`);
104
+ }
105
+ const data = await readLimitedResponse(response);
106
+ return validateImage(data, path.basename(current.pathname) || "remote-image");
107
+ }
108
+ throw new Error("Remote input image has too many redirects.");
109
+ }
110
+ export async function loadLocalImage(source) {
111
+ if (!path.isAbsolute(source)) {
112
+ throw new Error(`Local image path must be absolute: '${source}'.`);
113
+ }
114
+ const handle = await fs.open(source, "r");
115
+ try {
116
+ const stats = await handle.stat();
117
+ if (!stats.isFile())
118
+ throw new Error(`Input image is not a regular file: '${source}'.`);
119
+ if (stats.size > MAX_IMAGE_BYTES)
120
+ throw new Error(`Input image '${source}' exceeds the 50 MB limit.`);
121
+ return validateImage(await handle.readFile(), path.basename(source));
122
+ }
123
+ finally {
124
+ await handle.close();
125
+ }
126
+ }
127
+ export async function loadImages(sources) {
128
+ if (sources.length === 0)
129
+ throw new Error("At least one input image is required.");
130
+ if (sources.length > MAX_INPUT_IMAGES)
131
+ throw new Error("A maximum of 16 input images is supported.");
132
+ return Promise.all(sources.map((source) => /^https?:\/\//i.test(source) ? loadRemoteImage(source) : loadLocalImage(source)));
133
+ }
134
+ //# sourceMappingURL=imageInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageInput.js","sourceRoot":"","sources":["../src/imageInput.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { z } from "zod";
4
+ import { type LoadedImage } from "./imageInput.js";
5
+ declare const qualitySchema: z.ZodEnum<["auto", "low", "medium", "high"]>;
6
+ declare const outputFormatSchema: z.ZodEnum<["png", "jpeg", "webp"]>;
7
+ export interface ImageConfig {
8
+ apiKey: string;
9
+ baseUrl: string;
10
+ model: string;
11
+ }
12
+ export interface ImageGenerationResponse {
13
+ data?: Array<{
14
+ b64_json?: string;
15
+ }>;
16
+ }
17
+ export declare function resolveConfig(input: {
18
+ apiKey?: string;
19
+ baseUrl?: string;
20
+ model?: string;
21
+ }): ImageConfig;
22
+ export declare function requestImage(input: {
23
+ prompt: string;
24
+ config: ImageConfig;
25
+ size: string;
26
+ quality: z.infer<typeof qualitySchema>;
27
+ outputFormat: z.infer<typeof outputFormatSchema>;
28
+ inputImages?: LoadedImage[];
29
+ fetchImpl?: typeof fetch;
30
+ }): Promise<Buffer>;
31
+ export declare function saveImage(image: Buffer, outputPath: string): Promise<string>;
32
+ export declare function createServer(): McpServer;
33
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { promises as fs } from "node:fs";
5
+ import path from "node:path";
6
+ import { z } from "zod";
7
+ import { loadImages } from "./imageInput.js";
8
+ const DEFAULT_BASE_URL = "https://api.openai.com/v1";
9
+ const DEFAULT_MODEL = "gpt-image-2";
10
+ const qualitySchema = z.enum(["auto", "low", "medium", "high"]);
11
+ const outputFormatSchema = z.enum(["png", "jpeg", "webp"]);
12
+ function firstValue(...values) {
13
+ return values.map((value) => value?.trim()).find(Boolean);
14
+ }
15
+ export function resolveConfig(input) {
16
+ const apiKey = firstValue(input.apiKey, process.env.OPENAI_API_KEY);
17
+ if (!apiKey) {
18
+ throw new Error("Missing API key: pass api_key or set OPENAI_API_KEY.");
19
+ }
20
+ const model = firstValue(input.model, process.env.OPENAI_IMAGE_MODEL, DEFAULT_MODEL);
21
+ if (model !== DEFAULT_MODEL) {
22
+ throw new Error(`Unsupported model '${model}'. This MVP only supports ${DEFAULT_MODEL}.`);
23
+ }
24
+ const baseUrl = firstValue(input.baseUrl, process.env.OPENAI_BASE_URL, DEFAULT_BASE_URL);
25
+ let parsedUrl;
26
+ try {
27
+ parsedUrl = new URL(baseUrl);
28
+ }
29
+ catch {
30
+ throw new Error("base_url must be a valid absolute HTTP(S) URL.");
31
+ }
32
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
33
+ throw new Error("base_url must use HTTP or HTTPS.");
34
+ }
35
+ return {
36
+ apiKey,
37
+ model,
38
+ baseUrl: baseUrl.replace(/\/+$/, ""),
39
+ };
40
+ }
41
+ export async function requestImage(input) {
42
+ const fetchImpl = input.fetchImpl ?? fetch;
43
+ const commonFields = {
44
+ model: input.config.model,
45
+ prompt: input.prompt,
46
+ n: 1,
47
+ size: input.size,
48
+ quality: input.quality,
49
+ output_format: input.outputFormat,
50
+ };
51
+ const hasInputImages = Boolean(input.inputImages?.length);
52
+ let body;
53
+ let headers = { authorization: `Bearer ${input.config.apiKey}` };
54
+ if (hasInputImages) {
55
+ const form = new FormData();
56
+ for (const [name, value] of Object.entries(commonFields))
57
+ form.append(name, String(value));
58
+ for (const image of input.inputImages) {
59
+ form.append("image[]", new Blob([new Uint8Array(image.data)], { type: image.mimeType }), image.fileName);
60
+ }
61
+ body = form;
62
+ }
63
+ else {
64
+ headers = { ...headers, "content-type": "application/json" };
65
+ body = JSON.stringify(commonFields);
66
+ }
67
+ const endpoint = hasInputImages ? "edits" : "generations";
68
+ const response = await fetchImpl(`${input.config.baseUrl}/images/${endpoint}`, {
69
+ method: "POST",
70
+ headers,
71
+ body,
72
+ });
73
+ if (!response.ok) {
74
+ const detail = (await response.text()).slice(0, 500);
75
+ throw new Error(`OpenAI image request failed (${response.status}): ${detail || response.statusText}`);
76
+ }
77
+ const payload = (await response.json());
78
+ const encodedImage = payload.data?.[0]?.b64_json;
79
+ if (!encodedImage) {
80
+ throw new Error("OpenAI image response contained no base64 image data.");
81
+ }
82
+ const normalizedImage = encodedImage.replace(/\s+/g, "");
83
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(normalizedImage)) {
84
+ throw new Error("OpenAI image response contained invalid base64 image data.");
85
+ }
86
+ const image = Buffer.from(normalizedImage, "base64");
87
+ if (image.length === 0 || image.toString("base64").replace(/=+$/, "") !== normalizedImage.replace(/=+$/, "")) {
88
+ throw new Error("OpenAI image response contained invalid base64 image data.");
89
+ }
90
+ return image;
91
+ }
92
+ export async function saveImage(image, outputPath) {
93
+ const resolvedPath = path.resolve(outputPath);
94
+ await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
95
+ await fs.writeFile(resolvedPath, image, { flag: "wx" });
96
+ return resolvedPath;
97
+ }
98
+ async function imageResult(image, outputFormat, outputPath) {
99
+ const content = [
100
+ {
101
+ type: "image",
102
+ data: image.toString("base64"),
103
+ mimeType: `image/${outputFormat}`,
104
+ },
105
+ ];
106
+ if (outputPath) {
107
+ const savedPath = await saveImage(image, outputPath);
108
+ content.push({ type: "text", text: `Image saved to ${savedPath}` });
109
+ }
110
+ return { content };
111
+ }
112
+ export function createServer() {
113
+ const server = new McpServer({ name: "imageforge-mcp", version: "0.3.0" });
114
+ server.registerTool("generate_image", {
115
+ description: "Generate one image with gpt-image-2, optionally guided by reference images.",
116
+ inputSchema: {
117
+ prompt: z.string().trim().min(1).max(32_000).describe("Image description."),
118
+ model: z.string().optional().describe("Model name; only gpt-image-2 is supported."),
119
+ base_url: z.string().optional().describe("OpenAI-compatible base URL ending in /v1."),
120
+ api_key: z.string().optional().describe("Per-call API key; OPENAI_API_KEY is preferred."),
121
+ size: z.string().default("1024x1024").describe("Output size, for example 1536x1024."),
122
+ quality: qualitySchema.default("auto"),
123
+ output_format: outputFormatSchema.default("png"),
124
+ output_path: z.string().trim().min(1).optional().describe("Optional local path to save the generated image. Relative paths resolve from the MCP working directory. Existing files are not overwritten."),
125
+ reference_images: z.array(z.string()).max(16).optional().describe("Optional reference images as absolute local paths or HTTP(S) URLs."),
126
+ },
127
+ }, async ({ prompt, model, base_url, api_key, size, quality, output_format, output_path, reference_images }) => {
128
+ const config = resolveConfig({ apiKey: api_key, baseUrl: base_url, model });
129
+ const inputImages = reference_images?.length ? await loadImages(reference_images) : undefined;
130
+ const image = await requestImage({
131
+ prompt,
132
+ config,
133
+ size,
134
+ quality,
135
+ outputFormat: output_format,
136
+ inputImages,
137
+ });
138
+ return imageResult(image, output_format, output_path);
139
+ });
140
+ server.registerTool("edit_image", {
141
+ description: "Edit one or more input images with gpt-image-2.",
142
+ inputSchema: {
143
+ prompt: z.string().trim().min(1).max(32_000).describe("Instructions for the image edit."),
144
+ input_images: z.array(z.string()).min(1).max(16).describe("Input images as absolute local paths or HTTP(S) URLs."),
145
+ model: z.string().optional().describe("Model name; only gpt-image-2 is supported."),
146
+ base_url: z.string().optional().describe("OpenAI-compatible base URL ending in /v1."),
147
+ api_key: z.string().optional().describe("Per-call API key; OPENAI_API_KEY is preferred."),
148
+ size: z.string().default("1024x1024").describe("Output size, for example 1536x1024."),
149
+ quality: qualitySchema.default("auto"),
150
+ output_format: outputFormatSchema.default("png"),
151
+ output_path: z.string().trim().min(1).optional().describe("Optional local path to save the edited image. Relative paths resolve from the MCP working directory. Existing files are not overwritten."),
152
+ },
153
+ }, async ({ prompt, input_images, model, base_url, api_key, size, quality, output_format, output_path }) => {
154
+ const config = resolveConfig({ apiKey: api_key, baseUrl: base_url, model });
155
+ const image = await requestImage({
156
+ prompt,
157
+ config,
158
+ size,
159
+ quality,
160
+ outputFormat: output_format,
161
+ inputImages: await loadImages(input_images),
162
+ });
163
+ return imageResult(image, output_format, output_path);
164
+ });
165
+ return server;
166
+ }
167
+ async function main() {
168
+ await createServer().connect(new StdioServerTransport());
169
+ }
170
+ if (import.meta.url === `file://${process.argv[1]}`) {
171
+ main().catch((error) => {
172
+ console.error(error instanceof Error ? error.message : error);
173
+ process.exitCode = 1;
174
+ });
175
+ }
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "imageforge-mcp",
3
+ "version": "0.3.0",
4
+ "description": "Minimal MCP server for gpt-image-2 through the OpenAI Images API",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "imageforge-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "LICENSE",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "test": "vitest run",
18
+ "check": "npm run build && npm test"
19
+ },
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.30.0",
26
+ "zod": "^3.25.76"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.15.0",
30
+ "typescript": "^5.8.3",
31
+ "vitest": "^3.2.4"
32
+ }
33
+ }