davinci-resolve-mcp 2.79.0 → 2.79.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 +30 -0
- package/README.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/resolve-advanced/server/editorial.mjs +13 -4
- package/resolve-advanced/server/tools/drt.mjs +18 -3
- package/resolve-advanced/server/tools/editorial.mjs +6 -2
- package/src/granular/common.py +1 -1
- package/src/server.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
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.79.1
|
|
6
|
+
|
|
7
|
+
One redirect that was never written, in a dispatcher whose other container formats all have one.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`parse_interchange` told `.drt`/`.drp` callers the format was unknown — for formats the
|
|
12
|
+
same package parses.** `parseInterchange(format, content)` gives AAF and `.prproj` explicit,
|
|
13
|
+
helpful redirects to their path-based readers, but `drt` and `drp` had no case at all and
|
|
14
|
+
fell through to `unknown format 'drt' (edl|otio|xml|xmeml|fcp7|aaf|prproj)`. Meanwhile
|
|
15
|
+
`list_sequences` in the same cluster parses both by path, and the tool description advertises
|
|
16
|
+
it. So the cluster supports DRT, documents that it supports DRT, then tells the
|
|
17
|
+
content-shaped caller it does not. The failure is silent downstream: a consumer that reads
|
|
18
|
+
"unknown format" (or hands `{ content }` to `drt.parse`, whose schema wants `{ drtPath }`)
|
|
19
|
+
records **zero events**, and an empty sequence is indistinguishable from an unsupported one.
|
|
20
|
+
`parseInterchange` now throws the same shape of redirect the AAF and `.prproj` cases throw —
|
|
21
|
+
naming the ZIP container, `parseDRT(path)`, and the two callable entry points — and the
|
|
22
|
+
`default:` message lists `drt|drp` among the known formats. The `editorial`
|
|
23
|
+
`parse_interchange` schema accepts `drt`/`drp` so the tool-level caller gets that redirect
|
|
24
|
+
instead of a bare enum rejection, and `drt.parse` / `list_sequences` / `validate` now name
|
|
25
|
+
the path argument they wanted rather than emitting a raw zod issue dump.
|
|
26
|
+
|
|
27
|
+
### Validation
|
|
28
|
+
|
|
29
|
+
- `resolve-advanced` suite: 778 tests, 748 pass, 30 skipped, 0 fail (4 new assertions, written
|
|
30
|
+
first and confirmed failing on v2.79.0).
|
|
31
|
+
- Static/drift checks, `--help`/`--version`, `npm pack --dry-run`, `git diff --check`: clean.
|
|
32
|
+
- No Resolve behavior changed; this path never touches the Resolve API, so live validation was
|
|
33
|
+
not required.
|
|
34
|
+
|
|
5
35
|
## What's New in v2.79.0
|
|
6
36
|
|
|
7
37
|
Six silent failures in the conform path, found by conforming a real Avid picture turnover
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DaVinci Resolve MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
4
4
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
5
5
|
[](docs/reference/api-coverage.md)
|
|
6
6
|
[-blue.svg)](#server-modes)
|
package/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.79.
|
|
39
|
+
VERSION = "2.79.1"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
|
@@ -196,9 +196,13 @@ export function parseXMEMLEvents(xml, opts = {}) {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* Dispatch by format (SYNC, pure over TEXT). Binary formats are handled out-of-band by
|
|
200
|
-
* path-based readers — AAF via aaf.mjs `parseAAF` (async, pyaaf2), .prproj via prproj.mjs
|
|
201
|
-
* `parsePrproj` (gunzip+XML). This throws to route callers
|
|
199
|
+
* Dispatch by format (SYNC, pure over TEXT). Binary/container formats are handled out-of-band by
|
|
200
|
+
* their own path-based readers — AAF via aaf.mjs `parseAAF` (async, pyaaf2), .prproj via prproj.mjs
|
|
201
|
+
* `parsePrproj` (gunzip+XML), .drt/.drp via drt.mjs `parseDRT` (ZIP). This throws to route callers
|
|
202
|
+
* there rather than faking a parse. Every container format gets a NAMED redirect: falling through
|
|
203
|
+
* to `unknown format` would tell a caller the cluster does not support a format it does support
|
|
204
|
+
* (list_sequences parses .drt/.drp), and an empty parse is indistinguishable downstream from an
|
|
205
|
+
* unsupported one.
|
|
202
206
|
*/
|
|
203
207
|
export function parseInterchange(format, content, opts = {}) {
|
|
204
208
|
switch (String(format).toLowerCase()) {
|
|
@@ -218,8 +222,13 @@ export function parseInterchange(format, content, opts = {}) {
|
|
|
218
222
|
throw new Error(
|
|
219
223
|
'parse_interchange: .prproj is gzip-compressed XML — parse it via the path-based reader (prproj.mjs `parsePrproj`), not the sync parseInterchange().',
|
|
220
224
|
);
|
|
225
|
+
case 'drt':
|
|
226
|
+
case 'drp':
|
|
227
|
+
throw new Error(
|
|
228
|
+
`parse_interchange: .${String(format).toLowerCase()} is a ZIP container — parse it via the path-based reader (drt.mjs \`parseDRT(path)\`), not the sync parseInterchange(). Entry points: editorial \`list_sequences({path})\` or drt \`parse({drtPath})\`.`,
|
|
229
|
+
);
|
|
221
230
|
default:
|
|
222
|
-
throw new Error(`parse_interchange: unknown format '${format}' (edl|otio|xml|xmeml|fcp7|aaf|prproj)`);
|
|
231
|
+
throw new Error(`parse_interchange: unknown format '${format}' (edl|otio|xml|xmeml|fcp7|drt|drp|aaf|prproj)`);
|
|
223
232
|
}
|
|
224
233
|
}
|
|
225
234
|
|
|
@@ -70,17 +70,32 @@ const downgradeSchema = z.object({
|
|
|
70
70
|
appVersionString: z.string().optional().describe('DbAppVer comment to stamp (default derived from targetAppVersion, else "<v>.0.0.0000")'),
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* .drt/.drp are ZIP containers, so every reader here takes a FILE PATH. A caller reaching for the
|
|
75
|
+
* content-shaped convention its sibling text formats use (`{ content }`) otherwise gets a bare zod
|
|
76
|
+
* dump that never names the key it wanted.
|
|
77
|
+
*/
|
|
78
|
+
function requirePathArg(args, key, action) {
|
|
79
|
+
const a = args && typeof args === 'object' ? args : {};
|
|
80
|
+
if (typeof a[key] === 'string' && a[key]) return a;
|
|
81
|
+
const given = Object.keys(a).filter((k) => a[k] !== undefined);
|
|
82
|
+
throw new Error(
|
|
83
|
+
`drt.${action}: pass \`${key}\` — an absolute path to the .drt/.drp. A .drt/.drp is a ZIP container, so this action reads a FILE PATH, never file content` +
|
|
84
|
+
`${given.length ? ` (got: ${given.join(', ')})` : ' (got no arguments)'}.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
export const drtTool = {
|
|
74
89
|
name: 'drt',
|
|
75
90
|
description:
|
|
76
91
|
'DaVinci Resolve Timeline (.drt) operations — offline, no Resolve required. Actions: parse, list_sequences (enumerate the timelines inside a .drp/.drt → [{id,name,eventCount,index}] to drive a "which sequence?" picker), author, validate, inject_into_drp, extract_from_drp (pull one SeqContainer out as a .drt — feed the .drt to the Python davinci-resolve MCP timeline.import_timeline_checked, or use timeline.import_from_drp to do both), downgrade (stamp <ProjectVersion> down so an OLDER Resolve will import a .drt/.drp from a newer one — pass targetAppVersion like "19.1.3" or targetProjectVersion).',
|
|
77
92
|
async handler({ action, args }) {
|
|
78
93
|
if (action === 'parse') {
|
|
79
|
-
const p = parseSchema.parse(args);
|
|
94
|
+
const p = parseSchema.parse(requirePathArg(args, 'drtPath', 'parse'));
|
|
80
95
|
return drt().parseDRT(p.drtPath);
|
|
81
96
|
}
|
|
82
97
|
if (action === 'list_sequences') {
|
|
83
|
-
const p = listSequencesSchema.parse(args);
|
|
98
|
+
const p = listSequencesSchema.parse(requirePathArg(args, 'drpPath', 'list_sequences'));
|
|
84
99
|
const parsed = await drt().parseDRT(p.drpPath);
|
|
85
100
|
const sequences = summarizeDrtTimelines(parsed);
|
|
86
101
|
return { path: p.drpPath, count: sequences.length, sequences };
|
|
@@ -92,7 +107,7 @@ export const drtTool = {
|
|
|
92
107
|
return { outputPath: p.outputPath, bytes: buf.length };
|
|
93
108
|
}
|
|
94
109
|
if (action === 'validate') {
|
|
95
|
-
const p = validateSchema.parse(args);
|
|
110
|
+
const p = validateSchema.parse(requirePathArg(args, 'drtPath', 'validate'));
|
|
96
111
|
return drt().validateDRT(p.drtPath);
|
|
97
112
|
}
|
|
98
113
|
if (action === 'inject_into_drp') {
|
|
@@ -32,11 +32,15 @@ async function parseAnySource(sourcePath, sourceFormat) {
|
|
|
32
32
|
return parseInterchange(fmt, content, {});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// drt/drp are accepted only so the caller gets parseInterchange's NAMED redirect to the
|
|
36
|
+
// path-based reader instead of a bare enum-rejection that reads as "unsupported".
|
|
35
37
|
const parseSchema = z.object({
|
|
36
|
-
format: z.enum(['edl', 'otio', 'xml', 'xmeml', 'fcp7', 'aaf', 'prproj']),
|
|
38
|
+
format: z.enum(['edl', 'otio', 'xml', 'xmeml', 'fcp7', 'aaf', 'prproj', 'drt', 'drp']),
|
|
37
39
|
content: z
|
|
38
40
|
.union([z.string(), z.object({}).passthrough()])
|
|
39
|
-
.describe(
|
|
41
|
+
.describe(
|
|
42
|
+
'EDL text / OTIO JSON (string or object) / XMEML string. For AAF or PRPROJ (binary): the file PATH. For .drt/.drp (ZIP): use list_sequences or drt.parse — this action redirects.',
|
|
43
|
+
),
|
|
40
44
|
fps: z.number().optional(),
|
|
41
45
|
});
|
|
42
46
|
|
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.79.
|
|
88
|
+
VERSION = "2.79.1"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
logger.info(f"Detected platform: {get_platform()}")
|