@starreel/mcp 0.1.257 → 0.1.259

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.
@@ -20,6 +20,16 @@
20
20
  * 10-15 曾在这里写死「seedance-2.5 真人题材暂未开放」,结果控制台开放后还得另发一版 MCP 才能改口——别再写回来。
21
21
  */
22
22
  export const MODEL_AVAILABILITY_NOTE = '各引擎对写实真人/动漫等题材当前是否可用,以 get_production_menu 返回的 availability 为准(不可用的带 customer_note 与 expected_until,原样转告客户),不要凭本说明预设哪个模型不可用';
23
+ /**
24
+ * 自由宽高比(Seedance 2.5 专属,后端 lib/free-aspect.ts 是判据唯一出处)的对外口径——
25
+ * create_drama / update_project_settings 的 aspect_ratio 描述、WORKFLOW_HINT、建剧步 note 共用这一句,防多处漂移。
26
+ * 厂商合同:2.5 的 ratio 只收 6 档 + adaptive,首帧任务输出跟随首帧比例;文生 / 参考生只出 6 档。
27
+ * ⇒ 自由比例只在「有首帧的 2.5 单镜」上成立,场景组 / 参考图 / 关键帧组 / 纯文生都做不到。
28
+ */
29
+ export const FREE_ASPECT_NOTE = '自由宽高比(灰度):除固定档外可传数值(2.35 / "2.35" / "2.35:1" / "47:20",宽÷高须在 0.4~2.5),平台规范成「比值:1」(两位小数,如 2.35:1)。' +
30
+ '只支持 Seedance 2.5:本剧 video_engine 须为 seedance-2.5、image_model 须为 Seedream(doubao-seedream-*)、视频分辨率不能 4k;' +
31
+ '视频按首帧尺寸出比例,所以每镜必须先有首帧——场景组 / 参考图生视频 / 关键帧组 / 纯文生视频不可用(厂商只出 6 档固定比例)。' +
32
+ '按剧灰度,未开通的剧回 400(原样转告客户,改用固定档);建剧时只有全量开放才能直接设,否则先用固定档建剧、再用 update_project_settings 改';
23
33
  export const GUIDE_VERSION = '2026-10-13';
24
34
  /** 按客户手上的材料选入口——这是 agent 最常缺的那张表。 */
