davinci-resolve-mcp 2.104.7 → 2.104.8

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,40 @@
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.104.8
6
+
7
+ More laps: the aggregation class swept to completion, and the dependency
8
+ stack brought to zero known vulnerabilities.
9
+
10
+ **Four more envelope lies fixed.** An AST sweep for per-op result lists under
11
+ success-shaped envelopes (the class import_from_drp exposed) found four bulk
12
+ tools whose top level ignored their rows: `bulk_set_title_text`,
13
+ `fusion_comp.bulk_set_expressions`, and `bulk_set_inputs` returned bare
14
+ {results, op_count} — all-failed and all-succeeded calls indistinguishable
15
+ without reading every row — and `fusion_comp.add_mask` hard-coded
16
+ success:true over failable input writes, so a mask whose every parameter
17
+ failed to apply read as configured while sitting default-shaped on the clip.
18
+ All four now report success/succeeded/failed with partial warnings, through
19
+ one shared summarizer. Also swept and clean: flow-prescribing remediation
20
+ texts (every named action exists), absolute-belief comments in tests and
21
+ source, and the Python tree for further aggregation suspects (the remaining
22
+ hits are read-only listers).
23
+
24
+ **Dependency stack: 15 advisories to zero.** `npm audit fix` cleared the
25
+ non-breaking set (hono, fast-xml-parser, fast-uri, ip-address, js-yaml and
26
+ friends); adm-zip moved to 0.6.0; the `uuid` dependency is GONE (three call
27
+ sites now use Node's built-in crypto.randomUUID); and sharp moved to 0.35.4,
28
+ which clears four libvips CVEs in the image-decode paths that media QC feeds
29
+ untrusted files into.
30
+
31
+ **Node floor: >=20.9 (warn-don't-block).** sharp 0.35 requires Node 20.9+,
32
+ and Node 18 has been end-of-life since April 2025 — on an 18.20 interpreter
33
+ npm silently produced a BROKEN install (engines-skipped platform binding,
34
+ "up to date", no sharp module, 57 tests gone from the count). Both
35
+ package.json engines now say >=20.9, which warns older interpreters without
36
+ blocking, matching the Python floor policy. CI publishes on Node 24; the
37
+ full Node suite runs green on 20.19 (823 tests, sharp included).
38
+
5
39
  ## What's New in v2.104.7
6
40
 
7
41
  The DRT import thread, chased to ground by live bisection on Studio 19.1.3.7
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.104.7-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.104.8-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-36%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.104.7-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.104.8-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-36%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.104.7 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.104.8 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.104.7"
40
+ VERSION = "2.104.8"
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.104.7",
3
+ "version": "2.104.8",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -66,7 +66,7 @@
66
66
  "post-production"
67
67
  ],
68
68
  "engines": {
69
- "node": ">=18.17"
69
+ "node": ">=20.9"
70
70
  },
71
71
  "publishConfig": {
72
72
  "access": "public"
@@ -3,9 +3,9 @@
3
3
  "version": "0.0.0-dev",
4
4
  "private": true,
5
5
  "license": "MIT",
6
- "description": "Beyond-the-API DaVinci Resolve MCP offline .drp/.drt/.drx authoring/editing. Sibling Node server to the Python davinci-resolve-mcp. Dev-local package.json; deps promote to the repo root at packaging.",
7
- "comment_commonjs": "Intentionally NO \"type\": \"module\" the vendored libs are CommonJS (require()). The repo root is type:module, so this nested manifest scopes resolve-advanced/**/*.js back to CommonJS.",
8
- "comment_exports": "Public library surface so a downstream consumer can import the codec/grading/pipeline API, not just spawn the MCP bin. `.` = the lib barrel; `./mcp` = the stdio server entry. Still private:true publish name/registry + un-privating are a parked decision.",
6
+ "description": "Beyond-the-API DaVinci Resolve MCP \u2014 offline .drp/.drt/.drx authoring/editing. Sibling Node server to the Python davinci-resolve-mcp. Dev-local package.json; deps promote to the repo root at packaging.",
7
+ "comment_commonjs": "Intentionally NO \"type\": \"module\" \u2014 the vendored libs are CommonJS (require()). The repo root is type:module, so this nested manifest scopes resolve-advanced/**/*.js back to CommonJS.",
8
+ "comment_exports": "Public library surface so a downstream consumer can import the codec/grading/pipeline API, not just spawn the MCP bin. `.` = the lib barrel; `./mcp` = the stdio server entry. Still private:true \u2014 publish name/registry + un-privating are a parked decision.",
9
9
  "main": "./server/lib.mjs",
10
10
  "exports": {
11
11
  ".": "./server/lib.mjs",
@@ -20,11 +20,10 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@modelcontextprotocol/sdk": "^1.27.1",
23
- "adm-zip": "^0.5.16",
23
+ "adm-zip": "^0.6.0",
24
24
  "fast-xml-parser": "^5.7.3",
25
25
  "fzstd": "^0.1.1",
26
26
  "jszip": "^3.10.1",
27
- "uuid": "^9.0.0",
28
27
  "zod": "^3.25.76",
29
28
  "zstd-codec": "^0.1.5"
30
29
  },
@@ -32,10 +31,10 @@
32
31
  "better-sqlite3": "^11.10.0",
33
32
  "js-yaml": "^4.3.0",
34
33
  "pg": "^8.22.0",
35
- "sharp": "^0.33.0"
34
+ "sharp": "^0.35.4"
36
35
  },
37
36
  "peerDependenciesMeta": {
38
- "_comment": "audio (split/trim/convert) + conform frame ops need system ffmpeg/ffprobe on PATH (NOT bundled FFmpeg binaries are GPL; this package stays MIT). Install ffmpeg yourself: brew install ffmpeg."
37
+ "_comment": "audio (split/trim/convert) + conform frame ops need system ffmpeg/ffprobe on PATH (NOT bundled \u2014 FFmpeg binaries are GPL; this package stays MIT). Install ffmpeg yourself: brew install ffmpeg."
39
38
  },
40
39
  "scripts": {
41
40
  "test": "node --test vendor/drp-format/__tests__/*.test.js vendor/drt-format/__tests__/*.test.js vendor/drx-codec/__tests__/*.test.js test/*.test.mjs",
@@ -43,7 +42,7 @@
43
42
  "test:server": "node --test test/*.test.mjs"
44
43
  },
45
44
  "engines": {
46
- "node": ">=18.0.0"
45
+ "node": ">=20.9"
47
46
  },
48
47
  "devDependencies": {
49
48
  "prettier": "^3.9.4"
@@ -34,7 +34,7 @@ try {
34
34
  } catch {
35
35
  ZstdCodec = null;
36
36
  }
37
- const { v4: uuidv4 } = require('uuid');
37
+ const { randomUUID: uuidv4 } = require('node:crypto');
38
38
 
39
39
  // Import shared DRX parameter library
40
40
  const drxParams = require('../drx-parameters');
@@ -1859,7 +1859,7 @@ async function generateMultiNodeDRX(nodes, connections, metadata = {}) {
1859
1859
  const bodyHex = body.toString('hex');
1860
1860
 
1861
1861
  // Generate UUIDs
1862
- const { v4: uuidv4 } = require('uuid');
1862
+ const { randomUUID: uuidv4 } = require('node:crypto');
1863
1863
  const stillId = uuidv4();
1864
1864
  const clipVersionId = uuidv4();
1865
1865
 
@@ -1873,7 +1873,7 @@ async function generateMultiNodeDRX(nodes, connections, metadata = {}) {
1873
1873
  ${pTrackVerXml}`;
1874
1874
  } else {
1875
1875
  // Generate default pTrackVer body
1876
- const trackVersionId = require('uuid').v4();
1876
+ const trackVersionId = require('node:crypto').randomUUID();
1877
1877
  const trackBodyHex = await generatePTrackVerBody(width, height);
1878
1878
  pTrackVerSection = `
1879
1879
  <pTrackVer>
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.104.7"
90
+ VERSION = "2.104.8"
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.104.7"
14
+ VERSION = "2.104.8"
15
15
 
16
16
  import base64
17
17
  import os
@@ -5787,6 +5787,35 @@ def _timeline_set_title_text(tl, p: Dict[str, Any]) -> Dict[str, Any]:
5787
5787
  }
5788
5788
 
5789
5789
 
5790
+ def _summarize_bulk_results(results: List[Dict[str, Any]], op_count: int) -> Dict[str, Any]:
5791
+ """Envelope for per-op result lists whose top level must reflect the rows.
5792
+
5793
+ A bare {results, op_count} envelope made all-failed and all-succeeded
5794
+ calls indistinguishable to a caller reading only the top level (the same
5795
+ aggregation class as import_from_drp reporting success over failed rows).
5796
+ """
5797
+ succeeded = sum(1 for row in results if row.get("success"))
5798
+ failed = op_count - succeeded
5799
+ out: Dict[str, Any] = {
5800
+ "results": results,
5801
+ "op_count": op_count,
5802
+ "succeeded": succeeded,
5803
+ "failed": failed,
5804
+ "success": failed == 0,
5805
+ }
5806
+ if failed and succeeded:
5807
+ out["partial"] = True
5808
+ out["warning"] = (
5809
+ f"{succeeded} of {op_count} ops succeeded; see results[].error "
5810
+ "for the failures."
5811
+ )
5812
+ elif failed:
5813
+ out["error"] = (
5814
+ f"All {op_count} op(s) failed — see results[].error for each cause."
5815
+ )
5816
+ return out
5817
+
5818
+
5790
5819
  def _timeline_bulk_set_title_text(tl, p: Dict[str, Any]) -> Dict[str, Any]:
5791
5820
  ops = p.get("ops")
5792
5821
  if not isinstance(ops, list) or not ops:
@@ -5801,7 +5830,7 @@ def _timeline_bulk_set_title_text(tl, p: Dict[str, Any]) -> Dict[str, Any]:
5801
5830
  out = _timeline_set_title_text(tl, merged)
5802
5831
  out["index"] = index
5803
5832
  results.append(out)
5804
- return {"results": results, "op_count": len(ops)}
5833
+ return _summarize_bulk_results(results, len(ops))
5805
5834
 
5806
5835
 
5807
5836
  _TIMELINE_CONFORM_KERNEL_ACTIONS = [
@@ -27585,7 +27614,7 @@ def _fusion_comp_bulk_set_expressions(p: Dict[str, Any]) -> Dict[str, Any]:
27585
27614
  results.append({"index": index, "error": error_message})
27586
27615
  elif keep_undo:
27587
27616
  results.append({"index": index, "success": True, "expression": op["expression"]})
27588
- return {"results": results, "op_count": len(ops)}
27617
+ return _summarize_bulk_results(results, len(ops))
27589
27618
 
27590
27619
 
27591
27620
  def _fusion_probe_group_published_inputs(comp, p: Dict[str, Any]) -> Dict[str, Any]:
@@ -27710,7 +27739,7 @@ def _fusion_comp_bulk_set_inputs(p: Dict[str, Any]) -> Dict[str, Any]:
27710
27739
  elif keep_undo:
27711
27740
  results.append({"index": index, "success": True})
27712
27741
 
27713
- return {"results": results, "op_count": len(ops)}
27742
+ return _summarize_bulk_results(results, len(ops))
27714
27743
 
27715
27744
 
27716
27745
  _FUSION_KERNEL_ACTIONS = [
@@ -28115,12 +28144,28 @@ def _fusion_add_mask(comp, p: Dict[str, Any]) -> Dict[str, Any]:
28115
28144
  rec["error"] = str(exc)
28116
28145
  results.append(rec)
28117
28146
 
28147
+ failed_inputs = [row for row in results if not row.get("success")]
28118
28148
  out: Dict[str, Any] = {
28119
- "success": True,
28149
+ # The tool was added either way, but success must mean "the mask that
28150
+ # was asked for": an unconditional True over failed input rows left a
28151
+ # default-shaped mask reading as configured.
28152
+ "success": not failed_inputs or len(failed_inputs) < len(results),
28120
28153
  "tool_name": tool_name,
28121
28154
  "tool_type": attrs.get("TOOLS_RegID", tool_type),
28122
28155
  "inputs_set": results,
28123
28156
  }
28157
+ if failed_inputs and len(failed_inputs) == len(results):
28158
+ out["success"] = False
28159
+ out["error"] = (
28160
+ f"The {tool_name} tool was added but all {len(results)} input "
28161
+ "write(s) failed — the mask is default-shaped; see inputs_set[].error."
28162
+ )
28163
+ elif failed_inputs:
28164
+ out["partial"] = True
28165
+ out["warning"] = (
28166
+ f"{len(failed_inputs)} of {len(results)} mask input(s) failed to "
28167
+ "apply; see inputs_set[].error."
28168
+ )
28124
28169
 
28125
28170
  # Optional wiring: connect this mask into a tool's mask input.
28126
28171
  connect_to = p.get("connect_to")
@@ -28142,6 +28187,12 @@ def _fusion_add_mask(comp, p: Dict[str, Any]) -> Dict[str, Any]:
28142
28187
  }
28143
28188
  except Exception as exc:
28144
28189
  out["connection"] = {"success": False, "error": str(exc)}
28190
+ if isinstance(out.get("connection"), dict) and not out["connection"].get("success"):
28191
+ out["success"] = False
28192
+ out.setdefault(
28193
+ "error",
28194
+ "The mask was added but could not be connected — see `connection`.",
28195
+ )
28145
28196
  return out
28146
28197
 
28147
28198