davinci-resolve-mcp 2.145.1 → 2.146.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  Release history for the DaVinci Resolve MCP Server. The latest release is summarized in the root README; older entries live here to keep the README focused.
4
4
 
5
+ ## What's New in v2.146.0 — bins, and the folder registry law
6
+
7
+ ### Added
8
+
9
+ - **`assemble_project` `timelines[].folder`** — place reels in named Master
10
+ bins (entries sharing a name share the bin; media stays in Master).
11
+ Live-proven: a Reels bin holding both timeline clips, both timelines
12
+ materialized, and a binned reel rendering its exact content.
13
+
14
+ ### Measured (the folder registry law)
15
+
16
+ - **The parent folder's FieldsBlob is the subfolder registry.** Media and
17
+ timeline children are discovered by scan; subfolders are NOT — an
18
+ unregistered bin directory imports as nothing and silently takes its
19
+ clips' timelines with it. The registry's inner format is byte-verified
20
+ against the template harvest (a keyed child-id dict in a protobuf wrapper,
21
+ zstd-framed). Natively created Resolve projects carry an EMPTY folder blob
22
+ when binless — the assembly templates now match that convention
23
+ (render-verified as a no-op).
24
+
25
+ ## What's New in v2.145.2 — launcher metadata before dependencies
26
+
27
+ ### Fixed
28
+
29
+ - **`davinci-resolve-advanced-mcp --help`/`--version` now work in fresh
30
+ source checkouts** (before `npm install`) — adapted from
31
+ [PR #178](https://github.com/samuelgursky/davinci-resolve-mcp/pull/178) by
32
+ @Rohitkanithi: metadata flags are handled before the stdio server import
33
+ (and before the Node-floor refusal — help is harmless on any Node), where
34
+ previously even `--help` died with `ERR_MODULE_NOT_FOUND`.
35
+ - **The installer banner's tool counts were stale** (32/329 vs the real
36
+ 36/353) — fixed and wired into the `test_doc_tool_counts` drift guard so
37
+ the banner can never drift independently again.
38
+
5
39
  ## What's New in v2.145.1 — bridge config override, honored end to end
6
40
 
7
41
  ### Fixed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.145.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.146.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.145.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.146.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v2.145.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.146.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -11,8 +11,36 @@
11
11
  */
12
12
 
13
13
  import { fileURLToPath, pathToFileURL } from 'node:url';
14
+ import fs from 'node:fs';
14
15
  import path from 'node:path';
15
16
 
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ const packageRoot = path.resolve(__dirname, '..');
19
+ const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
20
+ const version = packageJson.version || '0.0.0-dev';
21
+
22
+ function usage() {
23
+ return `DaVinci Resolve Advanced MCP ${version}
24
+
25
+ Usage:
26
+ davinci-resolve-advanced-mcp
27
+ davinci-resolve-advanced-mcp --version
28
+ davinci-resolve-advanced-mcp --help
29
+
30
+ Starts the offline DaVinci Resolve advanced MCP server over stdio.
31
+ `;
32
+ }
33
+
34
+ const command = process.argv[2];
35
+ if (command === '--help' || command === '-h' || command === 'help') {
36
+ process.stdout.write(usage());
37
+ process.exit(0);
38
+ }
39
+ if (command === '--version' || command === '-v' || command === 'version') {
40
+ process.stdout.write(`${version}\n`);
41
+ process.exit(0);
42
+ }
43
+
16
44
  // Node floor (package.json engines: >=20.9), enforced at STARTUP rather than
17
45
  // discovered per-feature: under an old Node the pure-JS tools limp along
18
46
  // while native-dep paths (better-sqlite3: project_read, fairlight DB actions,
@@ -34,7 +62,6 @@ if (major < 20 || (major === 20 && minor < 9)) {
34
62
  process.exit(1);
35
63
  }
36
64
 
37
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
38
65
  const serverEntry = path.resolve(__dirname, '..', 'resolve-advanced', 'server', 'index.mjs');
39
66
 
40
67
  const { startServer } = await import(pathToFileURL(serverEntry).href);
@@ -62,6 +62,7 @@ window. `render.verify_output` covers the container-level checks.
62
62
  | Nested compounds | `compounds[].compounds` (depth-2 through depth-4 playback render-verified) | v2.134–2.141 |
63
63
  | Fusion titles | `elements: [{type:'title', text}]` — **21-gen hosts only** | v2.108 |
64
64
  | Multi-timeline projects | `drt.assemble_project` (reel-per-timeline .drp; import as a project) | v2.145 |
