@starreel/mcp 0.1.16 → 0.1.18

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/SKILL.md CHANGED
@@ -96,12 +96,22 @@ content that will be rejected.
96
96
  video: a portrait is one image, but **character sheets (multi-view turnarounds)
97
97
  are the shot consistency anchor every frame references** — portraits alone
98
98
  leave characters drifting across angle / lighting / wardrobe.
99
- `generate_portraits_and_sheets` does both (portraits first, then sheets). `generate_frames` makes **first frames only** — you do
99
+ `generate_portraits_and_sheets` does both (portraits first, then sheets). `generate_frames` makes **first frames only** by default — you do
100
100
  NOT generate first + last together. A **last frame** is optional and never
101
- auto-made; upload it via `upload_shot_frame` (`frame_type=last_frame`) only to
102
- pin a shot's ending (a big camera move or reveal). `generate_videos` needs at
101
+ auto-made; ask for one only to pin a shot's ending (a big camera move or
102
+ reveal), via `generate_frames` with `frame_type=last_frame` or, for a single
103
+ shot, `generate_shot_frame`. `generate_videos` needs at
103
104
  least one first frame in the episode.
104
105
 
106
+ **Redraw on-platform, never off-platform.** When a shot's image is wrong,
107
+ fix it with `quote_shot_frame` → `generate_shot_frame` (`frame_type`:
108
+ `first_frame` / `last_frame` / `both`). That path carries the shot's character
109
+ identity anchors, scene/prop references, style lock and frame audit, so the new
110
+ image still matches the rest of the film. Do **not** render the image in another
111
+ image tool and push it in with `upload_shot_frame` — that bypasses every anchor
112
+ (faces, wardrobe and style drift) and makes you hostage to that tool's queue.
113
+ `upload_shot_frame` is for art the customer already owns.
114
+
105
115
  7. **Poll, don't block; don't hammer.** Long steps return immediately as
106
116
  `status:generating`. Poll `get_pipeline_status` / `get_jobs` /
107
117
  `get_storyboards` with a backoff (start ~5–10s, widen on no change). Stable
@@ -23,7 +23,11 @@ const CONFIRM_HINT = '⚠️ 批量报价确认流程:先调对应的 quote_*
23
23
  // → compose_episode → get_final_cut / get_export
24
24
  // 项目设定随时可 update_project_settings;剧目级资产(色彩脚本/动作模板/世界观图/美术圣经)可选增强。
25
25
  const WORKFLOW_HINT = '照完整工作流按序推进,别跳步:set_script(原始)→ rewrite_script(AI改写)→ extract_assets →' +
