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 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
- [![Version](https://img.shields.io/badge/version-2.34.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.34.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
4
4
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
5
5
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
6
6
  [![Tools](https://img.shields.io/badge/MCP%20Tools-32%20(341%20full)-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.0"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.34.0",
3
+ "version": "2.34.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
80
80
  handlers=[logging.StreamHandler()],
81
81
  )
82
82
 
83
- VERSION = "2.34.0"
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.0"
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
- return {"success": bool(r.OpenPage(p["page"]))}
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)