davinci-resolve-mcp 2.103.1 → 2.103.2

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,63 @@
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.103.2
6
+
7
+ **A Windows setup that failed with nothing to read.** Reported in issue #158 by
8
+ @KMiNT21 on a machine carrying both Python 3.12 and 3.13: `npx davinci-resolve-mcp`
9
+ exited without a traceback, a log line, or an error. Three defects compounded.
10
+
11
+ The first is the one that mattered. The npm launcher tested for the Windows `py`
12
+ launcher with `py --version`, and that result gated the entire `py -3.12 / -3.11 /
13
+ -3.10` candidate list. `py` does not accept `--version` on every build — it exits
14
+ 101 on the ones it does not — so on those machines the probe reported no launcher,
15
+ every version-pinned candidate was discarded, and selection fell through to bare
16
+ `python`: the 3.13 that the candidate ordering exists specifically to avoid. The
17
+ 3.13 protections added in v2.26.1 were not wrong; they were being skipped past.
18
+
19
+ The fix removes the probe rather than correcting its flag. `checkPython()` already
20
+ validates each candidate by running it, so a machine without `py` costs one failed
21
+ spawn. A probe that can produce a false negative earns its place only if something
22
+ downstream cannot do without it, and nothing here needed it.
23
+
24
+ ### Fixed
25
+
26
+ - `bin/davinci-resolve-mcp.mjs` no longer gates the `py -3.x` candidates behind a
27
+ `py --version` probe.
28
+ - An access-violation exit is now explained instead of propagated bare. Windows
29
+ reports `STATUS_ACCESS_VIOLATION` as an exit code (`3221225477`, or `-1073741819`
30
+ read signed), not as a signal — the interpreter dies inside the native library
31
+ with no chance to print. Both the launcher and `install.py`'s connection probe now
32
+ name the code, say why there is no traceback, and give the remedy. Previously the
33
+ probe could only report `Process exited with code 3221225477`.
34
+ - `scripts/doctor.py` consults the runtime discovery helpers when every candidate
35
+ path misses, so Resolve installed off the conventional root (the reporter had it
36
+ on `D:`) is found rather than reported as four FAILs on a machine `install.py` had
37
+ just configured correctly. Same shape as issue #106.
38
+ - `scripts/doctor.py` no longer reports a client config as `missing` because of path
39
+ escaping. A Windows path written into JSON comes back with doubled separators, and
40
+ the literal substring test could never match it — a false negative in the tool
41
+ whose job is to say whether setup worked.
42
+
43
+ ### Not changed
44
+
45
+ Python 3.13 is still permitted. The policy set in v2.26.1 is a 3.10 floor with no
46
+ cap — warn, do not block — and issue #158 proposed enforcing 3.10-3.12 on Windows.
47
+ The candidate ordering already prefers the lower-risk interpreters; the bug was that
48
+ ordering being bypassed, which is now fixed.
49
+
50
+ ### Coverage and its limits
51
+
52
+ `tests/test_windows_python_crash.py` pins the launcher's candidate shape and the
53
+ crash-code translation; `tests/test_doctor_paths.py` gains the discovery and
54
+ path-escaping cases. All were confirmed to fail against the unfixed code.
55
+
56
+ What is **not** covered, and is not coverable from macOS: whether `py --version`
57
+ actually fails on any given Windows build. That claim comes from the reporter. The
58
+ fix does not rest on it — it removes the probe rather than correcting it, so the
59
+ code no longer has an opinion either way. The access-violation paths are likewise
60
+ tested by injecting the exit code, not by producing a real crash.
61
+
5
62
  ## What's New in v2.103.1
6
63
 
