@infimind/video-mcp-cli 1.0.10 → 1.0.11

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
@@ -135,7 +135,7 @@ Use `dryRun: true` to validate parameters, VIP access, media rules, and estimate
135
135
 
136
136
  ```json
137
137
  {
138
- "prompt": "@图片1 作为角色参考,生成自然的走秀镜头",
138
+ "prompt": "@image1 作为角色参考,生成自然的走秀镜头",
139
139
  "route": "S6",
140
140
  "taskMode": "reference",
141
141
  "sourceImages": ["/path/to/character.jpg"],
@@ -235,7 +235,7 @@ Image-to-video:
235
235
 
236
236
  ```json
237
237
  {
238
- "prompt": "@图片1 和 @图片2 作为角色参考,生成自然的走秀镜头",
238
+ "prompt": "@image1 和 @image2 作为角色参考,生成自然的走秀镜头",
239
239
  "route": "S6",
240
240
  "taskMode": "reference",
241
241
  "sourceImages": ["/path/to/character-1.jpg", "/path/to/character-2.jpg"],
@@ -335,7 +335,7 @@ Set `SORA_API_URL=http://localhost:3000/api` when testing against a local app se
335
335
  - If a client reports schema mismatch, run `npm run build` and `npm run test:smoke` in `mcp-server`.
336
336
  - If `get_video_status` cannot connect, verify `SORA_API_URL` includes `/api`, for example `http://localhost:3000/api`.
337
337
  - If S5/S6 returns VIP errors, confirm the MCP token belongs to a VIP user.
338
- - If S6 reference/edit prompts contain `@图片N`, keep the original markers; those modes skip prompt translation to preserve image references.
338
+ - If S6 reference/edit prompts contain canonical `@imageN` markers, keep the original markers; those modes skip prompt translation to preserve image references. Legacy `@图片N` markers are still accepted and normalized by the server.
339
339
  - If local file inputs fail, use absolute paths and supported media types: jpg/png/webp/gif, mp4/mov, mp3/wav.
340
340
 
341
341
  ## Publishing
package/dist/server.js CHANGED
@@ -8,7 +8,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
8
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { readFileSync, existsSync, statSync } from "fs";
10
10
  import { extname, basename } from "path";
11
- import { MCP_SERVER_VERSION, getMcpDefaultTaskMode, getMcpEngineName, getMcpVideoStatusHint, isMcpRoute, isValidMcpTaskMode, } from "./videoRules.js";
11
+ import { MCP_SERVER_VERSION, getMcpDefaultTaskMode, getMcpEngineName, getMcpVideoStatusHint, isMcpRoute, isValidMcpTaskMode, isMcpModel, MCP_VIDEO_MODELS, } from "./videoRules.js";
12
12
  class McpToolError extends Error {
13
13
  code;
14
14
  route;
@@ -125,10 +125,16 @@ export function createServer(token, apiBaseUrl) {
125
125
  minimum: 1,
126
126
  maximum: 12,
127
127
  },
128
+ model: {
129
+ type: "string",
130
+ description: "生成引擎短码(推荐):SA=极睿Sa专业版(默认,Sora 2,失败自动回退 S4)、SD=极睿Sd专业版(Seedance 2.0)、HA=极睿Ha专业版(HappyHorse 1.1)。注意:SD、HA 仅限 VIP 用户使用",
131
+ enum: ["SA", "SD", "HA"],
132
+ },
128
133
  route: {
129
134
  type: "string",
130
- description: "使用的生成引擎:S2=极睿Sa专业版(默认)、S5=极睿Sd专业版、S6=极睿Ha专业版。注意:S5、S6 仅限 VIP 用户使用",
135
+ description: "内部线路代码(已废弃,请改用 model)。兼容老客户端:S2=极睿Sa专业版、S5=极睿Sd专业版、S6=极睿Ha专业版",
131
136
  enum: ["S2", "S5", "S6"],
137
+ deprecated: true,
132
138
  },
133
139
  taskMode: {
134
140
  type: "string",
@@ -166,8 +172,10 @@ export function createServer(token, apiBaseUrl) {
166
172
  properties: {
167
173
  success: { type: "boolean" },
168
174
  taskId: { type: "number" },
175
+ model: { type: "string" },
169
176
  route: { type: "string" },
170
177
  engineName: { type: "string" },
178
+ usedRouteAlias: { type: "boolean" },
171
179
  taskMode: { type: "string" },
172
180
  videoCount: { type: "number" },
173
181
  dryRun: { type: "boolean" },
@@ -209,10 +217,7 @@ export function createServer(token, apiBaseUrl) {
209
217
  totalVideos: { type: "number" },
210
218
  completedVideos: { type: "number" },
211
219
  estimatedCredits: { type: "number" },
212
- chargedVideos: { type: "number" },
213
- actualRoute: { type: "string" },
214
220
  taskMode: { type: ["string", "null"] },
215
- retryCount: { type: "number" },
216
221
  statusHint: { type: "string" },
217
222
  createdAt: { type: ["string", "null"] },
218
223
  updatedAt: { type: ["string", "null"] },
@@ -238,9 +243,6 @@ export function createServer(token, apiBaseUrl) {
238
243
  errorCode: { type: ["string", "null"] },
239
244
  errorCategory: { type: ["string", "null"] },
240
245
  retryable: { type: ["boolean", "null"] },
241
- actualRoute: { type: ["string", "null"] },
242
- retryCount: { type: "number" },
243
- creditsCharged: { type: "boolean" },
244
246
  createdAt: { type: ["string", "null"] },
245
247
  completedAt: { type: ["string", "null"] },
246
248
  },
@@ -470,12 +472,27 @@ function hasItems(value) {
470
472
  function itemCount(value) {
471
473
  return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0).length : 0;
472
474
  }
473
- function normalizeRoute(value) {
474
- const route = typeof value === "string" ? value : "S2";
475
+ function normalizeRoute(args) {
476
+ // 优先 model;若 model 未传或不是 SA/SD/HA,再看 route(route 现在是 deprecated alias)
477
+ if (args && typeof args === "object" && isMcpModel(args.model)) {
478
+ const m = args.model;
479
+ return {
480
+ route: MCP_VIDEO_MODELS[m].route,
481
+ model: m,
482
+ usedRouteAlias: false,
483
+ };
484
+ }
485
+ const raw = args && typeof args === "object" ? args.route : undefined;
486
+ const route = typeof raw === "string" ? raw : "S2";
475
487
  if (!isMcpRoute(route)) {
476
- throw new McpToolError("route 只能是 S2、S5 或 S6。S2=极睿Sa专业版,S5=极睿Sd专业版,S6=极睿Ha专业版。", "INVALID_ROUTE");
488
+ throw new McpToolError("model 必须是 SA、SD 或 HA;route 只接受 S2、S5、S6(已废弃),请改用 model。", "INVALID_ENGINE");
477
489
  }
478
- return route;
490
+ const m = route === "S2" ? "SA" : route === "S5" ? "SD" : "HA";
491
+ return {
492
+ route,
493
+ model: m,
494
+ usedRouteAlias: true,
495
+ };
479
496
  }
480
497
  function normalizeTaskMode(route, value) {
481
498
  const mode = typeof value === "string" ? value : getMcpDefaultTaskMode(route);
@@ -501,7 +518,8 @@ function validateCreateVideoArgs(args) {
501
518
  if (args.count !== undefined && (!Number.isInteger(args.count) || args.count < 1 || args.count > 12)) {
502
519
  throw new McpToolError("count 必须是 1-12 的整数。", "INVALID_COUNT");
503
520
  }
504
- const route = normalizeRoute(args.route);
521
+ const norm = normalizeRoute(args);
522
+ const route = norm.route;
505
523
  const taskMode = normalizeTaskMode(route, args.taskMode);
506
524
  const resolution = typeof args.resolution === "string" ? args.resolution : "720p";
507
525
  const dryRun = args.dryRun === true;
@@ -587,6 +605,8 @@ function validateCreateVideoArgs(args) {
587
605
  }
588
606
  return {
589
607
  route,
608
+ model: norm.model,
609
+ usedRouteAlias: norm.usedRouteAlias,
590
610
  taskMode,
591
611
  resolution,
592
612
  dryRun,
@@ -609,6 +629,7 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
609
629
  seconds: args.seconds || 10,
610
630
  language: args.language || "zh-CN",
611
631
  count: args.count || 1,
632
+ model: normalized.model,
612
633
  route: normalized.route,
613
634
  dryRun: normalized.dryRun,
614
635
  };
@@ -663,8 +684,10 @@ async function handleCreateVideoTask(apiBaseUrl, token, args) {
663
684
  structuredContent: {
664
685
  success: response.success,
665
686
  taskId: response.taskId,
687
+ model: response.model ?? normalized.model,
666
688
  route,
667
689
  engineName,
690
+ usedRouteAlias: normalized.usedRouteAlias,
668
691
  taskMode,
669
692
  videoCount: response.videoCount,
670
693
  dryRun,
@@ -693,10 +716,7 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
693
716
  `- **进度**: ${response.progress}% (${response.completedVideos}/${response.totalVideos} 个视频)`,
694
717
  response.engineName ? `- **生成引擎**: ${response.engineName}` : undefined,
695
718
  response.taskMode ? `- **模式**: ${response.taskMode}` : undefined,
696
- response.actualRoute ? `- **实际线路**: ${response.actualRoute}` : undefined,
697
719
  typeof response.estimatedCredits === "number" ? `- **预估积分**: ${response.estimatedCredits}` : undefined,
698
- typeof response.chargedVideos === "number" ? `- **已扣费视频**: ${response.chargedVideos}/${response.totalVideos}` : undefined,
699
- typeof response.retryCount === "number" ? `- **重试次数**: ${response.retryCount}` : undefined,
700
720
  ``,
701
721
  ].filter((line) => line !== undefined);
702
722
  if (response.errorMessage) {
@@ -726,15 +746,6 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
726
746
  if (video.errorCode) {
727
747
  result.push(` 错误代码: ${video.errorCode}`);
728
748
  }
729
- if (video.actualRoute) {
730
- result.push(` 实际线路: ${video.actualRoute}`);
731
- }
732
- if (typeof video.retryCount === "number" && video.retryCount > 0) {
733
- result.push(` 重试次数: ${video.retryCount}`);
734
- }
735
- if (typeof video.creditsCharged === "boolean") {
736
- result.push(` 已扣费: ${video.creditsCharged ? "是" : "否"}`);
737
- }
738
749
  });
739
750
  }
740
751
  if (response.statusHint) {
@@ -757,9 +768,6 @@ async function handleGetVideoStatus(apiBaseUrl, token, args) {
757
768
  route: response.route,
758
769
  engineName: response.engineName,
759
770
  taskMode: response.taskMode,
760
- actualRoute: response.actualRoute,
761
- retryCount: response.retryCount,
762
- chargedVideos: response.chargedVideos,
763
771
  estimatedCredits: response.estimatedCredits,
764
772
  statusHint: response.statusHint,
765
773
  createdAt: response.createdAt,
@@ -14,4 +14,36 @@ export declare function getMcpEngineName(route: string): string;
14
14
  export declare function getMcpTaskModel(route: McpRoute): string;
15
15
  export declare function getMcpDefaultTaskMode(route: McpRoute, isFirstLastMode?: boolean): McpTaskMode;
16
16
  export declare function isValidMcpTaskMode(route: McpRoute, taskMode: string): taskMode is McpTaskMode;
17
+ /**
18
+ * 面向 MCP 客户端的引擎短码:
19
+ * SA = S2(极睿Sa专业版,Sora 2 模型,自动 S2→S4 兜底)
20
+ * SD = S5(极睿Sd专业版,Seedance 2.0)
21
+ * HA = S6(极睿Ha专业版,HappyHorse 1.1)
22
+ */
23
+ export type McpModel = "SA" | "SD" | "HA";
24
+ export declare const MCP_VIDEO_MODELS: Record<McpModel, {
25
+ route: McpRoute;
26
+ engineName: string;
27
+ providerModel: string;
28
+ }>;
29
+ export declare function isMcpModel(value: unknown): value is McpModel;
30
+ export declare function getMcpModelForRoute(route: McpRoute): McpModel;
31
+ /**
32
+ * 把客户端传入的 model / route 值解析成内部 route。
33
+ * - 如果 model 有效,直接用对应的内部 route。
34
+ * - 否则如果 route 是有效 McpRoute,返回该 route。
35
+ * - 否则返回 "SA"(与现有默认行为保持一致)。
36
+ *
37
+ * 第二返回值 true 表示 route 走的是 deprecated alias(仅使用 route 而非 model)。
38
+ */
39
+ export declare function resolveMcpEngine(args: {
40
+ model?: unknown;
41
+ route?: unknown;
42
+ }): {
43
+ route: McpRoute;
44
+ model: McpModel;
45
+ engineName: string;
46
+ providerModel: string;
47
+ usedRouteAlias: boolean;
48
+ };
17
49
  export declare function getMcpVideoStatusHint(dryRun?: boolean): string;
@@ -16,7 +16,7 @@ export const MCP_VIDEO_ENGINES = {
16
16
  },
17
17
  S6: {
18
18
  engineName: "极睿Ha专业版",
19
- model: "happyhorse-1.0",
19
+ model: "happyhorse-1.1",
20
20
  defaultTaskMode: "standard",
21
21
  taskModes: ["standard", "reference", "edit"],
22
22
  },
@@ -39,6 +39,64 @@ export function getMcpDefaultTaskMode(route, isFirstLastMode) {
39
39
  export function isValidMcpTaskMode(route, taskMode) {
40
40
  return MCP_VIDEO_ENGINES[route].taskModes.includes(taskMode);
41
41
  }
42
+ export const MCP_VIDEO_MODELS = {
43
+ SA: { route: "S2", engineName: "极睿Sa专业版", providerModel: "sora-2" },
44
+ SD: { route: "S5", engineName: "极睿Sd专业版", providerModel: "doubao-seedance-2-0-260128" },
45
+ HA: { route: "S6", engineName: "极睿Ha专业版", providerModel: "happyhorse-1.1" },
46
+ };
47
+ export function isMcpModel(value) {
48
+ return value === "SA" || value === "SD" || value === "HA";
49
+ }
50
+ export function getMcpModelForRoute(route) {
51
+ if (route === "S2")
52
+ return "SA";
53
+ if (route === "S5")
54
+ return "SD";
55
+ if (route === "S6")
56
+ return "HA";
57
+ return "SA"; // 默认回退
58
+ }
59
+ /**
60
+ * 把客户端传入的 model / route 值解析成内部 route。
61
+ * - 如果 model 有效,直接用对应的内部 route。
62
+ * - 否则如果 route 是有效 McpRoute,返回该 route。
63
+ * - 否则返回 "SA"(与现有默认行为保持一致)。
64
+ *
65
+ * 第二返回值 true 表示 route 走的是 deprecated alias(仅使用 route 而非 model)。
66
+ */
67
+ export function resolveMcpEngine(args) {
68
+ if (isMcpModel(args.model)) {
69
+ const entry = MCP_VIDEO_MODELS[args.model];
70
+ return {
71
+ route: entry.route,
72
+ model: args.model,
73
+ engineName: entry.engineName,
74
+ providerModel: entry.providerModel,
75
+ usedRouteAlias: false,
76
+ };
77
+ }
78
+ if (isMcpRoute(typeof args.route === "string" ? args.route : "")) {
79
+ const r = args.route;
80
+ const m = getMcpModelForRoute(r);
81
+ const entry = MCP_VIDEO_MODELS[m];
82
+ return {
83
+ route: r,
84
+ model: m,
85
+ engineName: entry.engineName,
86
+ providerModel: entry.providerModel,
87
+ usedRouteAlias: true,
88
+ };
89
+ }
90
+ // 默认 SA
91
+ const entry = MCP_VIDEO_MODELS.SA;
92
+ return {
93
+ route: entry.route,
94
+ model: "SA",
95
+ engineName: entry.engineName,
96
+ providerModel: entry.providerModel,
97
+ usedRouteAlias: false,
98
+ };
99
+ }
42
100
  export function getMcpVideoStatusHint(dryRun) {
43
101
  return dryRun ? MCP_VIDEO_DRY_RUN_HINT : MCP_VIDEO_STATUS_HINT;
44
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infimind/video-mcp-cli",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "MCP Server for Sora AI Video Generator - Access video generation features through Claude Desktop",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",