davinci-resolve-mcp 2.104.4 → 2.104.5
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 +36 -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 +1 -1
- package/src/server.py +33 -3
- package/src/utils/probe_catalogue.py +3 -1
- package/src/utils/project_cleanup.py +60 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
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.104.5
|
|
6
|
+
|
|
7
|
+
The recent bug classes, generalized into guards — and the sweeps found the
|
|
8
|
+
kwarg bug a second time.
|
|
9
|
+
|
|
10
|
+
**PR #165's bug existed twice.** The positional-only bridge rule was guarded
|
|
11
|
+
for src/server.py alone; sweeping ALL of src/ found
|
|
12
|
+
`StartRendering(isInteractiveMode=...)` again in the render-deliver probe
|
|
13
|
+
catalogue. Fixed, and the guard is rebuilt properly: it parses the Resolve
|
|
14
|
+
method names out of the shipped API reference and flags keyword arguments on
|
|
15
|
+
exactly those calls across the whole tree — which is what separates
|
|
16
|
+
StartRendering from Popen without drowning in stdlib false positives.
|
|
17
|
+
|
|
18
|
+
**Closing a project mid-render is now unreachable through this server.** The
|
|
19
|
+
wedge documented in v2.104.0 (orphaned render, stuck IsRenderingInProgress,
|
|
20
|
+
0% jobs, refused Quit) could still be triggered via project_manager.close or
|
|
21
|
+
a disposable-project delete. `close` now refuses while a render is in
|
|
22
|
+
progress — with the wedge named in the remediation — and accepts
|
|
23
|
+
stop_render=true to stop, wait for the flag to clear, and close.
|
|
24
|
+
delete_project_safely auto-stops first (deleting kills the render anyway;
|
|
25
|
+
stopping is strictly better) and refuses when the flag will not clear, which
|
|
26
|
+
is the already-wedged state where no delete ends well. Live-verified both
|
|
27
|
+
paths on Studio 19.1.3.7: mid-render close refused, stop_render=true stopped
|
|
28
|
+
and closed cleanly, no wedge.
|
|
29
|
+
|
|
30
|
+
**Audits that came back clean, on the record:** the remaining default-ON
|
|
31
|
+
analysis gates (marker plan is built unconditionally, vision is default-OFF
|
|
32
|
+
behind a capability gate) cannot reproduce the cache-poisoning shape, and the
|
|
33
|
+
Python tree carries no numeric-keyed hex tables of the kind that rotted in
|
|
34
|
+
the Node encoders.
|
|
35
|
+
|
|
36
|
+
PR #166's discarded-return guard fired on this release's own
|
|
37
|
+
StopRendering call — third catch in three releases; the allowlist entry
|
|
38
|
+
records that the helper verifies by polling the flag, stronger than the None
|
|
39
|
+
the API returns.
|
|
40
|
+
|
|
5
41
|
## What's New in v2.104.4
|
|
6
42
|
|
|
7
43
|
Hardening pass over the classes the v2.104.2 batch exposed, live-verified on
|
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
|
-
> 本翻译对应 v2.104.
|
|
15
|
+
> 本翻译对应 v2.104.5 版 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 = "2.104.
|
|
40
|
+
VERSION = "2.104.5"
|
|
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 = "2.104.
|
|
90
|
+
VERSION = "2.104.5"
|
|
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
|
@@ -11,7 +11,7 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 353-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.104.
|
|
14
|
+
VERSION = "2.104.5"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -17630,7 +17630,11 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
17630
17630
|
create(name, media_location_path?) -> {success, name}
|
|
17631
17631
|
load(name) -> {success}
|
|
17632
17632
|
save() -> {success}
|
|
17633
|
-
close() -> {success}
|
|
17633
|
+
close(stop_render?) -> {success}
|
|
17634
|
+
Refuses while a render is in progress — closing mid-render orphans the
|
|
17635
|
+
render and wedges Resolve's pipeline until restart (stuck
|
|
17636
|
+
IsRenderingInProgress, 0% jobs, refused Quit). stop_render=true stops
|
|
17637
|
+
the render, waits for the flag to clear, then closes.
|
|
17634
17638
|
delete(name) -> {success}
|
|
17635
17639
|
import_project(path, name?) -> {success}
|
|
17636
17640
|
export_project(name, path, with_stills_and_luts?) -> {success}
|
|
@@ -17734,7 +17738,33 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
17734
17738
|
return {"success": bool(pm.SaveProject())}
|
|
17735
17739
|
elif action == "close":
|
|
17736
17740
|
proj = pm.GetCurrentProject()
|
|
17737
|
-
|
|
17741
|
+
if not proj:
|
|
17742
|
+
return _err("No project open")
|
|
17743
|
+
# Closing mid-render orphans the render and wedges Resolve's pipeline
|
|
17744
|
+
# (api_truth IsRenderingInProgress entry, measured live). Refuse by
|
|
17745
|
+
# default; stop_render=true stops the render and waits before closing.
|
|
17746
|
+
try:
|
|
17747
|
+
rendering = bool(proj.IsRenderingInProgress())
|
|
17748
|
+
except Exception:
|
|
17749
|
+
rendering = False
|
|
17750
|
+
if rendering:
|
|
17751
|
+
if not p.get("stop_render"):
|
|
17752
|
+
return _err(
|
|
17753
|
+
"A render is in progress on this project. Closing now would "
|
|
17754
|
+
"orphan the render and wedge Resolve's render pipeline — the "
|
|
17755
|
+
"stuck IsRenderingInProgress flag blocks every later render "
|
|
17756
|
+
"and refuses Quit until Resolve is restarted.",
|
|
17757
|
+
category="invalid_input",
|
|
17758
|
+
remediation=(
|
|
17759
|
+
"Wait for the render (poll render.get_job_status), or pass "
|
|
17760
|
+
"stop_render=true to stop it and close once the flag clears."
|
|
17761
|
+
),
|
|
17762
|
+
)
|
|
17763
|
+
from src.utils.project_cleanup import stop_render_before_close
|
|
17764
|
+
render_state = stop_render_before_close(proj)
|
|
17765
|
+
if not render_state.get("safe"):
|
|
17766
|
+
return _err(render_state["detail"], category="api_error")
|
|
17767
|
+
return {"success": bool(pm.CloseProject(proj))}
|
|
17738
17768
|
elif action == "delete":
|
|
17739
17769
|
if not p.get("name"):
|
|
17740
17770
|
return _err("delete requires name")
|
|
@@ -504,7 +504,9 @@ def _probe_render_to_disk(ctx):
|
|
|
504
504
|
job = ctx.project.AddRenderJob()
|
|
505
505
|
if not job:
|
|
506
506
|
return False
|
|
507
|
-
|
|
507
|
+
# Positional: the free-edition bridge proxies Resolve calls positionally,
|
|
508
|
+
# and a keyword argument dies inside _BoundMethod (PR #165's bug class).
|
|
509
|
+
ctx.project.StartRendering(job, False)
|
|
508
510
|
deadline = _t.monotonic() + 420
|
|
509
511
|
while ctx.project.IsRenderingInProgress() and _t.monotonic() < deadline:
|
|
510
512
|
_t.sleep(1)
|
|
@@ -64,6 +64,52 @@ def save_project_if_safe(pm: Any) -> Dict[str, Any]:
|
|
|
64
64
|
return {"saved": False, "skipped": False, "project": name, "reason": repr(exc)}
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def stop_render_before_close(project: Any, *, wait_seconds: float = 8.0) -> Dict[str, Any]:
|
|
68
|
+
"""Stop any in-progress render and wait for the flag to clear.
|
|
69
|
+
|
|
70
|
+
Closing or deleting a project while its render runs orphans the render and
|
|
71
|
+
wedges Resolve's whole pipeline: IsRenderingInProgress sticks True on every
|
|
72
|
+
subsequent project, new jobs sit at 0%, StartRendering starts returning
|
|
73
|
+
False, and Quit() is refused behind a modal (api_truth, measured live on
|
|
74
|
+
Studio 19.1.3.7). Returns {safe, was_rendering, waited_seconds, detail}.
|
|
75
|
+
A flag that stays True after StopRendering at idle is the stuck state —
|
|
76
|
+
only restarting Resolve clears it, so `safe` comes back False.
|
|
77
|
+
"""
|
|
78
|
+
import time as _time
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
rendering = bool(project.IsRenderingInProgress())
|
|
82
|
+
except Exception:
|
|
83
|
+
return {"safe": True, "was_rendering": False, "waited_seconds": 0.0,
|
|
84
|
+
"detail": "IsRenderingInProgress unavailable; proceeding"}
|
|
85
|
+
if not rendering:
|
|
86
|
+
return {"safe": True, "was_rendering": False, "waited_seconds": 0.0, "detail": ""}
|
|
87
|
+
try:
|
|
88
|
+
project.StopRendering()
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
waited = 0.0
|
|
92
|
+
while waited < wait_seconds:
|
|
93
|
+
_time.sleep(0.5)
|
|
94
|
+
waited += 0.5
|
|
95
|
+
try:
|
|
96
|
+
if not project.IsRenderingInProgress():
|
|
97
|
+
return {"safe": True, "was_rendering": True,
|
|
98
|
+
"waited_seconds": waited, "detail": "render stopped"}
|
|
99
|
+
except Exception:
|
|
100
|
+
break
|
|
101
|
+
return {
|
|
102
|
+
"safe": False, "was_rendering": True, "waited_seconds": waited,
|
|
103
|
+
"detail": (
|
|
104
|
+
"IsRenderingInProgress is still True after StopRendering and "
|
|
105
|
+
f"{waited:.0f}s — closing now would orphan the render and wedge "
|
|
106
|
+
"Resolve's render pipeline (stuck flag, 0% jobs, refused Quit). "
|
|
107
|
+
"If Resolve is idle at 0% CPU this is the already-stuck state and "
|
|
108
|
+
"only restarting Resolve clears it."
|
|
109
|
+
),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
67
113
|
def delete_project_safely(
|
|
68
114
|
pm: Any,
|
|
69
115
|
name: str,
|
|
@@ -89,6 +135,20 @@ def delete_project_safely(
|
|
|
89
135
|
except Exception:
|
|
90
136
|
current = None
|
|
91
137
|
if current == name:
|
|
138
|
+
# Deleting the current project kills its render anyway — stopping
|
|
139
|
+
# first is strictly better than orphaning it (the wedge documented
|
|
140
|
+
# in stop_render_before_close). Refuse only when the flag will not
|
|
141
|
+
# clear, which is the already-wedged state where the delete cannot
|
|
142
|
+
# end well either.
|
|
143
|
+
try:
|
|
144
|
+
project = pm.GetCurrentProject()
|
|
145
|
+
except Exception:
|
|
146
|
+
project = None
|
|
147
|
+
if project is not None:
|
|
148
|
+
render_state = stop_render_before_close(project)
|
|
149
|
+
if not render_state.get("safe"):
|
|
150
|
+
return {"success": False, "attempts": 0, "leftover": name,
|
|
151
|
+
"detail": render_state["detail"]}
|
|
92
152
|
# CloseProject ALWAYS, and first. It is what releases the session's
|
|
93
153
|
# lock on the project; switching away with LoadProject does not, and
|
|
94
154
|
# DeleteProject then returns False permanently — retries never help.
|