davinci-resolve-mcp 2.33.6 → 2.33.7

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,18 @@
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.7
6
+
7
+ Read/write symmetry audit and a gap it surfaced.
8
+
9
+ - **Added** `scripts/audit_readwrite_symmetry.py` + generated
10
+ `docs/reference/readwrite-symmetry.md` — scans every tool's action surface and
11
+ reports `set_`/`add_`/`create_` writes that lack a read counterpart, so
12
+ write-without-read gaps surface before users hit them. A repeatable
13
+ feature-discovery method.
14
+ - **Added** `fusion_comp(action="get_frame_range")` — reads the comp's render
15
+ frame range, the read counterpart to `set_frame_range` (a gap the audit found).
16
+
5
17
  ## What's New in v2.33.6
6
18
 
7
19
  Internal consolidation: a declarative parameter-contract validator and centralized
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.33.6-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.33.7-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/docs/SKILL.md CHANGED
@@ -997,7 +997,7 @@ Key actions:
997
997
  temp `.setting` file), optionally renaming and repositioning it
998
998
  - `auto_arrange(tool_names?, direction?, spacing?, x?, y?)` — lay tools out in a row
999
999
  (`direction="horizontal"`, default) or column (`"vertical"`)
1000
- - `get_comp_info`, `set_frame_range(start, end)`, `render`
1000
+ - `get_comp_info`, `set_frame_range(start, end)`, `get_frame_range`, `render`
1001
1001
  - `start_undo(name?)` / `end_undo(keep?)`
1002
1002
  - `bulk_set_inputs(ops)` — batch set inputs across multiple timeline item comps in
1003
1003
  one call; each op requires timeline scope plus `tool_name`, `input_name`, `value`
@@ -0,0 +1,25 @@
1
+ <!-- Generated by scripts/audit_readwrite_symmetry.py — do not edit by hand. -->
2
+
3
+ # Read/Write Symmetry Audit
4
+
5
+ - write-style actions scanned: **106**
6
+ - with a matching read: **57**
7
+ - `set_` actions with no `get_`/`list_` (real readback gaps): **11**
8
+
9
+ ## High-signal gaps — `set_` with no read counterpart
10
+
11
+ - `set_cache`
12
+ - `set_cdl`
13
+ - `set_clips_linked`
14
+ - `set_high_priority`
15
+ - `set_keyframe_interpolation`
16
+ - `set_mcp_update_policy`
17
+ - `set_name`
18
+ - `set_node_enabled`
19
+ - `set_title_text`
20
+ - `set_track_enable`
21
+ - `set_track_lock`
22
+
23
+ ## Low-signal (create/add/insert/import — usually expected): 37
24
+
25
+ `add_clip_mattes`, `add_comp`, `add_subfolder`, `add_sync_event_markers`, `add_timeline_mattes`, `add_track`, `add_version`, `apply_arri_cdl_lut`, `apply_fairlight_preset`, `apply_grade_from_drx`, `apply_look_to_items`, `apply_spec`, `create_compound_clip`, `create_fusion_clip`, `create_magic_mask`, `create_stereo_clip`, `create_subtitles`, `create_timeline`, `create_timeline_from_clips`, `create_variant_from_ranges`, `import_burnin`, `import_comp`, `import_folder`, `import_into_timeline`, `import_media`, `import_preset`, `import_project`, `import_render`, `import_timeline`, `import_to_pool`, `insert_audio`, `insert_fusion_composition`, `insert_fusion_generator`, `insert_fusion_title`, `insert_generator`, `insert_ofx_generator`, `insert_title`
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.6"
38
+ VERSION = "2.33.7"
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.33.6",
3
+ "version": "2.33.7",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """Audit read/write symmetry across the compound server's action surface.
3
+
4
+ For every mutating action (set_/add_/clear_/enable_/...) it checks whether a
5
+ matching read action (get_/list_/...) exists on the same tool, and reports the
6
+ asymmetries. The goal is to find write-without-read gaps before users have to —
7
+ the repeatable feature-discovery method behind R5.
8
+
9
+ Reads the `_unknown(action, [...])` lists in src/server.py, which enumerate every
10
+ action a tool accepts. Prints a markdown report.
11
+ """
12
+ import ast
13
+ import os
14
+ import re
15
+ import sys
16
+
17
+ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ SERVER = os.path.join(ROOT, "src", "server.py")
19
+
20
+ READ_PREFIXES = ("get_", "list_", "probe_", "is_", "has_", "find_")
21
+ # `set_` is the high-signal class: a set with no get is a genuine readback gap.
22
+ # create_/add_/insert_/import_ are inherently writes that usually have no paired
23
+ # read of the same noun, so they're reported separately as low-signal.
24
+ HIGH_SIGNAL = ("set_",)
25
+ LOW_SIGNAL = ("add_", "create_", "insert_", "apply_", "import_")
26
+
27
+
28
+ def _action_lists(src: str):
29
+ """Yield lists of action-name string literals from each _unknown(action, [...])."""
30
+ for m in re.finditer(r"_unknown\(action,\s*\[(.*?)\]\)", src, re.DOTALL):
31
+ names = re.findall(r'"([a-z][a-z0-9_]*)"', m.group(1))
32
+ if names:
33
+ yield names
34
+
35
+
36
+ def _has_read(stem: str, aset: set) -> bool:
37
+ # Match get_<stem>, list_<stem>, and plural get_<stem>s (e.g. add_keyframe -> get_keyframes).
38
+ candidates = {rp + stem for rp in READ_PREFIXES}
39
+ candidates |= {rp + stem + "s" for rp in READ_PREFIXES}
40
+ candidates |= {rp + stem.rstrip("s") for rp in READ_PREFIXES}
41
+ return bool(candidates & aset)
42
+
43
+
44
+ def audit(src: str):
45
+ high, low, covered, total = set(), set(), 0, 0
46
+ for actions in _action_lists(src):
47
+ aset = set(actions)
48
+ for a in actions:
49
+ for wp in HIGH_SIGNAL + LOW_SIGNAL:
50
+ if a.startswith(wp):
51
+ total += 1
52
+ stem = a[len(wp):]
53
+ if _has_read(stem, aset):
54
+ covered += 1
55
+ elif wp in HIGH_SIGNAL:
56
+ high.add(a)
57
+ else:
58
+ low.add(a)
59
+ break
60
+ return total, covered, sorted(high), sorted(low)
61
+
62
+
63
+ def main():
64
+ src = open(SERVER, encoding="utf-8").read()
65
+ total, covered, high, low = audit(src)
66
+ print("# Read/Write Symmetry Audit\n")
67
+ print(f"- write-style actions scanned: **{total}**")
68
+ print(f"- with a matching read: **{covered}**")
69
+ print(f"- `set_` actions with no `get_`/`list_` (real readback gaps): **{len(high)}**\n")
70
+ if high:
71
+ print("## High-signal gaps — `set_` with no read counterpart\n")
72
+ for a in high:
73
+ print(f"- `{a}`")
74
+ print(f"\n## Low-signal (create/add/insert/import — usually expected): {len(low)}\n")
75
+ print(", ".join(f"`{a}`" for a in low))
76
+ return 0
77
+
78
+
79
+ if __name__ == "__main__":
80
+ sys.exit(main())
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
80
80
  handlers=[logging.StreamHandler()],
