davinci-resolve-mcp 4.8.4 → 4.8.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 CHANGED
@@ -2,6 +2,90 @@
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.5 — the offline suite no longer reaches Resolve through the granular server
6
+
7
+ No tool or action changed. One runtime change outside the tests: importing
8
+ `src.granular` no longer connects to Resolve, and the granular launchers now
9
+ connect explicitly at startup instead.
10
+
11
+ ### Fixed
12
+
13
+ - **With Resolve open, the offline suite connected to it through the granular server.**
14
+ `src/granular/common.py` ran `import DaVinciResolveScript` and `connect_resolve()`
15
+ at import time, so any test that imported a granular module called
16
+ `scriptapp("Resolve")` on the real `fusionscript.so`. `tests/offline_guard.py`
17
+ swapped `_launch_resolve`, `get_resolve` and `resolve_is_running` on `src.server`
18
+ only. The granular `get_resolve()` still fell through to its own `_launch_resolve()`.
19
+ Whether the real module loaded at all came down to import order, because the test
20
+ modules that `sys.modules.setdefault()` a stub only win when they run first.
21
+ `tests/test_live_api.py`, which pytest collects, calls `scriptapp` whenever the
22
+ real module wins. The compound server leaked the same way: the execution-lifecycle
23
+ state provider calls `src.server._try_connect()` directly before tool calls, and
24
+ the guard never swapped that function. Measured with a full
25
+ `python -m unittest discover -s tests -t .` run of v4.8.4, with a tripwire
26
+ standing in for the native library. The test process called `scriptapp("Resolve")`
27
+ 1,165 times, 1,162 of them from `_get_resolve_lifecycle_state`. It also tried
28
+ once to `open` the application, through the granular `ResolveProxy` →
29
+ `get_resolve()` → `_launch_resolve()`. The same run on this release makes
30
+ neither call.
31
+ - Before it imports `src.server`, the guard installs a `sys.meta_path` finder that
32
+ answers `DaVinciResolveScript` and `fusionscript` with an empty stub. It is a
33
+ finder rather than a `sys.modules` entry, so a test that pops the module cannot
34
+ let the next import reach the real library. The stub has no `scriptapp`, so
35
+ `connect_resolve()` raises before its bridge fallback instead of falling
36
+ through to it.
37
+ - `_try_connect`, `_launch_resolve` and `get_resolve` in `src.granular.common`
38
+ are swapped the same way as the compound server's. The swap covers every
39
+ granular module that holds one: `src/granular/__init__.py` imports each tool
40
+ module, and each binds its own copy through `from src.granular.common import *`
41
+ before the guard can swap `common`. The originals stay reachable as
42
+ `_*_unpatched`.
43
+ - For the duration of the run, the in-app bridge client points at a config file
44
+ that does not exist. `DAVINCI_RESOLVE_BRIDGE=1` in a developer's shell therefore
45
+ cannot open a socket to a bridge running inside Resolve.
46
+
47
+ ### Changed
48
+
49
+ - **`src/granular/common.py` no longer connects at import.** It still imports
50
+ DaVinciResolveScript, with the same diagnostics when that fails. The connection
51
+ moved to `connect_at_startup()`, which `src/resolve_mcp_server.py` and
52
+ `src/server.py --full` call right after importing the package. Starting the
53
+ granular server behaves as before: it connects to a Resolve that is already
54
+ open, logs it, and never launches one. Launching stays with `get_resolve()` on
55
+ the first tool call. Code that only imports the package no longer talks to
56
+ Resolve.
57
+
58
+ ### Validation
59
+
60
+ - New `tests/test_offline_guard_granular.py` puts a module shaped like Blackmagic's
61
+ loader first on `sys.path` and checks that a path import would load it. It then
62
+ asserts that neither a fresh import of `src.granular.common` nor
63
+ `connect_at_startup()` ever executes that module. It also pins the stand-ins in
64
+ every granular module, the stub's missing `scriptapp`, and the bridge redirect.
65
+ With the finder disabled it fails with
66
+ `['imported DaVinciResolveScript', "scriptapp ('Resolve',)"]`.
67
+ `tests/test_0000_offline_bootstrap.py` now also asserts that the finder is in
68
+ place before `src.server` imports.
69
+ - Full suite, `python -m unittest discover -s tests -t .`: 3,749 tests. The
70
+ errors are the same 11 as on v4.8.4 in this environment (no `numpy` or
71
+ `requests` in the venv, plus `test_offline_fallback` and
72
+ `test_lut_file_controls`). pytest was not run locally.
73
+ - Not covered: the control-panel tests (`test_control_panel_ipv6_loopback` and
74
+ one in `test_open_control_panel`) start the real `src/analysis_dashboard.py`
75
+ as a child process. An in-process guard cannot reach a child, and the child
76
+ still calls `scriptapp("Resolve")` read-only: 5 calls from 3 children in the
77
+ run above, the same as on v4.8.4.
78
+ - **Live-validated** on Resolve Studio 21.1.0.14, with Resolve already open. Both
79
+ launchers log `Connected to DaVinci Resolve: DaVinci Resolve Studio 21.1.0.14` at
80
+ startup, and neither started anything:
81
+ - `src/resolve_mcp_server.py` logs it before `Starting DaVinci Resolve MCP Server
82
+ v4.8.5 (389 granular tools)`, where the import-time connect used to log it.
83
+ - `src/server.py --full` logs it right after the granular import, before
84
+ `Threaded tool dispatch installed for 389 tools`.
85
+ - Not measured: startup with Resolve closed. That path logs `Failed to get Resolve
86
+ object` and leaves launching to the first tool call. The offline suite was never
87
+ run against the live Resolve.
88
+
5
89
  ## What's New in v4.8.4 — a Fusion nest control is refused with the controls it folds named
