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 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
- [![Version](https://img.shields.io/badge/version-2.79.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.79.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
@@ -36,7 +36,7 @@ from src.utils.update_check import (
36
36
 
37
37
  # ─── Version ──────────────────────────────────────────────────────────────────
38
38
 
39
- VERSION = "2.79.0"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.79.0",
3
+ "version": "2.79.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -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 their own
200
- * path-based readers — AAF via aaf.mjs `parseAAF` (async, pyaaf2), .prproj via prproj.mjs
201
- * `parsePrproj` (gunzip+XML). This throws to route callers there rather than faking a parse.
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('EDL text / OTIO JSON (string or object) / XMEML string. For AAF or PRPROJ (binary): the file PATH.'),
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
 
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
85
85
  handlers=[logging.StreamHandler()],
86
86
  )
87
87
 
88
- VERSION = "2.79.0"
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()}")
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.79.0"
14
+ VERSION = "2.79.1"
15
15
 
16
16
  import base64
17
17
  import os