davinci-resolve-mcp 2.54.0 → 2.54.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,28 @@
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.54.1
6
+
7
+ One more instance of the enum-keyed silent-failure class (issue #70), plus a
8
+ guard so the next one can't ship unnoticed.
9
+
10
+ - **Fixed** the raw `timeline.export` action passed `type`/`subtype` straight to
11
+ `Timeline.Export`, which needs resolved `resolve.EXPORT_*` enum *values* — a
12
+ JSON/MCP caller can't pass a live enum, so the action silently wrote nothing
13
+ for every caller. It now resolves friendly format names (and `EXPORT_*`
14
+ constant names) via the same `_timeline_export_spec` resolver that
15
+ `export_timeline_checked` uses, and reports the resolved `export_type`/
16
+ `export_subtype`.
17
+ - **Fixed** `export_timeline_checked` resolved enum constants against the module
18
+ global `resolve` (which can be `None` and silently degrade the `EXPORT_*` args
19
+ to strings); it now uses `get_resolve()`, matching the issue-#70 lesson.
20
+ - **Added** an `api_truth` ↔ mitigation guard test: every `enum`-tagged catalog
21
+ entry must declare a `mitigation` (the resolver/wrapper functions), each of
22
+ which must exist in `src.server`. The next raw enum passthrough — a documented
23
+ symbol with no real resolver, or a renamed/removed resolver — now fails CI.
24
+ Added a `Timeline.Export` catalog entry and wired `mitigation` onto the
25
+ `AutoSyncAudio`, `CreateSubtitlesFromAudio`, and CloudProject entries.
26
+
5
27
  ## What's New in v2.54.0
6
28
 
7
29
  Hardens the rest of the enum-keyed settings APIs against the same silent-failure
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.54.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.54.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.54.0"
38
+ VERSION = "2.54.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.54.0",
3
+ "version": "2.54.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.54.0"
83
+ VERSION = "2.54.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.54.0"
14
+ VERSION = "2.54.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -5042,7 +5042,10 @@ def _export_timeline_checked(tl, p: Dict[str, Any]):
5042
5042
  folder = os.path.dirname(os.path.abspath(path))
5043
5043
  if folder:
5044
5044
  os.makedirs(folder, exist_ok=True)
5045
- spec = _timeline_export_spec(p, resolve)
5045
+ # Resolve enum constants via get_resolve(), not the module global `resolve`
5046
+ # (which can be None and silently degrade the EXPORT_* args to strings — the
5047
+ # same failure class as issue #70).
5048
+ spec = _timeline_export_spec(p, get_resolve())
5046
5049
  if p.get("dry_run"):
5047
5050
  return _ok(path=path, would_export=True, spec={k: v for k, v in spec.items() if k != "export_type"})
5048
5051
  with long_resolve_op("timeline.export_timeline_checked"):
@@ -17851,8 +17854,18 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
17851
17854
  elif action == "import_into_timeline":
17852
17855
  return {"success": bool(tl.ImportIntoTimeline(p["path"], p.get("options", {})))}
17853
17856
  elif action == "export":
17857
+ # Timeline.Export needs resolved resolve.EXPORT_* enum *values*, which a
17858
+ # JSON/MCP caller cannot pass — handing it a string silently fails. Resolve
17859
+ # friendly type/subtype names (or EXPORT_* constant names) the same way
17860
+ # export_timeline_checked does. This raw action keeps no path sandbox.
17861
+ spec = _timeline_export_spec(p, get_resolve())
17854
17862
  with long_resolve_op("timeline.export"):
17855
- return {"success": bool(tl.Export(p["path"], p["type"], p.get("subtype", "")))}
17863
+ success = bool(tl.Export(p["path"], spec["export_type"], spec["export_subtype"]))
17864
+ return {
17865
+ "success": success,
17866
+ "export_type": spec["export_type_name"],
17867
+ "export_subtype": spec["export_subtype_name"],
17868
+ }
17856
17869
  elif action == "get_setting":
17857
17870
  return {"settings": _ser(tl.GetSetting(p.get("name", "")))}
17858
17871
  elif action == "set_setting":
@@ -28,6 +28,7 @@ API_TRUTH: List[Dict[str, Any]] = [
28
28
  "resolve handle, and verify by reading each clip's "
29
29
  "'Synced Audio' property (see verify_by_readback).",
30
30
  "tags": ["unreliable-return", "silent-failure", "audio", "enum"],
31
+ "mitigation": ["_normalize_auto_sync_settings", "_safe_auto_sync_audio"],
31
32
  },
32
33
  {
33
34
  "symbol": "Timeline.CreateSubtitlesFromAudio",
@@ -43,6 +44,7 @@ API_TRUTH: List[Dict[str, Any]] = [
43
44
  "and verify by reading the timeline's subtitle track count "
44
45
  "before/after (server._safe_create_subtitles).",
45
46
  "tags": ["unreliable-return", "silent-failure", "subtitle", "enum"],
47
+ "mitigation": ["_normalize_auto_caption_settings", "_safe_create_subtitles"],
46
48
  },
47
49
  {
48
50
  "symbol": "ProjectManager CloudProject family (Create/Load/Import/RestoreCloudProject)",
@@ -57,6 +59,23 @@ API_TRUTH: List[Dict[str, Any]] = [
57
59
  "before calling, and treat the bool return from "
58
60
  "Import/RestoreCloudProject as advisory.",
59
61
  "tags": ["silent-failure", "project", "cloud", "enum"],
62
+ "mitigation": ["_normalize_cloud_settings"],
63
+ },
64
+ {
65
+ "symbol": "Timeline.Export",
66
+ "object": "Timeline",
67
+ "signature": "(fileName, exportType, exportSubtype) -> bool",
68
+ "reality": "exportType/exportSubtype must be resolve.EXPORT_* enum *values* "
69
+ "resolved from the live handle. A JSON/MCP caller cannot pass a "
70
+ "live enum, and a plain string ('fcpxml', or even the constant "
71
+ "name 'EXPORT_FCPXML_1_10') is silently rejected with no file "
72
+ "written.",
73
+ "recommended": "Map a friendly format/subtype to the EXPORT_* constant and "
74
+ "resolve it against the live handle "
75
+ "(server._timeline_export_spec) before calling; verify the "
76
+ "output file exists afterward.",
77
+ "tags": ["silent-failure", "timeline", "export", "enum"],
78
+ "mitigation": ["_timeline_export_spec", "_timeline_export_value"],
60
79
  },
61
80
  {
62
81
  "symbol": "Composition.Paste",