davinci-resolve-mcp 4.3.0 → 4.4.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 +64 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +103 -56
- package/src/granular/timeline.py +5 -2
- package/src/server.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,70 @@
|
|
|
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 v4.4.0 — 85 granular tools stop lying to clients about what they do
|
|
6
|
+
|
|
7
|
+
Granular tools infer their MCP safety annotation from the leading verb in the tool
|
|
8
|
+
name. `delete_marker` matched; `ti_delete_marker` did not, because the namespace sits
|
|
9
|
+
in front of the verb. Every `ti_*`, `timeline_*`, `graph_*` and `folder_*` tool —
|
|
10
|
+
132 of them — matched no verb rule and took the plain-write default.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **43 destructive granular tools were advertised as ordinary writes.** Deletes,
|
|
15
|
+
clears, resets, sets and loads — `ti_delete_version`, `ti_clear_flags`,
|
|
16
|
+
`timeline_delete_track`, `timeline_delete_clips`, `folder_clear_transcription`,
|
|
17
|
+
`graph_reset_all_grades` and the rest — all carried `destructiveHint=False`. A
|
|
18
|
+
client that gates on that hint, by prompting the user or refusing in a read-only
|
|
19
|
+
mode, was told every one of them was safe. v4.3.0 fixed this for `ti_copy_grades`
|
|
20
|
+
by hand; the other 42 needed the classifier fixed instead.
|
|
21
|
+
|
|
22
|
+
- **42 pure readers were advertised as writes.** Every namespaced `*_get_*` tool —
|
|
23
|
+
`ti_get_info`, `timeline_get_markers`, `graph_get_lut` — claimed it could mutate,
|
|
24
|
+
so a read-only client had to refuse work it could safely have done.
|
|
25
|
+
|
|
26
|
+
- **`detect_` was a read prefix, and `Timeline.DetectSceneCuts` adds cuts.** The one
|
|
27
|
+
tool using it, `timeline_detect_scene_cuts`, was only ever classified correctly
|
|
28
|
+
because its namespace hid it from that list — teaching the classifier to see past
|
|
29
|
+
the namespace would have promoted a tool that restructures the timeline to
|
|
30
|
+
read-only. `detect_` is gone from the read list and the tool is now explicitly
|
|
31
|
+
destructive, matching how the compound server already rates it.
|
|
32
|
+
|
|
33
|
+
- **A bare `<namespace>_<verb>` name matched nothing even after stripping.** Every
|
|
34
|
+
verb prefix ends in `_`, so `timeline_export` became `export`, which does not start
|
|
35
|
+
with `export_`. `timeline_export`, `folder_export` and `timeline_duplicate` fell
|
|
36
|
+
through. The verb probe now appends the separator before matching.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **`tests/test_granular_tool_annotations.py` guards the classifier, not the names.**
|
|
41
|
+
Three properties, each pinning a way this failed:
|
|
42
|
+
- no tool hinted `readOnlyHint=True` calls a Resolve method outside the
|
|
43
|
+
`Get`/`Is`/`Has`/`List`/`Find`/`Export` shapes — this is what catches the next
|
|
44
|
+
`DetectSceneCuts`, and it is a property of the body, not of the name;
|
|
45
|
+
- no namespaced tool falls through to the default, checked against the verb lists
|
|
46
|
+
directly so a deliberate `WRITE` passes and a fallthrough does not;
|
|
47
|
+
- the allow-list of ruleless verbs must stay exact in both directions, so an entry
|
|
48
|
+
that later matches a verb has to be removed rather than left to rot.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- The verb lists move to module level in `src/granular/common.py`
|
|
53
|
+
(`READ_PREFIXES`, `DESTRUCTIVE_PREFIXES`, `WRITE_PREFIXES`) alongside
|
|
54
|
+
`NAMESPACE_PREFIXES` and `matches_a_verb`, so the guards can tell a deliberate
|
|
55
|
+
write from a name that matched nothing — the distinction the old code could not
|
|
56
|
+
express, and the reason the bug was invisible.
|
|
57
|
+
|
|
58
|
+
### Validation
|
|
59
|
+
|
|
60
|
+
- Full offline suite: **3,640 passed, 1 skipped, 0 failed**, 1,269 subtests.
|
|
61
|
+
- Every one of the 387 granular tools was classified before and after. 85 changed:
|
|
62
|
+
43 write→destructive, 42 write→read. The 42 that became *less* restrictive are all
|
|
63
|
+
`*_get_*` getters, and the read-only guard above independently confirms none of
|
|
64
|
+
them calls a mutating Resolve method — that check is the evidence, not the naming.
|
|
65
|
+
- All release drift guards green. No Resolve behaviour changed: annotations are
|
|
66
|
+
metadata a client reads before calling, and no tool body was touched except
|
|
67
|
+
`timeline_detect_scene_cuts`, which gained a docstring warning and its annotation.
|
|
68
|
+
|
|
5
69
|
## What's New in v4.3.0 — the granular grade-copy stops replacing grades on clips nobody named
|
|
6
70
|
|
|
7
71
|
v4.2.0 gated the compound `timeline_item_color copy_grades`. Its granular twin,
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v4.
|
|
15
|
+
> 本翻译对应 v4.4.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
|
|
16
16
|
|
|
17
17
|
一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
|
|
18
18
|
|
package/install.py
CHANGED
|
@@ -37,7 +37,7 @@ from src.utils.update_check import (
|
|
|
37
37
|
|
|
38
38
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
|
-
VERSION = "4.
|
|
40
|
+
VERSION = "4.4.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
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -92,7 +92,7 @@ if not logging.getLogger().handlers:
|
|
|
92
92
|
handlers=[logging.StreamHandler()],
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
-
VERSION = "4.
|
|
95
|
+
VERSION = "4.4.0"
|
|
96
96
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
97
97
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
98
98
|
logger.info(f"Detected platform: {get_platform()}")
|
|
@@ -145,65 +145,112 @@ EXTERNAL_DESTRUCTIVE_TOOL = ToolAnnotations(
|
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
#: Namespace segments that sit in FRONT of the verb in a granular tool name.
|
|
149
|
+
#:
|
|
150
|
+
#: The prefix heuristic below reads the leading verb, so a tool called
|
|
151
|
+
#: `ti_delete_marker_at_frame` matched none of the verb lists and fell through to
|
|
152
|
+
#: the plain write default — 86 tools were mis-hinted this way, 43 destructive ones
|
|
153
|
+
#: advertised as ordinary writes (a client gating on `destructiveHint` was told
|
|
154
|
+
#: `ti_copy_grades` was safe) and 43 pure readers advertised as writes. Every tool
|
|
155
|
+
#: carrying one of these is `<namespace>_<verb>_...`, so one strip exposes the verb.
|
|
156
|
+
NAMESPACE_PREFIXES = (
|
|
157
|
+
"ti_",
|
|
158
|
+
"timeline_",
|
|
159
|
+
"graph_",
|
|
160
|
+
"folder_",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _strip_namespace(name: str) -> str:
|
|
165
|
+
"""Drop one leading namespace segment so the verb heuristic can see the verb."""
|
|
166
|
+
for prefix in NAMESPACE_PREFIXES:
|
|
167
|
+
if name.startswith(prefix):
|
|
168
|
+
return name[len(prefix):]
|
|
169
|
+
return name
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _verb_probe(tool_name: str) -> str:
|
|
173
|
+
"""The stripped name, shaped so a BARE verb still matches its prefix.
|
|
174
|
+
|
|
175
|
+
Every verb prefix ends in "_", so `timeline_export` -> `export` would match
|
|
176
|
+
nothing: the tool name is exactly `<namespace>_<verb>` with no suffix. The
|
|
177
|
+
trailing "_" makes `export` match `export_` without loosening anything else.
|
|
178
|
+
"""
|
|
179
|
+
return _strip_namespace((tool_name or "").lower()) + "_"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
#: Verb prefixes, checked in this order against the name AFTER its namespace is
|
|
183
|
+
#: stripped. Module-level so `tests/test_granular_tool_annotations.py` can tell a
|
|
184
|
+
#: deliberate write from a name that matched nothing and fell through to the default.
|
|
185
|
+
READ_PREFIXES = (
|
|
186
|
+
"get_",
|
|
187
|
+
"list_",
|
|
188
|
+
"inspect_",
|
|
189
|
+
"probe_",
|
|
190
|
+
"validate_",
|
|
191
|
+
"compare_",
|
|
192
|
+
# NOT "detect_": Timeline.DetectSceneCuts adds cuts to the timeline, and the
|
|
193
|
+
# compound server rates detect_scene_cuts destructive. It only ever looked like
|
|
194
|
+
# a read because the `timeline_` namespace hid it from this list.
|
|
195
|
+
"summarize_",
|
|
196
|
+
"review_",
|
|
197
|
+
"is_",
|
|
198
|
+
"has_",
|
|
199
|
+
)
|
|
200
|
+
DESTRUCTIVE_PREFIXES = (
|
|
201
|
+
"delete_",
|
|
202
|
+
"remove_",
|
|
203
|
+
"clear_",
|
|
204
|
+
"reset_",
|
|
205
|
+
"replace_",
|
|
206
|
+
"unlink_",
|
|
207
|
+
"quit",
|
|
208
|
+
"restart",
|
|
209
|
+
"close_",
|
|
210
|
+
"stop_",
|
|
211
|
+
"overwrite_",
|
|
212
|
+
"lift_",
|
|
213
|
+
"set_",
|
|
214
|
+
"load_",
|
|
215
|
+
"switch_",
|
|
216
|
+
)
|
|
217
|
+
WRITE_PREFIXES = (
|
|
218
|
+
"add_",
|
|
219
|
+
"append_",
|
|
220
|
+
"apply_",
|
|
221
|
+
"assign_",
|
|
222
|
+
"copy_",
|
|
223
|
+
"create_",
|
|
224
|
+
"duplicate_",
|
|
225
|
+
"export_",
|
|
226
|
+
"import_",
|
|
227
|
+
"insert_",
|
|
228
|
+
"link_",
|
|
229
|
+
"move_",
|
|
230
|
+
"open_",
|
|
231
|
+
"render_",
|
|
232
|
+
"rename_",
|
|
233
|
+
"save_",
|
|
234
|
+
"start_",
|
|
235
|
+
"sync_",
|
|
236
|
+
"transcribe_",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def matches_a_verb(tool_name: str) -> bool:
|
|
241
|
+
"""Did the name resolve to a verb rule, or fall through to the default?"""
|
|
242
|
+
return _verb_probe(tool_name).startswith(
|
|
243
|
+
READ_PREFIXES + DESTRUCTIVE_PREFIXES + WRITE_PREFIXES)
|
|
244
|
+
|
|
245
|
+
|
|
148
246
|
def _annotations_for_tool_name(tool_name: str) -> ToolAnnotations:
|
|
149
247
|
"""Infer conservative MCP client-safety hints for legacy granular tools."""
|
|
150
|
-
name = (tool_name
|
|
151
|
-
|
|
152
|
-
"get_",
|
|
153
|
-
"list_",
|
|
154
|
-
"inspect_",
|
|
155
|
-
"probe_",
|
|
156
|
-
"validate_",
|
|
157
|
-
"compare_",
|
|
158
|
-
"detect_",
|
|
159
|
-
"summarize_",
|
|
160
|
-
"review_",
|
|
161
|
-
"is_",
|
|
162
|
-
"has_",
|
|
163
|
-
)
|
|
164
|
-
destructive_prefixes = (
|
|
165
|
-
"delete_",
|
|
166
|
-
"remove_",
|
|
167
|
-
"clear_",
|
|
168
|
-
"reset_",
|
|
169
|
-
"replace_",
|
|
170
|
-
"unlink_",
|
|
171
|
-
"quit",
|
|
172
|
-
"restart",
|
|
173
|
-
"close_",
|
|
174
|
-
"stop_",
|
|
175
|
-
"overwrite_",
|
|
176
|
-
"lift_",
|
|
177
|
-
"set_",
|
|
178
|
-
"load_",
|
|
179
|
-
"switch_",
|
|
180
|
-
)
|
|
181
|
-
write_prefixes = (
|
|
182
|
-
"add_",
|
|
183
|
-
"append_",
|
|
184
|
-
"apply_",
|
|
185
|
-
"assign_",
|
|
186
|
-
"copy_",
|
|
187
|
-
"create_",
|
|
188
|
-
"duplicate_",
|
|
189
|
-
"export_",
|
|
190
|
-
"import_",
|
|
191
|
-
"insert_",
|
|
192
|
-
"link_",
|
|
193
|
-
"move_",
|
|
194
|
-
"open_",
|
|
195
|
-
"render_",
|
|
196
|
-
"rename_",
|
|
197
|
-
"save_",
|
|
198
|
-
"start_",
|
|
199
|
-
"sync_",
|
|
200
|
-
"transcribe_",
|
|
201
|
-
)
|
|
202
|
-
if name.startswith(read_prefixes):
|
|
248
|
+
name = _verb_probe(tool_name)
|
|
249
|
+
if name.startswith(READ_PREFIXES):
|
|
203
250
|
return READ_ONLY_TOOL
|
|
204
|
-
if name.startswith(
|
|
251
|
+
if name.startswith(DESTRUCTIVE_PREFIXES):
|
|
205
252
|
return DESTRUCTIVE_TOOL
|
|
206
|
-
if name.startswith(
|
|
253
|
+
if name.startswith(WRITE_PREFIXES):
|
|
207
254
|
return WRITE_TOOL
|
|
208
255
|
return WRITE_TOOL
|
|
209
256
|
|
package/src/granular/timeline.py
CHANGED
|
@@ -878,9 +878,12 @@ def timeline_create_subtitles_from_audio(
|
|
|
878
878
|
return {"success": bool(result)}
|
|
879
879
|
|
|
880
880
|
|
|
881
|
-
|
|
881
|
+
# Explicit: the verb heuristic has no rule for "detect", and this one restructures
|
|
882
|
+
# the timeline by adding cuts. The compound server rates timeline_ai.detect_scene_cuts
|
|
883
|
+
# destructive; this is the same Resolve call, so it carries the same hint.
|
|
884
|
+
@mcp.tool(annotations=DESTRUCTIVE_TOOL)
|
|
882
885
|
def timeline_detect_scene_cuts() -> Dict[str, Any]:
|
|
883
|
-
"""Detect scene cuts in the current timeline."""
|
|
886
|
+
"""Detect scene cuts in the current timeline. DESTRUCTIVE — adds cuts to the timeline."""
|
|
884
887
|
_, tl, err = _get_timeline()
|
|
885
888
|
if err:
|
|
886
889
|
return err
|