davinci-resolve-mcp 2.56.0 → 2.56.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,23 @@
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.56.1
6
+
7
+ Final reliability batch from the exhaustive audit (Wave B + P2/P3 selections).
8
+
9
+ - **Fixed** (EX11) `audio_track_probe` reported `available: true` for track index 0
10
+ (and negatives); audio tracks are 1-indexed, so it now requires
11
+ `1 <= index <= track_count`.
12
+ - **Fixed** (EX10) `_find_timeline_item_by_id` no longer silently skips a whole
13
+ track type when `GetTrackCount` errors — it logs the failure so a real API error
14
+ isn't mistaken for "item not found".
15
+ - **Fixed** (P3) the raw `timeline_item_color.set_cdl` now validates the ASC-CDL
16
+ payload (shape/ranges) before `SetCDL`, matching its safe twin — malformed CDL
17
+ returns a structured error instead of being silently rejected by Resolve.
18
+ - **Fixed** (P2) `media_pool.delete_timelines` is read-back verified: it reports
19
+ `verified` from the project's timeline count dropping, not the unreliable boolean.
20
+ `import_folder` now validates its required `path`.
21
+
5
22
  ## What's New in v2.56.0
6
23
 
7
24
  Destructive-action registry audit (EX-REG) — a systemic version of the EX2 bug.
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.56.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.56.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-34%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.56.0"
38
+ VERSION = "2.56.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.56.0",
3
+ "version": "2.56.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.56.0"
83
+ VERSION = "2.56.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.55.2"
14
+ VERSION = "2.56.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -1891,7 +1891,10 @@ def _find_timeline_item_by_id(tl, timeline_item_id) -> Optional[Any]:
1891
1891
  for track_type in ("video", "audio", "subtitle"):
1892
1892
  try:
1893
1893
  track_count = int(tl.GetTrackCount(track_type) or 0)
1894
- except Exception:
1894
+ except Exception as exc:
1895
+ # Don't silently skip a whole track type on API error — log it so a
1896
+ # genuine API failure isn't mistaken for "item not found" (EX10).
1897
+ logger.warning("_find_timeline_item_by_id: GetTrackCount(%s) failed, skipping: %s", track_type, exc)
1895
1898
  continue
1896
1899
  for track_index in range(1, track_count + 1):
1897
1900
  for item in (tl.GetItemListInTrack(track_type, track_index) or []):
@@ -5691,8 +5694,11 @@ def _audio_capabilities():
5691
5694
  def _audio_track_probe(tl, p: Dict[str, Any]):
5692
5695
  track_index = int(p.get("track_index", 1))
5693
5696
  track_count = int(tl.GetTrackCount("audio") or 0)
5694
- out = {"track_index": track_index, "track_count": track_count, "available": track_index <= track_count}
5695
- if track_index > track_count:
5697
+ # Resolve audio tracks are 1-indexed; track_index < 1 is invalid (EX11 — the
5698
+ # old `track_index <= track_count` reported available:true for index 0).
5699
+ out = {"track_index": track_index, "track_count": track_count,
5700
+ "available": 1 <= track_index <= track_count}
5701
+ if not (1 <= track_index <= track_count):
5696
5702
  return out
5697
5703
  for key, getter, args in (
5698
5704
  ("sub_type", "GetTrackSubType", ("audio", track_index)),
@@ -15158,7 +15164,13 @@ def media_pool(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str
15158
15164
  blocked = _consume_confirm_token(action="media_pool.delete_timelines", params=p)
15159
15165
  if blocked:
15160
15166
  return blocked
15161
- return {"success": bool(mp.DeleteTimelines(timelines))}
15167
+ # Read-back: DeleteTimelines' bool is unreliable; verify by the project's
15168
+ # timeline count dropping (P2).
15169
+ before_n = proj.GetTimelineCount()
15170
+ raw = bool(mp.DeleteTimelines(timelines))
15171
+ after_n = proj.GetTimelineCount()
15172
+ return {"success": raw, "verified": after_n < before_n,
15173
+ "timelines_before": before_n, "timelines_after": after_n}
15162
15174
  elif action == "append_to_timeline":
15163
15175
  if p.get("clip_infos") is not None:
15164
15176
  raw = p["clip_infos"]
@@ -15321,6 +15333,8 @@ def media_pool(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str
15321
15333
  return _err("Clip not found")
15322
15334
  return {"success": bool(mp.DeleteClipMattes(clip, p["paths"]))}
15323
15335
  elif action == "import_folder":
15336
+ if not p.get("path"):
15337
+ return _err("import_folder requires path")
15324
15338
  return {"success": bool(mp.ImportFolderFromFile(p["path"], p.get("source_clips_path", "")))}
15325
15339
  elif action == "ingest_capabilities":
15326
15340
  return _media_pool_ingest_capabilities()
@@ -20377,7 +20391,12 @@ def timeline_item_color(action: str, params: Optional[Dict[str, Any]] = None) ->
20377
20391
  elif action == "grade_boundary_report":
20378
20392
  return _grade_boundary_report(proj, item, p)
20379
20393
  elif action == "set_cdl":
20380
- return {"success": bool(item.SetCDL(_normalize_cdl(p["cdl"])))}
20394
+ # Validate ASC-CDL shape before SetCDL (malformed CDL is silently rejected
20395
+ # by Resolve); the raw action shares the safe twin's validator now (P3 #22).
20396
+ validation, err = _validate_cdl_payload(p.get("cdl"))
20397
+ if err:
20398
+ return err
20399
+ return {"success": bool(item.SetCDL(_normalize_cdl(validation["cdl"])))}
20381
20400
  elif action == "copy_grades":
20382
20401
  # Find target items by IDs
20383
20402
  _, tl, _ = _get_tl()