gpteam 0.1.22 → 0.1.24
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 +2 -2
- package/lib/config.js +2 -3
- package/lib/help.js +1 -1
- package/lib/image-mcp/image.js +162 -14
- package/lib/image-mcp/server.js +60 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,14 +16,14 @@ Client config writing follows the same safety pattern as cc-switch: keep Codex t
|
|
|
16
16
|
|
|
17
17
|
The Image MCP config uses cc-switch-style per-client env blocks. Codex writes `[mcp_servers.gpteam_image.env]`, OpenCode writes `mcp.gpteam_image.environment`, and Claude Code writes `mcpServers.gpteam_image.env` in `~/.claude.json`. The MCP receives `GPTEAM_API_KEY` and `GPTEAM_BASE_URL` from that MCP config, so it does not depend on Codex `auth.json` or inherited `OPENAI_API_KEY`.
|
|
18
18
|
|
|
19
|
-
The Image MCP exposes
|
|
19
|
+
The Image MCP exposes an async-first local job flow plus a legacy compatibility alias:
|
|
20
20
|
|
|
21
21
|
- `create_image_job`: recommended for normal use. It starts a local background image job and returns `job_id` quickly, which avoids losing the whole generation when a VPN, proxy, or client has a 60-second idle timeout.
|
|
22
22
|
- `get_image_job_status`: checks whether the local job is queued, running, succeeded, failed, canceled, or expired.
|
|
23
23
|
- `cancel_image_job`: cancels a queued/running local job.
|
|
24
24
|
- `download_image_result`: returns the completed file metadata and image content. Use `metadata_only`, `include_image`, and `include_revised_prompt` to control large result payloads.
|
|
25
25
|
- `get_capabilities`: returns supported sizes, formats, quality values, async support, cancellation semantics, queue limits, and image-to-image support.
|
|
26
|
-
- `generate_image`:
|
|
26
|
+
- `generate_image`: legacy compatibility alias. It now creates the same async job and returns `job_id` immediately instead of blocking until the image is complete.
|
|
27
27
|
|
|
28
28
|
Image MCP results are returned as stable JSON text and MCP `structuredContent`. Successful results include final file path, model, action, size, format, quality, byte size, SHA-256, MIME type, image dimensions, duration, retry count, `job_id`, `trace_id`, and optional `idempotency_key`. Error results use stable `error.code`, `error.message`, `error.retryable`, `error.stage`, `error.upstream_status`, and `error.trace_id` fields while keeping compatibility fields such as `category` and `http_status`.
|
|
29
29
|
|
package/lib/config.js
CHANGED
|
@@ -13,8 +13,7 @@ const IMAGE_MCP_ENABLED_TOOLS = [
|
|
|
13
13
|
'get_image_job_status',
|
|
14
14
|
'download_image_result',
|
|
15
15
|
'cancel_image_job',
|
|
16
|
-
'get_capabilities'
|
|
17
|
-
'generate_image'
|
|
16
|
+
'get_capabilities'
|
|
18
17
|
];
|
|
19
18
|
|
|
20
19
|
export const CLIENTS = [
|
|
@@ -63,7 +62,7 @@ export function writeCodexConfig(settings) {
|
|
|
63
62
|
`command = ${tomlString(mcpCommand.command)}`,
|
|
64
63
|
`args = [${mcpCommand.args.map((arg) => tomlString(arg)).join(', ')}]`,
|
|
65
64
|
'startup_timeout_sec = 20',
|
|
66
|
-
'tool_timeout_sec =
|
|
65
|
+
'tool_timeout_sec = 900',
|
|
67
66
|
`enabled_tools = [${IMAGE_MCP_ENABLED_TOOLS.map((name) => tomlString(name)).join(', ')}]`,
|
|
68
67
|
'default_tools_approval_mode = "prompt"',
|
|
69
68
|
'',
|
package/lib/help.js
CHANGED
package/lib/image-mcp/image.js
CHANGED
|
@@ -14,9 +14,32 @@ export const DEFAULT_BASE_URL = 'https://api.gpteamservices.com/v1';
|
|
|
14
14
|
export const DEFAULT_IMAGE_MODEL = 'gpt-image-2';
|
|
15
15
|
export { DEFAULT_IMAGE_FORMAT };
|
|
16
16
|
|
|
17
|
+
export const IMAGE_FORMATS = ['png', 'jpeg', 'webp'];
|
|
18
|
+
export const IMAGE_QUALITY_LEVELS = ['low', 'medium', 'high', 'auto'];
|
|
19
|
+
export const IMAGE_BACKGROUND_OPTIONS = ['auto', 'opaque'];
|
|
20
|
+
export const IMAGE_MODERATION_OPTIONS = ['auto', 'low'];
|
|
21
|
+
export const POPULAR_IMAGE_SIZES = [
|
|
22
|
+
{ label: '1K 方图', size: '1024x1024', aspect_ratio: '1:1' },
|
|
23
|
+
{ label: '1K 横图', size: '1536x1024', aspect_ratio: '3:2' },
|
|
24
|
+
{ label: '1K 竖图', size: '1024x1536', aspect_ratio: '2:3' },
|
|
25
|
+
{ label: '2K 方图', size: '2048x2048', aspect_ratio: '1:1' },
|
|
26
|
+
{ label: '2K 宽屏', size: '2048x1152', aspect_ratio: '16:9' },
|
|
27
|
+
{ label: '2K 竖幅', size: '1152x2048', aspect_ratio: '9:16' },
|
|
28
|
+
{ label: '4K 横图', size: '3840x2160', aspect_ratio: '16:9' },
|
|
29
|
+
{ label: '4K 竖图', size: '2160x3840', aspect_ratio: '9:16' },
|
|
30
|
+
{ label: '自动', size: 'auto', aspect_ratio: 'auto' }
|
|
31
|
+
];
|
|
32
|
+
export const IMAGE_SIZE_CONSTRAINTS = {
|
|
33
|
+
max_edge_px: 3840,
|
|
34
|
+
edge_multiple_px: 16,
|
|
35
|
+
max_long_to_short_ratio: 3,
|
|
36
|
+
min_total_pixels: 655360,
|
|
37
|
+
max_total_pixels: 8294400
|
|
38
|
+
};
|
|
39
|
+
|
|
17
40
|
const defaultMaxAttempts = 3;
|
|
18
41
|
const defaultRetryDelayMs = 800;
|
|
19
|
-
const defaultRequestTimeoutMs =
|
|
42
|
+
const defaultRequestTimeoutMs = 15 * 60 * 1000;
|
|
20
43
|
const defaultMaxConcurrentJobs = 2;
|
|
21
44
|
const defaultMaxQueuedJobs = 20;
|
|
22
45
|
const defaultJobTTLMS = 30 * 60 * 1000;
|
|
@@ -32,7 +55,7 @@ export function buildImageGenerationPayload(input = {}, options = {}) {
|
|
|
32
55
|
stream: true,
|
|
33
56
|
size: String(input.size || '1024x1024'),
|
|
34
57
|
quality: String(input.quality || 'high'),
|
|
35
|
-
output_format:
|
|
58
|
+
output_format: resolveImageOutputFormat(input)
|
|
36
59
|
};
|
|
37
60
|
const imageOptions = { ...options, home: options.home };
|
|
38
61
|
const images = normalizeInputImages(collectInputImageValues(input), imageOptions);
|
|
@@ -41,6 +64,7 @@ export function buildImageGenerationPayload(input = {}, options = {}) {
|
|
|
41
64
|
if (mask) payload.mask = { image_url: mask };
|
|
42
65
|
copyOptionalImageToolOption(payload, input, 'background');
|
|
43
66
|
copyOptionalImageToolOption(payload, input, 'moderation');
|
|
67
|
+
copyOptionalImageToolOption(payload, input, 'output_compression');
|
|
44
68
|
return payload;
|
|
45
69
|
}
|
|
46
70
|
|
|
@@ -88,15 +112,7 @@ export function normalizeBaseUrl(value) {
|
|
|
88
112
|
|
|
89
113
|
export async function generateImage(input = {}, options = {}) {
|
|
90
114
|
const startedAt = now(options);
|
|
91
|
-
|
|
92
|
-
if (!prompt) {
|
|
93
|
-
throw new ImageMCPError('prompt 不能为空', {
|
|
94
|
-
code: 'prompt_required',
|
|
95
|
-
category: 'parameter',
|
|
96
|
-
stage: 'validate',
|
|
97
|
-
retryable: false
|
|
98
|
-
});
|
|
99
|
-
}
|
|
115
|
+
validateImageInput(input, { requirePrompt: true });
|
|
100
116
|
const credentials = loadGPTeamCredentials(options);
|
|
101
117
|
const payload = buildImageGenerationPayload(input, options);
|
|
102
118
|
const fetchImpl = options.fetch || globalThis.fetch;
|
|
@@ -147,6 +163,11 @@ export function createImageJobStore(options = {}) {
|
|
|
147
163
|
}
|
|
148
164
|
|
|
149
165
|
export function createImageJob(input = {}, options = {}) {
|
|
166
|
+
try {
|
|
167
|
+
validateImageInput(input, { requirePrompt: true });
|
|
168
|
+
} catch (error) {
|
|
169
|
+
return resultFromError(error);
|
|
170
|
+
}
|
|
150
171
|
const store = options.store || defaultJobStore;
|
|
151
172
|
configureJobStore(store, options);
|
|
152
173
|
cleanupImageJobs(store);
|
|
@@ -280,6 +301,7 @@ export function getCapabilities(options = {}) {
|
|
|
280
301
|
ok: true,
|
|
281
302
|
model: DEFAULT_IMAGE_MODEL,
|
|
282
303
|
default_model: DEFAULT_IMAGE_MODEL,
|
|
304
|
+
preferred_tool: 'create_image_job',
|
|
283
305
|
supports_async: true,
|
|
284
306
|
supports_cancel: true,
|
|
285
307
|
cancel_semantics: 'best_effort',
|
|
@@ -288,9 +310,18 @@ export function getCapabilities(options = {}) {
|
|
|
288
310
|
supports_mask: true,
|
|
289
311
|
image_input_fields: ['images', 'image', 'image_path', 'image_paths', 'input_image', 'input_images'],
|
|
290
312
|
mask_input_fields: ['mask', 'mask_path'],
|
|
291
|
-
sizes:
|
|
292
|
-
|
|
293
|
-
|
|
313
|
+
sizes: POPULAR_IMAGE_SIZES.map((item) => item.size),
|
|
314
|
+
popular_sizes: POPULAR_IMAGE_SIZES,
|
|
315
|
+
size_presets: ['1K', '2K', '4K', 'auto'],
|
|
316
|
+
size_constraints: IMAGE_SIZE_CONSTRAINTS,
|
|
317
|
+
aspect_ratios: ['1:1', '3:2', '2:3', '16:9', '9:16', 'custom'],
|
|
318
|
+
supports_custom_size: true,
|
|
319
|
+
formats: IMAGE_FORMATS,
|
|
320
|
+
output_formats: IMAGE_FORMATS,
|
|
321
|
+
quality: IMAGE_QUALITY_LEVELS,
|
|
322
|
+
background: IMAGE_BACKGROUND_OPTIONS,
|
|
323
|
+
moderation: IMAGE_MODERATION_OPTIONS,
|
|
324
|
+
supports_output_compression: true,
|
|
294
325
|
max_prompt_length: 32000,
|
|
295
326
|
statuses: ['queued', 'running', 'succeeded', 'failed', 'canceled', 'expired'],
|
|
296
327
|
default_output_format: DEFAULT_IMAGE_FORMAT,
|
|
@@ -301,6 +332,22 @@ export function getCapabilities(options = {}) {
|
|
|
301
332
|
};
|
|
302
333
|
}
|
|
303
334
|
|
|
335
|
+
export function validateImageInput(input = {}, options = {}) {
|
|
336
|
+
if (options.requirePrompt && !String(input.prompt || '').trim()) {
|
|
337
|
+
throw imageParamError('prompt_required', 'prompt 不能为空', 'prompt', input.prompt, {
|
|
338
|
+
hint: '请先让用户提供图片提示词。'
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
validateImageSize(input.size);
|
|
342
|
+
validateEnumImageParam('quality', input.quality, IMAGE_QUALITY_LEVELS);
|
|
343
|
+
validateImageFormatParam(input);
|
|
344
|
+
validateEnumImageParam('background', input.background, IMAGE_BACKGROUND_OPTIONS, {
|
|
345
|
+
hint: 'gpt-image-2 不支持 transparent,请使用 auto 或 opaque。'
|
|
346
|
+
});
|
|
347
|
+
validateEnumImageParam('moderation', input.moderation, IMAGE_MODERATION_OPTIONS);
|
|
348
|
+
validateOutputCompression(input.output_compression);
|
|
349
|
+
}
|
|
350
|
+
|
|
304
351
|
async function fetchImageWithRetry(fetchImpl, credentials, payload, options) {
|
|
305
352
|
const maxAttempts = resolveBoundedInt(1, options.maxAttempts, options.env && options.env.GPTEAM_IMAGE_MAX_ATTEMPTS, defaultMaxAttempts);
|
|
306
353
|
const retryDelayMs = resolveBoundedInt(0, options.retryDelayMs, options.env && options.env.GPTEAM_IMAGE_RETRY_DELAY_MS, defaultRetryDelayMs);
|
|
@@ -722,6 +769,107 @@ function resolveRevisedPromptFlag(input) {
|
|
|
722
769
|
return true;
|
|
723
770
|
}
|
|
724
771
|
|
|
772
|
+
function validateImageSize(value) {
|
|
773
|
+
if (value === undefined || value === null || value === '') return;
|
|
774
|
+
const text = String(value).trim().toLowerCase();
|
|
775
|
+
if (text === 'auto') return;
|
|
776
|
+
const match = text.match(/^(\d{2,5})x(\d{2,5})$/);
|
|
777
|
+
if (!match) {
|
|
778
|
+
throw imageParamError('invalid_size', 'size 必须是 auto 或类似 1024x1024 的宽高格式。', 'size', value, {
|
|
779
|
+
supported_values: POPULAR_IMAGE_SIZES.map((item) => item.size),
|
|
780
|
+
hint: '常用:1024x1024、1536x1024、1024x1536、2048x2048、2048x1152、1152x2048、3840x2160、2160x3840。'
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
const width = Number(match[1]);
|
|
784
|
+
const height = Number(match[2]);
|
|
785
|
+
const longSide = Math.max(width, height);
|
|
786
|
+
const shortSide = Math.min(width, height);
|
|
787
|
+
const pixels = width * height;
|
|
788
|
+
const valid = width % IMAGE_SIZE_CONSTRAINTS.edge_multiple_px === 0 &&
|
|
789
|
+
height % IMAGE_SIZE_CONSTRAINTS.edge_multiple_px === 0 &&
|
|
790
|
+
longSide <= IMAGE_SIZE_CONSTRAINTS.max_edge_px &&
|
|
791
|
+
longSide / shortSide <= IMAGE_SIZE_CONSTRAINTS.max_long_to_short_ratio &&
|
|
792
|
+
pixels >= IMAGE_SIZE_CONSTRAINTS.min_total_pixels &&
|
|
793
|
+
pixels <= IMAGE_SIZE_CONSTRAINTS.max_total_pixels;
|
|
794
|
+
if (!valid) {
|
|
795
|
+
throw imageParamError('invalid_size', 'size 超出 gpt-image-2 支持范围。', 'size', value, {
|
|
796
|
+
constraints: IMAGE_SIZE_CONSTRAINTS,
|
|
797
|
+
supported_values: POPULAR_IMAGE_SIZES.map((item) => item.size),
|
|
798
|
+
hint: '宽高需为 16 的倍数,长边不超过 3840,长短边比例不超过 3:1。'
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function validateEnumImageParam(field, value, supportedValues, extra = {}) {
|
|
804
|
+
if (value === undefined || value === null || value === '') return;
|
|
805
|
+
const normalized = String(value).trim().toLowerCase();
|
|
806
|
+
if (supportedValues.includes(normalized)) return;
|
|
807
|
+
throw imageParamError(`invalid_${field}`, `${field} 参数不支持。`, field, value, {
|
|
808
|
+
supported_values: supportedValues,
|
|
809
|
+
...extra
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function validateImageFormatParam(input) {
|
|
814
|
+
const formatValue = input.format;
|
|
815
|
+
const outputFormatValue = input.output_format;
|
|
816
|
+
const format = normalizeDeclaredImageFormat(formatValue);
|
|
817
|
+
const outputFormat = normalizeDeclaredImageFormat(outputFormatValue);
|
|
818
|
+
if (formatValue !== undefined && formatValue !== null && formatValue !== '' && !IMAGE_FORMATS.includes(format)) {
|
|
819
|
+
throw invalidImageFormatError('format', formatValue);
|
|
820
|
+
}
|
|
821
|
+
if (outputFormatValue !== undefined && outputFormatValue !== null && outputFormatValue !== '' && !IMAGE_FORMATS.includes(outputFormat)) {
|
|
822
|
+
throw invalidImageFormatError('output_format', outputFormatValue);
|
|
823
|
+
}
|
|
824
|
+
if (format && outputFormat && format !== outputFormat) {
|
|
825
|
+
throw imageParamError('invalid_format_conflict', 'format 和 output_format 不一致。', 'output_format', outputFormatValue, {
|
|
826
|
+
format,
|
|
827
|
+
output_format: outputFormat,
|
|
828
|
+
hint: '两个字段同时传入时必须表示同一种格式,例如 jpg 和 jpeg 可以同时使用。'
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function validateOutputCompression(value) {
|
|
834
|
+
if (value === undefined || value === null || value === '') return;
|
|
835
|
+
const amount = Number(value);
|
|
836
|
+
if (Number.isInteger(amount) && amount >= 0 && amount <= 100) return;
|
|
837
|
+
throw imageParamError('invalid_output_compression', 'output_compression 必须是 0 到 100 的整数。', 'output_compression', value, {
|
|
838
|
+
supported_values: ['0-100']
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function resolveImageOutputFormat(input = {}) {
|
|
843
|
+
return normalizeImageFormat(input.output_format || input.format || DEFAULT_IMAGE_FORMAT);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function normalizeDeclaredImageFormat(value) {
|
|
847
|
+
if (value === undefined || value === null || value === '') return '';
|
|
848
|
+
const normalized = String(value).trim().toLowerCase();
|
|
849
|
+
return normalized === 'jpg' ? 'jpeg' : normalized;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function invalidImageFormatError(field, received) {
|
|
853
|
+
return imageParamError('invalid_format', 'format/output_format 参数不支持。', field, received, {
|
|
854
|
+
supported_values: IMAGE_FORMATS,
|
|
855
|
+
hint: '支持 png、jpeg、webp,jpg 会按 jpeg 处理。'
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function imageParamError(code, message, field, received, details = {}) {
|
|
860
|
+
return new ImageMCPError(message, {
|
|
861
|
+
code,
|
|
862
|
+
category: 'parameter',
|
|
863
|
+
stage: 'validate',
|
|
864
|
+
retryable: false,
|
|
865
|
+
details: {
|
|
866
|
+
field,
|
|
867
|
+
received,
|
|
868
|
+
...details
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
725
873
|
function resolveCodexHome(env, home) {
|
|
726
874
|
return expandHome(firstNonEmpty(env.GPTEAM_CODEX_HOME, env.CODEX_HOME, path.join(home, '.codex')), home);
|
|
727
875
|
}
|
package/lib/image-mcp/server.js
CHANGED
|
@@ -6,87 +6,119 @@ import {
|
|
|
6
6
|
cancelImageJob,
|
|
7
7
|
createImageJob,
|
|
8
8
|
downloadImageResult,
|
|
9
|
-
generateImage,
|
|
10
9
|
getCapabilities,
|
|
10
|
+
IMAGE_BACKGROUND_OPTIONS,
|
|
11
|
+
IMAGE_FORMATS,
|
|
12
|
+
IMAGE_MODERATION_OPTIONS,
|
|
13
|
+
IMAGE_QUALITY_LEVELS,
|
|
14
|
+
POPULAR_IMAGE_SIZES,
|
|
11
15
|
getImageJobStatus,
|
|
12
16
|
resultFromError,
|
|
13
17
|
structuredToolResult,
|
|
14
18
|
toolResultContent
|
|
15
19
|
} from './image.js';
|
|
16
20
|
|
|
21
|
+
const imageToolPromptingInstruction = '调用前请先确认用户真实需求:尺寸或比例、质量、输出格式、保存目录或文件名。缺少这些关键信息时应先追问,不要直接默认生成。常规生图请优先用 create_image_job,避免长任务占住 MCP 连接。';
|
|
22
|
+
|
|
17
23
|
const imageInputProperties = {
|
|
18
24
|
prompt: {
|
|
19
25
|
type: 'string',
|
|
20
|
-
description: '
|
|
26
|
+
description: '图片提示词。需要尽量具体,若用户只给模糊需求,应先追问风格、主体、场景和用途。'
|
|
21
27
|
},
|
|
22
28
|
size: {
|
|
23
29
|
type: 'string',
|
|
24
|
-
description:
|
|
25
|
-
default: '1024x1024'
|
|
30
|
+
description: `图片尺寸。支持 auto、常用尺寸 ${POPULAR_IMAGE_SIZES.map((item) => `${item.label} ${item.size}`).join('、')},也支持满足约束的自定义宽高,例如 9:16 可用 1152x2048 或 2160x3840。`
|
|
26
31
|
},
|
|
27
32
|
quality: {
|
|
28
33
|
type: 'string',
|
|
29
|
-
description: '
|
|
30
|
-
|
|
34
|
+
description: '图片质量。生图前应让用户确认速度优先还是质量优先。',
|
|
35
|
+
enum: IMAGE_QUALITY_LEVELS
|
|
31
36
|
},
|
|
32
37
|
format: {
|
|
33
38
|
type: 'string',
|
|
34
|
-
description: '
|
|
35
|
-
enum:
|
|
36
|
-
|
|
39
|
+
description: '输出图片格式。',
|
|
40
|
+
enum: IMAGE_FORMATS
|
|
41
|
+
},
|
|
42
|
+
output_format: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: '输出图片格式别名,和 format 等价。',
|
|
45
|
+
enum: IMAGE_FORMATS
|
|
37
46
|
},
|
|
38
47
|
output_path: {
|
|
39
48
|
type: 'string',
|
|
40
|
-
description: '
|
|
49
|
+
description: '保存路径,可以是目录或完整文件路径。缺少时建议先询问用户想保存到哪里。'
|
|
41
50
|
},
|
|
42
51
|
overwrite: {
|
|
43
52
|
type: 'boolean',
|
|
44
|
-
description: '
|
|
53
|
+
description: '文件已存在时是否覆盖。默认 false,会自动生成 -v2、-v3 等新文件名。',
|
|
45
54
|
default: false
|
|
46
55
|
},
|
|
47
56
|
idempotency_key: {
|
|
48
57
|
type: 'string',
|
|
49
|
-
description: '
|
|
58
|
+
description: '幂等键。客户端超时后用同一个 key 重试,会复用同一进程内的本地任务,避免重复生成。'
|
|
59
|
+
},
|
|
60
|
+
image: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: '单张输入图片,支持 data URL、HTTPS URL 或本地文件路径,用于图生图或编辑。'
|
|
50
63
|
},
|
|
51
64
|
images: {
|
|
52
65
|
type: 'array',
|
|
53
66
|
items: { type: 'string' },
|
|
54
|
-
description: '
|
|
67
|
+
description: '多张输入图片,支持 data URL、HTTPS URL 或本地文件路径,用于图生图或编辑。'
|
|
55
68
|
},
|
|
56
69
|
image_path: {
|
|
57
70
|
type: 'string',
|
|
58
|
-
description: '
|
|
71
|
+
description: '单张本地输入图片路径别名,用于图生图或编辑。'
|
|
59
72
|
},
|
|
60
73
|
image_paths: {
|
|
61
74
|
type: 'array',
|
|
62
75
|
items: { type: 'string' },
|
|
63
|
-
description: '
|
|
76
|
+
description: '多张本地输入图片路径别名,用于图生图或编辑。'
|
|
64
77
|
},
|
|
65
78
|
input_image: {
|
|
66
79
|
type: 'string',
|
|
67
|
-
description: '
|
|
80
|
+
description: '单张输入图片别名,支持 data URL、HTTPS URL 或本地文件路径。'
|
|
68
81
|
},
|
|
69
82
|
input_images: {
|
|
70
83
|
type: 'array',
|
|
71
84
|
items: { type: 'string' },
|
|
72
|
-
description: '
|
|
85
|
+
description: '多张输入图片别名,支持 data URL、HTTPS URL 或本地文件路径。'
|
|
73
86
|
},
|
|
74
87
|
mask: {
|
|
75
88
|
type: 'string',
|
|
76
|
-
description: '
|
|
89
|
+
description: '编辑遮罩图片,支持 data URL、HTTPS URL 或本地文件路径。'
|
|
77
90
|
},
|
|
78
91
|
mask_path: {
|
|
79
92
|
type: 'string',
|
|
80
|
-
description: '
|
|
93
|
+
description: '本地遮罩图片路径别名。'
|
|
81
94
|
},
|
|
82
95
|
input_fidelity: {
|
|
83
96
|
type: 'string',
|
|
84
|
-
description: '
|
|
97
|
+
description: '兼容字段。当前 GPTeam Image 2 桥接会忽略该字段,因为上游 Codex 图片工具会拒绝 edits 中的该参数。',
|
|
85
98
|
enum: ['low', 'high']
|
|
86
99
|
},
|
|
100
|
+
background: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
description: '背景策略。gpt-image-2 当前支持 auto 或 opaque。',
|
|
103
|
+
enum: IMAGE_BACKGROUND_OPTIONS
|
|
104
|
+
},
|
|
105
|
+
moderation: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
description: '内容安全策略。通常保持 auto,需要更低过滤强度时可用 low。',
|
|
108
|
+
enum: IMAGE_MODERATION_OPTIONS
|
|
109
|
+
},
|
|
110
|
+
output_compression: {
|
|
111
|
+
type: 'integer',
|
|
112
|
+
description: '输出压缩比例,0 到 100 的整数。'
|
|
113
|
+
},
|
|
114
|
+
include_revised_prompt: {
|
|
115
|
+
type: 'boolean',
|
|
116
|
+
description: '是否返回上游修订后的提示词。',
|
|
117
|
+
default: true
|
|
118
|
+
},
|
|
87
119
|
return_revised_prompt: {
|
|
88
120
|
type: 'boolean',
|
|
89
|
-
description: '
|
|
121
|
+
description: '是否返回上游修订后的提示词,兼容旧字段。',
|
|
90
122
|
default: true
|
|
91
123
|
}
|
|
92
124
|
};
|
|
@@ -94,7 +126,7 @@ const imageInputProperties = {
|
|
|
94
126
|
const tools = [
|
|
95
127
|
{
|
|
96
128
|
name: 'create_image_job',
|
|
97
|
-
description:
|
|
129
|
+
description: `推荐常规使用。创建本地后台 GPTeam Image 2 任务并立即返回 job_id。${imageToolPromptingInstruction}`,
|
|
98
130
|
inputSchema: {
|
|
99
131
|
type: 'object',
|
|
100
132
|
properties: imageInputProperties,
|
|
@@ -104,22 +136,22 @@ const tools = [
|
|
|
104
136
|
},
|
|
105
137
|
{
|
|
106
138
|
name: 'get_image_job_status',
|
|
107
|
-
description: '
|
|
139
|
+
description: '查询本地 GPTeam Image 2 图片任务状态。',
|
|
108
140
|
inputSchema: jobIDSchema()
|
|
109
141
|
},
|
|
110
142
|
{
|
|
111
143
|
name: 'cancel_image_job',
|
|
112
|
-
description: '
|
|
144
|
+
description: '取消仍在 queued 或 running 的本地 GPTeam Image 2 图片任务。取消是 best-effort,上游已开始生成时不保证同步取消。',
|
|
113
145
|
inputSchema: jobIDSchema()
|
|
114
146
|
},
|
|
115
147
|
{
|
|
116
148
|
name: 'download_image_result',
|
|
117
|
-
description: '
|
|
149
|
+
description: '下载已完成图片任务的本地文件元数据和图片内容,可选择只返回 metadata。',
|
|
118
150
|
inputSchema: downloadSchema()
|
|
119
151
|
},
|
|
120
152
|
{
|
|
121
153
|
name: 'get_capabilities',
|
|
122
|
-
description: '
|
|
154
|
+
description: '返回 GPTeam Image MCP 能力,包括支持尺寸、格式、质量、异步任务、取消语义、队列上限和参数约束。',
|
|
123
155
|
inputSchema: {
|
|
124
156
|
type: 'object',
|
|
125
157
|
properties: {},
|
|
@@ -128,7 +160,7 @@ const tools = [
|
|
|
128
160
|
},
|
|
129
161
|
{
|
|
130
162
|
name: 'generate_image',
|
|
131
|
-
description:
|
|
163
|
+
description: `兼容旧调用的异步别名。为避免长图或高质量任务导致 MCP 连接断开,它不会等待图片完成,而是创建任务并立即返回 job_id;之后必须用 get_image_job_status 和 download_image_result 获取结果。${imageToolPromptingInstruction}`,
|
|
132
164
|
inputSchema: {
|
|
133
165
|
type: 'object',
|
|
134
166
|
properties: imageInputProperties,
|
|
@@ -192,7 +224,7 @@ export async function callImageTool(toolName, args = {}, deps = {}) {
|
|
|
192
224
|
case 'get_capabilities':
|
|
193
225
|
return getCapabilities(deps);
|
|
194
226
|
case 'generate_image':
|
|
195
|
-
return
|
|
227
|
+
return createImageJob(args || {}, deps);
|
|
196
228
|
default:
|
|
197
229
|
throw new McpError(ErrorCode.InvalidParams, `未知工具:${toolName}`);
|
|
198
230
|
}
|