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 +25 -0
- package/README.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/batch_cli.py +2 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +8 -3
- package/src/utils/media_analysis.py +35 -7
- package/src/utils/media_analysis_jobs.py +2 -1
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
|
-
[](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
|
@@ -35,7 +35,7 @@ from src.utils.update_check import (
|
|
|
35
35
|
|
|
36
36
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
VERSION = "2.52.
|
|
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
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)
|
package/src/granular/common.py
CHANGED
|
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
|
|
|
80
80
|
handlers=[logging.StreamHandler()],
|
|
81
81
|
)
|
|
82
82
|
|
|
83
|
-
VERSION = "2.52.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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",
|