davinci-resolve-mcp 2.34.0 → 2.34.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 +14 -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 +5 -2
- package/src/utils/page_lock.py +75 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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.34.1
|
|
6
|
+
|
|
7
|
+
Page-switch serialization — the concurrency primitive for safe multi-agent use.
|
|
8
|
+
|
|
9
|
+
- **Added** `src/utils/page_lock.py` — Resolve has a single globally-active page,
|
|
10
|
+
so two agents that flip pages concurrently corrupt each other. `page_lock()`
|
|
11
|
+
serializes page switches: a reentrant intra-process lock plus a best-effort
|
|
12
|
+
inter-process advisory file lock around the outermost section. The `open_page`
|
|
13
|
+
action now routes through it. This must be in place before any concurrent-agent
|
|
14
|
+
feature ships.
|
|
15
|
+
- Networked transport, sandboxed scripting, and capability/role scoping (the rest
|
|
16
|
+
of the safe remote/multi-user design) are security-critical and remain a
|
|
17
|
+
separate, sign-off-gated phase — they are intentionally not shipped here.
|
|
18
|
+
|
|
5
19
|
## What's New in v2.34.0
|
|
6
20
|
|
|
7
21
|
First phase of transcript-driven editing: a Cut intermediate representation and a
|
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.34.
|
|
38
|
+
VERSION = "2.34.1"
|
|
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.34.
|
|
83
|
+
VERSION = "2.34.1"
|
|
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.34.
|
|
14
|
+
VERSION = "2.34.1"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -46,6 +46,7 @@ from src.utils.mcp_stdio import run_fastmcp_stdio
|
|
|
46
46
|
from src.utils.api_truth import lookup_api_truth, VERIFIED_ON as _API_TRUTH_VERIFIED_ON
|
|
47
47
|
from src.utils.contracts import validate as _validate_params
|
|
48
48
|
from src.utils.cut_ir import build_cut_list as _build_cut_list
|
|
49
|
+
from src.utils.page_lock import open_page_serialized as _open_page_serialized
|
|
49
50
|
from src.utils.proc import safe_run
|
|
50
51
|
from src.utils.readback import verify_by_readback
|
|
51
52
|
from src.utils.update_check import (
|
|
@@ -10672,7 +10673,9 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
10672
10673
|
valid_pages = ["media", "cut", "edit", "color", "fusion", "fairlight", "deliver"]
|
|
10673
10674
|
if p["page"] not in valid_pages:
|
|
10674
10675
|
return _err(f"Invalid page '{p['page']}'. Valid pages: {', '.join(valid_pages)}")
|
|
10675
|
-
|
|
10676
|
+
# Serialize page switches so concurrent agents can't flip the single
|
|
10677
|
+
# globally-active page underneath each other.
|
|
10678
|
+
return {"success": bool(_open_page_serialized(r, p["page"]))}
|
|
10676
10679
|
elif action == "get_keyframe_mode":
|
|
10677
10680
|
return {"mode": r.GetKeyframeMode()}
|
|
10678
10681
|
elif action == "set_keyframe_mode":
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Serialize DaVinci Resolve page switches across threads and processes.
|
|
2
|
+
|
|
3
|
+
Resolve has a single globally-active page (Edit / Color / Fusion / Fairlight /
|
|
4
|
+
Deliver / Cut). An operation that switches the page, does work there, and reads a
|
|
5
|
+
result is only correct if no other operation flips the page underneath it. With a
|
|
6
|
+
single stdio client that never happens, but the moment two agents (threads, or
|
|
7
|
+
separate server processes) drive one Resolve, concurrent page switches corrupt
|
|
8
|
+
each other. This primitive serializes the critical section, so it must be in
|
|
9
|
+
place before any concurrent-agent feature ships.
|
|
10
|
+
|
|
11
|
+
- Intra-process: a reentrant lock (nested page_lock() calls are safe).
|
|
12
|
+
- Inter-process: a best-effort advisory file lock around the OUTERMOST section
|
|
13
|
+
(fcntl). On platforms without fcntl (Windows) the inter-process guard is a
|
|
14
|
+
no-op and only the intra-process lock applies.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
|
|
18
|
+
with page_lock():
|
|
19
|
+
resolve.OpenPage("color")
|
|
20
|
+
... do color-page work, read results ...
|
|
21
|
+
"""
|
|
22
|
+
import os
|
|
23
|
+
import tempfile
|
|
24
|
+
import threading
|
|
25
|
+
from contextlib import contextmanager
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import fcntl # type: ignore
|
|
29
|
+
_HAS_FCNTL = True
|
|
30
|
+
except ImportError: # pragma: no cover - Windows
|
|
31
|
+
_HAS_FCNTL = False
|
|
32
|
+
|
|
33
|
+
_INTRA = threading.RLock()
|
|
34
|
+
_LOCKFILE = os.path.join(tempfile.gettempdir(), "davinci_resolve_mcp_page.lock")
|
|
35
|
+
|
|
36
|
+
# Nesting depth and the held file handle, both guarded by _INTRA. The file lock
|
|
37
|
+
# is taken only at the outermost level — a second fcntl.flock() on a new fd from
|
|
38
|
+
# the same process would block on the first, deadlocking nested page_lock()s.
|
|
39
|
+
_depth = 0
|
|
40
|
+
_fh = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@contextmanager
|
|
44
|
+
def page_lock():
|
|
45
|
+
"""Hold the page-switch lock for the duration of the block (reentrant)."""
|
|
46
|
+
global _depth, _fh
|
|
47
|
+
_INTRA.acquire()
|
|
48
|
+
_depth += 1
|
|
49
|
+
try:
|
|
50
|
+
if _depth == 1 and _HAS_FCNTL:
|
|
51
|
+
try:
|
|
52
|
+
_fh = open(_LOCKFILE, "w")
|
|
53
|
+
fcntl.flock(_fh, fcntl.LOCK_EX)
|
|
54
|
+
except OSError:
|
|
55
|
+
# Advisory lock is best-effort; never block real work on it.
|
|
56
|
+
if _fh is not None:
|
|
57
|
+
_fh.close()
|
|
58
|
+
_fh = None
|
|
59
|
+
yield
|
|
60
|
+
finally:
|
|
61
|
+
_depth -= 1
|
|
62
|
+
if _depth == 0 and _fh is not None:
|
|
63
|
+
try:
|
|
64
|
+
fcntl.flock(_fh, fcntl.LOCK_UN)
|
|
65
|
+
except OSError:
|
|
66
|
+
pass
|
|
67
|
+
_fh.close()
|
|
68
|
+
_fh = None
|
|
69
|
+
_INTRA.release()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def open_page_serialized(resolve, page):
|
|
73
|
+
"""Switch Resolve to `page` under the page lock. Returns OpenPage's result."""
|
|
74
|
+
with page_lock():
|
|
75
|
+
return resolve.OpenPage(page)
|