26
- ' generate_portraits_and_sheets(定妆图+设定图·镜头一致性根锚,别只出定妆图)→ storyboards → frames → videos → compose。用 get_pipeline_status 查进度。';
26
+ ' generate_portraits_and_sheets(定妆图+设定图·镜头一致性根锚)→ assign_voices(分配音色,否则 generate_tts 无声)→' +
27
+ ' storyboards → frames → videos → generate_tts → compose。' +
28
+ '★广告(ad)另需 add_product 建产品库 + generate_product_sheet(广告主体,不建镜头产品会漂);' +
29
+ 'MV 走 set_mv_lyrics→generate_mv_story→generate_mv_script。' +
30
+ '用 get_pipeline_status 查进度——它按项目类型返回专属步骤(广告有 build_product_library,MV 有歌词/故事)。';
27
31
  const ETHNICITY_CODES = [
28
32
  'east_asian', 'southeast_asian', 'south_asian', 'central_asian',
29
33
  'caucasian', 'middle_eastern', 'african', 'latin_american',
@@ -32,6 +36,8 @@ const ETHNICITY_CODES = [
32
36
  // 画幅/分辨率的可选值 —— create_drama 与 update_project_settings **共用同一源**,防两处漂移。
33
37
  // 与后端发现接口 produce-options.ts 的 ASPECT_RATIO_OPTIONS / VIDEO_RESOLUTION_OPTIONS 键集一致
34
38
  // (video_resolution 与后端 VIDEO_RES_WHITELIST 钉死;aspect_ratio 后端柔性,这里给同一策展集)。
39
+ // 帧类型(与后端 produce-facade.parseFrameParam 同一枚举):整集/单镜出帧共用。
40
+ const FRAME_TYPE_ARG = z.enum(['first_frame', 'last_frame', 'both']);
35
41
  const ASPECT_RATIOS = ['9:16', '16:9', '1:1', '4:5', '4:3', '21:9'];
36
42
  const VIDEO_RESOLUTIONS = ['480p', '720p', '1080p', '4k'];
37
43
  /** 把友好 ethnicity 枚举 + note 组装成后端认的结构化族裔锁 { code, note? }。 */
@@ -120,9 +126,33 @@ export function registerProduceTools(server, client) {
120
126
  'insufficient_credits/transient)、retryable(true=可重试;false=改内容换图,重试无效)、fail_hint(人读文案)。' +
121
127
  '照 retryable 判该重试还是该改内容,别解析中文。免费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/storyboards`)));
122
128
  // ---------- 出首帧(frames) ----------
123
- server.tool('quote_frames', '报价:给某一集缺首帧的分镜批量出首帧要多少点。返回 frames_to_generate、estimated_points、quote_id。零扣费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/frames/quote`)));
124
- server.tool('generate_frames', '确认后批量出首帧:后台异步。用 get_storyboards 轮询,first_frame_image 逐镜填充即完成。' +
125
- '出视频前必须先出帧,否则视频会退化成无一致性锚点的画面。' + CONFIRM_HINT, { episode_id: z.number().int().positive(), quote_id: z.string().describe('来自 quote_frames') }, async ({ episode_id, quote_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/frames/generate`, { quote_id })));
129
+ server.tool('quote_frames', '报价:给某一集批量出帧要多少点。返回 frames_to_generate、estimated_points、quote_id。零扣费。' +
130
+ 'frame_type 默认 first_frame(只给缺首帧的镜出);last_frame 只给「已有首帧且缺尾帧」的镜出;both 两者都补。', {
131
+ episode_id: z.number().int().positive(),
132
+ frame_type: FRAME_TYPE_ARG.optional().describe('默认 first_frame;last_frame=补尾帧;both=首尾都补'),
133
+ }, async ({ episode_id, frame_type }) => jsonResult(await client.producePost(`/episodes/${episode_id}/frames/quote`, { frame_type })));
134
+ server.tool('generate_frames', '确认后批量出帧:后台异步。用 get_storyboards 轮询,first_frame_image/last_frame_image 逐镜填充即完成。' +
135
+ '出视频前必须先出帧,否则视频会退化成无一致性锚点的画面。' +
136
+ 'frame_type 要与 quote_frames 用的一致(默认 first_frame)。尾帧只在想固定某镜结尾画面时才补,常规只需首帧。' + CONFIRM_HINT, {
137
+ episode_id: z.number().int().positive(),
138
+ quote_id: z.string().describe('来自 quote_frames'),
139
+ frame_type: FRAME_TYPE_ARG.optional().describe('默认 first_frame,须与报价时一致'),
140
+ }, async ({ episode_id, quote_id, frame_type }) => jsonResult(await client.producePost(`/episodes/${episode_id}/frames/generate`, { quote_id, frame_type })));
141
+ // ---------- 单镜出帧/重生(改某一镜的画面走这里,别去外部平台出图再传回来) ----------
142
+ server.tool('quote_shot_frame', '报价:重画/补出**某一镜的某一帧**要多少点(一帧=一张图)。返回 estimated_points、quote_id。零扣费。' +
143
+ '客户说「第 N 镜画错了/要改」时用它,而不是拿别的图像平台出图再 upload_shot_frame。', {
144
+ storyboard_id: z.number().int().positive(),
145
+ frame_type: FRAME_TYPE_ARG.optional().describe('默认 first_frame;both=首尾各一张'),
146
+ }, async ({ storyboard_id, frame_type }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/frame/quote`, { frame_type })));
147
+ server.tool('generate_shot_frame', '确认后给**某一镜**出帧或重生该帧(异步)。这是修某一镜画面的正路:平台会带上该镜的角色身份锚、' +
148
+ '场景/道具参考图、画风锚与帧审计,重生出的图与全片一致;用外部工具出图再上传会绕开这整条链路,人物/服装/画风必漂。' +
149
+ '重生会覆盖该帧现有图(含此前上传的),并把本镜视频标为待重生。尾帧需本镜首帧已就绪(否则先用 first_frame 或 both)。' +
150
+ '完成判据:轮询 get_storyboards 看该镜 first_frame_image/last_frame_image 变化。' + CONFIRM_HINT, {
151
+ storyboard_id: z.number().int().positive(),
152
+ quote_id: z.string().describe('来自 quote_shot_frame'),
153
+ frame_type: FRAME_TYPE_ARG.optional().describe('默认 first_frame,须与报价时一致'),
154
+ replace_user_frame: z.boolean().optional().describe('默认 true(显式重生允许覆盖已上传帧);传 false 则保护已上传帧'),
155
+ }, async ({ storyboard_id, quote_id, frame_type, replace_user_frame }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/frame/generate`, { quote_id, frame_type, replace_user_frame })));
126
156
  // ---------- 出视频(videos,大额) ----------
127
157
  server.tool('quote_videos', '报价:给某一集所有分镜批量出视频要多少点(与实际扣费同函数,较准)。返回 estimated_points、quote_id。零扣费。' +
128
158
  '⚠️ 视频是大额花费,务必把点数清楚告诉用户并等其确认。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/videos/quote`)));
@@ -213,7 +243,9 @@ export function registerProduceTools(server, client) {
213
243
  throw new Error('file_path 或 image_url 至少提供一个');
214
244
  return jsonResult(await client.producePost(`/characters/${character_id}/portrait`, { image_url: url }));
215
245
  });
216
- server.tool('upload_shot_frame', '用客户自有图片作为某镜的首帧(first_frame)或尾帧(last_frame)。尾帧可选、系统不自动生成——仅在想固定某镜结尾画面(大运镜/揭示镜)时传;常规只需首帧。会把该镜已有视频标为过期待重生。自动上传+登记。免费。', {
246
+ server.tool('upload_shot_frame', '用**客户自有图片**作为某镜的首帧(first_frame)或尾帧(last_frame)。尾帧可选——仅在想固定某镜结尾画面(大运镜/揭示镜)时传;常规只需首帧。会把该镜已有视频标为过期待重生。自动上传+登记。免费。' +
247
+ '⚠️ 只用于客户自己提供的素材。想「重画/修某一镜」请用 generate_shot_frame 让平台重生——' +
248
+ '外部工具出的图不带本片的身份锚与画风锚,贴进来人物/服装/画风会漂。', {
217
249
  storyboard_id: z.number().int().positive(),
218
250
  file_path: z.string().describe('本地帧图路径'),
219
251
  frame_type: z.enum(['first_frame', 'last_frame']).optional().describe('默认 first_frame'),
@@ -272,7 +304,10 @@ export function registerProduceTools(server, client) {
272
304
  server.tool('get_cost_estimate', '查一部剧的整体成本预估(点数)。免费。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/cost-estimate`)));
273
305
  server.tool('get_budget_status', '查一部剧的预算状态(预算/已花费/剩余)。免费。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/budget-status`)));
274
306
  // ========== P1 · 整集配音(成片前的强制音频步)==========
275
- server.tool('generate_tts', '给整集所有对白批量配音(TTS)。成片前的音频步——不配音成片会缺对白。后台异步,按用量后付不欠费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/tts`)));
307
+ server.tool('assign_voices', '★给本集所有角色分配音色(配音导演 agent 按性别/性格/年龄/角色定位+项目语言选)。' +
308
+ 'voiceStyle 不是提取时自动填的——不分配,generate_tts 就没音色。文本步(LLM)后付。' +
309
+ '后台异步,轮询 get_pipeline_status:assign_voices=done 即完成,再 generate_tts。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/assign-voices`)));
310
+ server.tool('generate_tts', '给整集所有对白批量配音(TTS)。成片前的音频步——不配音成片会缺对白。前置:先 assign_voices 给角色分配音色。后台异步,按用量后付不欠费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/tts`)));
276
311
  // ========== P1 · MV 子流程(project_type=mv 专用)==========
277
312
  server.tool('set_mv_lyrics', 'MV 项目:设置歌词(整曲音频另在建剧时传)。免费。之后 generate_mv_story → generate_mv_script。', { drama_id: z.number().int().positive(), lyrics: z.string().min(1).describe('歌词全文') }, async ({ drama_id, lyrics }) => jsonResult(await client.producePost(`/dramas/${drama_id}/mv/lyrics`, { lyrics })));
278
313
  server.tool('generate_mv_story', 'MV 项目:据歌词 AI 编一个 MV 故事线。后台/同步,按用量后付。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.producePost(`/dramas/${drama_id}/mv/story`)));
@@ -326,13 +361,15 @@ export function registerProduceTools(server, client) {
326
361
  // ========== P2 · 品牌片交付物 / 广告商品库 ==========
327
362
  server.tool('get_deliverables', '品牌片:读交付物树(海报/母版/分轨/各平台版等)。免费。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/deliverables`)));
328
363
  server.tool('generate_deliverables', '品牌片:一键生成全部交付物。后台异步,按用量后付。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.producePost(`/dramas/${drama_id}/deliverables`)));
329
- server.tool('add_product', '广告项目:往商品库加一个商品(名称/卖点/图),供带货广告露出。免费。', {
364
+ server.tool('add_product', '★广告项目必做:往商品库加一个商品(名称/卖点/图),是广告的**主体**——出视频时按分镜文本匹配产品图当锚,' +
365
+ '不建产品库镜头里的产品会漂移/瞎编。建完用 generate_product_sheet 出设定图(锚更稳)。免费(仅建条目)。', {
330
366
  drama_id: z.number().int().positive(),
331
367
  name: z.string().min(1),
332
368
  description: z.string().optional().describe('卖点/描述'),
333
369
  image_url: z.string().optional().describe('商品图 URL(先 upload_image 拿)'),
334
370
  }, async ({ drama_id, ...fields }) => jsonResult(await client.producePost(`/dramas/${drama_id}/products`, fields)));
335
371
  server.tool('list_products', '广告项目:列出商品库。免费。', { drama_id: z.number().int().positive() }, async ({ drama_id }) => jsonResult(await client.produceGet(`/dramas/${drama_id}/products`)));
372
+ server.tool('generate_product_sheet', '★广告项目:给商品库里某商品 AI 出设定图(广告露出锚)。商品无设定图 → 出视频时按分镜 grep 产品名取图当锚会落空、产品漂移。图片步,按用量后付不欠费。', { product_id: z.number().int().positive() }, async ({ product_id }) => jsonResult(await client.producePost(`/products/${product_id}/sheet`)));
336
373
  // ========== AI 声音克隆(voice cloning)==========
337
374
  // 音色是**独立资产**:克隆 → 试听下载,全程不需要角色。绑角色只是「要出片」时的
338
375
  // 可选下游动作。voice_id 形如 `lib:12`,克隆/列表/试听/绑定四处同一命名空间。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starreel/mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "mcpName": "ai.starreel/starreel",
5
5
  "description": "StarReel MCP server — drive the AI short-drama production pipeline (script → storyboards → frames → video → final cut) from Claude, Cursor, or any MCP client",
6
6
  "license": "MIT",
package/server.json CHANGED
@@ -2,13 +2,13 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
3
3
  "name": "ai.starreel/starreel",
4
4
  "description": "Turn a script into a finished, downloadable short-drama episode — via MCP or REST.",
5
- "version": "0.1.13",
5
+ "version": "0.1.17",
6
6
  "websiteUrl": "https://starreel.ai",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@starreel/mcp",
11
- "version": "0.1.13",
11
+ "version": "0.1.17",
12
12
  "transport": { "type": "stdio" },
13
13
  "environmentVariables": [
14
14
  {