davinci-resolve-mcp 2.217.0 → 2.218.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 +59 -0
- package/README.md +4 -4
- package/README.zh-CN.md +5 -5
- package/docs/SKILL.md +3 -1
- package/docs/contributing.md +1 -1
- package/docs/install.md +2 -2
- package/docs/reference/api-coverage.md +3 -1
- package/docs/reference/api-limitations.md +1 -1
- package/docs/reference/readwrite-symmetry.md +3 -3
- package/docs/reference/resolve211-native-transitions.md +65 -0
- package/install.py +2 -2
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/granular/resolve_211.py +19 -2
- package/src/resolve_mcp_server.py +1 -1
- package/src/server.py +17 -6
- package/src/utils/api_truth.py +12 -3
- package/src/utils/destructive_hook.py +1 -0
- package/src/utils/execution_lifecycle.py +1 -0
- package/src/utils/resolve211_edits.py +29 -0
- package/src/utils/resolve_versions.py +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
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.218.0 — native Resolve 21.1 transition creation
|
|
6
|
+
|
|
7
|
+
Contributed by @legionsound (#209), live-validated on Studio 21.1.0.14.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`timeline_item add_transition`**, with the granular twin
|
|
12
|
+
`add_timeline_item_transition`, calling 21.1's native `AddTransition`. The
|
|
13
|
+
`options` dictionary requires `type` (e.g. `"Cross Dissolve"`), `category`
|
|
14
|
+
(`simple` | `fusion` | `ofx` | `audio`), `position` (`start` | `end`) and
|
|
15
|
+
`alignment` (`left` | `center` | `right`); `duration` in frames is optional
|
|
16
|
+
and, when omitted or null, is forwarded as given rather than replaced with an
|
|
17
|
+
invented default. Unknown keys, blank types, unrecognised enum values and
|
|
18
|
+
non-positive or fractional durations are refused before any write. A native
|
|
19
|
+
`None` or `False` stays `success: false`; a build without the method returns
|
|
20
|
+
the 21.1 floor error, confirmed here on Studio 19.1.3.7.
|
|
21
|
+
- The result reports the **transition Resolve actually created** — its id, name,
|
|
22
|
+
start, end and duration read back off the returned object — rather than
|
|
23
|
+
echoing the requested duration. Inserting a transition can change the track's
|
|
24
|
+
item indexes, and the tool documentation says so.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `add_transition` is registered in **both** write tables: the
|
|
29
|
+
`destructive_hook` action registry and the MEDIUM-risk set in
|
|
30
|
+
`execution_lifecycle`. Without both, safe mode, the dry-run refusal, the audit
|
|
31
|
+
log and the operation log would all treat a timeline mutation as a read. Tool
|
|
32
|
+
count 367 → 368 across the docs and the generated agent-rule files.
|
|
33
|
+
- The existing offline `.drp` transition workflow is unchanged and still the
|
|
34
|
+
render-proven route on builds below 21.1; the native call is an addition, not
|
|
35
|
+
a replacement.
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
- `docs/reference/resolve211-native-transitions.md` records the fixture and its
|
|
40
|
+
limits, and the `api_truth` entry for `TimelineItem.AddTransition` is upgraded
|
|
41
|
+
from "signature only, never invoked" to a contributor measurement — while
|
|
42
|
+
keeping the standing 21.1 gap it does not close: there is still no accessor
|
|
43
|
+
for an existing transition's type, alignment or duration beyond its name and
|
|
44
|
+
frame range, and no clone verb.
|
|
45
|
+
|
|
46
|
+
### Validation
|
|
47
|
+
|
|
48
|
+
- Full suite green: 3,436 passed, 1 skipped. Static checks, drift guards and
|
|
49
|
+
the agent-rule generator all clean.
|
|
50
|
+
- Write registration probed directly rather than inferred:
|
|
51
|
+
`classify_operation_risk("timeline_item", "add_transition")` returns MEDIUM /
|
|
52
|
+
destructive / recognised, and `destructive_hook.is_destructive` agrees.
|
|
53
|
+
- No live Resolve run on this machine, which is Studio 19.1.3.7 — below the 21.1
|
|
54
|
+
floor, where every one of these calls correctly refuses. The rendered
|
|
55
|
+
evidence is @legionsound's, measured on Studio 21.1.0.14: a 24-frame centered
|
|
56
|
+
Cross Dissolve with source handles landed at frames 59–83 around a cut at 71
|
|
57
|
+
with adjacent clip spans unchanged, both server modes rendered byte-identical
|
|
58
|
+
142-frame ProRes movies with a progressive red-to-blue blend, and the
|
|
59
|
+
zero-handle case failed cleanly with no transition written. That covers the
|
|
60
|
+
tested Cross Dissolve fixture, not every effect the API accepts — other
|
|
61
|
+
alignments, automatic duration, audio and Fusion/OFX transitions, and repeated
|
|
62
|
+
insertion remain unverified.
|
|
63
|
+
|
|
5
64
|
## What's New in v2.217.0 — native Resolve 21.1 speed and fade setters, registered as the mutations they are
|
|
6
65
|
|
|
7
66
|
Contributed by @legionsound (#208), live-validated on Studio 21.1.0.14.
|
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
|
-
[-blue.svg)](#server-modes)
|
|
9
9
|
[-18%20tools-blueviolet.svg)](#server-modes)
|
|
10
10
|
[](docs/reference/api-coverage.md#test-results)
|
|
11
11
|
[](https://www.blackmagicdesign.com/products/davinciresolve)
|
|
@@ -133,7 +133,7 @@ The command starts a loopback-only server and opens the control panel in your br
|
|
|
133
133
|
| Mode | Entry point | Tools | Best for |
|
|
134
134
|
|------|-------------|-------|----------|
|
|
135
135
|
| Compound | `src/server.py` | 36 | Default mode for most assistants. Related Resolve operations are grouped behind action parameters to keep context usage low. |
|
|
136
|
-
| Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` |
|
|
136
|
+
| Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 368 | Power users who want one MCP tool per Resolve API method. |
|
|
137
137
|
|
|
138
138
|
The compound server is recommended unless you specifically need the granular one-tool-per-method surface.
|
|
139
139
|
|
|
@@ -365,7 +365,7 @@ The default server is a local stdio process launched by your MCP client; it does
|
|
|
365
365
|
|
|
366
366
|
| Metric | Value |
|
|
367
367
|
|--------|-------|
|
|
368
|
-
| MCP Tools | **36** compound / **
|
|
368
|
+
| MCP Tools | **36** compound / **368** granular (live server) |
|
|
369
369
|
| Advanced (offline) tools | **18** — .drp/.drt/.drx + DB authoring, no Resolve running |
|
|
370
370
|
| Kernel Actions | **136** guarded workflow actions across 9 compound tools |
|
|
371
371
|
| API Methods Covered | **361/361** (100%) |
|
package/README.zh-CN.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
|
-
[-blue.svg)](#服务器模式)
|
|
9
9
|
[-18%20tools-blueviolet.svg)](#服务器模式)
|
|
10
10
|
[](docs/reference/api-coverage.md#test-results)
|
|
11
11
|
[](https://www.blackmagicdesign.com/products/davinciresolve)
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v2.
|
|
15
|
+
> 本翻译对应 v2.218.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
|
@@ -88,7 +88,7 @@ venv/bin/python -m src.control_panel
|
|
|
88
88
|
| 模式 | 入口 | 工具数 | 适合谁 |
|
|
89
89
|
|------|------|--------|--------|
|
|
90
90
|
| Compound(复合) | `src/server.py` | 36 | 大多数助手的默认模式。相关的 Resolve 操作按 action 参数分组,压低上下文占用。 |
|
|
91
|
-
| Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` |
|
|
91
|
+
| Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 368 | 想要"一个 Resolve API 方法 = 一个 MCP 工具"的重度用户。 |
|
|
92
92
|
|
|
93
93
|
除非你明确需要一方法一工具的细粒度界面,否则推荐复合模式。
|
|
94
94
|
|
|
@@ -226,7 +226,7 @@ DRX 调色写入**针对 Resolve Studio 做过实机校准**:调色参数默
|
|
|
226
226
|
|
|
227
227
|
| 指标 | 数值 |
|
|
228
228
|
|------|------|
|
|
229
|
-
| MCP 工具 | **36** 复合 / **
|
|
229
|
+
| MCP 工具 | **36** 复合 / **368** 细粒度(实时服务器) |
|
|
230
230
|
| Advanced(离线)工具 | **18**——.drp/.drt/.drx + 数据库创作,无需 Resolve 运行 |
|
|
231
231
|
| 内核 action | 9 个复合工具下 **136** 个带护栏的工作流 action |
|
|
232
232
|
| API 方法覆盖 | **361/361**(100%) |
|
package/docs/SKILL.md
CHANGED
|
@@ -345,7 +345,7 @@ to the user as verified.
|
|
|
345
345
|
| Mode | Entry point | Tool count | Use when |
|
|
346
346
|
|---|---|---|---|
|
|
347
347
|
| Compound (default) | `src/server.py` | 36 tools | Most workflows — keeps context lean |
|
|
348
|
-
| Granular (full) | `src/server.py --full` |
|
|
348
|
+
| Granular (full) | `src/server.py --full` | 368 tools | Power users needing one tool per API method |
|
|
349
349
|
|
|
350
350
|
Resolve 21.1 adds [twelve read-only discovery controls](reference/resolve211-read-controls.md)
|
|
351
351
|
for edition, presets, audio formats/codecs, normalization modes, speed, fades
|
|
@@ -2481,3 +2481,5 @@ setups:
|
|
|
2481
2481
|
The full API reference is in `docs/reference/resolve_scripting_api.txt`.
|
|
2482
2482
|
|
|
2483
2483
|
Native Resolve 21.1 speed and fade setters: see [speed/fades](reference/resolve211-speed-fades.md) for options, version guards and contributor validation limits.
|
|
2484
|
+
|
|
2485
|
+
Native 21.1 transition creation: see [transition controls](reference/resolve211-native-transitions.md) for options, item-index changes and contributor-rendered evidence.
|
package/docs/contributing.md
CHANGED
|
@@ -64,7 +64,7 @@ davinci-resolve-mcp/
|
|
|
64
64
|
├── install.py # Universal installer (macOS/Windows/Linux)
|
|
65
65
|
├── src/
|
|
66
66
|
│ ├── server.py # Compound MCP server — 36 tools (default)
|
|
67
|
-
│ ├── resolve_mcp_server.py # Thin full-server entrypoint —
|
|
67
|
+
│ ├── resolve_mcp_server.py # Thin full-server entrypoint — 368 tools
|
|
68
68
|
│ ├── granular/ # Modular full-server implementation
|
|
69
69
|
│ └── utils/ # Platform detection, Resolve connection helpers
|
|
70
70
|
├── tests/ # offline suite (test_*.py) + live harnesses (live_*.py):
|
package/docs/install.md
CHANGED
|
@@ -144,7 +144,7 @@ The MCP server comes in two modes:
|
|
|
144
144
|
| Mode | File | Tools | Best For |
|
|
145
145
|
|------|------|-------|----------|
|
|
146
146
|
| **Compound** (default) | `src/server.py` | 36 | Most users — fast, clean, low context usage |
|
|
147
|
-
| **Full** | `src/resolve_mcp_server.py` |
|
|
147
|
+
| **Full** | `src/resolve_mcp_server.py` | 368 | Power users who want one tool per API method |
|
|
148
148
|
|
|
149
149
|
The compound server's `timeline_item` tool includes dedicated actions for common workflows:
|
|
150
150
|
|
|
@@ -159,7 +159,7 @@ The compound server's `timeline_item` tool includes dedicated actions for common
|
|
|
159
159
|
|
|
160
160
|
The installer uses the compound server by default. To use the full server:
|
|
161
161
|
```bash
|
|
162
|
-
python src/server.py --full # Launch full
|
|
162
|
+
python src/server.py --full # Launch full 368-tool server
|
|
163
163
|
# Or point your MCP config directly at src/resolve_mcp_server.py
|
|
164
164
|
```
|
|
165
165
|
|
|
@@ -25,7 +25,7 @@ Every non-deprecated method in the bundled legacy README is represented. This
|
|
|
25
25
|
does not claim complete coverage of the newer Resolve 21.1 typed API. The
|
|
26
26
|
default compound server exposes **36 tools** that group related operations by
|
|
27
27
|
action parameter, keeping LLM context windows lean. The full granular server
|
|
28
|
-
provides **
|
|
28
|
+
provides **368 individual tools** for power users. The legacy coverage spans
|
|
29
29
|
13 API object classes. MCP-level kernel actions are tracked separately in
|
|
30
30
|
[Kernel Action Coverage](../kernels/README.md).
|
|
31
31
|
|
|
@@ -631,3 +631,5 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
631
631
|
---
|
|
632
632
|
|
|
633
633
|
Native speed and fade setters in both interfaces are documented in [Resolve 21.1 speed/fades](resolve211-speed-fades.md), including sampled video validation and unverified cases.
|
|
634
|
+
|
|
635
|
+
Native 21.1 transition creation: see [transition controls](resolve211-native-transitions.md) for options, item-index changes and contributor-rendered evidence.
|
|
@@ -82,7 +82,7 @@ equivalent, blocking full automation.
|
|
|
82
82
|
### Transition create / copy / clone
|
|
83
83
|
|
|
84
84
|
- **Object:** `Timeline / TimelineItem`
|
|
85
|
-
- **Behavior:** CREATION IS FIXED IN 21.1, READBACK IS NOT. Reported by @billcarroll (PR #197) from an attribute probe on Studio 21.1.0.14 (2026-09-08; not reproduced here, no 21.1 install): TimelineItem.AddTransition resolves to a <BlackmagicFusion.PyFunctionCall object>, not None. Its stub signature is AddTransition(transitionOptions) -> TimelineItem | None, where transitionOptions carries type (e.g. 'Cross Dissolve'), category ('simple'|'fusion'|'ofx'|'audio'), position ('start'|'end'), alignment ('left'|'center'|'right') and an optional duration in frames.
|
|
85
|
+
- **Behavior:** CREATION IS FIXED IN 21.1, READBACK IS NOT. Reported by @billcarroll (PR #197) from an attribute probe on Studio 21.1.0.14 (2026-09-08; not reproduced here, no 21.1 install): TimelineItem.AddTransition resolves to a <BlackmagicFusion.PyFunctionCall object>, not None. Its stub signature is AddTransition(transitionOptions) -> TimelineItem | None, where transitionOptions carries type (e.g. 'Cross Dissolve'), category ('simple'|'fusion'|'ofx'|'audio'), position ('start'|'end'), alignment ('left'|'center'|'right') and an optional duration in frames. That original probe did not invoke the method. UPDATE, contributor-validated by @legionsound on Studio 21.1.0.14, macOS, 2026-09-09 (not reproduced by the maintainer on 19.1.3.7): a synthetic red/blue pair with handles accepted a 24-frame centered Cross Dissolve. GetStart/End returned 59/83 around cut 71, GetDuration returned 24, and source clip spans were unchanged. Both community interfaces rendered identical 142-frame movies with a progressive red-to-blue blend. With zero handles the native call returned None. This validates that fixture, not other effects, audio transitions or alignments. See resolve211-native-transitions.md. WHAT REMAINS MISSING ON 21.1: reading a transition back. There is still no accessor for an existing transition's type, alignment or duration beyond its name string and frame range, and no clone verb — alignment and duration are write-only arguments to AddTransition. The pre-21.1 statement, kept as the historical record: there was no method to ADD or CLONE an edit transition — no AddTransition/CreateTransition/AddVideoTransition on Timeline or TimelineItem (dir(), 21.0.4.5). CORRECTION, measured on Studio 21.0.4.5 (2026-08-12): this entry previously said transitions applied in the UI are 'invisible to and unmodifiable by scripts'. BOTH HALVES WERE WRONG and are withdrawn. A transition IS a first-class timeline item: a 12-frame Cross Dissolve applied through the Edit-page right-click menu enumerates in GetItemListInTrack('video', 1) as GetName()=='Cross Dissolve', GetStart()==86426, GetDuration()==12 — centered on a cut at 86432 — with a stable GetUniqueId() and a working GetTrackTypeAndIndex(). A transition authored offline into a .drp and imported reads IDENTICALLY, so the route that created it does not matter. It is also REMOVABLE: Timeline.DeleteClips([transition], False) returns True and deletes it, leaving both adjacent clips at their original starts and durations. THE DISCRIMINATOR between a transition item and a clip item is GetProperty(): a transition returns an EMPTY dict where a video clip returns 26 transform keys; it also has no MediaPoolItem and no Fusion comp. WHAT IS GENUINELY MISSING (pre-21.1: creation too; on 21.1+ read the paragraph above): cloning, and any type/alignment/parameter detail — the transition's kind is knowable ONLY from its name string, and there is no way to read its alignment (centered/start/end) or edit its duration. AUDIO NUANCE (measured 2026-09-01 on 19.1.3.7, E113): an audio cross-fade enumerates in GetItemListInTrack('audio', n) with an EMPTY GetName() (24 frames, centered on the cut, between the two clips) — so on audio lanes even the kind is not readable from the name. The discriminator that holds for BOTH: GetMediaPoolItem() is None AND GetProperty() is empty — BUT a Solid Color generator AND a subtitle item read the same way (GetProperty() None, no MediaPoolItem; measured E115), so that pair only separates clips from non-clips. What separates a transition from a generator is GEOMETRY: a transition straddles a cut (one neighbour ends inside its span, another starts inside it) while a generator owns its span. timeline.get_items reports `kind` on that basis.
|
|
86
86
|
- **Workaround / current handling:** Automated QC of existing transitions IS possible and is the main practical need — enumerate GetItemListInTrack, treat any item whose GetProperty() is empty and whose GetMediaPoolItem() is None as a transition, and read its name, start and duration. Removal is scriptable via Timeline.DeleteClips. To CREATE one, either apply it in the Resolve UI, or author it offline and import: the advanced server's drp place_transition writes a cross dissolve at an abutting cut ({track, atFrame, durationFrames}) and it round-trips into Resolve 21.0.4.5 reading back at the expected centered range. On 21.1+ prefer TimelineItem.AddTransition, which takes the type, category, edge, alignment and duration directly.
|
|
87
87
|
- **Tags:** missing-method, timeline, transition
|
|
88
88
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Read/Write Symmetry Audit
|
|
4
4
|
|
|
5
|
-
- write-style action occurrences scanned: **
|
|
5
|
+
- write-style action occurrences scanned: **120**
|
|
6
6
|
- write-style action occurrences with a matching read: **73**
|
|
7
7
|
- distinct high-signal `set_` actions without a direct/known readback: **4**
|
|
8
8
|
|
|
@@ -13,6 +13,6 @@
|
|
|
13
13
|
- `set_keyframe_interpolation`
|
|
14
14
|
- `set_node_enabled`
|
|
15
15
|
|
|
16
|
-
## Low-signal (create/add/insert/apply/import — usually expected):
|
|
16
|
+
## Low-signal (create/add/insert/apply/import — usually expected): 42 distinct names
|
|
17
17
|
|
|
18
|
-
`add_clip_mattes`, `add_comp`, `add_fusion_mask`, `add_subfolder`, `add_sync_event_markers`, `add_timeline_mattes`, `add_track`, `add_version`, `apply_arri_cdl_lut`, `apply_cuts`, `apply_fairlight_preset`, `apply_grade_from_drx`, `apply_look_to_items`, `apply_spec`, `apply_trace_plan`, `create_compound_clip`, `create_fusion_clip`, `create_magic_mask`, `create_stereo_clip`, `create_subtitles`, `create_timeline`, `create_timeline_from_clips`, `create_variant_from_ranges`, `import_comp`, `import_folder`, `import_from_drp`, `import_into_timeline`, `import_media`, `import_preset`, `import_project`, `import_render`, `import_timeline`, `import_timeline_checked`, `import_to_pool`, `insert_audio`, `insert_fusion_composition`, `insert_fusion_generator`, `insert_fusion_title`, `insert_generator`, `insert_ofx_generator`, `insert_title`
|
|
18
|
+
`add_clip_mattes`, `add_comp`, `add_fusion_mask`, `add_subfolder`, `add_sync_event_markers`, `add_timeline_mattes`, `add_track`, `add_transition`, `add_version`, `apply_arri_cdl_lut`, `apply_cuts`, `apply_fairlight_preset`, `apply_grade_from_drx`, `apply_look_to_items`, `apply_spec`, `apply_trace_plan`, `create_compound_clip`, `create_fusion_clip`, `create_magic_mask`, `create_stereo_clip`, `create_subtitles`, `create_timeline`, `create_timeline_from_clips`, `create_variant_from_ranges`, `import_comp`, `import_folder`, `import_from_drp`, `import_into_timeline`, `import_media`, `import_preset`, `import_project`, `import_render`, `import_timeline`, `import_timeline_checked`, `import_to_pool`, `insert_audio`, `insert_fusion_composition`, `insert_fusion_generator`, `insert_fusion_title`, `insert_generator`, `insert_ofx_generator`, `insert_title`
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Native Resolve 21.1 transitions
|
|
2
|
+
|
|
3
|
+
`timeline_item("add_transition", {"options": {...}, ...})` and granular
|
|
4
|
+
`add_timeline_item_transition(options, track_type="video", track_index=1,
|
|
5
|
+
item_index=0)` call TimelineItem.AddTransition. This is an additional native
|
|
6
|
+
route; the existing offline project-file transition workflow remains available.
|
|
7
|
+
|
|
8
|
+
Required options are `type` (non-empty native transition name), `category`
|
|
9
|
+
(`simple`, `fusion`, `ofx`, `audio`), `position` (`start`, `end`) and `alignment`
|
|
10
|
+
(`left`, `center`, `right`). Optional `duration` is a positive integer in frames,
|
|
11
|
+
or null/omitted to request Resolve's automatic duration. Unknown keys and malformed
|
|
12
|
+
values are refused before writes. The wrapper does not guess which transition
|
|
13
|
+
names are installed, calculate source handles, or silently substitute an effect.
|
|
14
|
+
|
|
15
|
+
Example options:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{"type":"Cross Dissolve","category":"simple","position":"end","alignment":"center","duration":24}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The result is `success: false` when the native API returns None/False. Otherwise
|
|
22
|
+
`transition` contains its actual id, name, start, end and duration. Actual values
|
|
23
|
+
come from the returned item, not the requested options. A created object is not
|
|
24
|
+
proof of a correct render; verify output for each effect and source configuration.
|
|
25
|
+
|
|
26
|
+
Native transitions appear in GetItemListInTrack on the measured build. Adding one
|
|
27
|
+
therefore changes subsequent item indexes. Re-query the track before selecting
|
|
28
|
+
another item. Track indexes remain 1-based and item indexes 0-based.
|
|
29
|
+
|
|
30
|
+
The method has a 21.1 floor and is registered as a destructive write and MEDIUM
|
|
31
|
+
risk in both classifier tables. Explicit compound dry-run requests are refused
|
|
32
|
+
before the handler because this action has no native dry-run implementation.
|
|
33
|
+
Granular tool annotations also identify a destructive, non-idempotent write.
|
|
34
|
+
|
|
35
|
+
## Contributor evidence and limits
|
|
36
|
+
|
|
37
|
+
Contributor-validated on macOS Studio **21.1.0.14**, using generated red/blue
|
|
38
|
+
six-second clips in a disposable 24 fps project. A 24-frame centered Cross
|
|
39
|
+
Dissolve with source handles returned start 59/end 83 around cut 71, without
|
|
40
|
+
moving either source clip. ProRes movie rendering completed. The rendered
|
|
41
|
+
boundary changed from red through a red/blue blend to blue. The same request
|
|
42
|
+
with zero outgoing/incoming handles returned failure and no transition.
|
|
43
|
+
|
|
44
|
+
The included scratch test exercises both community interfaces and creates render
|
|
45
|
+
jobs for independent movie inspection. Unit tests cover actual returned spans,
|
|
46
|
+
missing native methods, None/False failures, malformed options, optional duration,
|
|
47
|
+
write classification, and dry-run refusal. Audio transitions, Fusion/OFX effects,
|
|
48
|
+
other alignments, automatic duration and repeated insertion are not live-validated
|
|
49
|
+
by this contribution. Support for their documented options is pass-through.
|
|
50
|
+
|
|
51
|
+
`python tests/live_resolve211_transitions.py OUTPUT_DIR` requires a disposable
|
|
52
|
+
project named `Codex Native Transition Validation 20260909`, set to 640x360/24 fps,
|
|
53
|
+
with synthetic red.mov and blue.mov in its root bin. Generate each with FFmpeg:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
ffmpeg -f lavfi -i 'color=c=red:s=640x360:r=24:d=6' \
|
|
57
|
+
-c:v prores_ks -profile:v 0 -pix_fmt yuv422p10le red.mov
|
|
58
|
+
ffmpeg -f lavfi -i 'color=c=blue:s=640x360:r=24:d=6' \
|
|
59
|
+
-c:v prores_ks -profile:v 0 -pix_fmt yuv422p10le blue.mov
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
It creates disposable timelines, renders movies and saves the scratch project.
|
|
63
|
+
Do not supply production media. Inspect frames 47, 65, 71, 77 and 95 in the
|
|
64
|
+
resulting movies: red before the overlap, progressively more blue through the
|
|
65
|
+
transition, then blue afterward. Also verify frame count and source-clip spans.
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "2.
|
|
40
|
+
VERSION = "2.218.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
|
|
@@ -1543,7 +1543,7 @@ def verify_resolve_connection(python_path, api_path, lib_path):
|
|
|
1543
1543
|
|
|
1544
1544
|
def print_banner():
|
|
1545
1545
|
title = f"DaVinci Resolve MCP Server — Installer v{VERSION}"
|
|
1546
|
-
subtitle = "36 compound ·
|
|
1546
|
+
subtitle = "36 compound · 368 full · 3 platforms"
|
|
1547
1547
|
print()
|
|
1548
1548
|
print(bold(" ╔══════════════════════════════════════════════════════╗"))
|
|
1549
1549
|
print(bold(f" ║{title:^54}║"))
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.
|
|
90
|
+
VERSION = "2.218.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()}")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Native Resolve 21.1 discovery and editing controls."""
|
|
2
|
-
from src.utils.resolve211_edits import validate_edit_options
|
|
2
|
+
from src.utils.resolve211_edits import validate_edit_options, validate_transition_options, transition_result
|
|
3
3
|
from src.granular.common import (
|
|
4
|
-
mcp, READ_ONLY_TOOL, WRITE_TOOL, get_resolve, get_current_project,
|
|
4
|
+
mcp, READ_ONLY_TOOL, WRITE_TOOL, DESTRUCTIVE_TOOL, get_resolve, get_current_project,
|
|
5
5
|
_get_timeline, _get_timeline_item, _requires_method, has_method,
|
|
6
6
|
)
|
|
7
7
|
|
|
@@ -181,3 +181,20 @@ def set_timeline_item_fades(options: dict, track_type: str = "video", track_inde
|
|
|
181
181
|
if missing:
|
|
182
182
|
return missing
|
|
183
183
|
return {"success": bool(item.SetFades(dict(options)))}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@mcp.tool(annotations=DESTRUCTIVE_TOOL)
|
|
187
|
+
def add_timeline_item_transition(options: dict, track_type: str = "video", track_index: int = 1, item_index: int = 0) -> dict:
|
|
188
|
+
"""Add a native 21.1 transition using type/category/position/alignment and optional duration in frames. Returns actual span; clip indexes can change after insertion."""
|
|
189
|
+
error = validate_transition_options(options)
|
|
190
|
+
if error:
|
|
191
|
+
return {"error": error}
|
|
192
|
+
if track_type not in ("video", "audio") or track_index < 1 or item_index < 0:
|
|
193
|
+
return {"error": "Use video/audio, a 1-based track index and a non-negative item index"}
|
|
194
|
+
item, error = _get_timeline_item(track_type, track_index, item_index)
|
|
195
|
+
if error:
|
|
196
|
+
return error
|
|
197
|
+
missing = _requires_method(item, "AddTransition", "21.1")
|
|
198
|
+
if missing:
|
|
199
|
+
return missing
|
|
200
|
+
return transition_result(item.AddTransition(dict(options)))
|
|
@@ -34,7 +34,7 @@ from src.utils.update_check import start_background_update_check
|
|
|
34
34
|
if __name__ == "__main__":
|
|
35
35
|
try:
|
|
36
36
|
start_background_update_check(VERSION, project_dir, logger)
|
|
37
|
-
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (
|
|
37
|
+
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (368 granular tools)")
|
|
38
38
|
run_fastmcp_stdio(mcp)
|
|
39
39
|
except KeyboardInterrupt:
|
|
40
40
|
logger.info("Server shutdown requested")
|
package/src/server.py
CHANGED
|
@@ -8,10 +8,10 @@ Each tool groups related operations via an 'action' parameter.
|
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
10
|
python src/server.py # Start the MCP server
|
|
11
|
-
python src/server.py --full # Start the
|
|
11
|
+
python src/server.py --full # Start the 368-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.
|
|
14
|
+
VERSION = "2.218.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -42,7 +42,7 @@ for p in [current_dir, project_dir]:
|
|
|
42
42
|
if p not in sys.path:
|
|
43
43
|
sys.path.insert(0, p)
|
|
44
44
|
|
|
45
|
-
from src.utils.resolve211_edits import validate_edit_options
|
|
45
|
+
from src.utils.resolve211_edits import validate_edit_options, validate_transition_options, transition_result
|
|
46
46
|
|
|
47
47
|
# Platform-specific Resolve paths
|
|
48
48
|
from src.utils.cdl import normalize_cdl_payload
|
|
@@ -26274,6 +26274,7 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
26274
26274
|
Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
|
|
26275
26275
|
|
|
26276
26276
|
Actions:
|
|
26277
|
+
add_transition(options, ...) -> {success, transition?} — native 21.1 transition; reports actual span.
|
|
26277
26278
|
set_speed(options, ...) -> {success} — native 21.1 speed options; RippleTimeline defaults false.
|
|
26278
26279
|
set_fades(options, ...) -> {success} — native 21.1 FadeIn/FadeOut integer frames.
|
|
26279
26280
|
get_speed(...) -> {speed} — documented on Resolve 21.1+.
|
|
@@ -26330,6 +26331,16 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
26330
26331
|
if err:
|
|
26331
26332
|
return err
|
|
26332
26333
|
|
|
26334
|
+
if action == "add_transition":
|
|
26335
|
+
options = p.get("options")
|
|
26336
|
+
error = validate_transition_options(options)
|
|
26337
|
+
if error:
|
|
26338
|
+
return _err(error)
|
|
26339
|
+
missing = _requires_method(item, "AddTransition", "21.1")
|
|
26340
|
+
if missing:
|
|
26341
|
+
return missing
|
|
26342
|
+
return transition_result(item.AddTransition(dict(options)))
|
|
26343
|
+
|
|
26333
26344
|
if action in ("set_speed", "set_fades"):
|
|
26334
26345
|
options = p.get("options")
|
|
26335
26346
|
error = validate_edit_options(action, options)
|
|
@@ -26541,7 +26552,7 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
26541
26552
|
return _err(f"Invalid interpolation. Must be one of: {', '.join(valid)}")
|
|
26542
26553
|
return {"success": bool(item.SetKeyframeInterpolation(p["property"], p["frame"], p["interpolation"]))}
|
|
26543
26554
|
|
|
26544
|
-
return _unknown(action, ["set_speed","set_fades","get_speed","get_fades","get_output_blanking","get_use_timeline_for_output_blanking","get_name","get_property","set_property","get_duration","get_start","get_end","get_source_start_frame","get_source_end_frame","get_source_start_time","get_source_end_time","get_left_offset","get_right_offset","set_clip_enabled","get_clip_enabled","update_sidecar","get_unique_id","get_media_pool_item","get_stereo_convergence","get_stereo_left_window","get_stereo_right_window","get_linked_items","get_track_type_and_index","get_source_audio_mapping","load_burnin_preset","set_name","get_voice_isolation_state","set_voice_isolation_state","get_retime","set_retime","get_transform","set_transform","get_crop","set_crop","get_composite","set_composite","get_audio","set_audio","get_keyframes","add_keyframe","modify_keyframe","delete_keyframe","set_keyframe_interpolation"])
|
|
26555
|
+
return _unknown(action, ["add_transition","set_speed","set_fades","get_speed","get_fades","get_output_blanking","get_use_timeline_for_output_blanking","get_name","get_property","set_property","get_duration","get_start","get_end","get_source_start_frame","get_source_end_frame","get_source_start_time","get_source_end_time","get_left_offset","get_right_offset","set_clip_enabled","get_clip_enabled","update_sidecar","get_unique_id","get_media_pool_item","get_stereo_convergence","get_stereo_left_window","get_stereo_right_window","get_linked_items","get_track_type_and_index","get_source_audio_mapping","load_burnin_preset","set_name","get_voice_isolation_state","set_voice_isolation_state","get_retime","set_retime","get_transform","set_transform","get_crop","set_crop","get_composite","set_composite","get_audio","set_audio","get_keyframes","add_keyframe","modify_keyframe","delete_keyframe","set_keyframe_interpolation"])
|
|
26545
26556
|
|
|
26546
26557
|
|
|
26547
26558
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -32663,9 +32674,9 @@ if __name__ == "__main__":
|
|
|
32663
32674
|
start_background_update_check(VERSION, project_dir, logger, env=_setup_update_env())
|
|
32664
32675
|
_install_threaded_tool_dispatch(mcp)
|
|
32665
32676
|
|
|
32666
|
-
# Support --full flag to run the
|
|
32677
|
+
# Support --full flag to run the 368-tool granular server instead
|
|
32667
32678
|
if "--full" in sys.argv:
|
|
32668
|
-
logger.info("Starting full
|
|
32679
|
+
logger.info("Starting full 368-tool granular server...")
|
|
32669
32680
|
sys.argv = [arg for arg in sys.argv if arg != "--full"]
|
|
32670
32681
|
from src.granular import mcp as granular_mcp
|
|
32671
32682
|
|
package/src/utils/api_truth.py
CHANGED
|
@@ -904,9 +904,18 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
904
904
|
"None, where transitionOptions carries type (e.g. 'Cross "
|
|
905
905
|
"Dissolve'), category ('simple'|'fusion'|'ofx'|'audio'), position "
|
|
906
906
|
"('start'|'end'), alignment ('left'|'center'|'right') and an "
|
|
907
|
-
"optional duration in frames.
|
|
908
|
-
"the
|
|
909
|
-
"
|
|
907
|
+
"optional duration in frames. That original probe did not invoke "
|
|
908
|
+
"the method. UPDATE, contributor-validated by @legionsound on "
|
|
909
|
+
"Studio 21.1.0.14, macOS, 2026-09-09 (not reproduced by the "
|
|
910
|
+
"maintainer on 19.1.3.7): a synthetic red/blue pair with handles "
|
|
911
|
+
"accepted a 24-frame centered Cross Dissolve. GetStart/End "
|
|
912
|
+
"returned 59/83 around cut 71, GetDuration returned 24, and "
|
|
913
|
+
"source clip spans were unchanged. Both community interfaces "
|
|
914
|
+
"rendered identical 142-frame movies with a progressive "
|
|
915
|
+
"red-to-blue blend. With zero handles the native call returned "
|
|
916
|
+
"None. This validates that fixture, not other effects, audio "
|
|
917
|
+
"transitions or alignments. See resolve211-native-transitions.md. "
|
|
918
|
+
"WHAT REMAINS MISSING ON 21.1: reading a "
|
|
910
919
|
"transition back. There is still no accessor for an existing "
|
|
911
920
|
"transition's type, alignment or duration beyond its name string "
|
|
912
921
|
"and frame range, and no clone verb — alignment and duration are "
|
|
@@ -151,6 +151,7 @@ DESTRUCTIVE_ACTIONS_BY_TOOL: Dict[str, FrozenSet[str]] = {
|
|
|
151
151
|
# classifier did not recognise them, so safe mode, the dry-run refusal,
|
|
152
152
|
# the audit log and the operation log all skipped a call that rewrites a
|
|
153
153
|
# clip's speed — and, with RippleTimeline true, moves every clip after it.
|
|
154
|
+
"add_transition",
|
|
154
155
|
"set_speed",
|
|
155
156
|
"set_fades",
|
|
156
157
|
}),
|
|
@@ -274,6 +274,7 @@ class RiskClassificationHook(LifecycleHook):
|
|
|
274
274
|
# Native 21.1 setters (#208): `set_speed` changes duration and, with
|
|
275
275
|
# RippleTimeline true, moves every clip after it; `set_fades` rewrites
|
|
276
276
|
# how the clip's edges render. Existing content altered, not deleted.
|
|
277
|
+
("timeline_item", "add_transition"),
|
|
277
278
|
("timeline_item", "set_speed"),
|
|
278
279
|
("timeline_item", "set_fades"),
|
|
279
280
|
# Pool reorganisation: clips and bins move, nothing is destroyed, but
|
|
@@ -24,3 +24,32 @@ def validate_edit_options(action, options):
|
|
|
24
24
|
if not finite:
|
|
25
25
|
return key + " must be a finite number"
|
|
26
26
|
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_transition_options(options):
|
|
30
|
+
if not isinstance(options, dict) or not options:
|
|
31
|
+
return "options must be a non-empty dictionary"
|
|
32
|
+
allowed = {"type", "category", "position", "alignment", "duration"}
|
|
33
|
+
if set(options) - allowed:
|
|
34
|
+
return "Unknown transition options: " + ", ".join(sorted(map(str, set(options) - allowed)))
|
|
35
|
+
if not isinstance(options.get("type"), str) or not options["type"].strip():
|
|
36
|
+
return "type must be a non-empty transition name"
|
|
37
|
+
for key, choices in (("category", ("simple", "fusion", "ofx", "audio")),
|
|
38
|
+
("position", ("start", "end")),
|
|
39
|
+
("alignment", ("left", "center", "right"))):
|
|
40
|
+
if options.get(key) not in choices:
|
|
41
|
+
return key + " must be one of: " + ", ".join(choices)
|
|
42
|
+
duration = options.get("duration")
|
|
43
|
+
if duration is not None and (type(duration) is not int or duration <= 0):
|
|
44
|
+
return "duration must be a positive integer number of frames or null"
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def transition_result(transition):
|
|
49
|
+
if transition is None or transition is False:
|
|
50
|
+
return {"success": False}
|
|
51
|
+
return {"success": True, "transition": {
|
|
52
|
+
"id": transition.GetUniqueId(), "name": transition.GetName(),
|
|
53
|
+
"start": transition.GetStart(), "end": transition.GetEnd(),
|
|
54
|
+
"duration": transition.GetDuration(),
|
|
55
|
+
}}
|
|
@@ -225,6 +225,7 @@ _EVIDENCE_GATES: List[Dict[str, Any]] = [
|
|
|
225
225
|
# disagree, which is what keeps the two from drifting apart again.
|
|
226
226
|
|
|
227
227
|
CODE_FLOORS: Dict[str, str] = {
|
|
228
|
+
"TimelineItem.AddTransition": "21.1",
|
|
228
229
|
"TimelineItem.SetSpeed": "21.1",
|
|
229
230
|
"TimelineItem.SetFades": "21.1",
|
|
230
231
|
# Documented in the shipped 21.1 scripting CHANGELOG; read-only contributor
|