davinci-resolve-mcp 4.8.24 → 4.8.26

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 CHANGED
@@ -2,6 +2,67 @@
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.26 — two dead layout-preset helpers removed
6
+
7
+ ### Removed
8
+
9
+ - `src/utils/layout_presets.py` no longer carries `save_layout_preset` and
10
+ `load_layout_preset`. Both went through `Resolve.GetUIManager()` and then
11
+ `SaveUILayout` / `LoadUILayout`, none of which exist on any build measured
12
+ (Studio 19.1.3.7; see the `api_truth` entry added in v4.8.25). Nothing called
13
+ them: the granular `save_layout_preset_tool` and `load_layout_preset_tool`
14
+ use `Resolve.SaveLayoutPreset` / `LoadLayoutPreset` directly, and still do.
15
+ No tool, action or count changes. The module docstring now says what the
16
+ file actually does (preset files on disk) and where live save/load happens.
17
+ - `scripts/audit_api_parity.py` drops `LoadUILayout` and `SaveUILayout` from
18
+ its allowlist, since no source calls them any more.
19
+
20
+ ### Validation
21
+
22
+ - No Resolve behavior changed; the removed functions had no callers. Live test
23
+ not required.
24
+
25
+ ## What's New in v4.8.25 — open_settings and open_app_preferences say what Resolve cannot do
26
+
27
+ ### Fixed
28
+
29
+ - **The granular `open_settings` and `open_app_preferences` tools could never
30
+ work, and reported that as an ordinary failure.** Both went through
31
+ `Resolve.GetUIManager()`, which does not exist. Measured on Studio 19.1.3.7:
32
+ `dir(resolve)` lists 23 methods and `GetUIManager` is not one of them;
33
+ `Fusion().UIManager` is real but has neither `OpenProjectSettings` nor
34
+ `OpenPreferences`; and none of the three names appears in the 21.1 typed API.
35
+ The call raised `'NoneType' object is not callable`, a broad `except`
36
+ swallowed it, an ERROR was logged, and the tool answered
37
+ `Failed to open Project Settings dialog` with no reason.
38
+ Both tools now answer `Not supported:` and name the call that is missing;
39
+ `open_settings` also names the tools that read and write project settings.
40
+ Nothing is logged as an error, because nothing went wrong.
41
+ - The route is now probed with `has_method` rather than `hasattr`, which is
42
+ true for every name on a Resolve object. If a future build does provide these
43
+ calls they are used, and their result is reported: the old code discarded the
44
+ return and answered success regardless, so a refusal would have read as a
45
+ dialog that opened.
46
+
47
+ ### Documentation
48
+
49
+ - `api_truth` records the absence, with what `Fusion().UIManager` does expose.
50
+ Whether `UIManager.DoAction` or `QueueAction` can open these dialogs was not
51
+ tried: both dialogs are modal, and a modal dialog blocks the scripting API
52
+ until a person closes it.
53
+ - `scripts/audit_api_parity.py` no longer describes `GetUIManager` as a
54
+ documented API.
55
+
56
+ ### Tests
57
+
58
+ - `tests/test_app_control_dialogs.py`: a fake that fabricates attributes the way
59
+ a Resolve object does (every `hasattr` true, a missing `getattr` is `None`).
60
+ Covers the measured build, a manager without the method, a build that has
61
+ the call, a refusal, an exception, and both granular tools. Eight of its
62
+ twelve tests fail against v4.8.24.
63
+ - `tests/test_discarded_resolve_returns.py` now covers every `Open*` call, not
64
+ only `OpenPage`.
65
+
5
66
  ## What's New in v4.8.24 — a frame capture leaves the render output folder and file name alone
6
67
 
7
68
  ### Fixed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-4.8.24-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.8.26-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-37%20(389%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-4.8.24-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.8.26-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-37%20(389%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v4.8.24 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v4.8.26 版 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.24"
40
+ VERSION = "4.8.26"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "4.8.24",
3
+ "version": "4.8.26",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -145,10 +145,12 @@ ALLOWLIST_UNDOCUMENTED: Set[str] = {
145
145
  "AddKeyframe", "DeleteKeyframe", "ModifyKeyframe", "RemoveKeyFrame",
146
146
  "GetKeyframeAtIndex", "GetKeyframeCount", "GetPropertyAtKeyframeIndex",
147
147
  "SetKeyframeInterpolation", "Render", "StartUndo",
148
- # UIManager / Resolve app-control API (documented under UIManager, not
149
- # the main Resolve scripting README)
150
- "GetUIManager", "OpenPreferences", "SetHighPriority",
151
- "OpenProjectSettings", "LoadUILayout", "SaveUILayout",
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 ...'). Called only
152
+ # behind has_method in src/utils/app_control.py.
153
+ "GetUIManager",
152
154
  # Lua-table iteration helper used as a fallback in object_inspection.py
153
155
  "GetKeyList",
154
156
  # Project metadata accessor used defensively (hasattr-guarded)
@@ -45,8 +45,6 @@ from src.utils.layout_presets import (
45
45
  export_layout_preset,
46
46
  import_layout_preset,
47
47
  list_layout_presets,
48
- load_layout_preset,
49
- save_layout_preset,
50
48
  )
51
49
  from src.utils.object_inspection import inspect_object, print_object_help
52
50
  from src.utils.platform import get_platform, get_resolve_paths
@@ -93,7 +91,7 @@ if not logging.getLogger().handlers:
93
91
  handlers=[logging.StreamHandler()],
94
92
  )
