davinci-resolve-mcp 2.35.1 → 2.35.2

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,19 @@
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.35.2
6
+
7
+ Verification observability and a validator consolidation.
8
+
9
+ - **Added** `resolve_control(action="verification_stats")` returns a
10
+ process-level tally of readback-verification outcomes
11
+ (verified / contradicted / unverified) since server start. A rising
12
+ `contradicted` count means the Resolve API reported success but a readback
13
+ disagreed. No connection required.
14
+ - **Changed** `open_page` now validates its `page` argument through the declarative
15
+ contract layer (`contracts.validate`) instead of a hand-written enum check —
16
+ behavior unchanged; part of folding scattered validation into one place.
17
+
5
18
  ## What's New in v2.35.1
6
19
 
7
20
  - **Added** `media_pool_item(action="extract_frames", clip_id, timestamps, output_dir?)`
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.35.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
3
+ [![Version](https://img.shields.io/badge/version-2.35.2-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-32%20(341%20full)-blue.svg)](#server-modes)
package/docs/SKILL.md CHANGED
@@ -320,6 +320,8 @@ Key actions:
320
320
  - `get_version` — returns `{product, version, version_string}`
321
321
  - `api_truth(query?)` — look up behaviorally-verified facts about quirky/unreliable
322
322
  Resolve API behavior (no connection needed); filter by substring
323
+ - `verification_stats` — readback-verification tally (verified/contradicted/
324
+ unverified) since server start (no connection needed)
323
325
  - `get_page` / `open_page(page)` — read or switch the active page
324
326
  - `get_keyframe_mode` / `set_keyframe_mode(mode)`
325
327
  - `get_fairlight_presets` — Resolve 20.2.2+; returns available Fairlight
package/install.py CHANGED
@@ -35,7 +35,7 @@ from src.utils.update_check import (
35
35
 
36
36
  # ─── Version ──────────────────────────────────────────────────────────────────
37
37
 
38
- VERSION = "2.35.1"
38
+ VERSION = "2.35.2"
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.35.1",
3
+ "version": "2.35.2",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -80,7 +80,7 @@ if not logging.getLogger().handlers:
80
80
  handlers=[logging.StreamHandler()],
81
81
  )
82
82
 
83
- VERSION = "2.35.1"
83
+ VERSION = "2.35.2"
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.35.1"
14
+ VERSION = "2.35.2"
15
15
 
16
16
  import base64
17
17
  import os
@@ -48,7 +48,7 @@ from src.utils.contracts import validate as _validate_params
48
48
  from src.utils.cut_ir import build_cut_list as _build_cut_list
49
49
  from src.utils.page_lock import open_page_serialized as _open_page_serialized
50
50
  from src.utils.proc import safe_run
51
- from src.utils.readback import verify_by_readback
51
+ from src.utils.readback import verify_by_readback, verification_stats as _verification_stats
52
52
  from src.utils.update_check import (
53
53
  check_for_updates,
54
54
  clear_update_prompt_preferences,
@@ -10632,6 +10632,8 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
10632
10632
  clear_mcp_update_preferences() -> {success, version, update, decision}
10633
10633
  api_truth(query?) -> {verified_on, count, facts} — look up behaviorally-verified
10634
10634
  facts about quirky/unreliable Resolve API behavior (no connection needed).
10635
+ verification_stats() -> {stats} — readback-verification tally
10636
+ (verified/contradicted/unverified) since server start (no connection needed).
10635
10637
  get_page() -> {page}
10636
10638
  open_page(page) -> {success} — page: edit, cut, color, fusion, fairlight, deliver
10637
10639
  get_keyframe_mode() -> {mode}
@@ -10656,6 +10658,11 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
10656
10658
  if action == "api_truth":
10657
10659
  facts = lookup_api_truth(p.get("query"))
10658
10660
  return {"verified_on": _API_TRUTH_VERIFIED_ON, "count": len(facts), "facts": facts}
10661
+ if action == "verification_stats":
10662
+ # Process-level readback-verification tally — no connection needed.
10663
+ stats = _verification_stats()
10664
+ return {"stats": stats, "note": "Counts since server start. A rising "
10665
+ "'contradicted' count means the API reported success but a readback disagreed."}
10659
10666
 
10660
10667
  # Control-panel actions don't require Resolve to be running.
10661
10668
  if action == "open_control_panel":
@@ -10720,12 +10727,15 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
10720
10727
  elif action == "get_page":
10721
10728
  return {"page": r.GetCurrentPage()}
10722
10729
  elif action == "open_page":
10723
- valid_pages = ["media", "cut", "edit", "color", "fusion", "fairlight", "deliver"]
10724
- if p["page"] not in valid_pages:
10725
- return _err(f"Invalid page '{p['page']}'. Valid pages: {', '.join(valid_pages)}")
10730
+ err, clean = _validate_params(p, {
10731
+ "page": {"enum": ["media", "cut", "edit", "color", "fusion", "fairlight", "deliver"],
10732
+ "required": True},
10733
+ })
10734
+ if err:
10735
+ return _err(err)
10726
10736
  # Serialize page switches so concurrent agents can't flip the single
10727
10737
  # globally-active page underneath each other.
10728
- return {"success": bool(_open_page_serialized(r, p["page"]))}
10738
+ return {"success": bool(_open_page_serialized(r, clean["page"]))}
10729
10739
  elif action == "get_keyframe_mode":
10730
10740
  return {"mode": r.GetKeyframeMode()}
10731
10741
  elif action == "set_keyframe_mode":
@@ -10746,7 +10756,7 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
10746
10756
  return missing
10747
10757
  r.DisableBackgroundTasksForCurrentResolveSession()
10748
10758
  return _ok()
10749
- return _unknown(action, ["launch","get_version","api_truth","mcp_update_status","set_mcp_update_policy","ignore_mcp_update","snooze_mcp_update","clear_mcp_update_preferences","get_page","open_page","get_keyframe_mode","set_keyframe_mode","quit","get_fairlight_presets","set_high_priority","disable_background_tasks_for_current_session","open_control_panel","control_panel_status","close_control_panel","save_state","restore_state"])
10759
+ return _unknown(action, ["launch","get_version","api_truth","verification_stats","mcp_update_status","set_mcp_update_policy","ignore_mcp_update","snooze_mcp_update","clear_mcp_update_preferences","get_page","open_page","get_keyframe_mode","set_keyframe_mode","quit","get_fairlight_presets","set_high_priority","disable_background_tasks_for_current_session","open_control_panel","control_panel_status","close_control_panel","save_state","restore_state"])
10750
10760
 
10751
10761
 
10752
10762
  # ─── V2 C4: Per-field corrections with provenance + changelog ────────────────
@@ -16,6 +16,21 @@ from typing import Any, Callable, Dict, Optional
16
16
 
17
17
  logger = logging.getLogger("davinci-resolve-mcp")
18
18
 
19
+ # Process-level tally of verification outcomes — lightweight observability into
20
+ # how often the Resolve API's self-reported success matches reality. A rising
21
+ # `contradicted` count is the signal worth watching.
22
+ _STATS = {"total": 0, "verified": 0, "contradicted": 0, "unverified": 0}
23
+
24
+
25
+ def verification_stats():
26
+ """Return a copy of the process-level verification tally."""
27
+ return dict(_STATS)
28
+
29
+
30
+ def reset_verification_stats():
31
+ for k in _STATS:
32
+ _STATS[k] = 0
33
+
19
34
 
20
35
  def verify_by_readback(
21
36
  mutate: Callable[[], Any],
@@ -56,6 +71,7 @@ def verify_by_readback(
56
71
 
57
72
  # A contradiction — reported success but the readback disagrees — is the
58
73
  # signal worth surfacing loudly.
74
+ _STATS["total"] += 1
59
75
  if result.get("success_raw") and not result.get("verified"):
60
76
  logger.warning(
61
77
  "readback contradiction%s: API reported success but post-state "
@@ -64,7 +80,12 @@ def verify_by_readback(
64
80
  f" (intent={intent})" if intent else "",
65
81
  )
66
82
  result["contradiction"] = True
83
+ _STATS["contradicted"] += 1
67
84
  else:
68
85
  result["contradiction"] = False
86
+ if result.get("verified"):
87
+ _STATS["verified"] += 1
88
+ else:
89
+ _STATS["unverified"] += 1
69
90
 
70
91
  return result