davinci-resolve-mcp 2.98.8 → 2.99.1

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,123 @@
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.99.1
6
+
7
+ **`bulk_set_item_properties` could not set a clip colour on its own.** Reported
8
+ and fixed in [#157](https://github.com/samuelgursky/davinci-resolve-mcp/pull/157)
9
+ by @matoberuc-afk.
10
+
11
+ The action documents `clip_color` and `enabled` as per-op keys and has code to
12
+ apply both — but that code was unreachable for the op shape that needs it most.
13
+ The payload is built by `_merge_property_groups`, which merges only
14
+ `properties`/`transform`/`crop`/`composite`/`audio` and the duplicate-keyframe
15
+ keys. `clip_color` and `enabled` are not `SetProperty` keys, so they never landed
16
+ in that dict, and the `if not properties: continue` guard above returned early —
17
+ leaving the `clip_color` branch twenty-five lines below dead on exactly the ops
18
+ that carry no transform. Colour triage in one round trip is the main reason to
19
+ call a *bulk* setter, and it was the one shape that could not work.
20
+
21
+ ### Fixed
22
+
23
+ - **A colour-only or enabled-only op is now accepted** and applied.
24
+ - **A colour-only op could not fail.** Per-op success was
25
+ `all(row.get("success") for row in ...properties.values())`, and `all([])` is
26
+ `True` — with no property rows the op passed regardless of what `SetClipColor`
27
+ returned. Every branch that runs now votes.
28
+ - **The bulk path trusted the bare bool.** It called `item.SetClipColor` directly,
29
+ bypassing `_set_clip_color_checked` — the helper added for
30
+ [#124](https://github.com/samuelgursky/davinci-resolve-mcp/issues/124) that the
31
+ single-item path already used, because that bool lies twice: a name outside the
32
+ 16-name Edit-page palette is refused with a bare `False`, and a generator or
33
+ title takes the call, returns `True`, and drops the colour. A failure now
34
+ carries `clip_color_detail`.
35
+ - `dry_run` reports `would_set_clip_color` / `would_set_enabled`, and the
36
+ `action_help` example shows the triage shape instead of a `properties`
37
+ dict with a `ClipColor` key that was never a valid `SetProperty` target.
38
+
39
+ ### Live validation
40
+
41
+ Studio 19.1.3.7: three colour-only ops in one call, live readback
42
+ `['Apricot', 'Chocolate', 'Purple']` on the timeline items; a refused colour
43
+ returns `success: false` instead of passing on the empty-list vote.
44
+
45
+ ## What's New in v2.99.0
46
+
47
+ **`timeline.ripple_insert`, and a verified-delete gate on `move_clips`.**
48
+ Contributed in [#156](https://github.com/samuelgursky/davinci-resolve-mcp/pull/156)
49
+ by @handst97, driven by a real data-loss incident: a session used `move_clips`
50
+ with a record offset smaller than the item duration to "open a gap",
51
+ `AppendToTimeline` returned items whose ids could not be read, the code counted
52
+ them as successful duplicates, and the delete phase removed 26 source clips.
53
+
54
+ ### Added
55
+
56
+ - **`timeline.ripple_insert`** — insert media-pool source ranges at a record
57
+ point and shift all later video/audio items right. There is no ripple-insert
58
+ primitive in the scripting API, and duplicate-then-delete corrupts the timeline
59
+ when the shift is smaller than an item. This plans a rebuild instead: capture
60
+ every tail item's pool media and source trim, delete the tail (verified),
61
+ re-append it shifted, then place the inserts into the opened gap — **tail
62
+ first, so the worst mid-failure state is a gap, never lost content**. Dry-run
63
+ by default, with straddler / blocker / locked-track / subtitle detection;
64
+ executing is confirm-token gated and archives the timeline first. Shifted items
65
+ are re-created from pool media with transform/crop/composite/retime re-applied;
66
+ grades, keyframes, transitions and link state are NOT preserved (the archive
67
+ keeps them).
68
+
69
+ ### Fixed
70
+
71
+ - **`move_clips` no longer deletes a source it could not verify.** Each duplicate
72
+ now carries `duplicate_verified` — a live item or a real id recovered from the
73
+ timeline — and sources are deleted only when every duplicate, primary and
74
+ linked, verified. Null-id appends keep the source with an explicit warning.
75
+ - **`safe_set_cdl` / `apply_look_to_items` preflight the node.** `NodeIndex` is
76
+ 1-based and there is no `GetCDL`, so a bare `False` was undiagnosable; the node
77
+ count is now read before `SetCDL` and a failure comes back with a structured
78
+ reason and a clip-type-aware diagnosis.
79
+ - **Magic Mask reports the human step instead of a bare false.** Magic Mask v2
80
+ isolates via operator clicks and the API cannot place them, so `CreateMagicMask`
81
+ on a fresh item can only return `False`. It now returns `needs_hitl` with the
82
+ exact Color-page steps, and the mode aliases (`'Forward'` → `'F'`) are
83
+ normalized — the granular `ti_create_magic_mask` defaulted to a spelling
84
+ Resolve rejects.
85
+
86
+ ### Fixed in review
87
+
88
+ Three defects found reviewing #156, each with a test that fails without the fix:
89
+
90
+ - **A dry-run plan archived a timeline version.** `ripple_insert` is the only
91
+ destructive action whose *default* call mutates nothing, and the pending-confirm
92
+ skip only fires while the confirm-token preference is on — so with it off,
93
+ routine planning calls littered the version chain.
94
+ - **Asymmetric per-track insert durations left an unreported gap.** Every track
95
+ shifts by the longest inserted run, so a shorter insert leaves a hole the
96
+ readback structurally cannot see — it only checks the positions it placed. The
97
+ plan and the result now carry `gap_frames_by_track` and a warning.
98
+ - **Subtitle straddlers passed the feasibility check.** Video and audio
99
+ straddlers already refuse the plan; a subtitle across the insert point stayed
100
+ put while the picture under it moved.
101
+
102
+ ### Live validation
103
+
104
+ Verified on Studio 19.1.3.7 via `tests/live_ripple_insert_validation.py`:
105
+ dry-run plan exact (insert@86448, shift 24, tail 2, no straddlers), confirm-token
106
+ round-trip, post-insert layout `[(86400,48),(86448,24),(86472,48),(86520,48)]`,
107
+ `readback.missing=[]`, and ZoomX/Y=0.5 surviving the shift with
108
+ `property_restore_failures=0`.
109
+
110
+ ### A note on the Fusion measurement in #156
111
+
112
+ The PR proposed recording that whether an API-created Fusion comp renders is
113
+ Resolve-version-dependent, from a 21.0.4 run where a wired comp delivered a render
114
+ bit-identical to the baseline. That reading does not survive: those comps were
115
+ built and set through this server, and the v2.98.5 comp-lock bug produced exactly
116
+ that symptom on any build. Running the PR's own phase-2 harness unchanged against
117
+ the fixed code now reports **RENDERED** (Blur 24.38 dB, Transform 7.95 dB) where
118
+ it previously reported IGNORED. The `AddFusionComp` entry records the 21.0.4
119
+ observation as independent evidence that the lock bug is not specific to
120
+ 19.1.3.7 — the one thing this machine cannot test.
121
+
5
122
  ## What's New in v2.98.8
6
123
 
7
124
  **The comp-lock mechanism, settled — and the v2.98.6 scope correction was itself
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.98.8-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.99.1-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-35%20(353%20full)-blue.svg)](#server-modes)
@@ -211,7 +211,7 @@ The open-source servers are complete and fully functional on their own.
211
211
  | App and project control | Launch/reconnect, page switching, project CRUD, project folders, databases, cloud project wrappers, settings, presets, archives |
212
212
  | Media pool and ingest | Safe import, image sequences, multicam prep timelines, bin organization, metadata normalization, metadata field inventory, marks, annotations, relink/proxy/full-resolution guards |
213
213
  | Media analysis | Source-safe file/clip/bin/project analysis, 2-pop/slate-clap sync-event detection, default Resolve metadata and Media Pool marker writeback, persisted analysis artifacts, existing-report reuse, host_chat_paths visual analysis (finalized per clip with `commit_vision`, works with any vision-capable MCP client) with opt-out, transcription with opt-out |
214
- | Timeline editing and conform | Track/item probing, title text key scans/writes, copy/move/duplicate helpers, range operations, gaps/overlaps, source ranges, checked interchange exports/imports |
214
+ | Timeline editing and conform | Track/item probing, title text key scans/writes, copy/move/duplicate helpers, ripple insert, range operations, gaps/overlaps, source ranges, checked interchange exports/imports |
215
215
  | Review annotations | Timeline/item/clip markers, custom data, flags, clip color, copy/move/sync cleanup, review reports, marker thumbnail review |
216
216
  | Color and grading | Node graph probing, CDL validation, grade copy, DRX/LUT helpers, versions, Gallery stills, color groups |
217
217
  | Fusion | Timeline-item comps, safe tool creation, input writes, port inspection, validated connections, scoped bulk writes |
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.98.8-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.99.1-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-35%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.98.8 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.99.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -142,7 +142,7 @@ DRX 调色写入**针对 Resolve Studio 做过实机校准**:调色参数默
142
142
  | 应用与项目控制 | 启动/重连、页面切换、项目增删改查、项目文件夹、数据库、云项目封装、设置、预设、归档 |
143
143
  | 媒体池与摄入 | 安全导入、图像序列、多机位预备时间线、bin 整理、元数据规范化、元数据字段清单、标记、注释、重链/代理/全分辨率护栏 |
144
144
  | 媒体分析 | 源媒体安全的文件/片段/bin/项目分析,2-pop/场记板同步事件检测,默认写回 Resolve 元数据与媒体池标记,持久化分析产物,复用既有报告,host_chat_paths 视觉分析(每个片段以 `commit_vision` 定稿,任何具备视觉能力的 MCP 客户端都可用)可选退出,转写可选退出 |
145
- | 时间线剪辑与套底 | 轨道/条目探测、标题文字键扫描/写入、复制/移动/克隆助手、区间操作、缝隙/重叠、源范围、带检查的交换格式导入导出 |
145
+ | 时间线剪辑与套底 | 轨道/条目探测、标题文字键扫描/写入、复制/移动/克隆助手、波纹插入、区间操作、缝隙/重叠、源范围、带检查的交换格式导入导出 |
146
146
  | 审阅注释 | 时间线/条目/片段标记、自定义数据、旗标、片段颜色、复制/移动/同步清理、审阅报告、标记缩略图审阅 |
147
147
  | 调色 | 节点图探测、CDL 校验、调色复制、DRX/LUT 助手、版本、Gallery 静帧、调色组 |
148
148
  | Fusion | 时间线条目合成、安全工具创建、输入写入、端口检查、带校验的连接、限定范围的批量写入 |
package/docs/SKILL.md CHANGED
@@ -444,7 +444,11 @@ same timecodes, then restore the previous active version or node-enabled state
444
444
  after any temporary bypass capture. Treat untreated frames as diagnostic
445
445
  evidence, not as permission to discard an existing creative grade.
446
446
 
447
- Prefer `safe_set_cdl` for small reversible primary corrections. Use DRX/stills
447
+ Prefer `safe_set_cdl` for small reversible primary corrections. `SetCDL`'s
448
+ `NodeIndex` is 1-BASED (scripting README line 6) and there is no `GetCDL`
449
+ readback — `safe_set_cdl` and `apply_look_to_items` now read the node graph's
450
+ `GetNumNodes` first and return a structured reason/diagnosis on a false
451
+ `SetCDL` instead of a bare boolean. Use DRX/stills
448
452
  or grade copy only when the user accepts whole-grade replacement/transfer
449
453
  semantics. Use DCTL/LUT authoring only for reusable mathematical transforms, not
450
454
  as a substitute for hand-built windows, qualifiers, or tracked secondaries. Do
@@ -1377,7 +1381,20 @@ Key actions:
1377
1381
  unsupported because Resolve's public scripting API does not expose transition
1378
1382
  cloning. `copy_keyframes=True` adds the `keyframes` group.
1379
1383
  - `copy_clips(...)` / `move_clips(...)` — same safe append path; `move_clips`
1380
- deletes successfully duplicated source items afterward
1384
+ deletes only sources whose duplicate was VERIFIED live on the timeline
1385
+ (AppendToTimeline can return null-id items — e.g. into an occupied span — and
1386
+ unverified sources are kept with a warning; see api_truth
1387
+ 'AppendToTimeline null-id'). NEVER use `move_clips` to open a gap for an
1388
+ insert; that is `ripple_insert`'s job.
1389
+ - `ripple_insert(clip_infos, record_frame|record_timecode, record_frame_mode?,
1390
+ dry_run?, confirm_token?)` — insert media-pool source ranges at a record point
1391
+ and shift ALL later video/audio items right. DRY-RUN by default (full plan
1392
+ with straddler/blocker detection); executing is confirm-token gated and
1393
+ archives the timeline first. Shifted items are re-created from pool media
1394
+ with transform/crop/composite/retime re-applied; grades, keyframes,
1395
+ transitions, and link state on shifted items are NOT preserved (the archive
1396
+ keeps them). Refuses mid-item insert points, non-pool items in the tail
1397
+ (titles/generators/Fusion comps), subtitle shifts, and locked tracks.
1381
1398
  - `copy_range` / `duplicate_range` — copy exact video/audio source segments
1382
1399
  from `start_frame`/`end_frame` or mark in/out to `record_frame`
1383
1400
  - `overwrite_range` — delete whole destination overlaps, then copy the exact
@@ -1393,7 +1410,11 @@ Key actions:
1393
1410
  `track_index?` (default 1), so multicam angles can be rebuilt onto V2/V3
1394
1411
  rather than collapsing onto V1; missing tracks are added
1395
1412
  - `bulk_set_item_properties(ops, dry_run?, readback?)` — apply transforms,
1396
- crop/composite/audio/property groups to many timeline items in one call
1413
+ crop/composite/audio/property groups to many timeline items in one call. An op
1414
+ may carry `clip_color` and/or `enabled` with nothing else, which is the triage
1415
+ shape: paint a whole selection in one round trip. A colour is verified by
1416
+ readback, so a name outside the Edit-page palette and the generator/title case
1417
+ that returns True and drops the colour both fail the op instead of passing
1397
1418
  - `apply_look_to_items(target_ids, cdl?|copy_from_item_id?, dry_run?)` — apply a
1398
1419
  normalized CDL and/or copy a source grade to multiple video items
1399
1420
  - `thumbnail_contact_sheet` / `marker_thumbnail_review` — sample Resolve
@@ -1403,7 +1424,11 @@ Key actions:
1403
1424
  page and only while it is frontmost; the tool switches page automatically and
1404
1425
  restores the previous one. Expect a page flash in the GUI,
1405
1426
  and note that landing on Color can kick off cache/render work for the current
1406
- clip — on a large timeline the switch is not free
1427
+ clip — on a large timeline the switch is not free.
1428
+ NOT WYSIWYG for Fusion: thumbnails do not reflect Fusion composition output
1429
+ (a warp demo read as identical before/after from a contact sheet,
1430
+ 2026-08-19). Prove Fusion/grade claims with `gallery_stills grab_and_export`
1431
+ or an extracted RENDERED frame, never a thumbnail
1407
1432
  - `edit_kernel_capabilities` — report supported, partially supported, and
1408
1433
  unsupported timeline edit kernel behavior
1409
1434
  - `probe_edit_kernel_item(clip_ids? selected? timeline_item?)` — read-only
@@ -1602,7 +1627,12 @@ Key actions:
1602
1627
  clip version
1603
1628
  - `stabilize`, `smart_reframe`
1604
1629
  - `create_magic_mask(mode)` — mode: `"F"` forward, `"B"` backward, `"BI"` bidirectional
1605
- (requires DaVinci Neural Engine and Color page)
1630
+ (requires DaVinci Neural Engine and Color page). Magic Mask v2 isolates via
1631
+ operator CLICKS on the subject (manual ch. 139; strokes are legacy v1) and
1632
+ the API cannot place clicks — with none present this returns
1633
+ `{needs_hitl: true, hitl: {steps...}}` instead of a bare false. Never call it
1634
+ as if it isolates a subject unattended; prove any isolation with a rendered
1635
+ frame (`gallery_stills grab_and_export`).
1606
1636
 
1607
1637
  Color / Grade kernel actions (v2.11.0+) add safer grade inspection and
1608
1638
  boundary helpers: `grade_capabilities`, `probe_grade_item`,
@@ -2118,6 +2148,18 @@ The server provides several mechanisms to inspect a frame as Resolve has process
2118
2148
  it, including color grading, effects, and compositing — not just the raw source
2119
2149
  file.
2120
2150
 
2151
+ WYSIWYG hierarchy (live-verified 2026-08-20): a `grab_and_export` gallery still
2152
+ faithfully reflects edit sizing (Inspector transforms) and grades; media-pool
2153
+ thumbnails and `thumbnail_contact_sheet` output do NOT reflect Fusion
2154
+ composition output. Also note that whether an API-created Fusion comp is
2155
+ honoured at render is Resolve-version-dependent: a wired comp rendered on
2156
+ Studio 19.1.3.7, but on Studio 21.0.4 the same Blur configuration and a
2157
+ Transform variant both rendered bit-identical to the no-comp baseline, and no
2158
+ API selects an item's active composition (api_truth
2159
+ 'AddFusionComp'). The only acceptable proof of a Fusion or grade claim is a
2160
+ rendered frame: `grab_and_export`, an exported gallery still, or a frame
2161
+ extracted from a delivered render.
2162
+
2121
2163
  **Start here: `timeline_frame(action="capture")`** — Returns the frame at the
2122
2164
  playhead (or at any `timecode`/`frame` you name) as MCP image content, so a
2123
2165
  multimodal assistant can simply look at it. It renders that one frame, which is
@@ -12,7 +12,7 @@ that none exists).
12
12
 
