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 +34 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +2 -2
- package/resolve-advanced/package.json +7 -8
- package/resolve-advanced/vendor/drx-codec/drx-generator.js +3 -3
- package/src/granular/common.py +1 -1
- package/src/server.py +56 -5
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
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v2.104.
|
|
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.
|
|
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.
|
|
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": ">=
|
|
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
|
|
7
|
-
"comment_commonjs": "Intentionally NO \"type\": \"module\"
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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": ">=
|
|
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 {
|
|
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 {
|
|
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('
|
|
1876
|
+
const trackVersionId = require('node:crypto').randomUUID();
|
|
1877
1877
|
const trackBodyHex = await generatePTrackVerBody(width, height);
|
|
1878
1878
|
pTrackVerSection = `
|
|
1879
1879
|
<pTrackVer>
|
package/src/granular/common.py
CHANGED
|
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
|
|
|
87
87
|
handlers=[logging.StreamHandler()],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
VERSION = "2.104.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|