25
35
  export const ENTRY_POINTS = [
@@ -212,7 +222,7 @@ export const PIPELINE = [
212
222
  step: '1 建剧与项目设定',
213
223
  tools: ['list_project_options', 'create_drama', 'update_project_settings'],
214
224
  billing: '免费',
215
- note: 'setting_brief(世界观/ERA LOCK)、画幅、video_engine、image_model、cinematography_prompt/art_bible/visual_lock 都在这一步定;收费步前服务端会要求 setting_brief≥30 字 + aspect_ratio。',
225
+ note: 'setting_brief(世界观/ERA LOCK)、画幅、video_engine、image_model、cinematography_prompt/art_bible/visual_lock 都在这一步定;收费步前服务端会要求 setting_brief≥30 字 + aspect_ratio。' + FREE_ASPECT_NOTE + '。',
216
226
  },
217
227
  {
218
228
  step: '2 灌本',
@@ -382,6 +392,7 @@ export const COMMON_REQUESTS = [
382
392
  { customer_says: '我想自己剪', do: '`export_handoff_pack` + `build_av_timing_contract`(音画时长契约) + `get_handoff_toolchain`,不走 `compose_episode`。' },
383
393
  { customer_says: '做动作片 / 打斗 / 技能展示 / 这条参考动作片我们能不能做', do: '先 `get_capabilities_guide` section=action_film:资产照常走 `rewrite_script` → `review_script` → `extract_assets`,分镜改用自己写的表(`import_storyboard_table`)而不是 AI 拆镜,首帧停在起势,拖沓尾段用 `trim_shot` 剪;平台不支持镜内分段变速。' },
384
394
  { customer_says: '我有白模 / 3D 预演视频 / 想让某镜照一段视频的动作和运镜重新生成', do: '`create_motion_reference`(白模选 kind=greybox_coarse / greybox_fine)→ `attach_motion_reference`(多个色块传 greybox_bindings 写明哪个色块是哪个角色)→ `get_shot_motion_reference` 核对 → 照常 `review_frames` → `quote_videos` → `generate_videos`。别用 `upload_shot_footage`(那是把视频直接当成片);白模别带轨迹线 / 坐标轴 / 相机锥,四肢不建或建全动作。' },
395
+ { customer_says: '镜头景别 / 运镜 / 站位总是不听话、想先做白模预演但手上没有白模', do: '`generate_greybox_previs`(先 `dry_run=true` 看平台用了哪些字段、做了哪些假设,字段不对先 `update_shot`)→ 平台按分镜字段自动渲粗粒度白模并写好色块绑定 → `get_greybox_previs` 核对 → 照常 `review_frames` → `quote_videos` → `generate_videos`。先告诉客户:白模镜不送首尾帧、视频报价约贵两成;插入镜 / 无角色 / 超 15 秒 / 状态变化镜不适用。' },
385
396
  { customer_says: '要配音 / 不要视频原声', do: '`update_project_settings` use_clip_audio=false → `assign_voices` → `generate_tts` → `compose_episode`。' },
386
397
  ];
387
398
  /**
@@ -17,7 +17,7 @@ import { tmpdir } from 'node:os';
17
17
  import { dirname, join } from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import { assertSafeToolchainDir, TOOLCHAIN_FILENAMES } from '../path-guard.js';
20
- import { MODEL_AVAILABILITY_NOTE } from './guide-data.js';
20
+ import { FREE_ASPECT_NOTE, MODEL_AVAILABILITY_NOTE } from './guide-data.js';
21
21
  const TOOLCHAIN_USAGE = [
22
22
  '1. 三个脚本放同一目录(assemble.sh 需可执行位)。',
23
23
  '2. 把 export_handoff_pack 的返回体存成 manifest.json。',
@@ -298,7 +298,7 @@ export const WORKFLOW_HINT = AUTOPILOT_HINT +
298
298
  '③【可选增强·AI 主动提示客户·报价确认才做】美术圣经生成/视觉锁抽取/场景组/口型/海报/音效/配乐/字幕翻译——' +
299
299
  '这些提升一致性/质量、大多收费。★AI 应主动告知客户这些可做并给报价' + MODE_DECIDES('跑不跑') + ';既不默默跳过、也不擅自扣费。' +
300
300
  '★两条锁定纪律:①**画幅比例**在 create_drama 即定、drama 级锁定,之后所有出图/出视频/成片都用它、**别中途改**' +
301
- '(改了已生成内容画幅会不一致、漂移);不设默认 9:16。②**拆镜每镜 5-7 秒是对 AI 出视频优化的正常时长**,' +
301
+ '(改了已生成内容画幅会不一致、漂移);不设默认 9:16;Seedance 2.5 剧可用自由宽高比(数值,如 2.35),限制见 create_drama 的 aspect_ratio 说明。②**拆镜每镜 5-7 秒是对 AI 出视频优化的正常时长**,' +
302
302
  '别因「镜偏长」误判就重拆——generate_storyboards 会**替换整集所有分镜**、已出图白费,已有分镜后端会拦、需 confirm_replace。' +
303
303
  '★改写保真(默认 auto 智能路由):set_script 的原稿**本身已是剧本形态**(场景头/对白行结构)时,rewrite_script 自动走两步保真——' +
304
304
  '客户台词逐句由机器闸锁定(丢一句即内部拒收重做)、AI 绝不加戏;剧作缺口(钩子/情感锚点)不自动补,写进 get_script 返回的 dramaturgy_suggestions' + MODE_DECIDES('采不采纳') + '。' +
@@ -343,6 +343,31 @@ const BGM_LOCK_FIELDS = {
343
343
  };
344
344
  const FRAME_TYPE_ARG = z.enum(['first_frame', 'last_frame', 'both']);
345
345
  const ASPECT_RATIOS = ['9:16', '16:9', '1:1', '4:5', '4:3', '21:9'];
346
+ // 自由宽高比(Seedance 2.5 专属·按剧灰度):与后端 lib/free-aspect.ts classifyDramaAspectInput 同口径——
347
+ // 数值 / '2.35' / '2.35:1' / '47:20'(分隔符 : x * × /),宽÷高须在 [0.4, 2.5];规范化(两位小数)与组合校验都在后端。
348
+ // 这里只挡掉解析不出或越界的值,省一次 400;固定档仍走上面的枚举,旧调用逐字不变。
349
+ export const FREE_ASPECT_MIN = 0.4;
350
+ export const FREE_ASPECT_MAX = 2.5;
351
+ /** 数值写法 → 宽÷高;解析不出 → null(范围另判)。 */
352
+ export function parseAspectNumber(raw) {
353
+ if (typeof raw === 'number')
354
+ return Number.isFinite(raw) ? raw : null;
355
+ const s = typeof raw === 'string' ? raw.trim() : '';
356
+ if (/^\d+(?:\.\d+)?$/.test(s))
357
+ return Number(s);
358
+ const m = /^(\d+(?:\.\d+)?)\s*[:x*×/]\s*(\d+(?:\.\d+)?)$/i.exec(s);
359
+ return m && Number(m[2]) > 0 ? Number(m[1]) / Number(m[2]) : null;
360
+ }
361
+ const inFreeAspectRange = (raw) => {
362
+ const r = parseAspectNumber(raw);
363
+ return r != null && r >= FREE_ASPECT_MIN && r <= FREE_ASPECT_MAX;
364
+ };
365
+ const FREE_ASPECT_RANGE_MSG = `自由宽高比的宽÷高必须在 ${FREE_ASPECT_MIN}~${FREE_ASPECT_MAX} 之间`;
366
+ const ASPECT_RATIO_ARG = z.union([
367
+ z.enum(ASPECT_RATIOS),
368
+ z.number().min(FREE_ASPECT_MIN, FREE_ASPECT_RANGE_MSG).max(FREE_ASPECT_MAX, FREE_ASPECT_RANGE_MSG),
369
+ z.string().refine(inFreeAspectRange, FREE_ASPECT_RANGE_MSG),
370
+ ]);
346
371
  const VIDEO_RESOLUTIONS = ['480p', '720p', '1080p', '4k'];
347
372
  // 剧级视频引擎(与后端 video-engine-policy 白名单同源;短 id,后端归一化)。
348
373
  // kling-4.0 为内测引擎:后端按剧灰度(KLING_VIDEO_DRAMAS),未开放的剧传它会 400——那不是调用错误,换回其它引擎即可。
@@ -427,13 +452,14 @@ export function registerProduceTools(server, client) {
427
452
  server.tool('create_drama', '新建一部短剧(剧壳)。★强烈建议建剧时一并设好基础项目设定——project_type/setting_brief(世界观·ERA LOCK)/' +
428
453
  'ethnicity(族裔)/画幅分辨率,以及一致性锚(cinematography_prompt/art_bible/visual_lock)。这些全免费、' +
429
454
  '是驱动全链一致性的地基;只传 title 建空壳会让后续所有生成跑偏、返工重花钱。' +
430
- '按 total_episodes 自动建 N 空集,返回 drama_id 与各集 episode_id。免费。', {
455
+ '按 total_episodes 自动建 N 空集,返回 drama_id 与各集 episode_id。免费。' +
456
+ '★Seedance 2.5 剧的画幅可设自由宽高比(如 2.35),限制与灰度口径见 aspect_ratio 参数说明。', {
431
457
  title: z.string().describe('剧名'),
432
458
  total_episodes: z.number().int().min(1).max(200).optional().describe('集数(默认 1),自动建 N 个空集'),
433
459
  genre: z.string().optional().describe('题材,如 都市/悬疑/古装'),
434
460
  style: z.string().optional().describe('风格描述'),
435
461
  description: z.string().optional(),
436
- aspect_ratio: z.enum(ASPECT_RATIOS).optional().describe('画幅比例(默认 9:16 竖屏短剧)。★drama级锁定:建剧即定、之后所有出图/出视频/成片都用它,别中途改(改了已生成内容画幅不一致、漂移)'),
462
+ aspect_ratio: ASPECT_RATIO_ARG.optional().describe('画幅比例(默认 9:16 竖屏短剧;固定档 ' + ASPECT_RATIOS.join(' / ') + ')。★drama级锁定:建剧即定、之后所有出图/出视频/成片都用它,别中途改(改了已生成内容画幅不一致、漂移)。' + FREE_ASPECT_NOTE),
437
463
  video_resolution: z.enum(VIDEO_RESOLUTIONS).optional().describe('视频分辨率(默认 720p;成本随分辨率上升)。按引擎选:' +
438
464
  '倍数以 seedance-2.5 720p 为 1×,实价以 quote_* 报价为准。seedance-2.5 在售 480p/720p/1080p(约 0.45×/1×/2.5×,无 4K);hailuo-3 选 720p(=768P 约0.33×)或 1080p(=2K 约0.53×),无独立 480p 档;' +
439
465
  'wan3.0/prime 三档全售(wan3.0 约 0.2×/0.4×/0.8×,prime 约 0.3×/0.6×/1.2×)。草稿迭代用低档,成片交付才上高清'),
@@ -1440,6 +1466,7 @@ export function registerProduceTools(server, client) {
1440
1466
  '音频(bgm_source 自有vs原生/bgm_volume_preset 音量/use_clip_audio 用视频原声)、' +
1441
1467
  '字幕(show_subtitles/双语 subtitle_secondary_lang/位置/边距/动效)、转场——即项目设定页全部通用设定。免费。' +
1442
1468
  '⚠️ 世界观 Brief/族裔/画幅/一致性锚等改后,已生成的内容不会自动更新,需重生对应内容。' +
1469
+ '★aspect_ratio 除固定档外可设自由宽高比(数值如 2.35,只支持 Seedance 2.5 + Seedream + 非 4k、按剧灰度,未开通回 400),详见该参数说明。' +
1443
1470
  '★frame_chain_mode 管整剧跨镜帧链:off=整剧不做(出帧落定后的自动衔接、chain_frames、首尾呼应都不派图不改帧,报价里也不再计入);' +
1444
1471
  'reframe=默认;strict=相邻缝只裸拷贝上镜尾帧不重生(已设的首尾呼应仍按 reframe 重生、照常计入报价;要全不派图用 off)。关掉后想用 chain_frames 要先改回 reframe/strict;改模式不会自动重出已有帧。', {
1445
1472
  drama_id: z.number().int().positive(),
@@ -1449,7 +1476,7 @@ export function registerProduceTools(server, client) {
1449
1476
  style: z.string().optional(),
1450
1477
  description: z.string().optional(),
1451
1478
  tags: z.array(z.string()).optional(),
1452
- aspect_ratio: z.enum(ASPECT_RATIOS).optional(),
1479
+ aspect_ratio: ASPECT_RATIO_ARG.optional().describe('画幅比例(固定档 ' + ASPECT_RATIOS.join(' / ') + ')。已有分镜/生成内容后改画幅要带 confirm_aspect_change:true。' + FREE_ASPECT_NOTE),
1453
1480
  // v0.1.39 — 画幅锁定闸:本剧已有分镜/生成资产后改画幅会被 400 拦(已生成内容会漂移),
1454
1481
  // 确认要改必须显式带 true。通常正解是建剧时定好画幅、之后不改。
1455
1482
  confirm_aspect_change: z.boolean().optional(),
@@ -1674,6 +1701,22 @@ export function registerProduceTools(server, client) {
1674
1701
  }, async ({ storyboard_id, asset_id, greybox_bindings }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/motion-reference`, {
1675
1702
  asset_id, ...(greybox_bindings !== undefined ? { greybox_bindings } : {}),
1676
1703
  })));
1704
+ server.tool('generate_greybox_previs', '按分镜已有字段**自动生成粗粒度白模**并挂到镜上(免费):景别 / 机位高度与方位 / 运镜(推拉摇移跟升降环绕) / 站位 / 朝向 / 时长 / 动作(走近、后退、转身、坐下等)→ 平台本机渲成几何体白模视频(每个角色一种颜色、只建躯干),登记成 greybox_coarse 并**自动写好 greybox_bindings**,不用客户自己建白模。目的是把景别 / 运镜 / 站位从文字约束变成像素约束,治「镜头调度不听话」。★挂上后该镜改走参考视频路径:**首尾帧不再送达**(身份靠角色参考图),**视频报价会上涨约两成**(参考视频按秒计费)——生成前把这两点讲给客户。★先 dry_run=true 看平台按哪些字段、做了哪些假设(assumptions:缺失字段的兜底),字段不对先 update_shot 改再生成。不适用的镜会返回原因:插入镜、没有出镜角色、超过 15 秒、状态变化镜(依赖尾帧终态,确需生成传 include_state_change=true)。镜上已挂客户自己的运动参考时不覆盖(409),确要替换传 replace_existing=true。传 episode_id 不传 storyboard_id = 整集批量(一次 ≤40 镜,可用 storyboard_ids 选镜)。本剧未开放时回 409(灰度中)。生成后照常 review_frames → quote_videos → generate_videos。', {
1705
+ storyboard_id: z.number().int().positive().optional().describe('单镜;与 episode_id 二选一'),
1706
+ episode_id: z.number().int().positive().optional().describe('整集批量;不传 storyboard_ids = 本集全部镜(≤40)'),
1707
+ storyboard_ids: z.array(z.number().int().positive()).max(40).optional().describe('整集模式下只做这几镜'),
1708
+ dry_run: z.boolean().optional().describe('true = 只回规格与假设,不渲染不挂载'),
1709
+ replace_existing: z.boolean().optional().describe('true = 替换客户自己挂的运动参考'),
1710
+ include_state_change: z.boolean().optional().describe('true = 状态变化镜也生成(会失去尾帧终态约束)'),
1711
+ }, async ({ storyboard_id, episode_id, storyboard_ids, dry_run, replace_existing, include_state_change }) => {
1712
+ const body = { dry_run, replace_existing, include_state_change };
1713
+ if (storyboard_id)
1714
+ return jsonResult(await client.producePost(`/storyboards/${storyboard_id}/greybox-previs`, body));
1715
+ if (episode_id)
1716
+ return jsonResult(await client.producePost(`/episodes/${episode_id}/greybox-previs`, { ...body, ...(storyboard_ids ? { storyboard_ids } : {}) }));
1717
+ return jsonResult({ error: 'storyboard_id 或 episode_id 必须给一个' });
1718
+ });
1719
+ server.tool('get_greybox_previs', '看某镜的自动白模(免费):attached=当前挂的是不是平台自动生成的白模;stale=true 表示镜头字段改过、白模已过时,需要重新 generate_greybox_previs;current 是按当前字段会生成的规格与假设;skip 是不适用的原因。', { storyboard_id: z.number().int().positive() }, async ({ storyboard_id }) => jsonResult(await client.produceGet(`/storyboards/${storyboard_id}/greybox-previs`)));
1677
1720
  server.tool('list_unlinked_assets', '列出本剧「已解除引用」的旧资产(免费):清尾帧、换定妆图时被摘下来的旧尾帧/旧定妆图/旧设定图/发型·身体参考/视频锚,每个 URL 一条。' +
1678
1721
  '这些文件仍然存在于存储里。清单会尽量排除已被重新用上的图,但不保证完全排除——某张图是否还在别处被引用,' +
1679
1722
  '以平台的全库检查为准。客户问「旧图还在不在」时用它。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/unlinked-assets`)));