davinci-resolve-mcp 2.52.0 → 2.52.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,31 @@
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.52.1
6
+
7
+ Analysis reuse no longer blocks on missing capabilities when every clip is
8
+ satisfied by an existing report (issue #68). `build_plan` records
9
+ `capability_gaps` from the *requested* options before the per-clip reuse
10
+ decision runs; when a fully-reused plan only re-keys and imports existing
11
+ reports into the current root, no fresh transcription/vision/ffprobe happens,
12
+ so the missing-capability gate must not fire.
13
+
14
+ - **Fixed** the missing-capability gate is now evaluated against the clips that
15
+ still need fresh analysis, not the requested-options gaps. A clip is exempt
16
+ only when it both skips execution and has an existing report path; any clip
17
+ needing fresh work still enforces the gate.
18
+ - **Fixed** extended the exemption to the entry points that short-circuit
19
+ before `execute_plan_async` and were still blocking fully-reused runs: the
20
+ `media_analysis` analyze action, metadata-publish analysis, and batch-job
21
+ creation. The batch CLI `plan` preview no longer prints "Missing tools" for a
22
+ fully-reusable plan.
23
+ - **Changed** the reuse/capability logic is now a shared
24
+ `plan_requires_capabilities()` / `executing_clips()` helper, replacing the
25
+ duplicated inline comprehensions so every gate stays consistent.
26
+ - Includes PR #68 by @diesdaas, which fixed the inner `execute_plan_async` gate
27
+ and isolated the marker-param and host-vision tests from unrelated
28
+ capability/destructive-hook coupling.
29
+
5
30
  ## What's New in v2.52.0
6
31
 
7
32
  `edit_engine` tighten now carries audio. Previously `execute_tighten`
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.52.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.52.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.52.0"
38
+ VERSION = "2.52.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.52.0",
3
+ "version": "2.52.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
package/src/batch_cli.py CHANGED
@@ -17,6 +17,7 @@ from typing import Any, Dict, List, Optional
17
17
  from src.utils.media_analysis import (
18
18
  build_plan,
19
19
  detect_capabilities,
20
+ plan_requires_capabilities,
20
21
  )
21
22
  from src.utils.media_analysis_jobs import (
22
23
  batch_job_status,
@@ -187,7 +188,7 @@ def _cmd_plan(args: argparse.Namespace) -> int:
187
188
  f"Est. seconds : {plan.get('estimated_seconds_after_reuse', '?')}",
188
189
  json_mode=False,
189
190
  )
190
- if plan.get("capability_gaps"):
191
+ if plan.get("capability_gaps") and plan_requires_capabilities(plan):
191
192
  _emit(f"Missing tools: {plan['capability_gaps']}", json_mode=False)
192
193
  for w in warnings:
193
194
  _emit(f" warning: {w}", json_mode=False)
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
80
80
  handlers=[logging.StreamHandler()],
81
81
  )
82
82
 
83
- VERSION = "2.52.0"
83
+ VERSION = "2.52.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.52.0"
14
+ VERSION = "2.52.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -77,6 +77,7 @@ from src.utils.media_analysis import (
77
77
  execute_plan_async as execute_media_analysis_plan_async,
78
78
  install_guidance as media_analysis_install_guidance,
79
79
  load_report as load_media_analysis_report,
80
+ plan_requires_capabilities as media_analysis_plan_requires_capabilities,
80
81
  query_analysis_index,
81
82
  resolve_output_root as resolve_media_analysis_output_root,
82
83
  short_hash,
@@ -9226,7 +9227,7 @@ async def _publish_clip_metadata_from_analysis(
9226
9227
  )
9227
9228
  if not plan.get("success"):
9228
9229
  return plan
9229
- if plan.get("capability_gaps"):
9230
+ if plan.get("capability_gaps") and media_analysis_plan_requires_capabilities(plan):
9230
9231
  return _media_analysis_missing_capabilities_response(plan, action_label="metadata publish analysis")
9231
9232
 
