mcp-duojie-image 0.5.4 → 0.5.6
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/README.md +17 -7
- package/dist/index.js +2 -2
- package/dist/provider.js +10 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ MCP server for image generation via duojie.games API (Gemini Flash Image).
|
|
|
9
9
|
- **Async mode** — for long-running generations (especially with reference images), use `async_mode: true` to get a `task_id` immediately and poll for completion; prevents MCP client timeout
|
|
10
10
|
- **Sync mode** — for simple text-to-image generation without reference images, the default sync mode returns the result directly
|
|
11
11
|
- **File saving** — automatic output directory creation, conflict handling (`overwrite` flag, auto-incrementing filename suffix)
|
|
12
|
-
- **Configurable output** — size (512px/1K/2K/4K), aspect ratio, format
|
|
12
|
+
- **Configurable output** — size (512px/1K/2K/4K), templated aspect ratio, format
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -69,7 +69,7 @@ Generate an image and save it to disk.
|
|
|
69
69
|
**Optional parameters:**
|
|
70
70
|
- `reference_images` (string[]) — Local image paths for reference. Images are auto-compressed to max 1024px long-side before sending to API.
|
|
71
71
|
- `size` ("512px" | "1K" | "2K" | "4K") — Output image size. Default: `1K`
|
|
72
|
-
- `aspect_ratio` (
|
|
72
|
+
- `aspect_ratio` (`"1:1"` | `"3:2"` | `"2:3"`) — Templated aspect ratio. Default: `"1:1"` for GPT image models
|
|
73
73
|
- `format` (string) — Output format. Default: `"png"`
|
|
74
74
|
- `model` (string) — Model override
|
|
75
75
|
- `overwrite` (boolean) — Overwrite existing file. Default: `false` (auto-increments filename)
|
|
@@ -106,17 +106,27 @@ Check the status of an async image generation task.
|
|
|
106
106
|
|
|
107
107
|
## Usage Examples
|
|
108
108
|
|
|
109
|
-
### Simple text-to-image (sync)
|
|
109
|
+
### Simple text-to-image (sync)
|
|
110
110
|
|
|
111
111
|
```
|
|
112
112
|
generate_image(
|
|
113
113
|
prompt="A sunset over the ocean, photorealistic",
|
|
114
114
|
output_dir="/tmp/images",
|
|
115
115
|
output_filename="sunset"
|
|
116
|
-
)
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
###
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### GPT image aspect ratio templates
|
|
120
|
+
|
|
121
|
+
`gpt-image-2` does not accept free-form aspect ratios through MCP. Use one of these templates; the server converts it to a supported `size` value before calling the image API. For GPT image models, the `size` preset is ignored and the template controls the upstream size.
|
|
122
|
+
|
|
123
|
+
| `aspect_ratio` | `size` sent upstream |
|
|
124
|
+
|----------------|----------------------|
|
|
125
|
+
| `1:1` | `1024x1024` |
|
|
126
|
+
| `3:2` | `1536x1024` |
|
|
127
|
+
| `2:3` | `1024x1536` |
|
|
128
|
+
|
|
129
|
+
### With reference image (use async_mode to avoid timeout)
|
|
120
130
|
|
|
121
131
|
```
|
|
122
132
|
generate_image(
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { loadConfig } from "./config.js";
|
|
7
|
-
import { generateImage } from "./provider.js";
|
|
7
|
+
import { ASPECT_RATIOS, generateImage } from "./provider.js";
|
|
8
8
|
import { saveImage } from "./saver.js";
|
|
9
9
|
import { AppError } from "./errors.js";
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
@@ -30,7 +30,7 @@ server.registerTool("generate_image", {
|
|
|
30
30
|
output_filename: z.string().describe("Filename without ext"),
|
|
31
31
|
reference_images: z.array(z.string()).optional().describe("Reference image paths"),
|
|
32
32
|
size: z.enum(["512px", "1K", "2K", "4K"]).optional().describe("Default 1K"),
|
|
33
|
-
aspect_ratio: z.
|
|
33
|
+
aspect_ratio: z.enum(ASPECT_RATIOS).optional().describe("Aspect ratio template: 1:1, 3:2, or 2:3"),
|
|
34
34
|
format: z.string().optional().describe("Default png"),
|
|
35
35
|
model: z.enum(["gemini-3.1-flash-image", "gpt-image-2"]).describe("Required; ask once per conversation if unknown"),
|
|
36
36
|
overwrite: z.boolean().optional().describe("Default false"),
|
package/dist/provider.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AppError } from "./errors.js";
|
|
2
2
|
import { resizeIfNeeded } from "./resizer.js";
|
|
3
|
+
export const ASPECT_RATIOS = ["1:1", "3:2", "2:3"];
|
|
3
4
|
async function fetchWithRetry(url, init, timeoutMs, retries = 0) {
|
|
4
5
|
let lastErr;
|
|
5
6
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
@@ -41,50 +42,19 @@ async function buildParts(opts) {
|
|
|
41
42
|
function mapSize(size) {
|
|
42
43
|
if (size === "512px")
|
|
43
44
|
return "512";
|
|
44
|
-
return size;
|
|
45
|
+
return size;
|
|
45
46
|
}
|
|
46
47
|
function isOpenAIImageModel(model) {
|
|
47
48
|
return model === "gpt-image-2";
|
|
48
49
|
}
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
function mapOpenAI4KSize(aspectRatio) {
|
|
56
|
-
const [ratioWidth, ratioHeight] = parseAspectRatio(aspectRatio);
|
|
57
|
-
const maxPixels = 3840 * 2160;
|
|
58
|
-
const maxEdge = 3840;
|
|
59
|
-
let width = Math.floor(Math.sqrt((maxPixels * ratioWidth) / ratioHeight));
|
|
60
|
-
let height = Math.floor((width * ratioHeight) / ratioWidth);
|
|
61
|
-
if (width > maxEdge) {
|
|
62
|
-
width = maxEdge;
|
|
63
|
-
height = Math.floor((width * ratioHeight) / ratioWidth);
|
|
64
|
-
}
|
|
65
|
-
if (height > maxEdge) {
|
|
66
|
-
height = maxEdge;
|
|
67
|
-
width = Math.floor((height * ratioWidth) / ratioHeight);
|
|
68
|
-
}
|
|
69
|
-
width = Math.floor(width / 8) * 8;
|
|
70
|
-
height = Math.floor(height / 8) * 8;
|
|
71
|
-
return `${width}x${height}`;
|
|
72
|
-
}
|
|
73
|
-
function mapOpenAISize(size, aspectRatio) {
|
|
74
|
-
const squareBySize = {
|
|
75
|
-
"512px": "512x512",
|
|
76
|
-
"1K": "1024x1024",
|
|
77
|
-
"2K": "2048x2048",
|
|
50
|
+
function mapOpenAISize(aspectRatio) {
|
|
51
|
+
const ratio = aspectRatio ?? "1:1";
|
|
52
|
+
const sizes = {
|
|
53
|
+
"1:1": "1024x1024",
|
|
54
|
+
"3:2": "1536x1024",
|
|
55
|
+
"2:3": "1024x1536",
|
|
78
56
|
};
|
|
79
|
-
|
|
80
|
-
return mapOpenAI4KSize(aspectRatio);
|
|
81
|
-
if (aspectRatio === "16:9" || aspectRatio === "3:2" || aspectRatio === "4:3") {
|
|
82
|
-
return size === "2K" ? "2048x1536" : "1536x1024";
|
|
83
|
-
}
|
|
84
|
-
if (aspectRatio === "9:16" || aspectRatio === "2:3" || aspectRatio === "3:4") {
|
|
85
|
-
return size === "2K" ? "1536x2048" : "1024x1536";
|
|
86
|
-
}
|
|
87
|
-
return squareBySize[size] ?? "1024x1024";
|
|
57
|
+
return sizes[ratio];
|
|
88
58
|
}
|
|
89
59
|
async function imageFromOpenAIResponse(res) {
|
|
90
60
|
const data = (await res.json());
|
|
@@ -149,7 +119,7 @@ async function generateGeminiImage(config, opts) {
|
|
|
149
119
|
async function generateOpenAIImage(config, opts) {
|
|
150
120
|
const model = opts.model ?? config.model;
|
|
151
121
|
const prompt = opts.negativePrompt ? `${opts.prompt}\n\nAvoid: ${opts.negativePrompt}` : opts.prompt;
|
|
152
|
-
const size = mapOpenAISize(opts.
|
|
122
|
+
const size = mapOpenAISize(opts.aspectRatio);
|
|
153
123
|
const hasReferenceImages = Boolean(opts.referenceImages?.length);
|
|
154
124
|
const url = `${config.baseUrl}${hasReferenceImages ? "/v1/images/edits" : "/v1/images/generations"}`;
|
|
155
125
|
let body;
|