davinci-resolve-mcp 2.99.0 → 2.99.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,46 @@
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.99.1
6
+
7
+ **`bulk_set_item_properties` could not set a clip colour on its own.** Reported
8
+ and fixed in [#157](https://github.com/samuelgursky/davinci-resolve-mcp/pull/157)
9
+ by @matoberuc-afk.
10
+
11
+ The action documents `clip_color` and `enabled` as per-op keys and has code to
12
+ apply both — but that code was unreachable for the op shape that needs it most.
13
+ The payload is built by `_merge_property_groups`, which merges only
14
+ `properties`/`transform`/`crop`/`composite`/`audio` and the duplicate-keyframe
15
+ keys. `clip_color` and `enabled` are not `SetProperty` keys, so they never landed
16
+ in that dict, and the `if not properties: continue` guard above returned early —
17
+ leaving the `clip_color` branch twenty-five lines below dead on exactly the ops
18
+ that carry no transform. Colour triage in one round trip is the main reason to
19
+ call a *bulk* setter, and it was the one shape that could not work.
20
+
21
+ ### Fixed
22
+
23
+ - **A colour-only or enabled-only op is now accepted** and applied.
24
+ - **A colour-only op could not fail.** Per-op success was
25
+ `all(row.get("success") for row in ...properties.values())`, and `all([])` is
26
+ `True` — with no property rows the op passed regardless of what `SetClipColor`
27
+ returned. Every branch that runs now votes.
28
+ - **The bulk path trusted the bare bool.** It called `item.SetClipColor` directly,
29
+ bypassing `_set_clip_color_checked` — the helper added for
30
+ [#124](https://github.com/samuelgursky/davinci-resolve-mcp/issues/124) that the
31
+ single-item path already used, because that bool lies twice: a name outside the
32
+ 16-name Edit-page palette is refused with a bare `False`, and a generator or
33
+ title takes the call, returns `True`, and drops the colour. A failure now
34
+ carries `clip_color_detail`.
35
+ - `dry_run` reports `would_set_clip_color` / `would_set_enabled`, and the
36
+ `action_help` example shows the triage shape instead of a `properties`
37
+ dict with a `ClipColor` key that was never a valid `SetProperty` target.
38
+
39
+ ### Live validation
40
+
41
+ Studio 19.1.3.7: three colour-only ops in one call, live readback
42
+ `['Apricot', 'Chocolate', 'Purple']` on the timeline items; a refused colour
43
+ returns `success: false` instead of passing on the empty-list vote.
44
+
5
45
  ## What's New in v2.99.0
6
46
 
7
47
  **`timeline.ripple_insert`, and a verified-delete gate on `move_clips`.**
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.99.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.99.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-35%20(353%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.99.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.99.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-35%20(353%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v2.99.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.99.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
package/docs/SKILL.md CHANGED
@@ -1410,7 +1410,11 @@ Key actions:
1410
1410
  `track_index?` (default 1), so multicam angles can be rebuilt onto V2/V3
1411
1411
  rather than collapsing onto V1; missing tracks are added
1412
1412
  - `bulk_set_item_properties(ops, dry_run?, readback?)` — apply transforms,
1413
- crop/composite/audio/property groups to many timeline items in one call
1413
+ crop/composite/audio/property groups to many timeline items in one call. An op
1414
+ may carry `clip_color` and/or `enabled` with nothing else, which is the triage
1415
+ shape: paint a whole selection in one round trip. A colour is verified by
1416
+ readback, so a name outside the Edit-page palette and the generator/title case
1417
+ that returns True and drops the colour both fail the op instead of passing
1414
1418
  - `apply_look_to_items(target_ids, cdl?|copy_from_item_id?, dry_run?)` — apply a
1415
1419
  normalized CDL and/or copy a source grade to multiple video items
1416
1420
  - `thumbnail_contact_sheet` / `marker_thumbnail_review` — sample Resolve
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.99.0"
40
+ VERSION = "2.99.1"
41
41
  # Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
42
42
  # Resolve's scripting bridge loads into newer interpreters on recent builds
43
43
  # (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.99.0",
3
+ "version": "2.99.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.99.0"
90
+ VERSION = "2.99.1"
91
91
  logger = logging.getLogger("davinci-resolve-mcp")
92
92
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
93
93
  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 353-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.99.0"
14
+ VERSION = "2.99.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -6039,8 +6039,15 @@ def _timeline_bulk_set_item_properties(tl, p: Dict[str, Any]) -> Dict[str, Any]:
6039
6039
  results.append({"index": index, "success": False, "error": f"timeline item not found: {item_id}"})
6040
6040
  continue
6041
6041
  properties = _merge_property_groups(op)
6042
- if not properties:
6043
- results.append({"index": index, "success": False, "error": "op requires properties, transform, crop, composite, audio, or direct property keys"})
6042
+ # clip_color and enabled are not SetProperty keys, so they never reach
6043
+ # `properties`. Requiring a non-empty `properties` made a colour-only op
6044
+ # — the shape triage actually sends, `{"timeline_item_id": id,
6045
+ # "clip_color": "Apricot"}` — bail here, which left the clip_color and
6046
+ # enabled branches below unreachable on exactly the ops that need them.
6047
+ wants_color = "clip_color" in op
6048
+ wants_enabled = "enabled" in op
6049
+ if not properties and not wants_color and not wants_enabled:
6050
+ results.append({"index": index, "success": False, "error": "op requires properties, transform, crop, composite, audio, clip_color, enabled, or direct property keys"})
6044
6051
  continue
6045
6052
  item_result = {
6046
6053
  "index": index,
@@ -6050,6 +6057,10 @@ def _timeline_bulk_set_item_properties(tl, p: Dict[str, Any]) -> Dict[str, Any]:
6050
6057
  }
6051
6058
  if dry_run:
6052
6059
  item_result.update({"success": True, "would_set": properties})
6060
+ if wants_color:
6061
+ item_result["would_set_clip_color"] = op["clip_color"]
6062
+ if wants_enabled:
6063
+ item_result["would_set_enabled"] = bool(op["enabled"])
6053
6064
  results.append(item_result)
6054
6065
  continue
6055
6066
  for key, value in properties.items():
@@ -6065,17 +6076,33 @@ def _timeline_bulk_set_item_properties(tl, p: Dict[str, Any]) -> Dict[str, Any]:
6065
6076
  except Exception as exc:
6066
6077
  row["readback_error"] = str(exc)
6067
6078
  item_result["properties"][key] = row
6068
- if "clip_color" in op:
6079
+ outcomes = [bool(row.get("success")) for row in item_result["properties"].values()]
6080
+ if wants_color:
6081
+ # Routed through the checked helper rather than the bare bool: a name
6082
+ # outside the 16-name Edit-page palette is refused with False and no
6083
+ # reason, and a generator or title takes the call, returns True and
6084
+ # drops the colour (issue #124). The detail only appears on failure,
6085
+ # so the success shape stays the plain bool callers already read.
6069
6086
  try:
6070
- item_result["clip_color"] = bool(item.SetClipColor(op["clip_color"]))
6087
+ outcome = _set_clip_color_checked(item, op["clip_color"], kind="timeline item")
6071
6088
  except Exception as exc:
6072
- item_result["clip_color"] = {"success": False, "error": str(exc)}
6073
- if "enabled" in op:
6089
+ outcome = {"success": False, "error": str(exc)}
6090
+ applied = bool(outcome.get("success"))
6091
+ item_result["clip_color"] = applied
6092
+ if not applied:
6093
+ item_result["clip_color_detail"] = outcome
6094
+ outcomes.append(applied)
6095
+ if wants_enabled:
6074
6096
  try:
6075
- item_result["enabled"] = bool(item.SetClipEnabled(bool(op["enabled"])))
6097
+ enabled_ok = bool(item.SetClipEnabled(bool(op["enabled"])))
6098
+ item_result["enabled"] = enabled_ok
6076
6099
  except Exception as exc:
6100
+ enabled_ok = False
6077
6101
  item_result["enabled"] = {"success": False, "error": str(exc)}
6078
- item_result["success"] = all(row.get("success") for row in item_result["properties"].values())
6102
+ outcomes.append(enabled_ok)
6103
+ # all([]) is True, so a colour-only op used to be able to report success
6104
+ # no matter what SetClipColor did. Every branch that ran now votes.
6105
+ item_result["success"] = all(outcomes)
6079
6106
  results.append(item_result)
6080
6107
  return {"success": all(row.get("success") for row in results), "results": results, "op_count": len(ops)}
6081
6108
 
@@ -22853,6 +22880,8 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
22853
22880
  track_type, default 1); missing tracks are added, so V2/V3 multicam angles survive.
22854
22881
  # example: action_help(name='<action_name>')
22855
22882
  bulk_set_item_properties(ops, dry_run?, readback?) -> {results, op_count}
22883
+ an op may carry clip_color and/or enabled with no other payload -- that is
22884
+ the triage shape (paint N clips in one call).
22856
22885
  # example: action_help(name='<action_name>')
22857
22886
  apply_look_to_items(target_ids, cdl?|copy_from_item_id?, dry_run?) -> {success}
22858
22887
  # example: action_help(name='<action_name>')
@@ -24966,16 +24995,18 @@ _ACTION_HELP: Dict[str, Dict[str, Dict[str, Any]]] = {
24966
24995
  ),
24967
24996
  },
24968
24997
  "bulk_set_item_properties": {
24969
- "summary": "Batch SetProperty/clip_color/enabled across many items.",
24970
- "params": "ops: [{timeline_item_id|clip_id, properties|transform|crop|composite|audio, clip_color?, enabled?}], dry_run?, readback?",
24971
- "returns": "{success, results, op_count}",
24998
+ "summary": "Batch SetProperty/clip_color/enabled across many items. clip_color or enabled may be the only key in an op.",
24999
+ "params": "ops: [{timeline_item_id|clip_id, properties?|transform?|crop?|composite?|audio?, clip_color?, enabled?}], dry_run?, readback?",
25000
+ "returns": "{success, results, op_count}; a failed clip_color adds results[].clip_color_detail",
24972
25001
  "example": (
24973
25002
  'timeline(action="bulk_set_item_properties", params={\n'
24974
25003
  ' "ops": [\n'
24975
- ' {"timeline_item_id": "TimelineItem-abc",\n'
24976
- ' "properties": {"ClipColor": "Teal", "ZoomX": 1.05}},\n'
24977
- ' {"timeline_item_id": "TimelineItem-def",\n'
24978
- ' "properties": {"ClipColor": "Teal"}}\n'
25004
+ ' # triage: colour only, no other payload needed\n'
25005
+ ' {"timeline_item_id": "TimelineItem-abc", "clip_color": "Apricot"},\n'
25006
+ ' {"timeline_item_id": "TimelineItem-def", "clip_color": "Chocolate"},\n'
25007
+ ' # or colour plus a transform in the same op\n'
25008
+ ' {"timeline_item_id": "TimelineItem-ghi",\n'
25009
+ ' "clip_color": "Purple", "transform": {"ZoomX": 1.05}}\n'
24979
25010
  ' ],\n'
24980
25011
  ' "dry_run": True, "readback": True\n'
24981
25012
  '})'