13
13
  **Verified on:** DaVinci Resolve Studio 21.0.2
14
14
 
15
- **Totals:** 27 missing capabilities, 36 bugs / unreliable behaviors.
15
+ **Totals:** 29 missing capabilities, 37 bugs / unreliable behaviors.
16
16
 
17
17
  The authoritative source is the runtime-queryable `api_truth` ledger
18
18
  (`resolve_control api_truth "<query>"`); this document is generated from
@@ -239,6 +239,22 @@ equivalent, blocking full automation.
239
239
  - **Workaround / current handling:** Check GetRenderCodecs(format) first; when it is empty, treat the format as unreachable through this API rather than guessing a codec value. Render audio-only via ExportVideo=False on a format that does expose codecs, or drive it from a saved render preset.
240
240
  - **Tags:** render, deliver, audio, unsupported
241
241
 
242
+ ### TimelineItem.CreateMagicMask (needs operator clicks)
243
+
244
+ - **Object:** `TimelineItem`
245
+ - **Signature:** `(mode) -> bool`
246
+ - **Behavior:** Returns False when the item carries no Magic Mask clicks. Magic Mask v2 is click-driven (manual ch. 139; strokes are the legacy v1 interface) and the scripting API has no way to place a click, so on a fresh item the call can never isolate anything — it only tracks a mask the operator already seeded. Mode strings are 'F', 'B', 'BI'; long spellings like 'Forward' are rejected.
247
+ - **Workaround / current handling:** Treat CreateMagicMask as track-only: have the operator click the subject (Color page > Magic Mask palette) and then call it, or surface the HITL steps instead of a bare False. Verify isolation with a rendered frame (gallery_stills grab_and_export), never a thumbnail.
248
+ - **Tags:** ai, magic-mask, hitl, silent-failure
249
+
250
+ ### TimelineItem.SetCDL (write-only, no GetCDL)
251
+
252
+ - **Object:** `TimelineItem`
253
+ - **Signature:** `({NodeIndex, Slope, Offset, Power, Saturation}) -> bool`
254
+ - **Behavior:** There is no GetCDL, so applied CDL values cannot be read back; the bool is the only signal and it returns False with no reason (missing node, still/generator item, values silently rejected). NodeIndex is 1-based (README line 6) and must not exceed Graph.GetNumNodes() — note TimelineItem.GetNumNodes is deprecated; the count lives on item.GetNodeGraph().
255
+ - **Workaround / current handling:** Read item.GetNodeGraph().GetNumNodes() before SetCDL and diagnose a False against the node count and clip type. Prove the applied look with a rendered frame (gallery_stills grab_and_export or Project.ExportCurrentFrameAsStill), not the return value.
256
+ - **Tags:** color, cdl, readback, silent-failure
257
+
242
258
  ## Bugs / Unreliable Behavior (please fix)
