davinci-resolve-mcp 2.69.0 → 2.69.1
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 +43 -0
- package/README.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
- package/src/utils/bridge_differential.py +68 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
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.1
|
|
6
|
+
|
|
7
|
+
Bug fix. The Studio bridge differential no longer reports the Deliver page its
|
|
8
|
+
own render probes navigated to as a transport difference.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A clean bridge produced a red result on any run that did not start on
|
|
13
|
+
Deliver.** Run live on Studio 19.1.3.7, the differential reported
|
|
14
|
+
`resolve.GetCurrentPage` as a `value_mismatch` — bridge `edit`, native
|
|
15
|
+
`deliver` — and it reproduced on command: start on Edit, one difference; start
|
|
16
|
+
on Deliver, none. The transport was never involved. The harness's own render
|
|
17
|
+
probes navigate Resolve to the Deliver page, and the bridge pass runs before
|
|
18
|
+
the native pass, so the native pass read a page the bridge pass had moved. Read
|
|
19
|
+
at the same instant the two transports always agreed.
|
|
20
|
+
|
|
21
|
+
Adding the method to `VOLATILE_METHODS` would have turned red green and thrown
|
|
22
|
+
away the signal — a bridge that genuinely reports the wrong page is exactly
|
|
23
|
+
what this harness exists to catch. Instead each pass now records the page on
|
|
24
|
+
the way in and on the way out, and the value is compared **only when both
|
|
25
|
+
passes prove it held still**. If both were stable and the values still
|
|
26
|
+
disagree, that is the transport, and it is reported as before. A page that
|
|
27
|
+
cannot be read counts as unknown rather than stable, so an unreadable page
|
|
28
|
+
disables the comparison instead of quietly restoring the false positive.
|
|
29
|
+
|
|
30
|
+
`page_compared_by_value` and both passes' before/after values now ride in the
|
|
31
|
+
report either way. "The page was not compared" is a fact about the run, and
|
|
32
|
+
burying it would recreate the silence this fixes.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `tests/test_bridge_differential.py` — the module's first tests, 13 of them,
|
|
37
|
+
including the one that matters: a stable page that genuinely disagrees must
|
|
38
|
+
still be reported, or the fix is a mute button rather than a fix. The absence
|
|
39
|
+
of any coverage here is why the false positive shipped.
|
|
40
|
+
|
|
41
|
+
### Validation
|
|
42
|
+
|
|
43
|
+
- Static checks and the full unit suite: 2243 → 2256 tests, pyflakes clean.
|
|
44
|
+
- Live Resolve validation on Studio 19.1.3.7 after the fix: 142 probes, 0
|
|
45
|
+
differences, 110/112 read methods, started from the Edit page that previously
|
|
46
|
+
failed.
|
|
47
|
+
|
|
5
48
|
## What's New in v2.69.0
|
|
6
49
|
|
|
7
50
|
The free edition is documented as reachable, silence ripple stops clipping
|
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.1"
|
|
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
|
package/package.json
CHANGED
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.1"
|
|
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
|
@@ -61,6 +61,28 @@ VOLATILE_METHODS = frozenset({
|
|
|
61
61
|
"IsPlayheadOnVideoFrame",
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
#: App-global state that **the harness's own probes move**, which is a different
|
|
65
|
+
#: problem from `VOLATILE_METHODS` drifting on its own.
|
|
66
|
+
#:
|
|
67
|
+
#: Measured on Studio 19.1.3.7: the render probes navigate Resolve to the Deliver
|
|
68
|
+
#: page, and the bridge pass runs before the native pass — so the native pass
|
|
69
|
+
#: reads a page the bridge pass changed. That produced a `value_mismatch` on
|
|
70
|
+
#: `GetCurrentPage` (bridge `edit`, native `deliver`) on every run that did not
|
|
71
|
+
#: happen to start on Deliver, and none on the runs that did. Read at the same
|
|
72
|
+
#: instant the two transports always agreed.
|
|
73
|
+
#:
|
|
74
|
+
#: Excusing it unconditionally would throw away a real signal — a bridge that
|
|
75
|
+
#: genuinely reported the wrong page is exactly the bug this harness is for. So
|
|
76
|
+
#: each pass records the page on the way in and on the way out, and the value is
|
|
77
|
+
#: only excused when a pass proves it did not hold still. If both passes were
|
|
78
|
+
#: stable and the values still disagree, that is the transport and it is reported.
|
|
79
|
+
ORDER_SENSITIVE_METHODS = frozenset({"GetCurrentPage"})
|
|
80
|
+
|
|
81
|
+
#: Key `run_probes` adds for `compare`'s own use rather than as a probe result.
|
|
82
|
+
#: Underscore-prefixed so the diff skips it instead of reading it as a method
|
|
83
|
+
#: that one transport has and the other does not.
|
|
84
|
+
PASS_META_KEY = "_pass_meta"
|
|
85
|
+
|
|
64
86
|
#: Never invoked by the harness, whatever the enumeration says. These either
|
|
65
87
|
#: change the project, touch the filesystem, or block on UI. The write paths are
|
|
66
88
|
#: exercised by named scenarios instead, where the setup and teardown are
|
|
@@ -326,6 +348,19 @@ def probe_one(target: Any, method: str) -> Dict[str, Any]:
|
|
|
326
348
|
return {"outcome": "returned", "value": normalise(value), "type": describe(value)}
|
|
327
349
|
|
|
328
350
|
|
|
351
|
+
def _current_page(resolve: Any) -> Optional[str]:
|
|
352
|
+
"""The page, or None if it cannot be read.
|
|
353
|
+
|
|
354
|
+
Never raises: this is bookkeeping around the probes, and a failure here must
|
|
355
|
+
not take down a pass that is otherwise fine.
|
|
356
|
+
"""
|
|
357
|
+
try:
|
|
358
|
+
page = resolve.GetCurrentPage()
|
|
359
|
+
except Exception: # noqa: BLE001 - an unreadable page is simply unknown
|
|
360
|
+
return None
|
|
361
|
+
return page if isinstance(page, str) else None
|
|
362
|
+
|
|
363
|
+
|
|
329
364
|
def run_probes(resolve: Any, methods: Sequence[str]) -> Dict[str, Dict[str, Any]]:
|
|
330
365
|
"""Every enumerated safe method, against every object that has it.
|
|
331
366
|
|
|
@@ -333,6 +368,7 @@ def run_probes(resolve: Any, methods: Sequence[str]) -> Dict[str, Dict[str, Any]
|
|
|
333
368
|
not merely that they did.
|
|
334
369
|
"""
|
|
335
370
|
results: Dict[str, Dict[str, Any]] = {}
|
|
371
|
+
page_before = _current_page(resolve)
|
|
336
372
|
for label, chain in OBJECT_GRAPH:
|
|
337
373
|
target = walk(resolve, chain)
|
|
338
374
|
if target is None:
|
|
@@ -342,13 +378,28 @@ def run_probes(resolve: Any, methods: Sequence[str]) -> Dict[str, Dict[str, Any]
|
|
|
342
378
|
if getattr(target, method, None) is None:
|
|
343
379
|
continue # not on this object; absence is reported by the diff
|
|
344
380
|
results[f"{label}.{method}"] = probe_one(target, method)
|
|
381
|
+
page_after = _current_page(resolve)
|
|
382
|
+
results[PASS_META_KEY] = {
|
|
383
|
+
"page_before": page_before,
|
|
384
|
+
"page_after": page_after,
|
|
385
|
+
# None on either side means we could not tell, which must not read as
|
|
386
|
+
# "stable" — an unknown has to disable the value comparison, not enable it.
|
|
387
|
+
"page_stable": page_before is not None and page_before == page_after,
|
|
388
|
+
}
|
|
345
389
|
return results
|
|
346
390
|
|
|
347
391
|
|
|
348
392
|
def compare(native: Dict[str, Dict[str, Any]], bridged: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
|
|
349
393
|
"""Diff two probe runs. Empty `differences` is the pass condition."""
|
|
350
394
|
differences: List[Dict[str, Any]] = []
|
|
395
|
+
# A pass that moved the page cannot have its page compared by value against
|
|
396
|
+
# another pass taken at a different moment. Both must have held still.
|
|
397
|
+
native_meta = native.get(PASS_META_KEY) or {}
|
|
398
|
+
bridge_meta = bridged.get(PASS_META_KEY) or {}
|
|
399
|
+
page_held_still = bool(native_meta.get("page_stable")) and bool(bridge_meta.get("page_stable"))
|
|
351
400
|
for key in sorted(set(native) | set(bridged)):
|
|
401
|
+
if key.startswith("_"):
|
|
402
|
+
continue # harness bookkeeping, not a probe result
|
|
352
403
|
left, right = native.get(key), bridged.get(key)
|
|
353
404
|
if left is None or right is None:
|
|
354
405
|
differences.append({
|
|
@@ -364,17 +415,32 @@ def compare(native: Dict[str, Dict[str, Any]], bridged: Dict[str, Dict[str, Any]
|
|
|
364
415
|
differences.append({"key": key, "kind": "volatile_type_mismatch",
|
|
365
416
|
"native": left.get("type"), "bridge": right.get("type")})
|
|
366
417
|
continue
|
|
418
|
+
if method in ORDER_SENSITIVE_METHODS and not page_held_still:
|
|
419
|
+
# The harness moved it mid-run, so the two readings are of different
|
|
420
|
+
# moments. Still hold the transport to returning the same kind of
|
|
421
|
+
# thing, and say why the value was not compared.
|
|
422
|
+
if left.get("type") != right.get("type") or left["outcome"] != right["outcome"]:
|
|
423
|
+
differences.append({"key": key, "kind": "order_sensitive_type_mismatch",
|
|
424
|
+
"native": left.get("type"), "bridge": right.get("type")})
|
|
425
|
+
continue
|
|
367
426
|
if left != right:
|
|
368
427
|
differences.append({"key": key, "kind": "value_mismatch",
|
|
369
428
|
"native": left, "bridge": right})
|
|
370
|
-
|
|
429
|
+
probe_keys = {k for k in set(native) | set(bridged) if not k.startswith("_")}
|
|
430
|
+
agreed = len({k for k in set(native) & set(bridged) if not k.startswith("_")}) - len(
|
|
371
431
|
[d for d in differences if d["kind"] != "only_one_transport_reached_it"]
|
|
372
432
|
)
|
|
373
433
|
return {
|
|
374
|
-
"compared": len(
|
|
434
|
+
"compared": len(probe_keys),
|
|
375
435
|
"agreed": agreed,
|
|
376
436
|
"differences": differences,
|
|
377
437
|
"identical": not differences,
|
|
438
|
+
# Reported either way. "The page was not compared" is a fact about the
|
|
439
|
+
# run that a reader needs, and burying it would recreate the silence
|
|
440
|
+
# this fix exists to end.
|
|
441
|
+
"page_compared_by_value": page_held_still,
|
|
442
|
+
"page_native": {k: native_meta.get(k) for k in ("page_before", "page_after")},
|
|
443
|
+
"page_bridge": {k: bridge_meta.get(k) for k in ("page_before", "page_after")},
|
|
378
444
|
}
|
|
379
445
|
|
|
380
446
|
|