9232
9233
  manifest = await execute_media_analysis_plan_async(
@@ -16268,7 +16269,11 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
16268
16269
  plan["setup_defaults_applied"] = p.get("_setup_defaults_applied")
16269
16270
  if warnings:
16270
16271
  plan["warnings"] = warnings
16271
- if plan.get("capability_gaps") and not bool(p.get("dry_run", True)):
16272
+ if (
16273
+ plan.get("capability_gaps")
16274
+ and not bool(p.get("dry_run", True))
16275
+ and media_analysis_plan_requires_capabilities(plan)
16276
+ ):
16272
16277
  return _e2_wrap(_media_analysis_missing_capabilities_response(plan))
16273
16278
  if not bool(p.get("dry_run", True)):
16274
16279
  executed = await execute_media_analysis_plan_async(
@@ -5117,6 +5117,37 @@ async def _maybe_run_vision_analysis(
5117
5117
  return _vision_analysis(record, motion, options, artifacts, capabilities)
5118
5118
 
5119
5119
 
5120
+ def _clip_is_reused(clip: Any) -> bool:
5121
+ """A clip is satisfied by an existing report and runs no fresh analysis."""
5122
+ return bool(
5123
+ isinstance(clip, dict)
5124
+ and clip.get("skip_execution")
5125
+ and (clip.get("existing_report") or {}).get("path")
5126
+ )
5127
+
5128
+
5129
+ def executing_clips(plan: Dict[str, Any]) -> List[Dict[str, Any]]:
5130
+ """Clips in ``plan`` that still require fresh analysis (not pure reuse)."""
5131
+ return [
5132
+ clip
5133
+ for clip in plan.get("clips", [])
5134
+ if isinstance(clip, dict) and not _clip_is_reused(clip)
5135
+ ]
5136
+
5137
+
5138
+ def plan_requires_capabilities(plan: Dict[str, Any]) -> bool:
5139
+ """True when at least one clip needs fresh analysis.
5140
+
5141
+ ``build_plan`` records ``capability_gaps`` from the *requested* options
5142
+ before the per-clip reuse decision runs. When every clip is satisfied by an
5143
+ existing reusable report, execution only re-keys/imports those reports into
5144
+ the current root and performs no fresh transcription/vision/ffprobe — so the
5145
+ missing-capability gate must not fire. Callers gate with
5146
+ ``plan.get("capability_gaps") and plan_requires_capabilities(plan)``.
5147
+ """
5148
+ return bool(executing_clips(plan))
5149
+
5150
+
5120
5151
  async def execute_plan_async(
5121
5152
  plan: Dict[str, Any],
5122
5153
  params: Optional[Dict[str, Any]] = None,
@@ -5155,7 +5186,8 @@ async def execute_plan_async(
5155
5186
  for clip in blocked
5156
5187
  ],
5157
5188
  }
5158
- if plan.get("capability_gaps"):
5189
+ fresh_clips = executing_clips(plan)
5190
+ if plan.get("capability_gaps") and fresh_clips:
5159
5191
  return {
5160
5192
  "success": False,
5161
5193
  "error": "Cannot execute analysis with missing required capabilities",
@@ -5204,17 +5236,13 @@ async def execute_plan_async(
5204
5236
  and vision_uses_chat_context(options, caps)
5205
5237
  and not _coerce_bool(params.get("confirm_deep") or params.get("confirmDeep"), default=False)
5206
5238
  ):
5207
- executing = [
5208
- clip for clip in plan.get("clips", [])
5209
- if not (clip.get("skip_execution") and (clip.get("existing_report") or {}).get("path"))
5210
- ]
5211
- estimated_frames = sum(int(c.get("analysis_keyframe_budget") or 0) for c in executing)
5239
+ estimated_frames = sum(int(c.get("analysis_keyframe_budget") or 0) for c in fresh_clips)
5212
5240
  return {
5213
5241
  "success": True,
5214
5242
  "status": "confirmation_required",
5215
5243
  "reason": "deep_depth_cost_estimate",
5216
5244
  "estimate": {
5217
- "clip_count": len(executing),
5245
+ "clip_count": len(fresh_clips),
5218
5246
  "estimated_frames": estimated_frames,
5219
5247
  "estimated_vision_tokens": estimated_frames * AVG_VISION_TOKENS_PER_FRAME,
5220
5248
  "tokens_per_frame_assumption": AVG_VISION_TOKENS_PER_FRAME,
@@ -24,6 +24,7 @@ from src.utils.media_analysis import (
24
24
  detect_capabilities,
25
25
  execute_plan,
26
26
  normalize_path,
27
+ plan_requires_capabilities,
27
28
  resolve_output_root,
28
29
  stable_clip_directory,
29
30
  summarize_reports,
@@ -369,7 +370,7 @@ def create_batch_job(
369
370
  )
370
371
  if not plan.get("success"):
371
372
  return plan
372
- if plan.get("capability_gaps"):
373
+ if plan.get("capability_gaps") and plan_requires_capabilities(plan):
373
374
  return {
374
375
  "success": False,
375
376
  "status": "missing_required_capabilities",