243
259
 
244
260
  Methods that exist but misbehave — silent failures, unreliable return
@@ -350,9 +366,9 @@ values, or automation-hostile modal prompts.
350
366
  ### TimelineItem.AddFusionComp / LoadFusionCompByName
351
367
 
352
368
  - **Object:** `TimelineItem (media-backed clip)`
353
- - **Behavior:** A Fusion composition created on a media clip through the API is not applied at render WHEN MEDIAOUT HAS NO PATH FROM MEDIAIN. The original blanket form of this entry — 'never applied at render' — was too broad and was corrected on 2026-08-02: a comp wired MediaIn -> Blur -> MediaOut, created entirely through the API on an ordinary media clip, DOES render. PSNR between the plain and Fusion renders of the same timeline was 22.7 dB (identical would be infinite), the file shrank 22.5 MB -> 14.8 MB as a blur should, and the output was frame-for-frame identical in GUI and headless. A first attempt that wired ONLY MediaOut -> Blur, leaving the Blur with no source, made the render job come back 'Failed' with an 887-byte file — so an unrooted graph does not merely get bypassed, it can take the render down. What still stands is the original observation for the configuration it actually tested, which is retained below and has NOT been re-measured: AddFusionComp() returns the comp, AddTool/Connect/SetInput all succeed, and the whole graph reads back correctly (GetCompCount 1, MediaOut1.Input wired to the new tool, StyledText returning the value just set) — but the rendered output is byte-for-byte the untouched source media. Verified live on Studio 19.1.3.7 with the strongest form of the test: MediaOut1 fed ONLY by a Text+, with no path from MediaIn at all, still rendered the unmodified clip. LoadFusionCompByName on the sole comp does not activate it either. Contrast InsertFusionTitleIntoTimeline, whose comp DOES render — text set via SetInput('StyledText') appears in the output — so this is specific to comps attached to media-backed clips, not to Fusion through the API generally. REPRODUCED 2026-08-21 on Studio 19.1.3.7: a rooted MediaIn -> Blur -> MediaOut comp built entirely through the API renders (PSNR 24.38 dB vs the no-comp baseline), so the 2026-08-02 correction stands. Note that an important share of 'the comp was ignored' readings are NOT this entry at all but the Composition.Lock bug above — a parameter written under a comp lock reads back correctly and never reaches the render, which looks identical from the API side.
354
- - **Workaround / current handling:** Wire the graph so MediaOut descends from MediaIn — that is the difference between a comp that renders and one that is silently bypassed, and it is what made this look like 'Fusion never renders from the API'. Never leave a tool unrooted: a MediaOut fed by a tool with no source failed the render job outright. For text or effects over picture, insert a Fusion title/generator as its own timeline clip and set its Text+ (fusion_comp set_text_plus), rather than attaching a comp to the media clip. Note the destination track cannot be chosen from the API (see the Track Selector entry), so overlaying onto an existing clip's track is not currently reachable end-to-end. Building the comp in the Fusion page UI works; only the API-created comp is ignored.
355
- - **Tags:** fusion, silent-failure, render
369
+ - **Behavior:** A Fusion composition created on a media clip through the API is not applied at render WHEN MEDIAOUT HAS NO PATH FROM MEDIAIN. The original blanket form of this entry — 'never applied at render' — was too broad and was corrected on 2026-08-02: a comp wired MediaIn -> Blur -> MediaOut, created entirely through the API on an ordinary media clip, DOES render. PSNR between the plain and Fusion renders of the same timeline was 22.7 dB (identical would be infinite), the file shrank 22.5 MB -> 14.8 MB as a blur should, and the output was frame-for-frame identical in GUI and headless. A first attempt that wired ONLY MediaOut -> Blur, leaving the Blur with no source, made the render job come back 'Failed' with an 887-byte file — so an unrooted graph does not merely get bypassed, it can take the render down. What still stands is the original observation for the configuration it actually tested, which is retained below and has NOT been re-measured: AddFusionComp() returns the comp, AddTool/Connect/SetInput all succeed, and the whole graph reads back correctly (GetCompCount 1, MediaOut1.Input wired to the new tool, StyledText returning the value just set) — but the rendered output is byte-for-byte the untouched source media. Verified live on Studio 19.1.3.7 with the strongest form of the test: MediaOut1 fed ONLY by a Text+, with no path from MediaIn at all, still rendered the unmodified clip. LoadFusionCompByName on the sole comp does not activate it either. Contrast InsertFusionTitleIntoTimeline, whose comp DOES render — text set via SetInput('StyledText') appears in the output — so this is specific to comps attached to media-backed clips, not to Fusion through the API generally. REPRODUCED 2026-08-21 on Studio 19.1.3.7: a rooted MediaIn -> Blur -> MediaOut comp built entirely through the API renders (PSNR 24.38 dB vs the no-comp baseline), so the 2026-08-02 correction stands. Note that an important share of 'the comp was ignored' readings are NOT this entry at all but the Composition.Lock bug above — a parameter written under a comp lock reads back correctly and never reaches the render, which looks identical from the API side. That is what an independent 2026-08-20 report on Studio 21.0.4.5 was measuring: a wired MediaIn -> Blur -> MediaOut comp, and a Transform variant, both delivered renders bit-identical to the no-comp baseline (PSNR inf) when built and set through this server. It was read at the time as a version regression (19.1.3.7 honours a wired comp, 21.0.4.5 does not); with the lock bug identified, the simpler reading is that the same defect reproduces on 21.0.4.5 — and since the lock bug has only been isolated on 19.1.3.7, that report is the only evidence it is not build-specific. Treat 'a wired comp renders' as established for 19.1.3.7 and unverified elsewhere.
370
+ - **Workaround / current handling:** Wire the graph so MediaOut descends from MediaIn — that is the difference between a comp that renders and one that is silently bypassed, and it is what made this look like 'Fusion never renders from the API'. Never leave a tool unrooted: a MediaOut fed by a tool with no source failed the render job outright. For text or effects over picture, insert a Fusion title/generator as its own timeline clip and set its Text+ (fusion_comp set_text_plus), rather than attaching a comp to the media clip. Note the destination track cannot be chosen from the API (see the Track Selector entry), so overlaying onto an existing clip's track is not currently reachable end-to-end. Building the comp in the Fusion page UI works; only the API-created comp is ignored. Never claim a Fusion effect from comp readback alone — prove it with a rendered frame (gallery_stills grab_and_export before/after, or a delivered render). On setups where even the wired comp does not render (see the 2026-08-20 measurement), treat per-item Fusion effects as HITL (a human builds or activates the comp in the UI) and bake stills motion with ffmpeg when unattended output is required.
371
+ - **Tags:** fusion, silent-failure, render, readback, hitl
356
372
 
