davinci-resolve-mcp 2.40.0 → 2.41.0
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 +40 -0
- package/README.md +1 -1
- package/docs/SKILL.md +18 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +23 -4
- package/src/granular/common.py +1 -1
- package/src/server.py +90 -1
- package/src/utils/analysis_store.py +924 -0
- package/src/utils/media_analysis.py +27 -0
- package/src/utils/timeline_brain_db.py +179 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
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.41.0
|
|
6
|
+
|
|
7
|
+
DB-canonical clip analysis (C1) — Phase A of the analysis + edit-engine
|
|
8
|
+
program. The per-project SQLite DB is now the source of truth for clip
|
|
9
|
+
analysis; `analysis.json` becomes a derived export written in lockstep.
|
|
10
|
+
|
|
11
|
+
- **Added** schema v9 to the per-project timeline-brain DB: `clips`,
|
|
12
|
+
`clip_aliases`, `analysis_reports` (canonical full payload), `shots`,
|
|
13
|
+
`subjective_fields` + `field_changelog` (per-field provenance),
|
|
14
|
+
`transcript_segments`, `frames`, and `qc_observations`.
|
|
15
|
+
- **Added** `src/utils/analysis_store.py`: transactional ingest, export with
|
|
16
|
+
human-correction overlay (human rows always win and survive re-analysis),
|
|
17
|
+
alias-based clip lookup, shot ids stable under one-second boundary jitter,
|
|
18
|
+
and a round-trip guard verified against a real sample analysis root.
|
|
19
|
+
- **Added** `media_analysis` actions `db_status` (schema version + row
|
|
20
|
+
counts) and `db_ingest` (one-shot migration of existing JSON reports and
|
|
21
|
+
`corrections.json` sidecars into the DB).
|
|
22
|
+
- **Changed** the analysis write path: `execute_plan` and `commit_vision`
|
|
23
|
+
write DB rows first, then export the JSON. Panel clip/shot endpoints read
|
|
24
|
+
DB-first with JSON fallback for pre-v9 reports and job-linked report dirs.
|
|
25
|
+
- **Changed** `update_clip_field` / `update_shot_field` / `revert_field` to
|
|
26
|
+
mirror corrections into the DB as row-level provenance (the
|
|
27
|
+
`corrections.json` sidecar remains for compatibility).
|
|
28
|
+
- **Fixed** eight V2 actions that were unreachable from MCP dispatch since
|
|
29
|
+
v2.24.0 (`get_panel_state`, `set_panel_state`, `session_start_context`,
|
|
30
|
+
`update_shot_field`, `update_clip_field`, `get_field_history`,
|
|
31
|
+
`revert_field`, `list_corrections`): they were checked inside the
|
|
32
|
+
project-root dispatch block but missing from its membership set. The
|
|
33
|
+
control panel proxied to the helpers directly, which masked it.
|
|
34
|
+
- **Fixed** the action-list drift guard to inspect async tool functions
|
|
35
|
+
(media_analysis had drifted unchecked) and to fail on actions that are
|
|
36
|
+
unreachable inside membership blocks — the exact class above. Four
|
|
37
|
+
reachable-but-unadvertised actions (`coverage_report`,
|
|
38
|
+
`get_resolve_ai_usage`, `get_ai_governance`, `set_ai_governance`) are now
|
|
39
|
+
listed in the unknown-action error.
|
|
40
|
+
- **Validation**: full offline suite (1066 tests; 12 new), round-trip guard
|
|
41
|
+
on the real 2026-05-17 sample root, and a live headless pipeline run on
|
|
42
|
+
synthetic media verifying rows-then-export parity, row-level corrections,
|
|
43
|
+
and DB-first panel reads.
|
|
44
|
+
|
|
5
45
|
## What's New in v2.40.0
|
|
6
46
|
|
|
7
47
|
Control panel UX overhaul + docs refresh, from a full live audit of every
|
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/docs/SKILL.md
CHANGED
|
@@ -491,8 +491,24 @@ Key actions: `capabilities`, `install_guidance`, `resolve_output_root`, `plan`,
|
|
|
491
491
|
`publish_clip_metadata`, `commit_vision`, `summarize`, `get_report`,
|
|
492
492
|
`build_index`, `index_status`, `query_index`, `start_batch_job`,
|
|
493
493
|
`run_batch_job_slice`, `batch_job_status`, `list_batch_jobs`,
|
|
494
|
-
`cancel_batch_job`, `resume_batch_job`, `review_timeline_markers`,
|
|
495
|
-
`cleanup_artifacts
|
|
494
|
+
`cancel_batch_job`, `resume_batch_job`, `review_timeline_markers`,
|
|
495
|
+
`cleanup_artifacts`, `db_status`, `db_ingest`, `get_panel_state`,
|
|
496
|
+
`set_panel_state`, `session_start_context`, `update_clip_field`,
|
|
497
|
+
`update_shot_field`, `get_field_history`, `revert_field`, and
|
|
498
|
+
`list_corrections`.
|
|
499
|
+
|
|
500
|
+
**DB-canonical analysis store (v2.41.0+).** The per-project SQLite DB
|
|
501
|
+
(`_soul/timeline_brain.sqlite`, schema v9+) is the source of truth for clip
|
|
502
|
+
analysis; `analysis.json` is a derived export written in lockstep. Analysis
|
|
503
|
+
runs write rows first (clips, shots, per-field subjective provenance,
|
|
504
|
+
transcript segments, sampled frames, QC observations) and then export the
|
|
505
|
+
JSON. Human corrections recorded via `update_clip_field` / `update_shot_field`
|
|
506
|
+
live as row-level provenance and always survive re-analysis. Readers
|
|
507
|
+
(panel API, exports) load DB-first and fall back to `analysis.json` for
|
|
508
|
+
reports that predate v9. `db_status` reports schema version + row counts;
|
|
509
|
+
`db_ingest` migrates an existing project's JSON reports (and
|
|
510
|
+
`corrections.json` sidecars) into the DB — run it once on older analysis
|
|
511
|
+
roots.
|
|
496
512
|
The tool never installs
|
|
497
513
|
dependencies and validates that outputs stay under
|
|
498
514
|
`davinci-resolve-mcp-analysis` project roots rather than beside source media.
|
package/install.py
CHANGED
|
@@ -35,7 +35,7 @@ from src.utils.update_check import (
|
|
|
35
35
|
|
|
36
36
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
|
-
VERSION = "2.
|
|
38
|
+
VERSION = "2.41.0"
|
|
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
|
@@ -12450,6 +12450,25 @@ def _v2_load_analysis(clip_dir: str) -> Optional[Dict[str, Any]]:
|
|
|
12450
12450
|
return None
|
|
12451
12451
|
|
|
12452
12452
|
|
|
12453
|
+
def _v2_load_analysis_db_first(project_root: str, clip_dir: str) -> Optional[Dict[str, Any]]:
|
|
12454
|
+
"""C1 — DB-canonical reader with JSON fallback.
|
|
12455
|
+
|
|
12456
|
+
Falls back to analysis.json for reports that predate schema v9 and for
|
|
12457
|
+
job-linked report dirs whose rows live under another project's DB.
|
|
12458
|
+
"""
|
|
12459
|
+
try:
|
|
12460
|
+
from src.utils import analysis_store
|
|
12461
|
+
|
|
12462
|
+
report = analysis_store.load_db_report(
|
|
12463
|
+
project_root, clip_dir=os.path.basename(clip_dir.rstrip("/\\"))
|
|
12464
|
+
)
|
|
12465
|
+
if report is not None:
|
|
12466
|
+
return report
|
|
12467
|
+
except Exception:
|
|
12468
|
+
pass
|
|
12469
|
+
return _v2_load_analysis(clip_dir)
|
|
12470
|
+
|
|
12471
|
+
|
|
12453
12472
|
def _v2_clip_duration(report: Dict[str, Any]) -> Optional[float]:
|
|
12454
12473
|
marker_plan = report.get("clip_analysis_markers") if isinstance(report.get("clip_analysis_markers"), dict) else {}
|
|
12455
12474
|
duration = marker_plan.get("duration_seconds")
|
|
@@ -12530,7 +12549,7 @@ def list_analyzed_clips(project_root: str) -> Dict[str, Any]:
|
|
|
12530
12549
|
"""List analyzed clips for the bin grid. One row per analysis.json found."""
|
|
12531
12550
|
rows: List[Dict[str, Any]] = []
|
|
12532
12551
|
for slug, clip_dir in _v2_iter_analysis_dirs(project_root):
|
|
12533
|
-
report =
|
|
12552
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12534
12553
|
if report is None:
|
|
12535
12554
|
continue
|
|
12536
12555
|
rows.append(_v2_clip_summary_card(slug, clip_dir, report))
|
|
@@ -12561,7 +12580,7 @@ def get_analyzed_clip(project_root: str, clip_id: str) -> Dict[str, Any]:
|
|
|
12561
12580
|
clip_dir = _v2_find_clip_dir(project_root, clip_id)
|
|
12562
12581
|
if not clip_dir:
|
|
12563
12582
|
return {"success": False, "error": f"No analyzed clip found for id={clip_id}"}
|
|
12564
|
-
report =
|
|
12583
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12565
12584
|
if report is None:
|
|
12566
12585
|
return {"success": False, "error": "analysis.json unreadable"}
|
|
12567
12586
|
visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
|
|
@@ -12598,7 +12617,7 @@ def get_analyzed_clip_shots(project_root: str, clip_id: str) -> Dict[str, Any]:
|
|
|
12598
12617
|
clip_dir = _v2_find_clip_dir(project_root, clip_id)
|
|
12599
12618
|
if not clip_dir:
|
|
12600
12619
|
return {"success": False, "error": f"No analyzed clip found for id={clip_id}"}
|
|
12601
|
-
report =
|
|
12620
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12602
12621
|
if report is None:
|
|
12603
12622
|
return {"success": False, "error": "analysis.json unreadable"}
|
|
12604
12623
|
visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
|
|
@@ -12615,7 +12634,7 @@ def get_analyzed_clip_shot(project_root: str, clip_id: str, shot_index: int) ->
|
|
|
12615
12634
|
clip_dir = _v2_find_clip_dir(project_root, clip_id)
|
|
12616
12635
|
if not clip_dir:
|
|
12617
12636
|
return {"success": False, "error": f"No analyzed clip found for id={clip_id}"}
|
|
12618
|
-
report =
|
|
12637
|
+
report = _v2_load_analysis_db_first(project_root, clip_dir)
|
|
12619
12638
|
if report is None:
|
|
12620
12639
|
return {"success": False, "error": "analysis.json unreadable"}
|
|
12621
12640
|
visual = report.get("visual") if isinstance(report.get("visual"), dict) else {}
|
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.
|
|
83
|
+
VERSION = "2.41.0"
|
|
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.
|
|
14
|
+
VERSION = "2.41.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -11215,6 +11215,25 @@ def _v2_update_field(project_root: str, p: Dict[str, Any], *, entity_type: str)
|
|
|
11215
11215
|
write_result = _v2_write_corrections(path, data)
|
|
11216
11216
|
if not write_result.get("success"):
|
|
11217
11217
|
return write_result
|
|
11218
|
+
# C1 — mirror the correction into the DB-canonical store (row-level
|
|
11219
|
+
# provenance). Best-effort: the sidecar remains authoritative for projects
|
|
11220
|
+
# whose DB predates v9 or lacks ingested rows.
|
|
11221
|
+
db_result: Dict[str, Any]
|
|
11222
|
+
try:
|
|
11223
|
+
from src.utils import analysis_store
|
|
11224
|
+
db_result = analysis_store.record_human_correction(
|
|
11225
|
+
project_root,
|
|
11226
|
+
clip_ref=clip_id or os.path.basename(os.path.dirname(path)),
|
|
11227
|
+
entity_type=entity_type,
|
|
11228
|
+
entity_uuid=entity_uuid,
|
|
11229
|
+
field_path=field_path,
|
|
11230
|
+
value=new_value,
|
|
11231
|
+
author=author,
|
|
11232
|
+
reason=reason,
|
|
11233
|
+
confidence=str(confidence) if confidence else None,
|
|
11234
|
+
)
|
|
11235
|
+
except Exception as exc: # noqa: BLE001 — DB mirror must not break corrections
|
|
11236
|
+
db_result = {"success": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
11218
11237
|
return {
|
|
11219
11238
|
"success": True,
|
|
11220
11239
|
"entity_type": entity_type,
|
|
@@ -11225,6 +11244,7 @@ def _v2_update_field(project_root: str, p: Dict[str, Any], *, entity_type: str)
|
|
|
11225
11244
|
"author": author,
|
|
11226
11245
|
"timestamp": now,
|
|
11227
11246
|
"corrections_path": path,
|
|
11247
|
+
"db": db_result,
|
|
11228
11248
|
}
|
|
11229
11249
|
|
|
11230
11250
|
|
|
@@ -11307,6 +11327,35 @@ def _v2_revert_field(project_root: str, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
11307
11327
|
write_result = _v2_write_corrections(path, data)
|
|
11308
11328
|
if not write_result.get("success"):
|
|
11309
11329
|
return write_result
|
|
11330
|
+
# C1 — mirror the revert into the DB-canonical store. A revert back to a
|
|
11331
|
+
# human value re-records it; a revert to machine-derived clears the human
|
|
11332
|
+
# row so the export overlay falls back to the blob value.
|
|
11333
|
+
db_result: Dict[str, Any]
|
|
11334
|
+
try:
|
|
11335
|
+
from src.utils import analysis_store
|
|
11336
|
+
clip_ref = clip_id or os.path.basename(os.path.dirname(path))
|
|
11337
|
+
if action_taken == "removed (back to machine-derived)" or revert_source != "human":
|
|
11338
|
+
db_result = analysis_store.clear_human_field(
|
|
11339
|
+
project_root,
|
|
11340
|
+
clip_ref=clip_ref,
|
|
11341
|
+
entity_type=entity_type,
|
|
11342
|
+
entity_uuid=entity_uuid,
|
|
11343
|
+
field_path=field_path,
|
|
11344
|
+
author=author,
|
|
11345
|
+
)
|
|
11346
|
+
else:
|
|
11347
|
+
db_result = analysis_store.record_human_correction(
|
|
11348
|
+
project_root,
|
|
11349
|
+
clip_ref=clip_ref,
|
|
11350
|
+
entity_type=entity_type,
|
|
11351
|
+
entity_uuid=entity_uuid,
|
|
11352
|
+
field_path=field_path,
|
|
11353
|
+
value=revert_to,
|
|
11354
|
+
author=author,
|
|
11355
|
+
reason=f"revert by {author}",
|
|
11356
|
+
)
|
|
11357
|
+
except Exception as exc: # noqa: BLE001 — DB mirror must not break corrections
|
|
11358
|
+
db_result = {"success": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
11310
11359
|
return {
|
|
11311
11360
|
"success": True,
|
|
11312
11361
|
"action": action_taken,
|
|
@@ -11314,6 +11363,7 @@ def _v2_revert_field(project_root: str, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
11314
11363
|
"reverted_value": revert_to,
|
|
11315
11364
|
"timestamp": now,
|
|
11316
11365
|
"corrections_path": path,
|
|
11366
|
+
"db": db_result,
|
|
11317
11367
|
}
|
|
11318
11368
|
|
|
11319
11369
|
|
|
@@ -14955,6 +15005,21 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
14955
15005
|
"cancel_batch_job",
|
|
14956
15006
|
"resume_batch_job",
|
|
14957
15007
|
"cleanup_artifacts",
|
|
15008
|
+
# V2 session/panel state + C4 corrections. These checks lived inside
|
|
15009
|
+
# this block since v2.24.0 but were missing from the membership set,
|
|
15010
|
+
# making them unreachable from MCP dispatch (the panel proxied to the
|
|
15011
|
+
# helpers directly, which masked it). Fixed as part of C1 (Phase A).
|
|
15012
|
+
"get_panel_state",
|
|
15013
|
+
"set_panel_state",
|
|
15014
|
+
"session_start_context",
|
|
15015
|
+
"update_shot_field",
|
|
15016
|
+
"update_clip_field",
|
|
15017
|
+
"get_field_history",
|
|
15018
|
+
"revert_field",
|
|
15019
|
+
"list_corrections",
|
|
15020
|
+
# C1 — DB-canonical analysis store.
|
|
15021
|
+
"db_status",
|
|
15022
|
+
"db_ingest",
|
|
14958
15023
|
}:
|
|
14959
15024
|
root = resolve_media_analysis_output_root(
|
|
14960
15025
|
project_name=project_name,
|
|
@@ -15007,6 +15072,13 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15007
15072
|
return _v2_revert_field(project_root, p)
|
|
15008
15073
|
if action == "list_corrections":
|
|
15009
15074
|
return _v2_list_corrections(project_root, p)
|
|
15075
|
+
# C1 — DB-canonical analysis store (Phase A).
|
|
15076
|
+
if action == "db_status":
|
|
15077
|
+
from src.utils import analysis_store
|
|
15078
|
+
return analysis_store.db_status(project_root)
|
|
15079
|
+
if action == "db_ingest":
|
|
15080
|
+
from src.utils import analysis_store
|
|
15081
|
+
return analysis_store.ingest_project(project_root)
|
|
15010
15082
|
if action in {"build_index", "rebuild_index"}:
|
|
15011
15083
|
return build_analysis_index(project_root, index_path=p.get("index_path") or p.get("indexPath"))
|
|
15012
15084
|
if action == "index_status":
|
|
@@ -15408,6 +15480,23 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
15408
15480
|
"cancel_batch_job",
|
|
15409
15481
|
"resume_batch_job",
|
|
15410
15482
|
"cleanup_artifacts",
|
|
15483
|
+
"get_panel_state",
|
|
15484
|
+
"set_panel_state",
|
|
15485
|
+
"session_start_context",
|
|
15486
|
+
"update_shot_field",
|
|
15487
|
+
"update_clip_field",
|
|
15488
|
+
"get_field_history",
|
|
15489
|
+
"revert_field",
|
|
15490
|
+
"list_corrections",
|
|
15491
|
+
"db_status",
|
|
15492
|
+
"db_ingest",
|
|
15493
|
+
"get_caps",
|
|
15494
|
+
"set_caps_preset",
|
|
15495
|
+
"get_usage",
|
|
15496
|
+
"coverage_report",
|
|
15497
|
+
"get_resolve_ai_usage",
|
|
15498
|
+
"get_ai_governance",
|
|
15499
|
+
"set_ai_governance",
|
|
15411
15500
|
])
|
|
15412
15501
|
|
|
15413
15502
|
|