95
93
 
96
- VERSION = "4.8.24"
94
+ VERSION = "4.8.26"
97
95
  logger = logging.getLogger("davinci-resolve-mcp")
98
96
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
99
97
  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
- else:
384
- return "Failed to open Project Settings dialog"
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
- else:
399
- return "Failed to open Preferences dialog"
412
+ return result["message"]
400
413
 
401
414
 
402
415
  @mcp.tool()
package/src/server.py CHANGED
@@ -11,7 +11,7 @@ Usage:
11
11
  python src/server.py --full # Start the 377-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "4.8.24"
14
+ VERSION = "4.8.26"
15
15
 
16
16
  import base64
17
17
  import os
@@ -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",
@@ -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 open_project_settings(resolve_obj) -> bool:
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
- def open_preferences(resolve_obj) -> bool:
306
- """
307
- Open the Preferences dialog in DaVinci Resolve.
308
-
309
- Args:
310
- resolve_obj: DaVinci Resolve API object
311
-
312
- Returns:
313
- True if successful, False otherwise
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
- # Check if UI Manager is available
317
- ui_manager = resolve_obj.GetUIManager()
318
- if not ui_manager:
319
- logger.error("Failed to get UI Manager")
320
- return False
321
-
322
- # Open Preferences dialog
323
- if hasattr(ui_manager, 'OpenPreferences') and callable(getattr(ui_manager, 'OpenPreferences')):
324
- ui_manager.OpenPreferences()
325
- return True
326
-
327
- # Alternative method - send keyboard shortcut based on platform
328
- return False # Keyboard shortcuts not implemented yet
329
- except Exception as e:
330
- logger.error(f"Error opening preferences: {str(e)}")
331
- return False
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")
@@ -2,11 +2,17 @@
2
2
  """
3
3
  DaVinci Resolve MCP Server - Layout Presets Utilities
4
4
 
5
- This module provides functions for working with DaVinci Resolve UI layout presets:
6
- - Saving layout presets
7
- - Loading layout presets
5
+ This module works with DaVinci Resolve UI layout preset FILES on disk:
6
+ - Listing the presets in Resolve's preset folder
8
7
  - Exporting/importing preset files
9
- - Managing layout configurations
8
+ - Deleting a preset file
9
+
10
+ Saving and loading the live layout are not done here. They go through
11
+ Resolve.SaveLayoutPreset / LoadLayoutPreset, which the granular tools in
12
+ src/granular/resolve_control.py call directly. Two earlier helpers here tried
13
+ Resolve.GetUIManager().SaveUILayout / LoadUILayout instead; neither method
14
+ exists on any build measured (api_truth 'Resolve.GetUIManager ...'), nothing
15
+ called them, and they were removed in v4.8.26.
10
16
  """
11
17
 
12
18
  import os
@@ -137,71 +143,6 @@ def list_layout_presets(layout_type: str = "ui") -> List[Dict[str, Any]]:
137
143
 
138
144
  return presets
139
145
 
140
- def save_layout_preset(resolve_obj, preset_name: str, layout_type: str = "ui") -> bool:
141
- """
142
- Save the current layout as a preset.
143
-
144
- Args:
145
- resolve_obj: DaVinci Resolve API object
146
- preset_name: Name for the saved preset
147
- layout_type: Type of layout to save ('ui', 'window', 'workspace')
148
-
149
- Returns:
150
- True if successful, False otherwise
151
- """
152
- try:
153
- # Ensure preset name has no spaces or special characters
154
- safe_name = preset_name.replace(" ", "_").replace("/", "_").replace("\\", "_")
155
-
156
- # Different layout types have different save methods
157
- if layout_type.lower() == "ui":
158
- # For UI layouts, use the UI Manager
159
- ui_manager = resolve_obj.GetUIManager()
160
- if not ui_manager:
161
- logger.error("Failed to get UI Manager")
162
- return False
163
-
164
- # Save the current UI layout
165
- return ui_manager.SaveUILayout(safe_name)
166
- else:
167
- # Other layout types would be handled here
168
- logger.error(f"Unsupported layout type: {layout_type}")
169
- return False
170
- except Exception as e:
171
- logger.error(f"Error saving layout preset: {str(e)}")
172
- return False
173
-
174
- def load_layout_preset(resolve_obj, preset_name: str, layout_type: str = "ui") -> bool:
175
- """
176
- Load a layout preset.
177
-
178
- Args:
179
- resolve_obj: DaVinci Resolve API object
180
- preset_name: Name of the preset to load
181
- layout_type: Type of layout to load ('ui', 'window', 'workspace')
182
-
183
- Returns:
184
- True if successful, False otherwise
185
- """
186
- try:
187
- # Different layout types have different load methods
188
- if layout_type.lower() == "ui":
189
- # For UI layouts, use the UI Manager
190
- ui_manager = resolve_obj.GetUIManager()
191
- if not ui_manager:
192
- logger.error("Failed to get UI Manager")
193
- return False
194
-
195
- # Load the specified UI layout
196
- return ui_manager.LoadUILayout(preset_name)
197
- else:
198
- # Other layout types would be handled here
199
- logger.error(f"Unsupported layout type: {layout_type}")
200
- return False
201
- except Exception as e:
202
- logger.error(f"Error loading layout preset: {str(e)}")
203
- return False
204
-
205
146
  def export_layout_preset(preset_name: str, export_path: str, layout_type: str = "ui") -> bool:
206
147
  """
207
148
  Export a layout preset to a file.