357
373
  ### Composition.Paste
358
374
 
@@ -532,3 +548,11 @@ values, or automation-hostile modal prompts.
532
548
  - **Behavior:** PANEL-dependent, not mode-dependent — and the distinction took three revisions of this entry to pin down, so the evidence is recorded rather than summarised. Across four controlled 92-probe sweeps (2 GUI, 2 headless, 2026-08-01) it returned False and wrote nothing in ALL FOUR, while Timeline.GrabStill() succeeded in all four — so it is not being handed an empty still. In one earlier GUI session it DID work, returning True and writing 2 files. The variable that differed is not the mode: it is whether the Gallery panel was visible on the Color page, which depends on the restored workspace layout and which the harness does not control. A headless session can never satisfy it, so in practice the call never works headless; a GUI session satisfies it only sometimes. Project.ExportCurrentFrameAsStill worked in all four runs in both modes.
533
549
  - **Workaround / current handling:** Do not use ExportStills unattended in either mode — a GUI session is not sufficient, only a GUI session with the Gallery panel open. Use Project.ExportCurrentFrameAsStill for pixels (verified in both modes, four for four) or drp.extract_node_graphs for grades. If ExportStills must be used, have the user open Workspace > Gallery first and verify the written files rather than trusting the return.
534
550
  - **Tags:** gallery, stills, headless, unreliable-return
