davinci-resolve-mcp 2.33.3 → 2.33.4
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 +13 -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 +29 -13
- package/src/utils/readback.py +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
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.33.4
|
|
6
|
+
|
|
7
|
+
Internal reliability framework.
|
|
8
|
+
|
|
9
|
+
- **Added** `verify_by_readback` (`src/utils/readback.py`) — a primitive for
|
|
10
|
+
mutating Resolve ops that verifies an action by reading the real post-state
|
|
11
|
+
back instead of trusting the API's frequently-unreliable return value. A
|
|
12
|
+
contradiction (reported success but a failing readback) is logged as a
|
|
13
|
+
reliability signal.
|
|
14
|
+
- **Changed** Auto-sync audio now runs through `verify_by_readback` as its first
|
|
15
|
+
user and reports a `verified` field alongside `linked`/`newly_linked`. Behavior
|
|
16
|
+
is unchanged; the bespoke readback loop is replaced by the shared primitive.
|
|
17
|
+
|
|
5
18
|
## What's New in v2.33.3
|
|
6
19
|
|
|
7
20
|
Two read tools that surface existing project state.
|
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
|
@@ -35,7 +35,7 @@ from src.utils.update_check import (
|
|
|
35
35
|
|
|
36
36
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
VERSION = "2.33.
|
|
38
|
+
VERSION = "2.33.4"
|
|
39
39
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
40
40
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
41
41
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
|
|
|
80
80
|
handlers=[logging.StreamHandler()],
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
VERSION = "2.33.
|
|
83
|
+
VERSION = "2.33.4"
|
|
84
84
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
85
85
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
86
86
|
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 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.33.
|
|
14
|
+
VERSION = "2.33.4"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -43,6 +43,7 @@ for p in [current_dir, project_dir]:
|
|
|
43
43
|
# Platform-specific Resolve paths
|
|
44
44
|
from src.utils.cdl import normalize_cdl_payload
|
|
45
45
|
from src.utils.mcp_stdio import run_fastmcp_stdio
|
|
46
|
+
from src.utils.readback import verify_by_readback
|
|
46
47
|
from src.utils.update_check import (
|
|
47
48
|
check_for_updates,
|
|
48
49
|
clear_update_prompt_preferences,
|
|
@@ -5535,19 +5536,34 @@ def _safe_auto_sync_audio(mp, p: Dict[str, Any]):
|
|
|
5535
5536
|
# Read-back verification: AutoSyncAudio's boolean return is unreliable, so
|
|
5536
5537
|
# capture each clip's "Synced Audio" linkage before and after and report the
|
|
5537
5538
|
# delta. Trust `linked`/`newly_linked`, not `success`.
|
|
5538
|
-
before
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5539
|
+
def _categorize(before, observed):
|
|
5540
|
+
linked, newly, already = [], [], []
|
|
5541
|
+
for c, was, now in zip(clips, before, observed):
|
|
5542
|
+
if now:
|
|
5543
|
+
name = _clip_name(c)
|
|
5544
|
+
linked.append(name)
|
|
5545
|
+
(already if was else newly).append(name)
|
|
5546
|
+
return {
|
|
5547
|
+
"linked": linked,
|
|
5548
|
+
"newly_linked": newly,
|
|
5549
|
+
"already_linked": already,
|
|
5550
|
+
"verified": bool(linked),
|
|
5551
|
+
}
|
|
5552
|
+
|
|
5553
|
+
res = verify_by_readback(
|
|
5554
|
+
mutate=lambda: mp.AutoSyncAudio(clips, settings),
|
|
5555
|
+
observe=lambda: [_synced_audio(c) for c in clips],
|
|
5556
|
+
snapshot=lambda: [_synced_audio(c) for c in clips],
|
|
5557
|
+
compare=_categorize,
|
|
5558
|
+
label="auto_sync_audio",
|
|
5559
|
+
intent={"clip_count": len(clips)},
|
|
5560
|
+
)
|
|
5546
5561
|
return {
|
|
5547
|
-
"success":
|
|
5548
|
-
"
|
|
5549
|
-
"
|
|
5550
|
-
"
|
|
5562
|
+
"success": res["success_raw"],
|
|
5563
|
+
"verified": res["verified"],
|
|
5564
|
+
"linked": res["linked"],
|
|
5565
|
+
"newly_linked": res["newly_linked"],
|
|
5566
|
+
"already_linked": res["already_linked"],
|
|
5551
5567
|
"count": len(clips),
|
|
5552
5568
|
"missing": missing,
|
|
5553
5569
|
"settings": settings,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Readback verification for unreliable Resolve API mutations.
|
|
2
|
+
|
|
3
|
+
The Resolve scripting API frequently returns a success value that does not
|
|
4
|
+
reflect what actually happened: ``AutoSyncAudio`` returns a boolean unrelated to
|
|
5
|
+
whether clips linked, ``AppendToTimeline`` may not hand back the new item id,
|
|
6
|
+
many setters return ``True`` regardless of effect, and Fusion ``Paste()`` can
|
|
7
|
+
report nothing while creating nothing. The defense is to **verify by reading the
|
|
8
|
+
real post-state back** instead of trusting the return value.
|
|
9
|
+
|
|
10
|
+
``verify_by_readback`` is the small primitive every mutating op can use. A
|
|
11
|
+
mutation that reports success while the readback disagrees is a *contradiction*
|
|
12
|
+
— the single most valuable reliability signal — and is logged.
|
|
13
|
+
"""
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any, Callable, Dict, Optional
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("davinci-resolve-mcp")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def verify_by_readback(
|
|
21
|
+
mutate: Callable[[], Any],
|
|
22
|
+
observe: Callable[[], Any],
|
|
23
|
+
*,
|
|
24
|
+
snapshot: Optional[Callable[[], Any]] = None,
|
|
25
|
+
compare: Optional[Callable[[Any, Any], Dict[str, Any]]] = None,
|
|
26
|
+
intent: Optional[Dict[str, Any]] = None,
|
|
27
|
+
label: Optional[str] = None,
|
|
28
|
+
) -> Dict[str, Any]:
|
|
29
|
+
"""Run a mutation and verify it by reading actual state back.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
mutate: performs the Resolve call; its return value is the unreliable
|
|
33
|
+
``success_raw``.
|
|
34
|
+
observe: reads the relevant state AFTER the mutation.
|
|
35
|
+
snapshot: optional — captures pre-state for delta comparisons (passed to
|
|
36
|
+
``compare`` as ``before``).
|
|
37
|
+
compare: ``(before, observed) -> dict`` merged into the result. It should
|
|
38
|
+
set ``verified: bool``. Defaults to ``verified = truthy(observed)``.
|
|
39
|
+
intent: optional description of what we meant to do (for the ledger).
|
|
40
|
+
label: optional op name, used in the contradiction log line.
|
|
41
|
+
|
|
42
|
+
Returns a dict with at least ``success_raw`` and ``verified``, plus whatever
|
|
43
|
+
``compare`` contributed and (if given) ``intent``.
|
|
44
|
+
"""
|
|
45
|
+
before = snapshot() if snapshot is not None else None
|
|
46
|
+
raw = mutate()
|
|
47
|
+
observed = observe()
|
|
48
|
+
|
|
49
|
+
result: Dict[str, Any] = {"success_raw": bool(raw), "observed": observed}
|
|
50
|
+
if compare is not None:
|
|
51
|
+
result.update(compare(before, observed))
|
|
52
|
+
else:
|
|
53
|
+
result["verified"] = bool(observed)
|
|
54
|
+
if intent is not None:
|
|
55
|
+
result["intent"] = intent
|
|
56
|
+
|
|
57
|
+
# A contradiction — reported success but the readback disagrees — is the
|
|
58
|
+
# signal worth surfacing loudly.
|
|
59
|
+
if result.get("success_raw") and not result.get("verified"):
|
|
60
|
+
logger.warning(
|
|
61
|
+
"readback contradiction%s: API reported success but post-state "
|
|
62
|
+
"verification failed%s",
|
|
63
|
+
f" [{label}]" if label else "",
|
|
64
|
+
f" (intent={intent})" if intent else "",
|
|
65
|
+
)
|
|
66
|
+
result["contradiction"] = True
|
|
67
|
+
else:
|
|
68
|
+
result["contradiction"] = False
|
|
69
|
+
|
|
70
|
+
return result
|