65
+ | Pool bins | `assemble_project` `timelines[].folder` (named Master subfolders, registry-backed) | v2.146 |
65
66
 
66
67
  `assemble_from_interchange` drives the same engine from an EDL / OTIO /
67
68
  FCP7-XML / AAF / **.prproj** (Premiere, read offline — no Premiere needed)
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.145.1"
40
+ VERSION = "2.146.0"
41
41
  # Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
42
42
  # Resolve's scripting bridge loads into newer interpreters on recent builds
43
43
  # (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
@@ -1474,7 +1474,7 @@ def verify_resolve_connection(python_path, api_path, lib_path):
1474
1474
 
1475
1475
  def print_banner():
1476
1476
  title = f"DaVinci Resolve MCP Server — Installer v{VERSION}"
1477
- subtitle = "32 compound · 329 full · 3 platforms"
1477
+ subtitle = "36 compound · 353 full · 3 platforms"
1478
1478
  print()
1479
1479
  print(bold(" ╔══════════════════════════════════════════════════════╗"))
1480
1480
  print(bold(f" ║{title:^54}║"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.145.1",
3
+ "version": "2.146.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -303,16 +303,23 @@ export const drtTool = {
303
303
  // single-timeline .drt import path only takes one timeline per file.
304
304
  const p = z.object({
305
305
  timelines: z.array(z.object({}).passthrough()).min(2)
306
- .describe('Two or more assembleTimeline specs (same shape as `assemble` spec); timelineName required and unique per entry'),
306
+ .describe("Two or more assembleTimeline specs (same shape as `assemble` spec); timelineName required and unique per entry. Optional per-entry `folder` places that timeline's pool clip in a named Master subfolder (bins for reel-per-timeline packages; entries sharing a name share the bin)."),
307
307
  outputPath: z.string().describe('Where the multi-timeline .drp is written'),
308
308
  targetAppVersion: z.union([z.string(), z.number()]).optional(),
309
309
  }).parse(args);
310
310
  const names = p.timelines.map((t, i) => t.timelineName || `Timeline ${i + 1}`);
311
311
  if (new Set(names).size !== names.length) throw new Error(`assemble_project: timelineName must be unique per timeline (got: ${names.join(', ')})`);
312
+ const folders = p.timelines.map((t) => {
313
+ if (t.folder === undefined) return null;
314
+ const f = String(t.folder).trim();
315
+ if (!f || /[\/\\]/.test(f)) throw new Error(`assemble_project: folder must be a plain bin name (no path separators): ${JSON.stringify(t.folder)}`);
316
+ return f;
317
+ });
312
318
  const { assembleTimeline } = drp();
313
319
  const buffers = [];
314
320
  for (const [i, spec] of p.timelines.entries()) {
315
321
  const s = { ...spec, timelineName: names[i] };
322
+ delete s.folder;
316
323
  if (s.templateVersion === undefined && p.targetAppVersion !== undefined) {
317
324
  s.templateVersion = parseFloat(p.targetAppVersion) >= 21 ? 21 : 19;
318
325
  }
@@ -434,6 +441,60 @@ export const drtTool = {
434
441
  for (const id of remap.values()) baseIds.add(id);
435
442
  for (const id of clusterText.match(UUID_RE) || []) if (!remap.has(id)) baseIds.add(id);
436
443
  }
444
+ // SUBFOLDERS (E87): a bin is just a directory + its own MpFolder.xml —
445
+ // the directory TREE is the registry (measured: no folder vec exists;
446
+ // children carry <MpFolder> back-refs). Move each foldered timeline's
447
+ // pool clip from Master's MediaVec into its bin's, and repoint the
448
+ // back-ref. Media elements stay in Master (shared by design).
449
+ if (folders.some(Boolean)) {
450
+ const masterFolderId = mpXml.match(/<Sm2MpFolder DbId="([^"]+)"/)[1];
451
+ const poolId = (mpXml.match(/<MediaPool>([^<]+)<\/MediaPool>/) || [])[1] || '';
452
+ const bins = new Map();
453
+ for (const [i, folder] of folders.entries()) {
454
+ if (!folder) continue;
455
+ if (!bins.has(folder)) {
456
+ const binId = randomUUID();
457
+ const bin = { id: binId, entry: `MediaPool/Master/${folder}/MpFolder.xml`, clips: [] };
458
+ bins.set(folder, bin);
459
+ }
460
+ const bin = bins.get(folder);
461
+ const tlRe = new RegExp(`<Element>\\s*<Sm2MpTimelineClip DbId="[^"]+">(?:(?!<\\/Element>\\s*<Element>)[\\s\\S])*?<Name>${names[i].replace(/[.*+?^$()|[\]{}]/g, '\\$&')}<\\/Name>[\\s\\S]*?<\\/Sm2MpTimelineClip>\\s*<\\/Element>`);
462
+ const hit = mpXml.match(tlRe);
463
+ if (!hit) throw new Error(`assemble_project: could not locate pool clip for timeline ${names[i]} to move into folder ${folder}`);
464
+ mpXml = mpXml.replace(hit[0], '');
465
+ bin.clips.push(hit[0].replace(/<MpFolder>[^<]*<\/MpFolder>/, `<MpFolder>${bin.id}</MpFolder>`));
466
+ }
467
+ // Register the bins in Master's FieldsBlob — the parent folder blob
468
+ // is the SUBFOLDER registry (measured: with it blanked, a bin's
469
+ // directory + MpFolder.xml import as NOTHING — its clips and their
470
+ // timelines all vanish; media/timeline children are discovered by
471
+ // scan, subfolders are not). Inner format byte-verified against the
472
+ // template harvest: protobuf{field2: keyedDict{"0": binId, ...},
473
+ // field4: time-varint} in the [u32 2][u32 len][0x81][zstd] wrapper.
474
+ const { zstdRawFrame } = requireCjs('../../vendor/drp-format/timeline-markers-blob.js');
475
+ const binIds = [...bins.values()].map((b) => b.id);
476
+ const childDict = encodeKeyedDict({ hdr: 1, entries: binIds.map((id, i) => ({ key: String(i), type: 0x0a, subType: 0, value: id })) });
477
+ const inner = Buffer.concat([
478
+ Buffer.from([0x12, childDict.length]), childDict,
479
+ Buffer.from([0x20]), Buffer.from('b6cba6a90d', 'hex'),
480
+ ]);
481
+ const frame = zstdRawFrame(inner);
482
+ const folderBlob = Buffer.concat([
483
+ Buffer.from([0, 0, 0, 2]),
484
+ (() => { const b = Buffer.alloc(4); b.writeUInt32BE(frame.length + 1, 0); return b; })(),
485
+ Buffer.from([0x81]), frame,
486
+ ]).toString('hex');
487
+ mpXml = mpXml.replace(/(<Sm2MpFolder DbId="[^"]+">\s*)<FieldsBlob\/>/, `$1<FieldsBlob>${folderBlob}</FieldsBlob>`);
488
+ for (const [folder, bin] of bins) {
489
+ base.file(bin.entry,
490
+ `<?xml version="1.0" encoding="UTF-8"?>\n` +
491
+ `<Sm2MpFolder DbId="${bin.id}">\n <FieldsBlob/>\n <Name>${folder}</Name>\n` +
492
+ ` <MpFolder>${masterFolderId}</MpFolder>\n <UniqueMediaPoolItemId>${randomUUID()}</UniqueMediaPoolItemId>\n` +
493
+ ` <MediaVec>\n${bin.clips.join('\n')}\n </MediaVec>\n` +
494
+ ` <MediaPool>${poolId}</MediaPool>\n <Folded>false</Folded>\n <ColorTag>FOLDER_COLOR_NONE</ColorTag>\n` +
495
+ ` <LockSysId/>\n <DbSavedTime>0</DbSavedTime>\n</Sm2MpFolder>\n`);
496
+ }
497
+ }
437
498
  base.file(mpP, mpXml);
438
499
  base.file('project.xml', pjXml);
439
500
  let outBuf = await base.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
@@ -156,4 +156,4 @@ function decodeTimelineMarkersBlob(buf) {
156
156
  return markers;
157
157
  }
158
158
 
159
- module.exports = { encodeTimelineMarkersBlob, decodeTimelineMarkersBlob, MARKER_COLOR_BITS };
159
+ module.exports = { encodeTimelineMarkersBlob, decodeTimelineMarkersBlob, MARKER_COLOR_BITS, zstdRawFrame };
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.145.1"
90
+ VERSION = "2.146.0"
91
91
  logger = logging.getLogger("davinci-resolve-mcp")
92
92
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
93
93
  logger.info(f"Detected platform: {get_platform()}")
package/src/server.py CHANGED
@@ -11,7 +11,7 @@ Usage:
11
11
  python src/server.py --full # Start the 353-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.145.1"
14
+ VERSION = "2.146.0"
15
15
 
16
16
  import base64
17
17
  import os