6
90
 
7
91
  ### 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.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.8.5-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.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.8.5-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.4 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v4.8.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 = "4.8.4"
40
+ VERSION = "4.8.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "4.8.4",
3
+ "version": "4.8.5",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -93,7 +93,7 @@ if not logging.getLogger().handlers:
93
93
  handlers=[logging.StreamHandler()],
94
94
  )
95
95
 
96
- VERSION = "4.8.4"
96
+ VERSION = "4.8.5"
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()}")
@@ -297,16 +297,13 @@ _OPTIONAL_DEPENDENCY_CONTRACT = (
297
297
  "DaVinciResolveScript: always routed through connect_resolve(), which is None-tolerant"
298
298
  )
299
299
 
300
+ # Loading the module is not connecting. `scriptapp` is what reaches a running
301
+ # Resolve, and it waits for `connect_at_startup()` or the first `get_resolve()`.
302
+ # Connecting here made *importing* this module talk to whatever Resolve was
303
+ # open, including from the offline test suite, where any `import src.granular.*`
304
+ # connected before a single test could stop it.
300
305
  try:
301
306
  import DaVinciResolveScript as dvr_script # type: ignore
302
-
303
- resolve = connect_resolve(dvr_script)
304
- if resolve:
305
- logger.info(
306
- f"Connected to DaVinci Resolve: {resolve.GetProductName()} {resolve.GetVersionString()}"
307
- )
308
- else:
309
- logger.error("Failed to get Resolve object. Is DaVinci Resolve running?")
310
307
  except ImportError as exc:
311
308
  logger.error(f"Failed to import DaVinciResolveScript: {exc}")
312
309
  logger.error("Check that DaVinci Resolve is installed and running.")
@@ -314,10 +311,38 @@ except ImportError as exc:
314
311
  logger.error(f"RESOLVE_SCRIPT_LIB: {RESOLVE_LIB_PATH}")
315
312
  logger.error(f"RESOLVE_MODULES_PATH: {RESOLVE_MODULES_PATH}")
316
313
  logger.error(f"sys.path: {sys.path}")
317
- resolve = None
314
+ dvr_script = None
318
315
  except Exception as exc:
319
316
  logger.error(f"Unexpected error initializing Resolve: {exc}")
320
- resolve = None
317
+ dvr_script = None
318
+
319
+
320
+ def connect_at_startup():
321
+ """Connect to a running Resolve as the granular server starts, and log it.
322
+
323
+ This is the connection that used to run at import. The launchers
324
+ (`src/resolve_mcp_server.py` and `src/server.py --full`) call it right after
325
+ importing the package, so starting the server behaves as before: it
326
+ connects to a Resolve that is already open and never launches one. Launching
327
+ is still left to `get_resolve()` on the first tool call. A missing
328
+ DaVinciResolveScript was already reported above and skips the attempt, as
329
+ the import failure did before.
330
+ """
331
+ global resolve
332
+ if dvr_script is None:
333
+ return None
334
+ try:
335
+ resolve = connect_resolve(dvr_script)
336
+ if resolve:
337
+ logger.info(
338
+ f"Connected to DaVinci Resolve: {resolve.GetProductName()} {resolve.GetVersionString()}"
339
+ )
340
+ else:
341
+ logger.error("Failed to get Resolve object. Is DaVinci Resolve running?")
342
+ except Exception as exc:
343
+ logger.error(f"Unexpected error initializing Resolve: {exc}")
344
+ resolve = None
345
+ return resolve
321
346
 
322
347
 
323
348
  def _normalize_cdl(cdl):
@@ -26,13 +26,14 @@ if modules_path and modules_path not in sys.path:
26
26
  sys.path.append(modules_path)
27
27
 
28
28
  from src.granular import VERSION, mcp
29
- from src.granular.common import logger
29
+ from src.granular.common import connect_at_startup, logger
30
30
  from src.utils.mcp_stdio import run_fastmcp_stdio
31
31
  from src.utils.update_check import start_background_update_check
32
32
 
33
33
 
34
34
  if __name__ == "__main__":
35
35
  try:
36
+ connect_at_startup()
36
37
  start_background_update_check(VERSION, project_dir, logger)
37
38
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (389 granular tools)")
38
39
  run_fastmcp_stdio(mcp)
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.4"
14
+ VERSION = "4.8.5"
15
15
 
16
16
  import base64
17
17
  import os
@@ -33130,7 +33130,9 @@ if __name__ == "__main__":
33130
33130
  logger.info("Starting full 377-tool granular server...")
33131
33131
  sys.argv = [arg for arg in sys.argv if arg != "--full"]
33132
33132
  from src.granular import mcp as granular_mcp
33133
+ from src.granular.common import connect_at_startup
33133
33134
 
33135
+ connect_at_startup()
33134
33136
  _install_threaded_tool_dispatch(granular_mcp)
33135
33137
  run_fastmcp_stdio(granular_mcp)
33136
33138
  sys.exit(0)