@starreel/mcp 0.1.35 → 0.1.37
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 +20 -1
- package/dist/client.js +4 -0
- package/dist/path-guard.js +45 -0
- package/dist/tools/produce.js +12 -3
- package/package.json +1 -1
- package/server.json +2 -2
package/SKILL.md
CHANGED
|
@@ -59,6 +59,14 @@ Don't ask the user at every step. Sort work into three tiers:
|
|
|
59
59
|
`video_style_prompt`) are all free and the foundation that steers every later
|
|
60
60
|
generation. Set them up front via `create_drama` / `update_project_settings`
|
|
61
61
|
— don't build an empty shell, or all downstream generation drifts.
|
|
62
|
+
**Never pin a specific character's wardrobe / hair / look inside
|
|
63
|
+
`visual_lock` or `art_bible`** — those hold scene-level and world-level locks
|
|
64
|
+
only. The **single source of truth** for a character's appearance is the
|
|
65
|
+
character profile that `extract_assets` produces (edit it via
|
|
66
|
+
`update_character`). Writing appearance in both places guarantees they
|
|
67
|
+
contradict: portraits follow the profile, sheets follow the lock, and the
|
|
68
|
+
consistency gate then rejects the sheet against the portrait **every single
|
|
69
|
+
retry** — a structural dead loop that only burns money.
|
|
62
70
|
2. **Pipeline backbone — metered; in order; quote-then-confirm.** portraits →
|
|
63
71
|
frames → videos → TTS → compose. Spending stages follow the normal
|
|
64
72
|
quote → show the user → confirm flow (discipline 2).
|
|
@@ -110,7 +118,18 @@ content that will be rejected.
|
|
|
110
118
|
somewhere else.
|
|
111
119
|
|
|
112
120
|
6. **Follow the pipeline order — do not skip.** `set_script` → `rewrite_script`
|
|
113
|
-
→ `extract_assets` → portraits **+ sheets** → storyboards → frames → videos.
|
|
121
|
+
→ `extract_assets` → portraits **+ sheets** → storyboards → frames → videos.
|
|
122
|
+
**The rewrite step is mandatory and server-enforced**: put your raw material
|
|
123
|
+
(outline, synopsis, or even a finished script you wrote) into `set_script`,
|
|
124
|
+
then run `rewrite_script` — the platform rewrite produces a shootable draft
|
|
125
|
+
that stays self-consistent with the character profiles and storyboards built
|
|
126
|
+
from it. Pasting your own script straight into `edit_rewritten_script` to
|
|
127
|
+
skip the rewrite gets a 400 (no rewritten draft exists to edit), and
|
|
128
|
+
`extract_assets` likewise requires the rewritten draft. After the rewrite,
|
|
129
|
+
make every change in the AI output's structured format: polish the draft with
|
|
130
|
+
`edit_rewritten_script`, edit character profiles with `update_character`,
|
|
131
|
+
edit shots with `update_shot` / `replace_shot_dialogue` — never rewrite the
|
|
132
|
+
whole script out-of-band or duplicate its facts into settings fields. Always
|
|
114
133
|
generate frames **before** videos; skipping frames degrades video into
|
|
115
134
|
anchorless t2v — wasted money. Lock character portraits **and sheets** before
|
|
116
135
|
video: a portrait is one image, but **character sheets (multi-view turnarounds)
|
package/dist/client.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { createReadStream, statSync } from 'node:fs';
|
|
14
14
|
import { basename } from 'node:path';
|
|
15
15
|
import { Readable } from 'node:stream';
|
|
16
|
+
import { assertSafeLocalMediaPath } from './path-guard.js'; // v0.1.37 — P0③ 路径安全闸
|
|
16
17
|
/** 扩展名 → MIME(presign 需要 content_type)。 */
|
|
17
18
|
const MIME_BY_EXT = {
|
|
18
19
|
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp',
|
|
@@ -159,6 +160,9 @@ export class StarReelClient {
|
|
|
159
160
|
* 字节不经我们的业务服务器,只走 COS。kind: image(默认)/video/audio。
|
|
160
161
|
*/
|
|
161
162
|
async uploadLocalFile(filePath, kind = 'image') {
|
|
163
|
+
// v0.1.37 — P0③:媒体扩展名白名单 + 隐藏/系统目录拒绝(realpath 后判,防符号链接绕过)。
|
|
164
|
+
const safePath = assertSafeLocalMediaPath(filePath, kind);
|
|
165
|
+
filePath = safePath;
|
|
162
166
|
const size = statSync(filePath).size;
|
|
163
167
|
const filename = basename(filePath);
|
|
164
168
|
const ext = (filename.match(/\.[a-z0-9]+$/i)?.[0] || '').toLowerCase();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.1.37 — 本地文件上传的路径安全闸(安全评估 P0③)。
|
|
3
|
+
*
|
|
4
|
+
* uploadLocalFile 此前接受任意绝对路径:一个被注入的第三方 agent 可以把
|
|
5
|
+
* ~/.ssh/id_rsa、.env、浏览器资料库当"素材"上传到 COS(公共可读 URL)。
|
|
6
|
+
* 本闸不引入 workspace 概念(MCP 进程不知道调用方的工作目录语义),
|
|
7
|
+
* 而是用两条与"上传媒体素材"这一合法用途严格对齐的规则:
|
|
8
|
+
* ① 扩展名必须是对应 kind 的媒体白名单(.env/.pem/id_rsa 天然出局);
|
|
9
|
+
* ② realpath 后的路径中不得含任何以 '.' 开头的路径段(~/.ssh、~/.codex、
|
|
10
|
+
* ~/.aws、.git 等敏感目录全部命中),也不得落在系统配置区(/etc、/private/etc)。
|
|
11
|
+
* 符号链接先解析再判(防 media.jpg -> ~/.ssh/id_rsa 绕过)。
|
|
12
|
+
*/
|
|
13
|
+
import { realpathSync } from 'node:fs';
|
|
14
|
+
import { sep } from 'node:path';
|
|
15
|
+
export const MEDIA_EXT_BY_KIND = {
|
|
16
|
+
image: new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif']),
|
|
17
|
+
video: new Set(['.mp4', '.mov', '.webm', '.m4v']),
|
|
18
|
+
audio: new Set(['.mp3', '.wav', '.m4a', '.aac', '.flac', '.ogg']),
|
|
19
|
+
};
|
|
20
|
+
const SYSTEM_PREFIXES = ['/etc/', '/private/etc/', '/proc/', '/sys/'];
|
|
21
|
+
/** 校验通过返回解析后的真实路径;不通过抛 Error(消息面向第三方 agent,说清正路)。 */
|
|
22
|
+
export function assertSafeLocalMediaPath(filePath, kind, resolve = realpathSync) {
|
|
23
|
+
const ext = (filePath.match(/\.[a-z0-9]+$/i)?.[0] || '').toLowerCase();
|
|
24
|
+
if (!MEDIA_EXT_BY_KIND[kind].has(ext)) {
|
|
25
|
+
throw new Error(`file_path 只接受${kind === 'image' ? '图片' : kind === 'video' ? '视频' : '音频'}媒体文件`
|
|
26
|
+
+ `(${[...MEDIA_EXT_BY_KIND[kind]].join('/')});收到 "${ext || '无扩展名'}"。`
|
|
27
|
+
+ '不要用本工具上传配置/密钥/文档类文件。');
|
|
28
|
+
}
|
|
29
|
+
let real;
|
|
30
|
+
try {
|
|
31
|
+
real = resolve(filePath);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`file_path 不存在或不可读:${filePath}`);
|
|
35
|
+
}
|
|
36
|
+
const segments = real.split(sep);
|
|
37
|
+
if (segments.some(s => s.startsWith('.') && s.length > 1)) {
|
|
38
|
+
throw new Error(`file_path 位于隐藏目录(${real}),已拒绝——敏感目录(如 ~/.ssh、~/.aws、.git)不允许作为上传源。`
|
|
39
|
+
+ '把素材放到普通目录再上传。');
|
|
40
|
+
}
|
|
41
|
+
if (SYSTEM_PREFIXES.some(p => real.startsWith(p))) {
|
|
42
|
+
throw new Error(`file_path 位于系统目录(${real}),已拒绝。`);
|
|
43
|
+
}
|
|
44
|
+
return real;
|
|
45
|
+
}
|
package/dist/tools/produce.js
CHANGED
|
@@ -26,7 +26,14 @@ const WORKFLOW_HINT = '★三档执行策略(别把三档混着问客户):' +
|
|
|
26
26
|
'①【基础项目设定·免费·必做地基·建剧即设好,别建空壳】project_type/setting_brief(世界观·ERA LOCK)/' +
|
|
27
27
|
'ethnicity(族裔)/画幅分辨率,以及一致性锚 cinematography_prompt(摄影DNA)·art_bible(美术圣经)·visual_lock(视觉锁定)——' +
|
|
28
28
|
'全免费,是驱动全链一致性的地基;不设好,后续所有生成都跑偏、返工重花钱。用 create_drama/update_project_settings 直接设。' +
|
|
29
|
+
'★visual_lock/art_bible 只写画面级/世界级锁(镜头语言·环境·美术基调·禁入元素),**绝不为具体角色钉服装/发型/外观细节**——' +
|
|
30
|
+
'角色外观的唯一真相源是 extract_assets 产出的人物档案(要改走 update_character);两处都写必然互相矛盾,' +
|
|
31
|
+
'定妆图跟档案、设定图跟视觉锁,一致性闸按定妆图拒收 → 设定图/镜头帧**结构性连拒**,重掷多少次都过不了、纯白花钱。' +
|
|
29
32
|
'②【产线主干·按序不跳步·★先分镜再建资产】set_script→rewrite_script→extract_assets→storyboards(先分镜·纯文本拆镜)→' +
|
|
33
|
+
'★剧本纪律(端点强制,绕不过):原始素材(梗概/大纲/成品稿都算)一律放 set_script,**必须经 rewrite_script 产出 AI 改写稿**——' +
|
|
34
|
+
'把自己写好的剧本直接贴进 edit_rewritten_script 绕过改写会被 400 拒(没有改写稿就没有可改的对象),extract_assets 同样要求基于改写稿。' +
|
|
35
|
+
'改写后的所有修改按 AI 产物的结构化格式做:改稿 edit_rewritten_script(润色/纠正)、人物档案 update_character、分镜 update_shot/replace_shot_dialogue——' +
|
|
36
|
+
'别回头整篇替换剧本或在设定字段里另写一套,两套真相源打架是一致性事故的头号根源。' +
|
|
30
37
|
'generate_portraits_and_sheets(定妆图+设定图·分镜后建只给出场角色出图更省)→assign_voices(分配音色)→frames→videos→generate_tts→compose;' +
|
|
31
38
|
'★别先建角色形象/道具设定图/动作模板再分镜——分镜是纯文本步、不依赖任何图;资产在分镜后建更省更准(动作模板本就必须分镜后)。收费步照现有 quote 报价确认流程。' +
|
|
32
39
|
'广告另需 add_product+generate_product_sheet;MV 走 set_mv_lyrics→generate_mv_story→generate_mv_script。' +
|
|
@@ -147,7 +154,8 @@ export function registerProduceTools(server, client) {
|
|
|
147
154
|
});
|
|
148
155
|
// ---------- 灌本(原始内容) ----------
|
|
149
156
|
server.tool('set_script', '给某一集设置**原始剧本**(content)。这是 AI 改写的输入,不是最终可拍稿。免费。' +
|
|
150
|
-
'
|
|
157
|
+
'梗概/大纲/自己写好的成品稿都放这里,设完**必须调 rewrite_script 做 AI 改写**——' +
|
|
158
|
+
'不能跳过改写直接把稿子贴进 edit_rewritten_script(会被拒)。' + WORKFLOW_HINT, {
|
|
151
159
|
episode_id: z.number().int().positive(),
|
|
152
160
|
script: z.string().min(1).describe('该集的原始剧本文本(原稿)'),
|
|
153
161
|
}, async ({ episode_id, script }) => jsonResult(await client.producePut(`/episodes/${episode_id}/script`, { content: script })));
|
|
@@ -158,13 +166,14 @@ export function registerProduceTools(server, client) {
|
|
|
158
166
|
'★典型耗时 2~4 分钟(生产实测 ≈169 秒)。**60 秒内查不到结果是正常的,不是失败**——' +
|
|
159
167
|
'用 get_run_status 判断还在不在跑,别急着重发。' + WORKFLOW_HINT, { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/rewrite`)));
|
|
160
168
|
server.tool('get_script', '读某一集的原始内容 + AI 改写后的可拍稿 + 改写状态(供审阅、决定是否 edit_rewritten_script)。免费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/script`)));
|
|
161
|
-
server.tool('edit_rewritten_script', '客户改 AI 改写后的可拍稿(写 script_content)
|
|
169
|
+
server.tool('edit_rewritten_script', '客户改 AI 改写后的可拍稿(写 script_content)。只用于修改 AI 改写产出的稿(人工润色/纠正);' +
|
|
170
|
+
'本集还没跑过 rewrite_script 时会被 400 拒——这不是绕过改写的通道,别把自己写好的剧本直接贴进来。免费。', {
|
|
162
171
|
episode_id: z.number().int().positive(),
|
|
163
172
|
script: z.string().min(1).describe('改好的可拍剧本(覆盖 AI 改写稿)'),
|
|
164
173
|
}, async ({ episode_id, script }) => jsonResult(await client.producePut(`/episodes/${episode_id}/rewritten-script`, { script_content: script })));
|
|
165
174
|
// ---------- 提取(角色/场景/道具) ----------
|
|
166
175
|
server.tool('extract_assets', '从可拍稿提取角色/场景/道具(一次写三表,是下游一致性的地基)。后台异步,文本步后付不欠费。' +
|
|
167
|
-
'前置:已 rewrite_script(
|
|
176
|
+
'前置:已 rewrite_script 产出改写稿(新项目强制;人物档案从改写稿提取才与剧本、分镜自洽)。' +
|
|
168
177
|
'★分钟级后台任务;用 get_run_status 判断是否还在跑,别拿 60 秒当失败判据。' + WORKFLOW_HINT, { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/extract`)));
|
|
169
178
|
// ---------- 完整工作流进度 ----------
|
|
170
179
|
server.tool('get_pipeline_status', '查某一集完整工作流的进度(script_rewrite/提取/分镜/语音/出图/出视频/合成/配乐/终拼…各步 ' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starreel/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
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.
|
|
5
|
+
"version": "0.1.37",
|
|
6
6
|
"websiteUrl": "https://starreel.ai",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@starreel/mcp",
|
|
11
|
-
"version": "0.1.
|
|
11
|
+
"version": "0.1.37",
|
|
12
12
|
"transport": { "type": "stdio" },
|
|
13
13
|
"environmentVariables": [
|
|
14
14
|
{
|