551
+
552
+ ### MediaPool.AppendToTimeline (null-id timeline items)
553
+
554
+ - **Object:** `MediaPool`
555
+ - **Signature:** `([{clipInfo}, ...]) -> [TimelineItem]`
556
+ - **Behavior:** The returned TimelineItem objects can have an unreadable/empty GetUniqueId, notably when the clipInfo recordFrame lands in a span still occupied by another item (any duplicate-then-delete 'move' whose offset is smaller than the item duration hits this). The item may not actually exist on the timeline. A session that trusted the non-empty return and deleted the sources lost 26 clips (Portugal timeline, 2026-08-19).
557
+ - **Workaround / current handling:** Never treat AppendToTimeline's return as proof of placement. Re-enumerate the track and match on record frame + duration before any dependent delete; never shift items by duplicate-then-delete into occupied spans — use timeline.ripple_insert, which rebuilds the tail into free space and verifies by readback.
558
+ - **Tags:** editorial, silent-failure, unreliable-return, timeline
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.98.8"
40
+ VERSION = "2.99.1"
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.98.8",
3
+ "version": "2.99.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.98.8"
90
+ VERSION = "2.99.1"
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()}")
@@ -1926,17 +1926,34 @@ def ti_load_burn_in_preset(preset_name: str, item_index: int = 0, track_type: st
1926
1926
 
1927
1927
 
1928
1928
  @mcp.tool()
1929
- def ti_create_magic_mask(mode: str = "Forward", item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
1929
+ def ti_create_magic_mask(mode: str = "F", item_index: int = 0, track_type: str = "video", track_index: int = 1) -> Dict[str, Any]:
1930
1930
  """Create a Magic Mask on a timeline item.
1931
1931
 
1932
+ Magic Mask v2 needs operator CLICKS on the subject (Color page > Magic Mask
1933
+ palette > click subject > Track Forward); the API cannot place them, so with
1934
+ no clicks present Resolve returns False and no isolation exists.
1935
+
1932
1936
  Args:
1933
- mode: 'Forward' or 'Backward'. Default: 'Forward'.
1937
+ mode: 'F' (forward), 'B' (backward), or 'BI' (bidirection) per the
1938
+ scripting README ('Forward'/'Backward' are accepted as aliases).
1934
1939
  item_index: 0-based item index. Default: 0.
1935
1940
  """
1941
+ aliases = {"F": "F", "FORWARD": "F", "B": "B", "BACKWARD": "B",
1942
+ "BI": "BI", "BIDIRECTION": "BI", "BIDIRECTIONAL": "BI"}
1943
+ normalized = aliases.get(str(mode).strip().upper())
1944
+ if not normalized:
1945
+ return {"error": "mode must be 'F', 'B', or 'BI'"}
1936
1946
  item, err = _get_timeline_item(track_type, track_index, item_index)
1937
1947
  if err:
1938
1948
  return err
1939
- return {"success": bool(item.CreateMagicMask(mode))}
1949
+ if bool(item.CreateMagicMask(normalized)):
1950
+ return {"success": True, "mode": normalized}
1951
+ return {
1952
+ "success": False,
1953
+ "needs_hitl": True,
1954
+ "hitl": "No Magic Mask clicks on this item — a human must click the subject "
1955
+ "on the Color page Magic Mask palette, then press Track Forward.",
1956
+ }
1940
1957
 
1941
1958
 
1942
1959
  @mcp.tool()