jimeng-cli 0.2.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,831 @@
1
+ import {
2
+ DEFAULT_IMAGE_MODEL,
3
+ DEFAULT_MODEL,
4
+ buildRegionInfo,
5
+ environment_default,
6
+ generateImageComposition,
7
+ generateImages,
8
+ generateVideo,
9
+ getLiveModels,
10
+ getTaskResponse,
11
+ session_pool_default,
12
+ util_default,
13
+ waitForTaskResponse
14
+ } from "../chunk-JZY62VNI.js";
15
+
16
+ // src/mcp/index.ts
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+
19
+ // src/mcp/config.ts
20
+ function parseBoolean(value, defaultValue) {
21
+ if (value == null) return defaultValue;
22
+ const normalized = value.trim().toLowerCase();
23
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
24
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
25
+ return defaultValue;
26
+ }
27
+ function parseNumber(value, defaultValue) {
28
+ if (value == null) return defaultValue;
29
+ const parsed = Number(value);
30
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultValue;
31
+ }
32
+ function loadMcpConfig() {
33
+ var _a;
34
+ const apiToken = (_a = process.env.JIMENG_API_TOKEN) == null ? void 0 : _a.trim();
35
+ return {
36
+ apiToken: apiToken || void 0,
37
+ httpTimeoutMs: parseNumber(process.env.MCP_HTTP_TIMEOUT_MS, 12e4),
38
+ enableAdvancedTools: parseBoolean(process.env.MCP_ENABLE_ADVANCED_TOOLS, true),
39
+ requireRunConfirm: parseBoolean(process.env.MCP_REQUIRE_RUN_CONFIRM, true)
40
+ };
41
+ }
42
+
43
+ // src/mcp/server.ts
44
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
45
+
46
+ // src/mcp/client.ts
47
+ import fs from "fs";
48
+ import path from "path";
49
+ function resolveTaskType(value) {
50
+ return value === "video" ? "video" : "image";
51
+ }
52
+ var JimengApiClient = class {
53
+ defaultToken;
54
+ tokenPoolReady = false;
55
+ constructor(config) {
56
+ this.defaultToken = config.apiToken;
57
+ }
58
+ resolveToken(options) {
59
+ return (options == null ? void 0 : options.token) || this.defaultToken;
60
+ }
61
+ async ensureTokenPoolReady() {
62
+ if (this.tokenPoolReady) return;
63
+ await session_pool_default.init();
64
+ this.tokenPoolReady = true;
65
+ }
66
+ async pickModelToken(requestedModel, taskType, options, requiredCapabilityTags = []) {
67
+ await this.ensureTokenPoolReady();
68
+ const token = this.resolveToken(options);
69
+ const tokenPick = session_pool_default.pickTokenForRequest({
70
+ authorization: token ? `Bearer ${token}` : void 0,
71
+ requestedModel,
72
+ taskType,
73
+ requiredCapabilityTags
74
+ });
75
+ if (!tokenPick.token || !tokenPick.region) {
76
+ throw new Error(tokenPick.reason || "Missing available token for model request");
77
+ }
78
+ return {
79
+ token: tokenPick.token,
80
+ regionInfo: buildRegionInfo(tokenPick.region)
81
+ };
82
+ }
83
+ async pickTaskToken(options, type = "image") {
84
+ await this.ensureTokenPoolReady();
85
+ const token = this.resolveToken(options);
86
+ if (token) {
87
+ const entry = session_pool_default.getTokenEntry(token);
88
+ if (!(entry == null ? void 0 : entry.region)) {
89
+ throw new Error("Missing region for token. Register token with region in token-pool.");
90
+ }
91
+ return { token, regionInfo: buildRegionInfo(entry.region) };
92
+ }
93
+ const candidates = session_pool_default.getEntries(false).filter((item) => item.enabled && item.live !== false && item.region).filter((item) => {
94
+ var _a;
95
+ const modelHint = type === "video" ? "video" : "jimeng";
96
+ return !((_a = item.allowedModels) == null ? void 0 : _a.length) || item.allowedModels.some((m) => m.includes(modelHint));
97
+ });
98
+ if (candidates.length === 0) {
99
+ throw new Error("No token available for task request. Configure token-pool or pass token.");
100
+ }
101
+ return { token: candidates[0].token, regionInfo: buildRegionInfo(candidates[0].region) };
102
+ }
103
+ async healthCheck() {
104
+ return "pong";
105
+ }
106
+ async listModels(options) {
107
+ var _a;
108
+ await this.ensureTokenPoolReady();
109
+ const token = this.resolveToken(options);
110
+ const authorization = token ? `Bearer ${token}` : void 0;
111
+ const region = token ? (_a = session_pool_default.getTokenEntry(token)) == null ? void 0 : _a.region : void 0;
112
+ const result = await getLiveModels(authorization, region);
113
+ return {
114
+ source: result.source,
115
+ data: result.data
116
+ };
117
+ }
118
+ async generateImage(body, options) {
119
+ const model = typeof body.model === "string" && body.model.trim().length > 0 ? body.model : DEFAULT_IMAGE_MODEL;
120
+ const prompt = String(body.prompt || "");
121
+ const tokenCtx = await this.pickModelToken(model, "image", options);
122
+ const responseFormat = body.response_format === "b64_json" ? "b64_json" : "url";
123
+ const imageResult = await generateImages(
124
+ model,
125
+ prompt,
126
+ {
127
+ ratio: body.ratio,
128
+ resolution: body.resolution,
129
+ sampleStrength: body.sample_strength,
130
+ negativePrompt: body.negative_prompt,
131
+ intelligentRatio: body.intelligent_ratio,
132
+ wait: body.wait,
133
+ waitTimeoutSeconds: body.wait_timeout_seconds,
134
+ pollIntervalMs: body.poll_interval_ms
135
+ },
136
+ tokenCtx.token,
137
+ tokenCtx.regionInfo
138
+ );
139
+ if (!Array.isArray(imageResult)) {
140
+ return imageResult;
141
+ }
142
+ const data = responseFormat === "b64_json" ? (await Promise.all(imageResult.map((url) => util_default.fetchFileBASE64(url)))).map((b64) => ({ b64_json: b64 })) : imageResult.map((url) => ({ url }));
143
+ return {
144
+ created: util_default.unixTimestamp(),
145
+ data
146
+ };
147
+ }
148
+ async editImage(body, options) {
149
+ const model = typeof body.model === "string" && body.model.trim().length > 0 ? body.model : DEFAULT_IMAGE_MODEL;
150
+ const prompt = String(body.prompt || "");
151
+ const images = Array.isArray(body.images) ? body.images.filter((item) => typeof item === "string") : [];
152
+ const tokenCtx = await this.pickModelToken(model, "image", options);
153
+ const responseFormat = body.response_format === "b64_json" ? "b64_json" : "url";
154
+ const compositionResult = await generateImageComposition(
155
+ model,
156
+ prompt,
157
+ images,
158
+ {
159
+ ratio: body.ratio,
160
+ resolution: body.resolution,
161
+ sampleStrength: body.sample_strength,
162
+ negativePrompt: body.negative_prompt,
163
+ intelligentRatio: body.intelligent_ratio,
164
+ wait: body.wait,
165
+ waitTimeoutSeconds: body.wait_timeout_seconds,
166
+ pollIntervalMs: body.poll_interval_ms
167
+ },
168
+ tokenCtx.token,
169
+ tokenCtx.regionInfo
170
+ );
171
+ if (!Array.isArray(compositionResult)) {
172
+ return compositionResult;
173
+ }
174
+ const data = responseFormat === "b64_json" ? (await Promise.all(compositionResult.map((url) => util_default.fetchFileBASE64(url)))).map((b64) => ({ b64_json: b64 })) : compositionResult.map((url) => ({ url }));
175
+ return {
176
+ created: util_default.unixTimestamp(),
177
+ data,
178
+ input_images: images.length,
179
+ composition_type: "multi_image_synthesis"
180
+ };
181
+ }
182
+ async generateVideo(body, options) {
183
+ const model = typeof body.model === "string" && body.model.trim().length > 0 ? body.model : DEFAULT_MODEL;
184
+ const prompt = String(body.prompt || "");
185
+ const functionMode = typeof body.functionMode === "string" ? body.functionMode : "first_last_frames";
186
+ const requiredTags = functionMode === "omni_reference" ? ["omni_reference"] : [];
187
+ const tokenCtx = await this.pickModelToken(model, "video", options, requiredTags);
188
+ const videoResult = await generateVideo(
189
+ model,
190
+ prompt,
191
+ {
192
+ ratio: body.ratio,
193
+ resolution: body.resolution,
194
+ duration: body.duration,
195
+ filePaths: body.filePaths || body.file_paths,
196
+ files: body.files,
197
+ httpRequest: { body },
198
+ functionMode,
199
+ wait: body.wait,
200
+ waitTimeoutSeconds: body.wait_timeout_seconds,
201
+ pollIntervalMs: body.poll_interval_ms
202
+ },
203
+ tokenCtx.token,
204
+ tokenCtx.regionInfo
205
+ );
206
+ if (typeof videoResult !== "string") {
207
+ return videoResult;
208
+ }
209
+ if (body.response_format === "b64_json") {
210
+ const videoBase64 = await util_default.fetchFileBASE64(videoResult);
211
+ return {
212
+ created: util_default.unixTimestamp(),
213
+ data: [{ b64_json: videoBase64, revised_prompt: prompt }]
214
+ };
215
+ }
216
+ return {
217
+ created: util_default.unixTimestamp(),
218
+ data: [{ url: videoResult, revised_prompt: prompt }]
219
+ };
220
+ }
221
+ async getTask(taskId, options, query) {
222
+ const type = (query == null ? void 0 : query.type) === "video" ? "video" : (query == null ? void 0 : query.type) === "image" ? "image" : void 0;
223
+ const tokenCtx = await this.pickTaskToken(options, resolveTaskType(type));
224
+ return getTaskResponse(taskId, tokenCtx.token, tokenCtx.regionInfo, {
225
+ type,
226
+ responseFormat: (query == null ? void 0 : query.response_format) === "b64_json" ? "b64_json" : "url"
227
+ });
228
+ }
229
+ async waitTask(taskId, body, options) {
230
+ const type = body.type === "video" ? "video" : body.type === "image" ? "image" : void 0;
231
+ const tokenCtx = await this.pickTaskToken(options, resolveTaskType(type));
232
+ return waitForTaskResponse(taskId, tokenCtx.token, tokenCtx.regionInfo, {
233
+ type,
234
+ responseFormat: body.response_format === "b64_json" ? "b64_json" : "url",
235
+ waitTimeoutSeconds: body.wait_timeout_seconds,
236
+ pollIntervalMs: body.poll_interval_ms
237
+ });
238
+ }
239
+ async generateVideoOmni(body, options, uploadFiles = []) {
240
+ const files = {};
241
+ for (const file of uploadFiles) {
242
+ files[file.fieldName] = {
243
+ filepath: file.filePath,
244
+ originalFilename: path.basename(file.filePath)
245
+ };
246
+ if (!fs.existsSync(file.filePath)) {
247
+ throw new Error(`Local file not found: ${file.filePath}`);
248
+ }
249
+ }
250
+ return this.generateVideo(
251
+ {
252
+ ...body,
253
+ functionMode: "omni_reference",
254
+ files
255
+ },
256
+ options
257
+ );
258
+ }
259
+ };
260
+
261
+ // src/mcp/errors.ts
262
+ import axios from "axios";
263
+ var McpToolError = class extends Error {
264
+ code;
265
+ details;
266
+ constructor(code, message, details) {
267
+ super(message);
268
+ this.code = code;
269
+ this.details = details;
270
+ }
271
+ };
272
+ function normalizeToolError(error) {
273
+ var _a, _b, _c;
274
+ if (error instanceof McpToolError) return error;
275
+ if (axios.isAxiosError(error)) {
276
+ if (!error.response) {
277
+ return new McpToolError(
278
+ "NETWORK_ERROR",
279
+ "Failed to reach jimeng-cli service",
280
+ error.message
281
+ );
282
+ }
283
+ const status = error.response.status;
284
+ const message = String(((_b = (_a = error.response.data) == null ? void 0 : _a.error) == null ? void 0 : _b.message) || ((_c = error.response.data) == null ? void 0 : _c.message) || error.message);
285
+ if (status === 401 || status === 403) {
286
+ return new McpToolError("AUTH_ERROR", message, error.response.data);
287
+ }
288
+ if (status >= 400 && status < 500) {
289
+ return new McpToolError("VALIDATION_ERROR", message, error.response.data);
290
+ }
291
+ if (status >= 500) {
292
+ return new McpToolError("UPSTREAM_ERROR", message, error.response.data);
293
+ }
294
+ }
295
+ if (error instanceof Error) {
296
+ return new McpToolError("INTERNAL_ERROR", error.message);
297
+ }
298
+ return new McpToolError("INTERNAL_ERROR", "Unknown internal error");
299
+ }
300
+ function formatToolError(error) {
301
+ const normalized = normalizeToolError(error);
302
+ return `[${normalized.code}] ${normalized.message}`;
303
+ }
304
+
305
+ // src/mcp/guards.ts
306
+ function assertRunConfirm(config, confirm) {
307
+ if (!config.requireRunConfirm) return;
308
+ if (confirm === "RUN") return;
309
+ throw new McpToolError(
310
+ "VALIDATION_ERROR",
311
+ 'This tool requires explicit confirmation: set "confirm" to "RUN".'
312
+ );
313
+ }
314
+
315
+ // src/mcp/schemas.ts
316
+ import * as z from "zod";
317
+ function buildIndexedUrlFields(prefix, max) {
318
+ return Object.fromEntries(
319
+ Array.from({ length: max }, (_, index) => [
320
+ `${prefix}_${index + 1}`,
321
+ z.string().url().optional()
322
+ ])
323
+ );
324
+ }
325
+ function normalizeUniqueValues(values) {
326
+ return [...new Set(values.map((item) => item.trim()).filter(Boolean))];
327
+ }
328
+ var healthCheckInputSchema = z.object({});
329
+ var listModelsInputSchema = z.object({
330
+ token: z.string().optional()
331
+ });
332
+ var generateImageInputSchema = z.object({
333
+ prompt: z.string().min(1),
334
+ model: z.string().optional(),
335
+ negative_prompt: z.string().optional(),
336
+ ratio: z.string().optional(),
337
+ resolution: z.string().optional(),
338
+ intelligent_ratio: z.boolean().optional(),
339
+ sample_strength: z.number().optional(),
340
+ response_format: z.enum(["url", "b64_json"]).optional(),
341
+ wait: z.boolean().optional(),
342
+ wait_timeout_seconds: z.number().int().positive().optional(),
343
+ poll_interval_ms: z.number().int().positive().optional(),
344
+ token: z.string().optional(),
345
+ confirm: z.string().optional()
346
+ });
347
+ var editImageInputSchema = z.object({
348
+ prompt: z.string().min(1),
349
+ images: z.array(z.string().url()).min(1).max(10),
350
+ model: z.string().optional(),
351
+ negative_prompt: z.string().optional(),
352
+ ratio: z.string().optional(),
353
+ resolution: z.string().optional(),
354
+ intelligent_ratio: z.boolean().optional(),
355
+ sample_strength: z.number().optional(),
356
+ response_format: z.enum(["url", "b64_json"]).optional(),
357
+ wait: z.boolean().optional(),
358
+ wait_timeout_seconds: z.number().int().positive().optional(),
359
+ poll_interval_ms: z.number().int().positive().optional(),
360
+ token: z.string().optional(),
361
+ confirm: z.string().optional()
362
+ });
363
+ var generateVideoInputSchema = z.object({
364
+ prompt: z.string().min(1),
365
+ model: z.string().optional(),
366
+ ratio: z.string().optional(),
367
+ resolution: z.string().optional(),
368
+ duration: z.number().int().min(4).max(15).optional(),
369
+ wait: z.boolean().optional(),
370
+ wait_timeout_seconds: z.number().int().positive().optional(),
371
+ poll_interval_ms: z.number().int().positive().optional(),
372
+ token: z.string().optional(),
373
+ confirm: z.string().optional()
374
+ });
375
+ var generateVideoOmniInputSchema = z.object({
376
+ prompt: z.string().min(1),
377
+ model: z.string().optional(),
378
+ ratio: z.string().optional(),
379
+ resolution: z.string().optional(),
380
+ duration: z.number().int().min(4).max(15).optional(),
381
+ response_format: z.enum(["url", "b64_json"]).optional(),
382
+ wait: z.boolean().optional(),
383
+ wait_timeout_seconds: z.number().int().positive().optional(),
384
+ poll_interval_ms: z.number().int().positive().optional(),
385
+ file_paths: z.array(z.string().url()).max(9).optional(),
386
+ filePaths: z.array(z.string().url()).max(9).optional(),
387
+ image_urls: z.array(z.string().url()).max(9).optional(),
388
+ video_urls: z.array(z.string().url()).max(3).optional(),
389
+ image_files: z.array(z.string()).max(9).optional(),
390
+ video_files: z.array(z.string()).max(3).optional(),
391
+ ...buildIndexedUrlFields("image_file", 9),
392
+ ...buildIndexedUrlFields("video_file", 3),
393
+ token: z.string().optional(),
394
+ confirm: z.string().optional()
395
+ }).superRefine((value, ctx) => {
396
+ var _a, _b;
397
+ const imageSlotUrls = Array.from({ length: 9 }, (_, index) => value[`image_file_${index + 1}`]);
398
+ const videoSlotUrls = Array.from({ length: 3 }, (_, index) => value[`video_file_${index + 1}`]);
399
+ const imageCount = normalizeUniqueValues(value.image_urls || []).length + (((_a = value.image_files) == null ? void 0 : _a.length) || 0) + normalizeUniqueValues([...value.file_paths || [], ...value.filePaths || []]).length + normalizeUniqueValues(imageSlotUrls.filter((item) => typeof item === "string")).length;
400
+ const videoCount = normalizeUniqueValues(value.video_urls || []).length + (((_b = value.video_files) == null ? void 0 : _b.length) || 0) + normalizeUniqueValues(videoSlotUrls.filter((item) => typeof item === "string")).length;
401
+ if (imageCount > 9) {
402
+ ctx.addIssue({
403
+ code: z.ZodIssueCode.custom,
404
+ message: "Omni mode supports at most 9 images."
405
+ });
406
+ }
407
+ if (videoCount > 3) {
408
+ ctx.addIssue({
409
+ code: z.ZodIssueCode.custom,
410
+ message: "Omni mode supports at most 3 videos."
411
+ });
412
+ }
413
+ if (imageCount + videoCount > 12) {
414
+ ctx.addIssue({
415
+ code: z.ZodIssueCode.custom,
416
+ message: "Omni mode supports at most 12 total materials."
417
+ });
418
+ }
419
+ if (imageCount + videoCount === 0) {
420
+ ctx.addIssue({
421
+ code: z.ZodIssueCode.custom,
422
+ message: "Omni mode requires at least one material."
423
+ });
424
+ }
425
+ });
426
+ var getTaskInputSchema = z.object({
427
+ task_id: z.string().min(1),
428
+ type: z.enum(["image", "video"]).optional(),
429
+ response_format: z.enum(["url", "b64_json"]).optional(),
430
+ token: z.string().optional()
431
+ });
432
+ var waitTaskInputSchema = z.object({
433
+ task_id: z.string().min(1),
434
+ type: z.enum(["image", "video"]).optional(),
435
+ response_format: z.enum(["url", "b64_json"]).optional(),
436
+ wait_timeout_seconds: z.number().int().positive().optional(),
437
+ poll_interval_ms: z.number().int().positive().optional(),
438
+ token: z.string().optional()
439
+ });
440
+
441
+ // src/mcp/result.ts
442
+ function toStructuredContent(data) {
443
+ if (data != null && typeof data === "object" && !Array.isArray(data)) {
444
+ return data;
445
+ }
446
+ return { data };
447
+ }
448
+ function toToolResult(data) {
449
+ return {
450
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
451
+ structuredContent: toStructuredContent(data)
452
+ };
453
+ }
454
+ async function withToolError(fn) {
455
+ try {
456
+ return await fn();
457
+ } catch (error) {
458
+ throw new Error(formatToolError(error));
459
+ }
460
+ }
461
+
462
+ // src/mcp/tool-factory.ts
463
+ function registerSafeTool(server, name, options, handler) {
464
+ const { title, description, inputSchema, annotations } = options;
465
+ server.registerTool(
466
+ name,
467
+ {
468
+ title,
469
+ description,
470
+ inputSchema,
471
+ ...annotations ? { annotations } : {}
472
+ },
473
+ (async (args) => withToolError(async () => {
474
+ const parsedArgs = inputSchema.parse(args);
475
+ const result = await handler(parsedArgs);
476
+ return toToolResult(result);
477
+ }))
478
+ );
479
+ }
480
+
481
+ // src/mcp/tools/edit-image.ts
482
+ function registerEditImageTool({ server, config, client }) {
483
+ registerSafeTool(
484
+ server,
485
+ "edit_image",
486
+ {
487
+ title: "Edit Image",
488
+ description: "Compose image from prompt and image URLs",
489
+ inputSchema: editImageInputSchema
490
+ },
491
+ async (args) => {
492
+ assertRunConfirm(config, args.confirm);
493
+ return client.editImage(
494
+ {
495
+ prompt: args.prompt,
496
+ images: args.images,
497
+ model: args.model,
498
+ negative_prompt: args.negative_prompt,
499
+ ratio: args.ratio,
500
+ resolution: args.resolution || "1k",
501
+ intelligent_ratio: args.intelligent_ratio,
502
+ sample_strength: args.sample_strength,
503
+ response_format: args.response_format,
504
+ wait: args.wait,
505
+ wait_timeout_seconds: args.wait_timeout_seconds,
506
+ poll_interval_ms: args.poll_interval_ms
507
+ },
508
+ { token: args.token }
509
+ );
510
+ }
511
+ );
512
+ }
513
+
514
+ // src/mcp/tools/generate-image.ts
515
+ function registerGenerateImageTool({ server, config, client }) {
516
+ registerSafeTool(
517
+ server,
518
+ "generate_image",
519
+ {
520
+ title: "Generate Image",
521
+ description: "Generate image from prompt",
522
+ inputSchema: generateImageInputSchema
523
+ },
524
+ async (args) => {
525
+ assertRunConfirm(config, args.confirm);
526
+ return client.generateImage(
527
+ {
528
+ prompt: args.prompt,
529
+ model: args.model,
530
+ negative_prompt: args.negative_prompt,
531
+ ratio: args.ratio,
532
+ resolution: args.resolution || "1k",
533
+ intelligent_ratio: args.intelligent_ratio,
534
+ sample_strength: args.sample_strength,
535
+ response_format: args.response_format,
536
+ wait: args.wait,
537
+ wait_timeout_seconds: args.wait_timeout_seconds,
538
+ poll_interval_ms: args.poll_interval_ms
539
+ },
540
+ { token: args.token }
541
+ );
542
+ }
543
+ );
544
+ }
545
+
546
+ // src/mcp/tools/video-utils.ts
547
+ import fs2 from "fs";
548
+ var MAX_IMAGE_SLOTS = 9;
549
+ var MAX_VIDEO_SLOTS = 3;
550
+ function buildBaseVideoPayload(args, functionMode) {
551
+ return {
552
+ prompt: args.prompt,
553
+ model: args.model,
554
+ ratio: args.ratio,
555
+ resolution: args.resolution,
556
+ duration: args.duration ?? 5,
557
+ response_format: args.response_format,
558
+ wait: args.wait,
559
+ wait_timeout_seconds: args.wait_timeout_seconds,
560
+ poll_interval_ms: args.poll_interval_ms,
561
+ functionMode
562
+ };
563
+ }
564
+ function uniqueStrings(values) {
565
+ return [...new Set(values.map((item) => item.trim()).filter(Boolean))];
566
+ }
567
+ function collectStringArray(values) {
568
+ if (!Array.isArray(values)) return [];
569
+ return values.filter((item) => typeof item === "string");
570
+ }
571
+ function collectIndexedSlotUrls(args, prefix, max) {
572
+ const values = /* @__PURE__ */ new Map();
573
+ for (let i = 1; i <= max; i++) {
574
+ const key = `${prefix}_${i}`;
575
+ const slot = args[key];
576
+ if (typeof slot === "string" && slot.length > 0) {
577
+ values.set(i, slot);
578
+ }
579
+ }
580
+ return values;
581
+ }
582
+ function assertLocalFilesExist(paths) {
583
+ for (const filePath of paths) {
584
+ if (!fs2.existsSync(filePath)) {
585
+ throw new McpToolError("VALIDATION_ERROR", `Local file not found: ${filePath}`);
586
+ }
587
+ }
588
+ }
589
+ function takeNextAvailableSlot(occupiedSlots, maxSlots, materialLabel) {
590
+ for (let i = 1; i <= maxSlots; i++) {
591
+ if (!occupiedSlots.has(i)) {
592
+ occupiedSlots.add(i);
593
+ return i;
594
+ }
595
+ }
596
+ throw new McpToolError(
597
+ "VALIDATION_ERROR",
598
+ `No available ${materialLabel} slot. Maximum supported: ${maxSlots}.`
599
+ );
600
+ }
601
+ function appendUrlMaterials(body, occupiedSlots, urls, prefix, maxSlots, materialLabel) {
602
+ for (const url of urls) {
603
+ const slot = takeNextAvailableSlot(occupiedSlots, maxSlots, materialLabel);
604
+ body[`${prefix}_${slot}`] = url;
605
+ }
606
+ }
607
+ function appendFileMaterials(uploadFiles, occupiedSlots, filePaths, prefix, maxSlots, materialLabel) {
608
+ for (const filePath of filePaths) {
609
+ const slot = takeNextAvailableSlot(occupiedSlots, maxSlots, materialLabel);
610
+ uploadFiles.push({
611
+ fieldName: `${prefix}_${slot}`,
612
+ filePath
613
+ });
614
+ }
615
+ }
616
+
617
+ // src/mcp/tools/generate-video-omni.ts
618
+ function normalizeOmniPayload(args) {
619
+ const imageSlotUrls = collectIndexedSlotUrls(args, "image_file", MAX_IMAGE_SLOTS);
620
+ const videoSlotUrls = collectIndexedSlotUrls(args, "video_file", MAX_VIDEO_SLOTS);
621
+ const imageUrls = uniqueStrings([
622
+ ...collectStringArray(args.image_urls),
623
+ ...collectStringArray(args.file_paths),
624
+ ...collectStringArray(args.filePaths)
625
+ ]);
626
+ const videoUrls = uniqueStrings(collectStringArray(args.video_urls));
627
+ const imageFiles = uniqueStrings(collectStringArray(args.image_files));
628
+ const videoFiles = uniqueStrings(collectStringArray(args.video_files));
629
+ assertLocalFilesExist(imageFiles);
630
+ assertLocalFilesExist(videoFiles);
631
+ const body = buildBaseVideoPayload(args, "omni_reference");
632
+ for (const [slot, url] of imageSlotUrls) {
633
+ body[`image_file_${slot}`] = url;
634
+ }
635
+ for (const [slot, url] of videoSlotUrls) {
636
+ body[`video_file_${slot}`] = url;
637
+ }
638
+ const occupiedImageSlots = new Set(imageSlotUrls.keys());
639
+ const occupiedVideoSlots = new Set(videoSlotUrls.keys());
640
+ appendUrlMaterials(body, occupiedImageSlots, imageUrls, "image_file", MAX_IMAGE_SLOTS, "image");
641
+ appendUrlMaterials(body, occupiedVideoSlots, videoUrls, "video_file", MAX_VIDEO_SLOTS, "video");
642
+ const uploadFiles = [];
643
+ appendFileMaterials(
644
+ uploadFiles,
645
+ occupiedImageSlots,
646
+ imageFiles,
647
+ "image_file",
648
+ MAX_IMAGE_SLOTS,
649
+ "image"
650
+ );
651
+ appendFileMaterials(
652
+ uploadFiles,
653
+ occupiedVideoSlots,
654
+ videoFiles,
655
+ "video_file",
656
+ MAX_VIDEO_SLOTS,
657
+ "video"
658
+ );
659
+ return { body, uploadFiles };
660
+ }
661
+ function registerGenerateVideoOmniTool({ server, config, client }) {
662
+ registerSafeTool(
663
+ server,
664
+ "generate_video_omni",
665
+ {
666
+ title: "Generate Video Omni",
667
+ description: "Generate omni_reference video with URL and local-file materials",
668
+ inputSchema: generateVideoOmniInputSchema
669
+ },
670
+ async (args) => {
671
+ assertRunConfirm(config, args.confirm);
672
+ const { body, uploadFiles } = normalizeOmniPayload(args);
673
+ return client.generateVideoOmni(body, { token: args.token }, uploadFiles);
674
+ }
675
+ );
676
+ }
677
+
678
+ // src/mcp/tools/generate-video.ts
679
+ function registerGenerateVideoTool({ server, config, client }) {
680
+ registerSafeTool(
681
+ server,
682
+ "generate_video_flf",
683
+ {
684
+ title: "Generate Video FLF",
685
+ description: "Generate video for first_last_frames workflow only",
686
+ inputSchema: generateVideoInputSchema
687
+ },
688
+ async (args) => {
689
+ assertRunConfirm(config, args.confirm);
690
+ return client.generateVideo(
691
+ buildBaseVideoPayload(args, "first_last_frames"),
692
+ { token: args.token }
693
+ );
694
+ }
695
+ );
696
+ }
697
+
698
+ // src/mcp/tools/get-task.ts
699
+ function registerGetTaskTool({ server, client }) {
700
+ registerSafeTool(
701
+ server,
702
+ "get_task",
703
+ {
704
+ title: "Get Task",
705
+ description: "Get image/video task status by task_id",
706
+ inputSchema: getTaskInputSchema,
707
+ annotations: { readOnlyHint: true }
708
+ },
709
+ async (args) => client.getTask(
710
+ args.task_id,
711
+ { token: args.token },
712
+ {
713
+ type: args.type,
714
+ response_format: args.response_format
715
+ }
716
+ )
717
+ );
718
+ }
719
+
720
+ // src/mcp/tools/health-check.ts
721
+ function registerHealthCheckTool({ server, client }) {
722
+ registerSafeTool(
723
+ server,
724
+ "health_check",
725
+ {
726
+ title: "Health Check",
727
+ description: "Check jimeng-cli health endpoint",
728
+ inputSchema: healthCheckInputSchema
729
+ },
730
+ async () => {
731
+ const startedAt = Date.now();
732
+ const raw = await client.healthCheck();
733
+ return {
734
+ ok: true,
735
+ latencyMs: Date.now() - startedAt,
736
+ raw
737
+ };
738
+ }
739
+ );
740
+ }
741
+
742
+ // src/mcp/tools/list-models.ts
743
+ function registerListModelsTool({ server, client }) {
744
+ registerSafeTool(
745
+ server,
746
+ "list_models",
747
+ {
748
+ title: "List Models",
749
+ description: "Get available models from jimeng-cli",
750
+ inputSchema: listModelsInputSchema,
751
+ annotations: { readOnlyHint: true }
752
+ },
753
+ async ({ token }) => client.listModels({ token })
754
+ );
755
+ }
756
+
757
+ // src/mcp/tools/wait-task.ts
758
+ function registerWaitTaskTool({ server, client }) {
759
+ registerSafeTool(
760
+ server,
761
+ "wait_task",
762
+ {
763
+ title: "Wait Task",
764
+ description: "Wait for image/video task completion by task_id",
765
+ inputSchema: waitTaskInputSchema
766
+ },
767
+ async (args) => client.waitTask(
768
+ args.task_id,
769
+ {
770
+ type: args.type,
771
+ response_format: args.response_format,
772
+ wait_timeout_seconds: args.wait_timeout_seconds,
773
+ poll_interval_ms: args.poll_interval_ms
774
+ },
775
+ { token: args.token }
776
+ )
777
+ );
778
+ }
779
+
780
+ // src/mcp/tools/manifest.ts
781
+ var MCP_TOOL_MANIFEST = [
782
+ { id: "health_check", register: registerHealthCheckTool },
783
+ { id: "list_models", register: registerListModelsTool },
784
+ { id: "get_task", register: registerGetTaskTool },
785
+ { id: "wait_task", register: registerWaitTaskTool },
786
+ { id: "generate_image", register: registerGenerateImageTool },
787
+ { id: "edit_image", isAdvanced: true, register: registerEditImageTool },
788
+ { id: "generate_video_flf", isAdvanced: true, register: registerGenerateVideoTool },
789
+ { id: "generate_video_omni", isAdvanced: true, register: registerGenerateVideoOmniTool }
790
+ ];
791
+
792
+ // src/mcp/tools/index.ts
793
+ function registerMcpTools(deps) {
794
+ for (const item of MCP_TOOL_MANIFEST) {
795
+ if (item.isAdvanced && !deps.config.enableAdvancedTools) continue;
796
+ item.register(deps);
797
+ }
798
+ }
799
+
800
+ // src/mcp/server.ts
801
+ function createJimengMcpServer(config) {
802
+ const server = new McpServer({
803
+ name: "jimeng-cli-mcp",
804
+ version: environment_default.package.version || "1.0.0"
805
+ });
806
+ const client = new JimengApiClient(config);
807
+ registerMcpTools({ server, config, client });
808
+ return server;
809
+ }
810
+
811
+ // src/mcp/index.ts
812
+ async function main() {
813
+ const config = loadMcpConfig();
814
+ const server = createJimengMcpServer(config);
815
+ const transport = new StdioServerTransport();
816
+ process.on("SIGINT", async () => {
817
+ await server.close();
818
+ process.exit(0);
819
+ });
820
+ process.on("SIGTERM", async () => {
821
+ await server.close();
822
+ process.exit(0);
823
+ });
824
+ await server.connect(transport);
825
+ console.error("[jimeng-cli-mcp] Server started on stdio transport");
826
+ }
827
+ main().catch((error) => {
828
+ console.error("[jimeng-cli-mcp] Failed to start MCP server:", error);
829
+ process.exit(1);
830
+ });
831
+ //# sourceMappingURL=index.js.map