davinci-resolve-mcp 2.69.1 → 2.69.3
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 +112 -0
- package/README.md +1 -1
- package/install.py +8 -3
- package/package.json +1 -1
- package/resolve-advanced/package.json +3 -3
- package/scripts/install_resolve_bridge.py +75 -5
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/platform.py +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,118 @@
|
|
|
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.69.3
|
|
6
|
+
|
|
7
|
+
Ships the v2.69.2 bundle, which never reached npm. Same fixes, plus the one that
|
|
8
|
+
stopped it publishing.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **The offline guard failed the publish workflow instead of guarding it.**
|
|
13
|
+
`tests/__init__.py` installs the guard before any test module loads, and the
|
|
14
|
+
guard imports `src.server` — which imports `anyio`. The publish workflow
|
|
15
|
+
installs only pyflakes, by design: the static drift guards are pure AST
|
|
16
|
+
readers that deliberately run without the runtime stack. So every module
|
|
17
|
+
argument raised at import, six of them turned into six `_FailedTest` errors,
|
|
18
|
+
and the npm publish failed with what looked like six broken guards rather than
|
|
19
|
+
one missing package.
|
|
20
|
+
|
|
21
|
+
This shipped in v2.69.1's tail (the guard moved under `unittest` after that
|
|
22
|
+
tag was cut), so v2.69.2 was the first release to run it and the first to fail.
|
|
23
|
+
|
|
24
|
+
A missing third-party package now skips the swap: if `src.server` cannot be
|
|
25
|
+
imported there is no live-Resolve entry point to neuter, so the guard is
|
|
26
|
+
vacuously satisfied. The skip is narrow on purpose — a `ModuleNotFoundError`
|
|
27
|
+
naming something under `src/`, or any non-import error, still propagates.
|
|
28
|
+
Swallowing those would leave the static guards silently vacuous, which is a
|
|
29
|
+
worse failure than the one being fixed since it fails open.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- `tests/test_offline_guard.py` — five tests, including the two that matter:
|
|
34
|
+
a broken import inside `src/` must still raise, and a non-import error must
|
|
35
|
+
never be swallowed. A skip that fails open would be indistinguishable from a
|
|
36
|
+
passing suite.
|
|
37
|
+
|
|
38
|
+
### Validation
|
|
39
|
+
|
|
40
|
+
- Offline suite 2247 → 2252. Reproduced the CI failure locally against an
|
|
41
|
+
interpreter without `anyio` (six errors before, clean after), rather than
|
|
42
|
+
inferring it from the workflow log.
|
|
43
|
+
|
|
44
|
+
## What's New in v2.69.2
|
|
45
|
+
|
|
46
|
+
Community bug-fix bundle: #100, #101 and #102, plus the installer half of #104.
|
|
47
|
+
The headline is that a fresh clone had stopped working entirely.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **A fresh install produced a server that could not start** (issue #103, PR
|
|
52
|
+
#101 by @Mastaish). `install.py` installed `mcp[cli]` with no upper bound, and
|
|
53
|
+
the MCP Python SDK published 2.0.0 — which restructured the package and
|
|
54
|
+
dropped `mcp.server.fastmcp`, the module `src/server.py` imports. Every clone
|
|
55
|
+
after that release died at import, surfacing to the user as nothing more
|
|
56
|
+
informative than "Server disconnected".
|
|
57
|
+
|
|
58
|
+
The contributed fix caps the SDK in `requirements.txt`, which `install.py`
|
|
59
|
+
installs second. That works, but the unpinned first call still downloads 2.x
|
|
60
|
+
and its `httpx2` tree before downgrading it, and the fix evaporates silently
|
|
61
|
+
if the install order ever changes — so the cap is now on the `pip install`
|
|
62
|
+
call as well. `McpSdkPinTest` guards both sites, reading the actual string
|
|
63
|
+
literals rather than matching text: `install.py` discusses `mcp[cli]` in prose
|
|
64
|
+
too, and a first cut of that guard was satisfied by a comment. It is
|
|
65
|
+
conditional on `server.py` still importing `mcp.server.fastmcp`, so it retires
|
|
66
|
+
itself when the server is ported to the 2.x layout instead of blocking it.
|
|
67
|
+
|
|
68
|
+
- **The Advanced (Node) suite was not running at all on Node 20+** (PR #102 by
|
|
69
|
+
@double2tea). `node --test` resolves a bare directory argument as a module
|
|
70
|
+
entry point on Node 20 and later, so the suite exited with `MODULE_NOT_FOUND`
|
|
71
|
+
at 4 failures and 0 passes — a shape that reads more like a broken checkout
|
|
72
|
+
than 731 skipped tests. Explicit test-file globs restore the full run on 18,
|
|
73
|
+
20 and 22 alike.
|
|
74
|
+
|
|
75
|
+
- **`RESOLVE_SCRIPT_API` / `RESOLVE_SCRIPT_LIB` were silently overwritten** (PR
|
|
76
|
+
#100 by @abbc400). `get_resolve_paths()` returned only platform defaults, and
|
|
77
|
+
`src/server.py` writes those straight back into `os.environ` — so a client
|
|
78
|
+
that had correctly pointed at a Resolve installed outside `/Applications` had
|
|
79
|
+
its setting discarded at import. Every other signal looked healthy (Resolve
|
|
80
|
+
running, external scripting Local, `fuscript` listening) while `scriptapp()`
|
|
81
|
+
returned `None`. The override now wins, but only when the path exists, so a
|
|
82
|
+
stale variable cannot shadow a working default install.
|
|
83
|
+
|
|
84
|
+
- **The bridge installer wrote to a tree Resolve did not read** (issue #104,
|
|
85
|
+
reported by @RananjayRaj). Which script tree the free App Store build scans is
|
|
86
|
+
not decidable from outside Resolve: this installer was measured on a machine
|
|
87
|
+
where the documented Blackmagic Design tree listed, and #104 reports the exact
|
|
88
|
+
opposite on the same 21.0.3.7 build — the documented tree listed nothing, not
|
|
89
|
+
even the Lua canary (which rules out the framework-Python explanation and
|
|
90
|
+
points at the folder), while the Fusion standalone tree listed everything with
|
|
91
|
+
no restart.
|
|
92
|
+
|
|
93
|
+
Rather than pick a winner from two contradictory measurements, both container
|
|
94
|
+
trees now receive the files, documented path first. This stays inside the
|
|
95
|
+
sandbox: outside a container that path really is Fusion's own tree and is
|
|
96
|
+
still not targeted.
|
|
97
|
+
|
|
98
|
+
- **`--probe-only` reported success on a machine with no Resolve installed**
|
|
99
|
+
(also #104). A container outlives the app that created it, and the container's
|
|
100
|
+
existence is precisely what makes the installer target it — so a stale
|
|
101
|
+
container from an uninstalled Resolve produced a clean success listing files
|
|
102
|
+
that had genuinely been written and would never be read. The installer now
|
|
103
|
+
warns when no app bundle can be found, honoring `RESOLVE_APP` as
|
|
104
|
+
`scripts/doctor.py` already does. It warns rather than refuses: not every
|
|
105
|
+
legitimate install location can be enumerated, and refusing wrongly would
|
|
106
|
+
block a working install.
|
|
107
|
+
|
|
108
|
+
### Validation
|
|
109
|
+
|
|
110
|
+
- Offline suite 2237 → 2247 (six new installer-target tests, one pin guard).
|
|
111
|
+
Advanced Node suite 731 tests / 701 pass on Node 18.
|
|
112
|
+
- The new guards were verified to fail against the pre-fix code, not merely to
|
|
113
|
+
pass against the fix.
|
|
114
|
+
- No DaVinci Resolve scripting behavior changed; the path-resolution change is
|
|
115
|
+
env-var-gated with defaults unchanged. Live Resolve validation not required.
|
|
116
|
+
|
|
5
117
|
## What's New in v2.69.1
|
|
6
118
|
|
|
7
119
|
Bug fix. The Studio bridge differential no longer reports the Deliver page its
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
package/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.69.
|
|
39
|
+
VERSION = "2.69.3"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
|
@@ -796,9 +796,14 @@ def install_dependencies(venv_path, project_dir):
|
|
|
796
796
|
|
|
797
797
|
print(f" Installing dependencies...")
|
|
798
798
|
|
|
799
|
-
# Install MCP SDK
|
|
799
|
+
# Install MCP SDK. The upper bound is load-bearing: SDK 2.0.0 restructured
|
|
800
|
+
# the package and dropped `mcp.server.fastmcp`, which src/server.py imports.
|
|
801
|
+
# requirements.txt carries the same constraint, but it is installed second —
|
|
802
|
+
# pinning here too means we never download 2.x (and its httpx2 tree) only to
|
|
803
|
+
# downgrade it, and the fix does not depend on install ordering. Lift both
|
|
804
|
+
# together when server.py is ported to the 2.x layout.
|
|
800
805
|
subprocess.run(
|
|
801
|
-
[str(pip), "install", "-q", "mcp[cli]"],
|
|
806
|
+
[str(pip), "install", "-q", "mcp[cli]>=1.29,<2"],
|
|
802
807
|
check=True, capture_output=True
|
|
803
808
|
)
|
|
804
809
|
|
package/package.json
CHANGED
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"_comment": "audio (split/trim/convert) + conform frame ops need system ffmpeg/ffprobe on PATH (NOT bundled — FFmpeg binaries are GPL; this package stays MIT). Install ffmpeg yourself: brew install ffmpeg."
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node --test vendor/drp-format/__tests__
|
|
42
|
-
"test:libs": "node --test vendor/drp-format/__tests__
|
|
43
|
-
"test:server": "node --test test
|
|
41
|
+
"test": "node --test vendor/drp-format/__tests__/*.test.js vendor/drt-format/__tests__/*.test.js vendor/drx-codec/__tests__/*.test.js test/*.test.mjs",
|
|
42
|
+
"test:libs": "node --test vendor/drp-format/__tests__/*.test.js vendor/drt-format/__tests__/*.test.js vendor/drx-codec/__tests__/*.test.js",
|
|
43
|
+
"test:server": "node --test test/*.test.mjs"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=18.0.0"
|
|
@@ -53,11 +53,26 @@ _SCRIPTS_SUFFIX = "Library/Application Support/Blackmagic Design/DaVinci Resolve
|
|
|
53
53
|
#: `<container>/Data/Library/Application Support/Fusion/Scripts/Utility` appeared
|
|
54
54
|
#: under Workspace ▸ Scripts, as did one in
|
|
55
55
|
#: `<container>/Data/Documents/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Utility`.
|
|
56
|
-
#: So Lite scans more locations than the README documents.
|
|
57
|
-
#:
|
|
58
|
-
#:
|
|
59
|
-
#:
|
|
56
|
+
#: So Lite scans more locations than the README documents.
|
|
57
|
+
#:
|
|
58
|
+
#: **Second correction (issue #104), same version, opposite result:** on another
|
|
59
|
+
#: free 21.0.3.7 App Store install the documented tree listed *nothing* — not
|
|
60
|
+
#: even the Lua canary, which rules out the Python-detection explanation and
|
|
61
|
+
#: points at the folder — while the Fusion standalone tree listed all four files
|
|
62
|
+
#: with no restart. Two machines, same build, contradictory results.
|
|
63
|
+
#:
|
|
64
|
+
#: We cannot tell them apart from outside Resolve, so we stop trying to pick a
|
|
65
|
+
#: winner and install to BOTH container trees. The documented path stays first
|
|
66
|
+
#: (it is the one Blackmagic commits to), the standalone tree is the fallback
|
|
67
|
+
#: that demonstrably works where the documented one silently does not. The cost
|
|
68
|
+
#: is one extra App Management prompt; the cost of guessing wrong is a bridge
|
|
69
|
+
#: that never appears and gives the user nothing to diagnose.
|
|
60
70
|
_SANDBOX_MARKER = "com.blackmagic-design."
|
|
71
|
+
|
|
72
|
+
#: Scanned by Lite in addition to `_SCRIPTS_SUFFIX`, inside the container only.
|
|
73
|
+
#: NOT used outside a sandbox: on a normal install this is genuinely Fusion's
|
|
74
|
+
#: own tree and has nothing to do with Resolve.
|
|
75
|
+
_SANDBOX_FALLBACK_SUFFIX = "Library/Application Support/Fusion/Scripts/Utility"
|
|
61
76
|
#: Containers that are not Resolve (RAW Player, Speed Test, the IO XPC helper).
|
|
62
77
|
_NON_RESOLVE_CONTAINERS = ("BlackmagicRaw", "IOXPC")
|
|
63
78
|
|
|
@@ -73,7 +88,9 @@ def script_targets() -> list[Path]:
|
|
|
73
88
|
|
|
74
89
|
Sandboxed containers are included because the App Store build cannot see the
|
|
75
90
|
normal per-user path — that isolation is exactly what lets the free edition
|
|
76
|
-
coexist with a direct-download Studio install.
|
|
91
|
+
coexist with a direct-download Studio install. Each container contributes
|
|
92
|
+
TWO targets (see the module comment on issue #104): the documented tree
|
|
93
|
+
first, then the Fusion standalone tree that Lite also scans.
|
|
77
94
|
"""
|
|
78
95
|
home = Path.home()
|
|
79
96
|
candidates = [
|
|
@@ -87,6 +104,7 @@ def script_targets() -> list[Path]:
|
|
|
87
104
|
for entry in sorted(containers.iterdir()):
|
|
88
105
|
if _is_resolve_container(entry) and (entry / "Data").is_dir():
|
|
89
106
|
sandboxed.append(entry / "Data" / _SCRIPTS_SUFFIX)
|
|
107
|
+
sandboxed.append(entry / "Data" / _SANDBOX_FALLBACK_SUFFIX)
|
|
90
108
|
|
|
91
109
|
usable: list[Path] = []
|
|
92
110
|
for path in candidates:
|
|
@@ -98,6 +116,51 @@ def script_targets() -> list[Path]:
|
|
|
98
116
|
return list(dict.fromkeys(usable))
|
|
99
117
|
|
|
100
118
|
|
|
119
|
+
#: Where a Resolve app bundle actually lives. The App Store build installs flat
|
|
120
|
+
#: into /Applications; the direct download uses its own folder. RESOLVE_APP
|
|
121
|
+
#: overrides both, matching scripts/doctor.py.
|
|
122
|
+
_APP_BUNDLE_CANDIDATES = (
|
|
123
|
+
"/Applications/DaVinci Resolve/DaVinci Resolve.app",
|
|
124
|
+
"/Applications/DaVinci Resolve.app",
|
|
125
|
+
"/Applications/DaVinci Resolve Studio.app",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def installed_app_bundles() -> list[str]:
|
|
130
|
+
"""Resolve app bundles present on this machine."""
|
|
131
|
+
override = os.environ.get("RESOLVE_APP")
|
|
132
|
+
candidates = (override,) + _APP_BUNDLE_CANDIDATES if override else _APP_BUNDLE_CANDIDATES
|
|
133
|
+
return [path for path in candidates if path and Path(path).is_dir()]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def stale_container_warning(targets: list[Path]) -> str | None:
|
|
137
|
+
"""Are we about to install into a container left by an *uninstalled* Resolve?
|
|
138
|
+
|
|
139
|
+
A container outlives the app that created it — uninstalling Resolve leaves
|
|
140
|
+
the whole `~/Library/Containers/com.blackmagic-design.*` tree behind. Since
|
|
141
|
+
a container's existence is what makes us target it, the installer would
|
|
142
|
+
otherwise report a clean success on a machine with no Resolve at all, and
|
|
143
|
+
the user would go hunting through the Scripts menu of an app they do not
|
|
144
|
+
have (issue #104). Warn rather than refuse: we cannot enumerate every place
|
|
145
|
+
an app bundle might legitimately live, and being wrong in the refusing
|
|
146
|
+
direction blocks a working install.
|
|
147
|
+
"""
|
|
148
|
+
if not any("Containers" in str(target) for target in targets):
|
|
149
|
+
return None
|
|
150
|
+
if installed_app_bundles():
|
|
151
|
+
return None
|
|
152
|
+
return (
|
|
153
|
+
"Installed into a sandbox container, but no DaVinci Resolve app bundle "
|
|
154
|
+
"was found on this machine. A container OUTLIVES the app that created "
|
|
155
|
+
"it, so this is what an uninstalled Resolve looks like — the files were "
|
|
156
|
+
"written, but nothing will ever read them. Checked: "
|
|
157
|
+
+ ", ".join(_APP_BUNDLE_CANDIDATES)
|
|
158
|
+
+ ". If Resolve is installed somewhere else, re-run with RESOLVE_APP "
|
|
159
|
+
"set to its .app bundle to silence this. If it is not installed, "
|
|
160
|
+
"install it first, then re-run."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
101
164
|
#: Resolve enumerates `.py` scripts in Workspace > Scripts only when it finds a
|
|
102
165
|
#: **framework** Python. Homebrew/pyenv/conda interpreters are not detected, and
|
|
103
166
|
#: the failure is completely silent: the script sits in the right folder with the
|
|
@@ -233,6 +296,8 @@ def install(*, probe_only: bool, port: int, rotate: bool) -> dict:
|
|
|
233
296
|
launcher_path.write_text(launcher, encoding="utf-8")
|
|
234
297
|
installed.append(str(launcher_path))
|
|
235
298
|
result = {"installed": installed, "probe_only": probe_only, "python": python_preflight()}
|
|
299
|
+
stale = stale_container_warning(targets)
|
|
300
|
+
result["warnings"] = [stale] if stale else []
|
|
236
301
|
if not probe_only:
|
|
237
302
|
loaded = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
238
303
|
result["config"] = {"path": str(CONFIG_PATH),
|
|
@@ -258,6 +323,11 @@ def main() -> int:
|
|
|
258
323
|
print("WARNING: " + result["python"]["advice"])
|
|
259
324
|
print("!" * 72)
|
|
260
325
|
print()
|
|
326
|
+
for warning in result["warnings"]:
|
|
327
|
+
print("!" * 72)
|
|
328
|
+
print("WARNING: " + warning)
|
|
329
|
+
print("!" * 72)
|
|
330
|
+
print()
|
|
261
331
|
print("Next:")
|
|
262
332
|
print(" 1. Restart DaVinci Resolve so it re-scans the Scripts folders.")
|
|
263
333
|
print(" 2. Open a saved project (the Scripts menu is empty in Project Manager).")
|
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.69.
|
|
88
|
+
VERSION = "2.69.3"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
package/src/utils/platform.py
CHANGED
|
@@ -55,6 +55,16 @@ def get_resolve_paths():
|
|
|
55
55
|
lib_path = "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so"
|
|
56
56
|
modules_path = os.path.join(api_path, "Modules")
|
|
57
57
|
|
|
58
|
+
# An explicit install location wins over the platform default: Resolve is
|
|
59
|
+
# not always under /Applications (external volume, custom install dir).
|
|
60
|
+
env_api = os.environ.get("RESOLVE_SCRIPT_API")
|
|
61
|
+
if env_api and os.path.isdir(env_api):
|
|
62
|
+
api_path = env_api
|
|
63
|
+
modules_path = os.path.join(api_path, "Modules")
|
|
64
|
+
env_lib = os.environ.get("RESOLVE_SCRIPT_LIB")
|
|
65
|
+
if env_lib and os.path.isfile(env_lib):
|
|
66
|
+
lib_path = env_lib
|
|
67
|
+
|
|
58
68
|
return {
|
|
59
69
|
"api_path": api_path,
|
|
60
70
|
"lib_path": lib_path,
|