davinci-resolve-mcp 2.216.1 → 2.217.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 +41 -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/readwrite-symmetry.md +2 -2
- package/docs/reference/resolve211-speed-fades.md +59 -0
- package/install.py +2 -2
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/granular/resolve_211.py +37 -2
- package/src/resolve_mcp_server.py +1 -1
- package/src/server.py +24 -5
- package/src/utils/destructive_hook.py +6 -0
- package/src/utils/execution_lifecycle.py +5 -0
- package/src/utils/resolve211_edits.py +26 -0
- package/src/utils/resolve_versions.py +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
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.217.0 — native Resolve 21.1 speed and fade setters, registered as the mutations they are
|
|
6
|
+
|
|
7
|
+
Contributed by @legionsound (#208), live-validated on Studio 21.1.0.14.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`timeline_item set_speed` and `set_fades`**, with granular twins
|
|
12
|
+
`set_timeline_item_speed` / `set_timeline_item_fades`, calling 21.1's native
|
|
13
|
+
`SetSpeed` and `SetFades`. Each takes an `options` dictionary — `Percentage`
|
|
14
|
+
(finite number, zero freezes), `PitchCorrection`, `StretchKeyframesToFit`,
|
|
15
|
+
`RippleTimeline` (strict booleans; ripple defaults to false) and `FadeIn` /
|
|
16
|
+
`FadeOut` (non-negative integer frames). Unknown keys, non-finite numbers,
|
|
17
|
+
non-boolean flags and fractional or negative fades are refused before any
|
|
18
|
+
write; valid partial dictionaries are forwarded unchanged, including zero
|
|
19
|
+
and false; a native `False` stays `success: false`; a build without the
|
|
20
|
+
method returns the 21.1 floor error — confirmed here on Studio 19.1.3.7 for
|
|
21
|
+
both. The legacy retime interface is untouched. Granular count 365 → 367;
|
|
22
|
+
generated rules, docs, version floors and the read/write report updated.
|
|
23
|
+
The contributor's frame evidence: at 50% speed the exported timeline-2s
|
|
24
|
+
frame matched the untreated 1s frame pixel-for-pixel; with 24-frame fades
|
|
25
|
+
the first and last exported frames were black and the interior frame
|
|
26
|
+
unchanged. Reverse, freeze, ripple, keyframe stretching and audio pitch are
|
|
27
|
+
not claimed validated. See `docs/reference/resolve211-speed-fades.md`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **On landing, both actions are registered in the destructive registry**
|
|
32
|
+
beside `set_retime`, `set_transform` and the other item setters. As
|
|
33
|
+
contributed, the risk classifier did not recognise them
|
|
34
|
+
(`recognised: false, destructive: false`), so safe mode, the dry-run
|
|
35
|
+
refusal, the security audit and the operation log would all have skipped
|
|
36
|
+
a call that changes a clip's speed — and, with `RippleTimeline: true`,
|
|
37
|
+
moves every clip after it. A test now pins both as recognised, destructive
|
|
38
|
+
writes.
|
|
39
|
+
|
|
40
|
+
### Validation
|
|
41
|
+
|
|
42
|
+
- The PR's offline contracts, the registry test, full offline suite, drift
|
|
43
|
+
guards and the advanced Node suite. Positive behaviour is the contributor's
|
|
44
|
+
21.1 measurement; the version-floor refusal is measured on 19.1.3.7.
|
|
45
|
+
|
|
5
46
|
## What's New in v2.216.1 — ValidateDCTL's layout sensitivity is in the API ledger
|
|
6
47
|
|
|
7
48
|
### Documentation
|
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` | 367 | 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 / **367** 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.217.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` | 367 | 想要"一个 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** 复合 / **367** 细粒度(实时服务器) |
|
|
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` | 367 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
|
|
@@ -2479,3 +2479,5 @@ setups:
|
|
|
2479
2479
|
| `Timeline.AnalyzeDolbyVision` | HDR / Dolby Vision content |
|
|
2480
2480
|
|
|
2481
2481
|
The full API reference is in `docs/reference/resolve_scripting_api.txt`.
|
|
2482
|
+
|
|
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.
|
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 — 367 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` | 367 | 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 367-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 **367 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
|
|
|
@@ -629,3 +629,5 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
|
|
|
629
629
|
| 5 | `GetPostClipNodeGraph()` | ✅ | Returns Graph object |
|
|
630
630
|
|
|
631
631
|
---
|
|
632
|
+
|
|
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.
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# Read/Write Symmetry Audit
|
|
4
4
|
|
|
5
|
-
- write-style action occurrences scanned: **
|
|
6
|
-
- write-style action occurrences with a matching read: **
|
|
5
|
+
- write-style action occurrences scanned: **119**
|
|
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
|
|
|
9
9
|
## High-signal gaps — `set_` with no direct/known readback
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Native Resolve 21.1 speed and fades
|
|
2
|
+
|
|
3
|
+
Compound `timeline_item` now accepts `set_speed` and `set_fades`, with an
|
|
4
|
+
`options` dictionary and the existing track/item locators. Granular mode exposes
|
|
5
|
+
`set_timeline_item_speed` and `set_timeline_item_fades` with the same dictionary.
|
|
6
|
+
These call native SetSpeed/SetFades, require callable methods and the 21.1 floor,
|
|
7
|
+
and return the native boolean as success. They do not replace legacy retime tools.
|
|
8
|
+
|
|
9
|
+
Speed options: Percentage (finite number; zero means freeze), PitchCorrection,
|
|
10
|
+
StretchKeyframesToFit and RippleTimeline (strict booleans). Native RippleTimeline
|
|
11
|
+
and StretchKeyframesToFit defaults are false. Omitted options are not synthesized.
|
|
12
|
+
No undocumented speed bounds are imposed; Resolve decides whether a finite speed
|
|
13
|
+
is supported for the target clip. Freeze, reverse, ripple and pitch behavior are
|
|
14
|
+
not claimed live-validated by this contribution.
|
|
15
|
+
|
|
16
|
+
Fade options: FadeIn and/or FadeOut, non-negative integer frame counts as defined
|
|
17
|
+
by the shipped FadeInfo stub. GetFades may return floats, so callers round-trip
|
|
18
|
+
integral readback values by explicitly converting to integers. Fractional setter
|
|
19
|
+
values are refused. Partial dictionaries are forwarded unchanged to Resolve.
|
|
20
|
+
Empty dictionaries, unknown keys and malformed types return errors before writes.
|
|
21
|
+
|
|
22
|
+
Example: `timeline_item("set_speed", {"options": {"Percentage": 50,
|
|
23
|
+
"RippleTimeline": false}, "track_type": "video", "track_index": 1,
|
|
24
|
+
"item_index": 0})`. For a one-second fade on a 24 fps clip, use
|
|
25
|
+
`{"options": {"FadeIn": 24, "FadeOut": 24}}` with `set_fades`.
|
|
26
|
+
|
|
27
|
+
## Contributor validation
|
|
28
|
+
|
|
29
|
+
Contributor-validated on macOS Studio **21.1.0.14** using synthetic media in a
|
|
30
|
+
disposable project. Both interfaces changed speed to 50% and fades to 24 frames
|
|
31
|
+
with matching native readback. Resolve-exported stills at timeline 2s after the
|
|
32
|
+
speed change matched the untreated source-at-1s export pixel-for-pixel, while
|
|
33
|
+
untreated timeline 2s differed. Fade start and final frame were black; the interior frame matched
|
|
34
|
+
the untreated frame exactly. This verifies these sampled video results, not an
|
|
35
|
+
entire rendered movie, audio pitch/volume, retimed keyframes, ripple edits or
|
|
36
|
+
other clip types. The native setters restore their original values after testing.
|
|
37
|
+
|
|
38
|
+
`tests/live_resolve211_speed_fades.py OUTPUT_DIR` enforces the named scratch
|
|
39
|
+
fixture before writes and exports the comparison frames. It requires a 24 fps,
|
|
40
|
+
zero-start timeline with one six-second synthetic test-pattern.mov video clip.
|
|
41
|
+
Do not run it on production media. Unit contracts cover both layers, native
|
|
42
|
+
failure, absent methods, invalid options, explicit false/zero and partial fields.
|
|
43
|
+
|
|
44
|
+
Synthetic fixture creation (outside Resolve, no source media involved):
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
ffmpeg -f lavfi -i 'testsrc2=size=640x360:rate=24:duration=6' \
|
|
48
|
+
-f lavfi -i 'sine=frequency=440:sample_rate=48000:duration=6' \
|
|
49
|
+
-c:v prores_ks -profile:v 0 -pix_fmt yuv422p10le -c:a pcm_s16le \
|
|
50
|
+
test-pattern.mov
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Create a disposable project named `Codex Speed Fades Validation 20260909`, set
|
|
54
|
+
640x360 and 24 fps, create a timeline starting at 00:00:00:00, and append this
|
|
55
|
+
clip once. The fixture name is deliberately explicit to make accidental writes
|
|
56
|
+
to an unrelated project fail. Compare decoded RGB pixels of the exported PNGs;
|
|
57
|
+
speed50_2s should equal baseline_1s, fade_2s should equal baseline_2s, and
|
|
58
|
+
fade_0s/fade_end should be black for each interface. The test exports stills;
|
|
59
|
+
it does not submit render jobs or establish whole-movie/audio acceptance.
|
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.217.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 · 367 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.217.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,6 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Native Resolve 21.1 discovery and editing controls."""
|
|
2
|
+
from src.utils.resolve211_edits import validate_edit_options
|
|
2
3
|
from src.granular.common import (
|
|
3
|
-
mcp, READ_ONLY_TOOL, get_resolve, get_current_project,
|
|
4
|
+
mcp, READ_ONLY_TOOL, WRITE_TOOL, get_resolve, get_current_project,
|
|
4
5
|
_get_timeline, _get_timeline_item, _requires_method, has_method,
|
|
5
6
|
)
|
|
6
7
|
|
|
@@ -146,3 +147,37 @@ def get_timeline_item_use_timeline_for_output_blanking(track_type: str = "video"
|
|
|
146
147
|
if missing:
|
|
147
148
|
return missing
|
|
148
149
|
return {"use_timeline": item.GetUseTimelineForOutputBlanking()}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@mcp.tool(annotations=WRITE_TOOL)
|
|
153
|
+
def set_timeline_item_speed(options: dict, track_type: str = "video", track_index: int = 1, item_index: int = 0) -> dict:
|
|
154
|
+
"""Set native 21.1 Percentage, PitchCorrection, StretchKeyframesToFit and/or RippleTimeline. Percentage 0 freezes; RippleTimeline defaults false."""
|
|
155
|
+
error = validate_edit_options("set_speed", options)
|
|
156
|
+
if error:
|
|
157
|
+
return {"error": error}
|
|
158
|
+
if track_type not in ("video", "audio") or track_index < 1 or item_index < 0:
|
|
159
|
+
return {"error": "Use video/audio, a 1-based track index and a non-negative item index"}
|
|
160
|
+
item, error = _get_timeline_item(track_type, track_index, item_index)
|
|
161
|
+
if error:
|
|
162
|
+
return error
|
|
163
|
+
missing = _requires_method(item, "SetSpeed", "21.1")
|
|
164
|
+
if missing:
|
|
165
|
+
return missing
|
|
166
|
+
return {"success": bool(item.SetSpeed(dict(options)))}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@mcp.tool(annotations=WRITE_TOOL)
|
|
170
|
+
def set_timeline_item_fades(options: dict, track_type: str = "video", track_index: int = 1, item_index: int = 0) -> dict:
|
|
171
|
+
"""Set native 21.1 FadeIn and/or FadeOut as non-negative integer frame durations. Omitted fields remain native defaults/current state."""
|
|
172
|
+
error = validate_edit_options("set_fades", options)
|
|
173
|
+
if error:
|
|
174
|
+
return {"error": error}
|
|
175
|
+
if track_type not in ("video", "audio") or track_index < 1 or item_index < 0:
|
|
176
|
+
return {"error": "Use video/audio, a 1-based track index and a non-negative item index"}
|
|
177
|
+
item, error = _get_timeline_item(track_type, track_index, item_index)
|
|
178
|
+
if error:
|
|
179
|
+
return error
|
|
180
|
+
missing = _requires_method(item, "SetFades", "21.1")
|
|
181
|
+
if missing:
|
|
182
|
+
return missing
|
|
183
|
+
return {"success": bool(item.SetFades(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} (367 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 367-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.
|
|
14
|
+
VERSION = "2.217.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -42,6 +42,8 @@ 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
|
|
46
|
+
|
|
45
47
|
# Platform-specific Resolve paths
|
|
46
48
|
from src.utils.cdl import normalize_cdl_payload
|
|
47
49
|
from src.utils import resolve_writes as _resolve_writes
|
|
@@ -26272,6 +26274,8 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
26272
26274
|
Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
|
|
26273
26275
|
|
|
26274
26276
|
Actions:
|
|
26277
|
+
set_speed(options, ...) -> {success} — native 21.1 speed options; RippleTimeline defaults false.
|
|
26278
|
+
set_fades(options, ...) -> {success} — native 21.1 FadeIn/FadeOut integer frames.
|
|
26275
26279
|
get_speed(...) -> {speed} — documented on Resolve 21.1+.
|
|
26276
26280
|
get_fades(...) -> {fades} — documented on Resolve 21.1+. Native frame durations.
|
|
26277
26281
|
get_output_blanking(...) -> {blanking} — documented on Resolve 21.1+. Pixel coordinates; empty on a clip inheriting timeline blanking.
|
|
@@ -26326,6 +26330,21 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
26326
26330
|
if err:
|
|
26327
26331
|
return err
|
|
26328
26332
|
|
|
26333
|
+
if action in ("set_speed", "set_fades"):
|
|
26334
|
+
options = p.get("options")
|
|
26335
|
+
error = validate_edit_options(action, options)
|
|
26336
|
+
if error:
|
|
26337
|
+
return _err(error)
|
|
26338
|
+
if action == "set_speed":
|
|
26339
|
+
missing = _requires_method(item, "SetSpeed", "21.1")
|
|
26340
|
+
if missing:
|
|
26341
|
+
return missing
|
|
26342
|
+
return {"success": bool(item.SetSpeed(dict(options)))}
|
|
26343
|
+
missing = _requires_method(item, "SetFades", "21.1")
|
|
26344
|
+
if missing:
|
|
26345
|
+
return missing
|
|
26346
|
+
return {"success": bool(item.SetFades(dict(options)))}
|
|
26347
|
+
|
|
26329
26348
|
if action == "get_speed":
|
|
26330
26349
|
missing = _requires_method(item, "GetSpeed", "21.1")
|
|
26331
26350
|
if missing:
|
|
@@ -26522,7 +26541,7 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
26522
26541
|
return _err(f"Invalid interpolation. Must be one of: {', '.join(valid)}")
|
|
26523
26542
|
return {"success": bool(item.SetKeyframeInterpolation(p["property"], p["frame"], p["interpolation"]))}
|
|
26524
26543
|
|
|
26525
|
-
return _unknown(action, ["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"])
|
|
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"])
|
|
26526
26545
|
|
|
26527
26546
|
|
|
26528
26547
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -32644,9 +32663,9 @@ if __name__ == "__main__":
|
|
|
32644
32663
|
start_background_update_check(VERSION, project_dir, logger, env=_setup_update_env())
|
|
32645
32664
|
_install_threaded_tool_dispatch(mcp)
|
|
32646
32665
|
|
|
32647
|
-
# Support --full flag to run the
|
|
32666
|
+
# Support --full flag to run the 367-tool granular server instead
|
|
32648
32667
|
if "--full" in sys.argv:
|
|
32649
|
-
logger.info("Starting full
|
|
32668
|
+
logger.info("Starting full 367-tool granular server...")
|
|
32650
32669
|
sys.argv = [arg for arg in sys.argv if arg != "--full"]
|
|
32651
32670
|
from src.granular import mcp as granular_mcp
|
|
32652
32671
|
|
|
@@ -147,6 +147,12 @@ DESTRUCTIVE_ACTIONS_BY_TOOL: Dict[str, FrozenSet[str]] = {
|
|
|
147
147
|
"set_retime",
|
|
148
148
|
"set_composite",
|
|
149
149
|
"set_audio",
|
|
150
|
+
# Native 21.1 setters (#208). Registered on landing: as contributed the
|
|
151
|
+
# classifier did not recognise them, so safe mode, the dry-run refusal,
|
|
152
|
+
# the audit log and the operation log all skipped a call that rewrites a
|
|
153
|
+
# clip's speed — and, with RippleTimeline true, moves every clip after it.
|
|
154
|
+
"set_speed",
|
|
155
|
+
"set_fades",
|
|
150
156
|
}),
|
|
151
157
|
"timeline_item_markers": frozenset({
|
|
152
158
|
"add",
|
|
@@ -271,6 +271,11 @@ class RiskClassificationHook(LifecycleHook):
|
|
|
271
271
|
# whatever the caller passed; `set_retime` changes duration and sync.
|
|
272
272
|
("timeline_item", "set_property"),
|
|
273
273
|
("timeline_item", "set_retime"),
|
|
274
|
+
# Native 21.1 setters (#208): `set_speed` changes duration and, with
|
|
275
|
+
# RippleTimeline true, moves every clip after it; `set_fades` rewrites
|
|
276
|
+
# how the clip's edges render. Existing content altered, not deleted.
|
|
277
|
+
("timeline_item", "set_speed"),
|
|
278
|
+
("timeline_item", "set_fades"),
|
|
274
279
|
# Pool reorganisation: clips and bins move, nothing is destroyed, but
|
|
275
280
|
# paths other work depends on change underneath it.
|
|
276
281
|
("media_pool", "move_clips"),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Validate native 21.1 editing dictionaries without guessing undocumented bounds."""
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def validate_edit_options(action, options):
|
|
6
|
+
if not isinstance(options, dict) or not options:
|
|
7
|
+
return "options must be a non-empty dictionary"
|
|
8
|
+
allowed = ({"Percentage", "PitchCorrection", "StretchKeyframesToFit", "RippleTimeline"}
|
|
9
|
+
if action == "set_speed" else {"FadeIn", "FadeOut"})
|
|
10
|
+
if set(options) - allowed:
|
|
11
|
+
return "Unknown options: " + ", ".join(sorted(map(str, set(options) - allowed)))
|
|
12
|
+
for key, value in options.items():
|
|
13
|
+
if key in {"PitchCorrection", "StretchKeyframesToFit", "RippleTimeline"}:
|
|
14
|
+
if type(value) is not bool:
|
|
15
|
+
return key + " must be a boolean"
|
|
16
|
+
elif action == "set_fades":
|
|
17
|
+
if type(value) is not int or value < 0:
|
|
18
|
+
return key + " must be a non-negative integer number of frames"
|
|
19
|
+
else:
|
|
20
|
+
try:
|
|
21
|
+
finite = type(value) in (int, float) and math.isfinite(value)
|
|
22
|
+
except OverflowError:
|
|
23
|
+
finite = False
|
|
24
|
+
if not finite:
|
|
25
|
+
return key + " must be a finite number"
|
|
26
|
+
return None
|
|
@@ -225,6 +225,8 @@ _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.SetSpeed": "21.1",
|
|
229
|
+
"TimelineItem.SetFades": "21.1",
|
|
228
230
|
# Documented in the shipped 21.1 scripting CHANGELOG; read-only contributor
|
|
229
231
|
# validation on Studio 21.1.0.14. No claim of a live older-build bisect.
|
|
230
232
|
"Resolve.GetKeyboardPresetList": "21.1",
|