davinci-resolve-mcp 3.2.0 → 3.2.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 +33 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/lut_files.py +43 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
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 v3.2.1 — three correctness fixes to the LUT tool
|
|
6
|
+
|
|
7
|
+
Contributed by @Dev-next-gen (#225, #226, #227), each found by reading the v3.2.0
|
|
8
|
+
`lut` tool rather than by hitting it in use.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`install` reported `overwritten: true` on a fresh write.** The flag was
|
|
13
|
+
`bool(overwrite and payload is not None)`, and `payload` can never be `None`
|
|
14
|
+
where that line runs, so the field handed the caller's own `overwrite`
|
|
15
|
+
argument back instead of an observation. An install passing `overwrite=true`
|
|
16
|
+
for idempotence, landing on an empty `MCP/`, was reported as having replaced
|
|
17
|
+
existing work. `execution_lifecycle` rates `lut install` MEDIUM precisely
|
|
18
|
+
because it "can replace with overwrite=true", so this flag is what a caller
|
|
19
|
+
and the execution trace read to learn whether an install destroyed anything.
|
|
20
|
+
It is now the `os.path.exists` observation already taken one line above — the
|
|
21
|
+
measured pre-state, not the permission. (#225)
|
|
22
|
+
- **`install(source_path=...)` could not copy a binary LUT.** The copy went
|
|
23
|
+
through a UTF-8 text round-trip, so the two binary extensions this server
|
|
24
|
+
advertises — `.dat` and `.olut` — raised `UnicodeDecodeError` before anything
|
|
25
|
+
was written, surfacing as `LUT_ERROR`. So did an ordinary `.cube` whose vendor
|
|
26
|
+
wrote its `TITLE` line in latin-1. The file-copy branch is now byte-exact; the
|
|
27
|
+
text branch (`source=`) is unchanged. (#226)
|
|
28
|
+
- **`list` marked siblings of the writable subdir as writable.** The `writable`
|
|
29
|
+
flag used `realpath(current).startswith(writable_root)`, a string prefix with
|
|
30
|
+
no separator, so `MCP_old/` left by a hand backup or a vendor pack unpacking
|
|
31
|
+
as `MCPresets/` cleared it. The listing then contradicted the only tool that
|
|
32
|
+
consumes the flag: `remove` resolves names inside `MCP/` and refused the very
|
|
33
|
+
`set_lut_path` the listing had just handed out. It now compares by path
|
|
34
|
+
segment with `commonpath`, which is what `_is_relative_to` and `resolve_writable`
|
|
35
|
+
already use elsewhere in this repo. Nothing that worked before stops working —
|
|
36
|
+
the flag only flips for paths `remove` was already refusing. (#227)
|
|
37
|
+
|
|
5
38
|
## What's New in v3.2.0 — LUT files: find them, install them, remove them, gated
|
|
6
39
|
|
|
7
40
|
Contributed by @legionsound (#223), live-validated on Studio 21.1.0.14.
|
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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v3.2.
|
|
15
|
+
> 本翻译对应 v3.2.1 版 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 = "3.2.
|
|
40
|
+
VERSION = "3.2.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
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 = "3.2.
|
|
90
|
+
VERSION = "3.2.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()}")
|
package/src/server.py
CHANGED
package/src/utils/lut_files.py
CHANGED
|
@@ -50,6 +50,20 @@ def writable_dir() -> str:
|
|
|
50
50
|
return os.path.join(master_lut_dir(), WRITABLE_SUBDIR)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
def _is_within(path: str, parent: str) -> bool:
|
|
54
|
+
"""Whether ``path`` is ``parent`` itself or sits under it, compared by segment.
|
|
55
|
+
|
|
56
|
+
``commonpath`` is the comparison the rest of the repo uses for confinement
|
|
57
|
+
checks (`media_analysis._is_relative_to`), and it is the only one that is
|
|
58
|
+
correct here: a string prefix test also accepts a sibling whose name merely
|
|
59
|
+
starts with the same characters.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
return os.path.commonpath([path, parent]) == parent
|
|
63
|
+
except (ValueError, OSError):
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
53
67
|
def normalize_relative(name: str, *, default_ext: Optional[str] = None) -> str:
|
|
54
68
|
"""Validate a caller-supplied LUT name and return it as a POSIX relative path.
|
|
55
69
|
|
|
@@ -123,7 +137,13 @@ def list_luts(subdir: Optional[str] = None) -> Dict[str, Any]:
|
|
|
123
137
|
except OSError:
|
|
124
138
|
size = None
|
|
125
139
|
try:
|
|
126
|
-
|
|
140
|
+
# Segment-wise, not a string prefix: a sibling of the writable
|
|
141
|
+
# subdir whose name starts with it -- MCP_old/ after a manual
|
|
142
|
+
# backup, MCPresets/ from a vendor pack -- cleared
|
|
143
|
+
# `startswith(writable_root)` and was listed as writable, while
|
|
144
|
+
# `remove` resolves names under MCP/ and refuses the very path
|
|
145
|
+
# the listing handed back.
|
|
146
|
+
is_writable = _is_within(os.path.realpath(current), writable_root)
|
|
127
147
|
except OSError:
|
|
128
148
|
is_writable = False
|
|
129
149
|
found.append({
|
|
@@ -154,28 +174,45 @@ def install_lut(name: str, *, source: Optional[str] = None,
|
|
|
154
174
|
if (source is None) == (source_path is None):
|
|
155
175
|
raise LutPathError("provide exactly one of source (text) or source_path (a file to copy)")
|
|
156
176
|
absolute, set_lut_path = resolve_writable(name)
|
|
157
|
-
|
|
177
|
+
existed = os.path.exists(absolute)
|
|
178
|
+
if existed and not overwrite:
|
|
158
179
|
raise LutPathError(
|
|
159
180
|
f"{set_lut_path} already exists. Pass overwrite=true to replace it."
|
|
160
181
|
)
|
|
161
182
|
if source_path is not None:
|
|
162
183
|
if not os.path.isfile(source_path):
|
|
163
184
|
raise LutPathError(f"source_path not found: {source_path}")
|
|
164
|
-
|
|
185
|
+
# A copy has to be byte-exact. Two of the extensions this server
|
|
186
|
+
# advertises -- .dat and .olut -- are binary, and a .cube carries its
|
|
187
|
+
# TITLE in whatever encoding the vendor wrote it in, so decoding the
|
|
188
|
+
# source as UTF-8 turned "install the LUT I already have on disk" into
|
|
189
|
+
# a UnicodeDecodeError surfacing as LUT_ERROR.
|
|
190
|
+
with open(source_path, "rb") as handle:
|
|
165
191
|
payload = handle.read()
|
|
166
192
|
else:
|
|
167
193
|
payload = source
|
|
168
194
|
if not payload.strip():
|
|
169
195
|
raise LutPathError("refusing to install an empty LUT")
|
|
170
196
|
os.makedirs(os.path.dirname(absolute), exist_ok=True)
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
if isinstance(payload, bytes):
|
|
198
|
+
with open(absolute, "wb") as handle:
|
|
199
|
+
handle.write(payload)
|
|
200
|
+
else:
|
|
201
|
+
with open(absolute, "w", encoding="utf-8") as handle:
|
|
202
|
+
handle.write(payload)
|
|
173
203
|
return {
|
|
174
204
|
"success": True,
|
|
175
205
|
"path": absolute,
|
|
176
206
|
"set_lut_path": set_lut_path,
|
|
177
207
|
"bytes": os.path.getsize(absolute),
|
|
178
|
-
|
|
208
|
+
# Whether a file was actually replaced, not whether the caller allowed
|
|
209
|
+
# it: `payload is not None` is always true here, so this reported a
|
|
210
|
+
# replacement for every overwrite=true install, including the ones that
|
|
211
|
+
# landed on an empty MCP/. This flag is the record of what an install
|
|
212
|
+
# destroyed -- execution_lifecycle rates `lut install` on the fact that
|
|
213
|
+
# it "can replace with overwrite=true" -- so it has to be the observed
|
|
214
|
+
# pre-state, not the permission.
|
|
215
|
+
"overwritten": existed,
|
|
179
216
|
"note": ("Call project_settings(action='refresh_luts') so Resolve picks up "
|
|
180
217
|
"the new file before applying it."),
|
|
181
218
|
}
|