81
81
  )
82
82
 
83
- VERSION = "2.33.6"
83
+ VERSION = "2.33.7"
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.6"
14
+ VERSION = "2.33.7"
15
15
 
16
16
  import base64
17
17
  import os
@@ -18730,6 +18730,7 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
18730
18730
  auto_arrange(tool_names?, direction?, spacing?, x?, y?) -> {success, arranged, count}
18731
18731
  Lay tools out in a row (direction="horizontal", default) or column ("vertical").
18732
18732
  set_frame_range(start, end) -> {success}
18733
+ get_frame_range() -> {start, end} — read the comp's render frame range
18733
18734
  render() -> {success}
18734
18735
  start_undo(name?) -> {success}
18735
18736
  end_undo(keep?) -> {success}
@@ -19021,6 +19022,13 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
19021
19022
  comp.SetAttrs({"COMPN_RenderStartTime": p["start"], "COMPN_RenderEndTime": p["end"]})
19022
19023
  return _ok()
19023
19024
 
19025
+ elif action == "get_frame_range":
19026
+ attrs = comp.GetAttrs() or {}
19027
+ return {
19028
+ "start": attrs.get("COMPN_RenderStartTime"),
19029
+ "end": attrs.get("COMPN_RenderEndTime"),
19030
+ }
19031
+
19024
19032
  elif action == "render":
19025
19033
  comp.Render()
19026
19034
  return _ok()
@@ -19159,7 +19167,7 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
19159
19167
  "connect","disconnect","get_inputs","get_outputs",
19160
19168
  "set_input","get_input","set_attrs","get_attrs",
19161
19169
  "add_keyframe","get_keyframes","delete_keyframe",
19162
- "get_comp_info","set_frame_range","render",
19170
+ "get_comp_info","set_frame_range","get_frame_range","render",
19163
19171
  "start_undo","end_undo",
19164
19172
  "get_position","set_position","copy_tool","auto_arrange",
19165
19173
  "bulk_set_inputs",