7
64
  **A loudness measurement could silently become a single frame's reading.**
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-2.103.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.103.2-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-36%20(353%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-2.103.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.103.2-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-36%20(353%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
- > 本翻译对应 v2.103.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.103.2 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -179,14 +179,6 @@ function syncManagedInstall(root) {
179
179
  return root;
180
180
  }
181
181
 
182
- function commandExists(command, args = []) {
183
- const result = spawnSync(command, [...args, "--version"], {
184
- encoding: "utf8",
185
- stdio: ["ignore", "pipe", "pipe"],
186
- });
187
- return result.status === 0;
188
- }
189
-
190
182
  function parseExecutable(value) {
191
183
  if (!value) {
192
184
  return null;
@@ -207,7 +199,14 @@ function pythonCandidates() {
207
199
  // Prefer the lowest-ABI-risk interpreters first, then newer ones, then the
208
200
  // generic launchers. All 3.10+ are accepted; ordering just picks the safest
209
201
  // when several are installed.
210
- if (process.platform === "win32" && commandExists("py")) {
202
+ // No existence probe in front of these. `py --version` is not a reliable
203
+ // one — the Windows launcher does not accept it on every build, and it
204
+ // exits 101 on the ones it does not (issue #158). A probe that gets that
205
+ // wrong discards every version-pinned candidate below and falls through to
206
+ // bare `python`, which is exactly the 3.13 the ordering exists to avoid.
207
+ // checkPython() runs each candidate anyway, so a missing `py` costs one
208
+ // failed spawn and is skipped.
209
+ if (process.platform === "win32") {
211
210
  candidates.push(
212
211
  { command: "py", args: ["-3.12"] },
213
212
  { command: "py", args: ["-3.11"] },
@@ -317,6 +316,24 @@ function venvPython(root) {
317
316
  return info;
318
317
  }
319
318
 
319
+ // Windows reports a hard access violation as the process exit code, not as a
320
+ // signal and not as a traceback: the interpreter is gone before it can say
321
+ // anything. Loading Resolve's fusionscript under a Python its C ABI does not
322
+ // match is one way to get there, so name that possibility rather than letting
323
+ // the run end in a bare unexplained code (issue #158).
324
+ const WINDOWS_ACCESS_VIOLATION = [3221225477, -1073741819];
325
+
326
+ function accessViolationNote(code) {
327
+ return [
328
+ `The Python process was terminated by an access violation (0x${(code >>> 0).toString(16).toUpperCase()}).`,
329
+ "It crashed inside a native library before it could report anything, so there is no traceback above.",
330
+ "The usual cause is Resolve's scripting library being loaded by a Python whose C ABI it was not built",
331
+ "against. If you are on Python 3.13+, install Python 3.10-3.12 and pin it:",
332
+ " DAVINCI_RESOLVE_MCP_PYTHON=C:\\Path\\To\\python3.12.exe",
333
+ "then re-run setup so the managed venv is rebuilt on that interpreter.",
334
+ ].join("\n");
335
+ }
336
+
320
337
  function run(command, args, options = {}) {
321
338
  const child = spawn(command, args, {
322
339
  cwd: options.cwd,
@@ -329,6 +346,9 @@ function run(command, args, options = {}) {
329
346
  process.kill(process.pid, signal);
330
347
  return;
331
348
  }
349
+ if (WINDOWS_ACCESS_VIOLATION.includes(code)) {
350
+ console.error(accessViolationNote(code));
351
+ }
332
352
  process.exit(code ?? 1);
333
353
  });
334
354
  child.on("error", (error) => {
package/docs/install.md CHANGED
@@ -86,6 +86,7 @@ The installer can automatically configure any of these clients:
86
86
 
87
87
  | Client | Config Written To |
88
88
  |--------|-------------------|
89
+ | Antigravity (Google) | `~/.gemini/antigravity/mcp_config.json` (all platforms) |
89
90
  | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS); `%APPDATA%\Claude\claude_desktop_config.json` (Windows, see MSIX note below) |
90
91
  | Claude Code | `.mcp.json` (project root) |
91
92
  | Cursor | `~/.cursor/mcp.json` |
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.103.1"
40
+ VERSION = "2.103.2"
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
@@ -1253,6 +1253,54 @@ def install_dependencies(venv_path, project_dir):
1253
1253
 
1254
1254
  # ─── Connection Verification ─────────────────────────────────────────────────
1255
1255
 
1256
+ #: Windows surfaces a hard access violation (STATUS_ACCESS_VIOLATION) as a
1257
+ #: process exit code. Both spellings appear in the wild: the unsigned value and
1258
+ #: the signed reading of the same 32 bits. The interpreter dies inside the
1259
+ #: native library, so there is no traceback and no stderr to report — without
1260
+ #: this translation the probe can only say "exited with code 3221225477",
1261
+ #: which is the shape issue #158 was reported as.
1262
+ WINDOWS_ACCESS_VIOLATION_CODES = (3221225477, -1073741819)
1263
+
1264
+
1265
+ def _version_parts(version):
1266
+ """A `(major, minor, ...)` int tuple, or None if it cannot be read.
1267
+
1268
+ `is_abi_risk_python_version` unpacks `version[:2]`, so handing it the string
1269
+ "3.13.3" yields `("3", ".")` and a quiet False — the interpreter most likely
1270
+ to have caused the crash would be reported as not at risk. Normalizing here
1271
+ means a caller cannot make that mistake by passing the obvious thing.
1272
+ """
1273
+ if version is None:
1274
+ return None
1275
+ if isinstance(version, str):
1276
+ try:
1277
+ return tuple(int(part) for part in version.split(".")[:3])
1278
+ except ValueError:
1279
+ return None
1280
+ try:
1281
+ return tuple(int(part) for part in tuple(version)[:3])
1282
+ except (TypeError, ValueError):
1283
+ return None
1284
+
1285
+
1286
+ def access_violation_message(returncode, version=None):
1287
+ """Explain an access-violation exit, naming the ABI theory when it fits."""
1288
+ text = (
1289
+ "Python crashed with an access violation (0xC0000005) while loading "
1290
+ "Resolve's scripting library — no traceback is possible, the process "
1291
+ "was terminated by the OS."
1292
+ )
1293
+ parts = _version_parts(version)
1294
+ if parts is None or is_abi_risk_python_version(parts):
1295
+ text += (
1296
+ " This is the signature of a C ABI mismatch: recreate the venv on "
1297
+ "Python 3.10-3.12 (e.g. DAVINCI_RESOLVE_MCP_PYTHON=...\\python3.12.exe)."
1298
+ )
1299
+ else:
1300
+ text += " Check that RESOLVE_SCRIPT_LIB names the library from your Resolve install."
1301
+ return text
1302
+
1303
+
1256
1304
  def verify_resolve_connection(python_path, api_path, lib_path):
1257
1305
  """Try to import DaVinciResolveScript and connect."""
1258
1306
  if not api_path:
@@ -1311,6 +1359,12 @@ def verify_resolve_connection(python_path, api_path, lib_path):
1311
1359
  elif output.startswith("IMPORTED_OK:"):
1312
1360
  return True, "API module loaded (Resolve not running)"
1313
1361
  else:
1362
+ if result.returncode in WINDOWS_ACCESS_VIOLATION_CODES:
1363
+ try:
1364
+ version = _version_for_python(python_path)
1365
+ except Exception:
1366
+ version = None
1367
+ return False, access_violation_message(result.returncode, version)
1314
1368
  if output:
1315
1369
  return False, output
1316
1370
  return False, f"Process exited with code {result.returncode}"
@@ -2219,7 +2273,10 @@ def main():
2219
2273
  " Point RESOLVE_SCRIPT_LIB at the fusionscript library inside your "
2220
2274
  "Resolve install, or start Resolve and re-run setup."
2221
2275
  )
2222
- if py_abi_risk:
2276
+ if py_abi_risk and "0xC0000005" not in message:
2277
+ # The access-violation message already carries this remedy, and
2278
+ # states it as a diagnosis rather than a maybe. Do not follow it
2279
+ # with a weaker restatement of itself.
2223
2280
  print(
2224
2281
  f" On Python 3.13+ this may be an ABI mismatch with Resolve's "
2225
2282
  f"scripting library — try Python 3.10-3.12 if it persists."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.103.1",
3
+ "version": "2.103.2",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
package/scripts/doctor.py CHANGED
@@ -95,13 +95,64 @@ def _platform_key(platform: str | None = None) -> str:
95
95
  return "linux"
96
96
 
97
97
 
98
+ def _discovered_default(kind: str) -> str | None:
99
+ """Ask the runtime helpers where Resolve actually is.
100
+
101
+ The candidate table above is a table of *conventional* locations. Resolve is
102
+ routinely installed somewhere else — a second drive is the common case,
103
+ because the application and its caches are large (issue #158 reported
104
+ `D:\\Programs\\DaVinci Resolve`). install.py already looks past the table
105
+ via these helpers; doctor did not, so it reported FAIL on installs the
106
+ installer had just configured correctly. That disagreement between two tools
107
+ describing one machine is the same failure shape as issue #106.
108
+
109
+ Only consulted when no candidate exists, and only for the two kinds the
110
+ helpers can answer. `api` has no discovery helper, so a non-default install
111
+ can still miss there — worth knowing when reading a report.
112
+ """
113
+ if str(REPO) not in sys.path:
114
+ # doctor is run standalone (`python scripts/doctor.py`) as often as it is
115
+ # run through the launcher, and only the latter puts the repo on the path.
116
+ sys.path.insert(0, str(REPO))
117
+
118
+ if kind == "lib":
119
+ try:
120
+ from src.utils.platform import discover_scripting_lib
121
+ except Exception:
122
+ return None
123
+ try:
124
+ discovered = discover_scripting_lib()
125
+ except Exception:
126
+ return None
127
+ return str(discovered) if discovered and Path(discovered).exists() else None
128
+
129
+ if kind == "app":
130
+ try:
131
+ from src.utils.resolve_runtime import _executable_from_line, resolve_processes
132
+ except Exception:
133
+ return None
134
+ try:
135
+ processes = resolve_processes() or []
136
+ except Exception:
137
+ return None
138
+ for line in processes:
139
+ executable = _executable_from_line(line)
140
+ if executable and Path(executable).exists():
141
+ return str(executable)
142
+ return None
143
+
144
+
98
145
  def _resolve_default(kind: str, platform: str | None = None) -> str:
99
- """First candidate of `kind` that exists, else the first (so the FAIL line
100
- names the canonical location rather than an arbitrary miss)."""
146
+ """First candidate of `kind` that exists, then discovery, else the first
147
+ candidate (so the FAIL line names the canonical location rather than an
148
+ arbitrary miss)."""
101
149
  candidates = _RESOLVE_PATH_CANDIDATES[_platform_key(platform)][kind]
102
150
  for candidate in candidates:
103
151
  if Path(candidate).exists():
104
152
  return candidate
153
+ discovered = _discovered_default(kind)
154
+ if discovered:
155
+ return discovered
105
156
  return candidates[0]
106
157
 
107
158
 
@@ -170,11 +221,27 @@ def check(results: list[dict[str, str]], status: str, name: str, detail: str) ->
170
221
  results.append({"status": status, "name": name, "detail": detail})
171
222
 
172
223
 
224
+ def _normalize_separators(text: str) -> str:
225
+ r"""Collapse every run of backslashes to a single forward slash.
226
+
227
+ A Windows path does not survive a literal substring test against the file it
228
+ was written into: JSON escapes `C:\Users\x` as `C:\\Users\\x`, and TOML
229
+ may keep it single-escaped or write a literal string. doctor compared the
230
+ unescaped path against the raw file text and so reported the entry missing
231
+ on configs it had itself just written (issue #158).
232
+
233
+ Collapsing runs — rather than only the doubled form — is what makes both
234
+ spellings compare equal, and normalizing the needle the same way keeps the
235
+ comparison symmetric.
236
+ """
237
+ return re.sub(r"\\+", "/", text)
238
+
239
+
173
240
  def file_contains(path: Path, needles: list[str]) -> tuple[bool, str]:
174
241
  if not path.exists():
175
242
  return False, "missing"
176
- text = path.read_text(errors="replace")
177
- missing = [needle for needle in needles if needle not in text]
243
+ text = _normalize_separators(path.read_text(errors="replace"))
244
+ missing = [needle for needle in needles if _normalize_separators(needle) not in text]
178
245
  if missing:
179
246
  return False, "missing: " + ", ".join(missing)
180
247
  return True, "contains davinci-resolve MCP entry"
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.103.1"
90
+ VERSION = "2.103.2"
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.103.1"
14
+ VERSION = "2.103.2"
15
15
 
16
16
  import base64
17
17
  import os