mcp-duojie-image 0.7.0 → 0.8.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/README.md CHANGED
@@ -5,7 +5,9 @@ MCP server for image generation via duojie.games GPT image API.
5
5
  ## Features
6
6
 
7
7
  - Text-to-image generation with `gpt-image-2`
8
- - Reference image editing via local image paths, with optional image roles
8
+ - Reference image support with role-based endpoint routing:
9
+ - `reference` / `style_reference` / `composition_reference` / `insert_source` → `/v1/images/generations` (JSON body + base64 data URIs; stable endpoint)
10
+ - `edit_target` → `/v1/images/edits` (multipart form; for modifying an existing image)
9
11
  - Prompt structuring helpers for use case, asset type, constraints, and effective prompt echo
10
12
  - Optional chroma-key transparent background post-processing
11
13
  - Async submission with task status lookup
@@ -61,6 +63,8 @@ Supported `input_images.role` values:
61
63
  - `composition_reference`
62
64
  - `insert_source`
63
65
 
66
+ **Endpoint routing:** Images with the `edit_target` role are sent to `/v1/images/edits` (multipart form). All other roles (`reference`, `style_reference`, `composition_reference`, `insert_source`) are sent to `/v1/images/generations` as base64 data URIs in a JSON body. This mirrors Codex's imagegen skill, which treats style/composition/mood references as "generate" rather than "edit". If a request mixes `edit_target` with other roles, the edits endpoint is used.
67
+
64
68
  When both `input_images` and `reference_images` are provided, `input_images` takes precedence. `generate_image` returns `effective_prompt` so callers can diagnose the exact prompt sent upstream.
65
69
 
66
70
  Response:
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ const server = new McpServer({
34
34
  version: "0.1.0",
35
35
  });
36
36
  server.registerTool("generate_image", {
37
- description: "Generate image async with gpt-image-2. Return after tool call; don't wait/check.",
37
+ description: "Generate image async with gpt-image-2. Reference/style/composition images route through /v1/images/generations (stable); edit_target role routes through /v1/images/edits. Return after tool call; don't wait/check.",
38
38
  inputSchema: z.object({
39
39
  prompt: z.string().min(1).describe("Prompt"),
40
40
  output_dir: z.string().describe("Absolute output dir"),
@@ -105,6 +105,8 @@ server.registerTool("generate_image", {
105
105
  }
106
106
  const task = createTask();
107
107
  updateTask(task.id, { effectivePrompt: preparedPrompt.prompt });
108
+ // 只有 edit_target 角色才走 edits 端点;reference/style/composition 走 generations
109
+ const hasEditTarget = preparedPrompt.imageInputs?.some((img) => img.role === "edit_target") ?? false;
108
110
  // Start background generation (don't await)
109
111
  (async () => {
110
112
  try {
@@ -112,6 +114,7 @@ server.registerTool("generate_image", {
112
114
  let imageBuffer = await generateImage(config, {
113
115
  prompt: preparedPrompt.prompt,
114
116
  referenceImages: preparedPrompt.imagePaths,
117
+ useEditsEndpoint: hasEditTarget,
115
118
  size: args.size ?? "1K",
116
119
  aspectRatio: args.aspect_ratio,
117
120
  referenceMaxSide: args.preserve_reference_detail ? false : args.reference_max_side,
package/dist/prompt.js CHANGED
@@ -83,5 +83,6 @@ export function preparePrompt(opts) {
83
83
  return {
84
84
  prompt,
85
85
  imagePaths: inputImages?.map((image) => image.path) ?? [],
86
+ imageInputs: inputImages,
86
87
  };
87
88
  }
package/dist/provider.js CHANGED
@@ -105,12 +105,16 @@ async function generateOpenAIImage(config, opts) {
105
105
  const prompt = opts.prompt;
106
106
  const size = mapOpenAISize(opts.size, opts.aspectRatio);
107
107
  const hasReferenceImages = Boolean(opts.referenceImages?.length);
108
- const url = `${config.baseUrl}${hasReferenceImages ? "/v1/images/edits" : "/v1/images/generations"}`;
108
+ // edit_target → /v1/images/edits (multipart form)
109
+ // reference/style/composition → /v1/images/generations (JSON body + base64 data URIs)
110
+ const useEdits = hasReferenceImages && opts.useEditsEndpoint === true;
111
+ const url = `${config.baseUrl}${useEdits ? "/v1/images/edits" : "/v1/images/generations"}`;
109
112
  let body;
110
113
  let headers = {
111
114
  Authorization: `Bearer ${config.apiKey}`,
112
115
  };
113
- if (hasReferenceImages) {
116
+ if (useEdits) {
117
+ // edit_target: multipart form(修改已有图片)
114
118
  const form = new FormData();
115
119
  form.set("model", GPT_IMAGE_MODEL);
116
120
  form.set("prompt", prompt);
@@ -125,7 +129,24 @@ async function generateOpenAIImage(config, opts) {
125
129
  }
126
130
  body = form;
127
131
  }
132
+ else if (hasReferenceImages) {
133
+ // reference/style/composition: generations + base64 data URIs in JSON body
134
+ const imageUrls = [];
135
+ for (const imgPath of opts.referenceImages) {
136
+ const data = await resizeIfNeeded(imgPath, opts.referenceMaxSide);
137
+ imageUrls.push(`data:image/webp;base64,${data.toString("base64")}`);
138
+ }
139
+ headers = { ...headers, "Content-Type": "application/json" };
140
+ body = JSON.stringify({
141
+ model: GPT_IMAGE_MODEL,
142
+ prompt,
143
+ size,
144
+ image: imageUrls,
145
+ ...(opts.aspectRatio ? { aspect_ratio: opts.aspectRatio } : {}),
146
+ });
147
+ }
128
148
  else {
149
+ // 纯文本生成
129
150
  headers = { ...headers, "Content-Type": "application/json" };
130
151
  body = JSON.stringify({
131
152
  model: GPT_IMAGE_MODEL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-duojie-image",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "MCP server for image generation via duojie.games",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",