davinci-resolve-mcp 4.8.24 → 4.8.25
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 +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/scripts/audit_api_parity.py +7 -4
- package/src/granular/common.py +1 -1
- package/src/granular/resolve_control.py +25 -12
- package/src/server.py +1 -1
- package/src/utils/api_truth.py +28 -0
- package/src/utils/app_control.py +64 -59
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 v4.8.25 — open_settings and open_app_preferences say what Resolve cannot do
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **The granular `open_settings` and `open_app_preferences` tools could never
|
|
10
|
+
work, and reported that as an ordinary failure.** Both went through
|
|
11
|
+
`Resolve.GetUIManager()`, which does not exist. Measured on Studio 19.1.3.7:
|
|
12
|
+
`dir(resolve)` lists 23 methods and `GetUIManager` is not one of them;
|
|
13
|
+
`Fusion().UIManager` is real but has neither `OpenProjectSettings` nor
|
|
14
|
+
`OpenPreferences`; and none of the three names appears in the 21.1 typed API.
|
|
15
|
+
The call raised `'NoneType' object is not callable`, a broad `except`
|
|
16
|
+
swallowed it, an ERROR was logged, and the tool answered
|
|
17
|
+
`Failed to open Project Settings dialog` with no reason.
|
|
18
|
+
Both tools now answer `Not supported:` and name the call that is missing;
|
|
19
|
+
`open_settings` also names the tools that read and write project settings.
|
|
20
|
+
Nothing is logged as an error, because nothing went wrong.
|
|
21
|
+
- The route is now probed with `has_method` rather than `hasattr`, which is
|
|
22
|
+
true for every name on a Resolve object. If a future build does provide these
|
|
23
|
+
calls they are used, and their result is reported: the old code discarded the
|
|
24
|
+
return and answered success regardless, so a refusal would have read as a
|
|
25
|
+
dialog that opened.
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
- `api_truth` records the absence, with what `Fusion().UIManager` does expose.
|
|
30
|
+
Whether `UIManager.DoAction` or `QueueAction` can open these dialogs was not
|
|
31
|
+
tried: both dialogs are modal, and a modal dialog blocks the scripting API
|
|
32
|
+
until a person closes it.
|
|
33
|
+
- `scripts/audit_api_parity.py` no longer describes `GetUIManager` as a
|
|
34
|
+
documented API.
|
|
35
|
+
|
|
36
|
+
### Tests
|
|
37
|
+
|
|
38
|
+
- `tests/test_app_control_dialogs.py`: a fake that fabricates attributes the way
|
|
39
|
+
a Resolve object does (every `hasattr` true, a missing `getattr` is `None`).
|
|
40
|
+
Covers the measured build, a manager without the method, a build that has
|
|
41
|
+
the call, a refusal, an exception, and both granular tools. Eight of its
|
|
42
|
+
twelve tests fail against v4.8.24.
|
|
43
|
+
- `tests/test_discarded_resolve_returns.py` now covers every `Open*` call, not
|
|
44
|
+
only `OpenPage`.
|
|
45
|
+
|
|
5
46
|
## What's New in v4.8.24 — a frame capture leaves the render output folder and file name alone
|
|
6
47
|
|
|
7
48
|
### Fixed
|
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.8.
|
|
15
|
+
> 本翻译对应 v4.8.25 版 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.8.
|
|
40
|
+
VERSION = "4.8.25"
|
|
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
|
@@ -145,10 +145,13 @@ ALLOWLIST_UNDOCUMENTED: Set[str] = {
|
|
|
145
145
|
"AddKeyframe", "DeleteKeyframe", "ModifyKeyframe", "RemoveKeyFrame",
|
|
146
146
|
"GetKeyframeAtIndex", "GetKeyframeCount", "GetPropertyAtKeyframeIndex",
|
|
147
147
|
"SetKeyframeInterpolation", "Render", "StartUndo",
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
# Resolve app-control method, not in the scripting README this audit parses
|
|
149
|
+
"SetHighPriority",
|
|
150
|
+
# NOT a documented API: Resolve has no GetUIManager on any build measured
|
|
151
|
+
# (Studio 19.1.3.7; api_truth 'Resolve.GetUIManager ...'). It is called
|
|
152
|
+
# only behind has_method in src/utils/app_control.py, and from the unused
|
|
153
|
+
# helpers in src/utils/layout_presets.py along with the other two.
|
|
154
|
+
"GetUIManager", "LoadUILayout", "SaveUILayout",
|
|
152
155
|
# Lua-table iteration helper used as a fallback in object_inspection.py
|
|
153
156
|
"GetKeyList",
|
|
154
157
|
# Project metadata accessor used defensively (hasattr-guarded)
|
package/src/granular/common.py
CHANGED
|
@@ -93,7 +93,7 @@ if not logging.getLogger().handlers:
|
|
|
93
93
|
handlers=[logging.StreamHandler()],
|
|
94
94
|
)
|
|
95
95
|
|
|
96
|
-
VERSION = "4.8.
|
|
96
|
+
VERSION = "4.8.25"
|
|
97
97
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
98
98
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
99
99
|
logger.info(f"Detected platform: {get_platform()}")
|
|
@@ -371,32 +371,45 @@ def restart_app(wait_seconds: int = 5) -> str:
|
|
|
371
371
|
|
|
372
372
|
@mcp.tool()
|
|
373
373
|
def open_settings() -> str:
|
|
374
|
-
"""Open the Project Settings dialog in DaVinci Resolve.
|
|
374
|
+
"""Open the Project Settings dialog in DaVinci Resolve.
|
|
375
|
+
|
|
376
|
+
Not available on any Resolve build measured so far: the scripting API has
|
|
377
|
+
no call that opens this dialog (Studio 19.1.3.7 measured; absent from the
|
|
378
|
+
21.1 typed API). The reply says so by name instead of a bare failure. To
|
|
379
|
+
read or change project settings use get_project_settings,
|
|
380
|
+
get_project_setting and set_project_setting.
|
|
381
|
+
"""
|
|
375
382
|
resolve = get_resolve()
|
|
376
383
|
if resolve is None:
|
|
377
384
|
return "Error: Not connected to DaVinci Resolve"
|
|
378
|
-
|
|
385
|
+
|
|
379
386
|
result = open_project_settings(resolve)
|
|
380
|
-
|
|
381
|
-
if result:
|
|
387
|
+
if result["success"]:
|
|
382
388
|
return "Project Settings dialog opened successfully"
|
|
383
|
-
|
|
384
|
-
return
|
|
389
|
+
if not result["supported"]:
|
|
390
|
+
return (
|
|
391
|
+
result["message"]
|
|
392
|
+
+ " Use get_project_settings, get_project_setting and set_project_setting instead."
|
|
393
|
+
)
|
|
394
|
+
return result["message"]
|
|
385
395
|
|
|
386
396
|
|
|
387
397
|
@mcp.tool()
|
|
388
398
|
def open_app_preferences() -> str:
|
|
389
|
-
"""Open the Preferences dialog in DaVinci Resolve.
|
|
399
|
+
"""Open the Preferences dialog in DaVinci Resolve.
|
|
400
|
+
|
|
401
|
+
Not available on any Resolve build measured so far: the scripting API has
|
|
402
|
+
no call that opens this dialog (Studio 19.1.3.7 measured; absent from the
|
|
403
|
+
21.1 typed API). The reply says so by name instead of a bare failure.
|
|
404
|
+
"""
|
|
390
405
|
resolve = get_resolve()
|
|
391
406
|
if resolve is None:
|
|
392
407
|
return "Error: Not connected to DaVinci Resolve"
|
|
393
|
-
|
|
408
|
+
|
|
394
409
|
result = open_preferences(resolve)
|
|
395
|
-
|
|
396
|
-
if result:
|
|
410
|
+
if result["success"]:
|
|
397
411
|
return "Preferences dialog opened successfully"
|
|
398
|
-
|
|
399
|
-
return "Failed to open Preferences dialog"
|
|
412
|
+
return result["message"]
|
|
400
413
|
|
|
401
414
|
|
|
402
415
|
@mcp.tool()
|
package/src/server.py
CHANGED
package/src/utils/api_truth.py
CHANGED
|
@@ -2749,6 +2749,34 @@ API_TRUTH: List[Dict[str, Any]] = [
|
|
|
2749
2749
|
"verified_on": "DaVinci Resolve Studio 19.1.3.7",
|
|
2750
2750
|
"mitigation": ["_render_target_dir", "_playhead_frame_render"],
|
|
2751
2751
|
},
|
|
2752
|
+
{
|
|
2753
|
+
"symbol": "Resolve.GetUIManager / UIManager.OpenProjectSettings / OpenPreferences (do not exist)",
|
|
2754
|
+
"object": "Resolve",
|
|
2755
|
+
"reality": "No scripting call opens the Project Settings or Preferences "
|
|
2756
|
+
"dialog. Measured 2026-09-30 on Studio 19.1.3.7, direct "
|
|
2757
|
+
"connection: dir(resolve) lists 23 methods and GetUIManager "
|
|
2758
|
+
"is not among them (getattr returns None; hasattr says True, "
|
|
2759
|
+
"as it does for every name). Fusion().UIManager is a real "
|
|
2760
|
+
"object with 15 names — AddNotify, Comp, Composition, "
|
|
2761
|
+
"DoAction, FindWindow, FindWindows, GetData, GetEvent, GetID, "
|
|
2762
|
+
"GetReg, QueueAction, QueueEvent, RemoveNotify, SetData, "
|
|
2763
|
+
"TriggerEvent — and none of OpenProjectSettings, "
|
|
2764
|
+
"OpenPreferences, SaveUILayout or LoadUILayout. None of those "
|
|
2765
|
+
"names, nor GetUIManager, appears in the 21.1 typed API "
|
|
2766
|
+
"either. Code written against them calls None and raises "
|
|
2767
|
+
"\"'NoneType' object is not callable\"; wrapped in a broad "
|
|
2768
|
+
"except, that reads as an ordinary failure. Whether "
|
|
2769
|
+
"UIManager.DoAction or QueueAction can open these dialogs was "
|
|
2770
|
+
"not tried: both dialogs are modal, and a modal dialog blocks "
|
|
2771
|
+
"the scripting API until a person closes it.",
|
|
2772
|
+
"recommended": "Do not offer to open these dialogs. Read and write "
|
|
2773
|
+
"project settings through Project.GetSetting/SetSetting. "
|
|
2774
|
+
"UI layouts go through Resolve.SaveLayoutPreset / "
|
|
2775
|
+
"LoadLayoutPreset, which do exist. Probe with "
|
|
2776
|
+
"resolve_probe.has_method, never hasattr.",
|
|
2777
|
+
"tags": ["ui", "unsupported", "dialog"],
|
|
2778
|
+
"verified_on": "DaVinci Resolve Studio 19.1.3.7",
|
|
2779
|
+
},
|
|
2752
2780
|
{
|
|
2753
2781
|
"symbol": "ProjectManager.SaveProject",
|
|
2754
2782
|
"object": "ProjectManager",
|
package/src/utils/app_control.py
CHANGED
|
@@ -16,6 +16,8 @@ import platform
|
|
|
16
16
|
import subprocess
|
|
17
17
|
from typing import Dict, Any, Optional, Union, List
|
|
18
18
|
|
|
19
|
+
from src.utils.resolve_probe import has_method
|
|
20
|
+
|
|
19
21
|
# Configure logging
|
|
20
22
|
logger = logging.getLogger("davinci-resolve-mcp.app_control")
|
|
21
23
|
APP_CONTROL_TIMEOUT_SECONDS = 10
|
|
@@ -267,65 +269,68 @@ def restart_resolve_app(resolve_obj, wait_seconds: int = 5) -> bool:
|
|
|
267
269
|
logger.error(f"Error restarting DaVinci Resolve: {str(e)}")
|
|
268
270
|
return False
|
|
269
271
|
|
|
270
|
-
def
|
|
271
|
-
"""
|
|
272
|
-
Open the Project Settings dialog in DaVinci Resolve.
|
|
273
|
-
|
|
274
|
-
Args:
|
|
275
|
-
resolve_obj: DaVinci Resolve API object
|
|
276
|
-
|
|
277
|
-
Returns:
|
|
278
|
-
True if successful, False otherwise
|
|
279
|
-
"""
|
|
280
|
-
try:
|
|
281
|
-
# Check if UI Manager is available
|
|
282
|
-
ui_manager = resolve_obj.GetUIManager()
|
|
283
|
-
if not ui_manager:
|
|
284
|
-
logger.error("Failed to get UI Manager")
|
|
285
|
-
return False
|
|
286
|
-
|
|
287
|
-
# Open Project Settings dialog
|
|
288
|
-
if hasattr(ui_manager, 'OpenProjectSettings') and callable(getattr(ui_manager, 'OpenProjectSettings')):
|
|
289
|
-
ui_manager.OpenProjectSettings()
|
|
290
|
-
return True
|
|
291
|
-
|
|
292
|
-
# Alternative method - send keyboard shortcut based on platform
|
|
293
|
-
current_page = resolve_obj.GetCurrentPage()
|
|
294
|
-
|
|
295
|
-
# Ensure we're on a page that supports project settings
|
|
296
|
-
if current_page not in ['media', 'cut', 'edit', 'fusion', 'color', 'fairlight', 'deliver']:
|
|
297
|
-
logger.error(f"Can't open settings from page: {current_page}")
|
|
298
|
-
return False
|
|
299
|
-
|
|
300
|
-
return False # Keyboard shortcuts not implemented yet
|
|
301
|
-
except Exception as e:
|
|
302
|
-
logger.error(f"Error opening project settings: {str(e)}")
|
|
303
|
-
return False
|
|
272
|
+
def _open_dialog(resolve_obj, method_name: str, label: str) -> Dict[str, Any]:
|
|
273
|
+
"""Ask Resolve to open a dialog, and say what actually happened.
|
|
304
274
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
275
|
+
Returns {"success", "supported", "message"}.
|
|
276
|
+
|
|
277
|
+
The route this module has always used is Resolve.GetUIManager() and then a
|
|
278
|
+
method on the manager. Neither half exists on any build measured so far.
|
|
279
|
+
On Studio 19.1.3.7 (2026-09-30): dir(resolve) lists 23 methods and
|
|
280
|
+
GetUIManager is not one of them; Fusion().UIManager is real but offers
|
|
281
|
+
neither OpenProjectSettings nor OpenPreferences; and none of the three
|
|
282
|
+
names appears in the 21.1 typed API. So the old code raised "'NoneType'
|
|
283
|
+
object is not callable" on its first line, caught it, logged an error and
|
|
284
|
+
returned a bare False — a tool that could never work, reporting that as an
|
|
285
|
+
ordinary failure with no reason.
|
|
286
|
+
|
|
287
|
+
`hasattr` cannot make this distinction: it is True for every name on a
|
|
288
|
+
Resolve object (see src/utils/resolve_probe.py). `has_method` can, so the
|
|
289
|
+
route is probed with it, and a build that does grow these calls is used
|
|
290
|
+
and its answer reported instead of assumed.
|
|
314
291
|
"""
|
|
292
|
+
if not has_method(resolve_obj, "GetUIManager"):
|
|
293
|
+
return {
|
|
294
|
+
"success": False,
|
|
295
|
+
"supported": False,
|
|
296
|
+
"message": (
|
|
297
|
+
f"Not supported: DaVinci Resolve's scripting API has no call that opens the "
|
|
298
|
+
f"{label} dialog. Resolve.GetUIManager does not exist on this build."
|
|
299
|
+
),
|
|
300
|
+
}
|
|
301
|
+
ui_manager = resolve_obj.GetUIManager()
|
|
302
|
+
if not has_method(ui_manager, method_name):
|
|
303
|
+
return {
|
|
304
|
+
"success": False,
|
|
305
|
+
"supported": False,
|
|
306
|
+
"message": (
|
|
307
|
+
f"Not supported: DaVinci Resolve's scripting API has no call that opens the "
|
|
308
|
+
f"{label} dialog. UIManager.{method_name} does not exist on this build."
|
|
309
|
+
),
|
|
310
|
+
}
|
|
315
311
|
try:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
312
|
+
opened = getattr(ui_manager, method_name)()
|
|
313
|
+
except Exception as exc:
|
|
314
|
+
logger.error("UIManager.%s raised: %s", method_name, exc)
|
|
315
|
+
return {
|
|
316
|
+
"success": False,
|
|
317
|
+
"supported": True,
|
|
318
|
+
"message": f"Failed to open the {label} dialog: UIManager.{method_name} raised {exc}",
|
|
319
|
+
}
|
|
320
|
+
if opened is False:
|
|
321
|
+
return {
|
|
322
|
+
"success": False,
|
|
323
|
+
"supported": True,
|
|
324
|
+
"message": f"Failed to open the {label} dialog: UIManager.{method_name} returned False",
|
|
325
|
+
}
|
|
326
|
+
return {"success": True, "supported": True, "message": f"{label} dialog opened"}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def open_project_settings(resolve_obj) -> Dict[str, Any]:
|
|
330
|
+
"""Open the Project Settings dialog. See `_open_dialog` for the result shape."""
|
|
331
|
+
return _open_dialog(resolve_obj, "OpenProjectSettings", "Project Settings")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def open_preferences(resolve_obj) -> Dict[str, Any]:
|
|
335
|
+
"""Open the Preferences dialog. See `_open_dialog` for the result shape."""
|
|
336
|
+
return _open_dialog(resolve_obj, "OpenPreferences", "Preferences")
|