davinci-resolve-mcp 2.212.1 → 2.212.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,32 @@
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.212.2 — failed verification evidence survives the operation envelope
6
+
7
+ ### Fixed
8
+
9
+ - **A payload that carried its own `verification` block won outright, hiding
10
+ contradicting evidence beside it.** `extract_verification` returned any
11
+ pre-shaped `verification` dict untouched, so a top-level `contradiction:
12
+ true`, a `readback.missing` list, or a failed check inside the block itself
13
+ could sit next to `status: "passed"`. Evidence now merges: the explicit
14
+ status, readback misses, post-state readback and property-restore failures
15
+ are all collected, and precedence runs contradiction > failed > partial >
16
+ passed > unverified. Contributed in #195 by @denoise.
17
+ - **Bulk command counts no longer count as verification.** `succeeded` /
18
+ `failed` tallies record what the server sent, not what Resolve honoured; a
19
+ `succeeded: 3, failed: 0` result used to read `verification.status:
20
+ "passed"` with no readback at all. The counts still drive the envelope's
21
+ own `status` (`partial` when both are non-zero), but the verification block
22
+ stays `unverified` until real evidence — a readback — arrives.
23
+
24
+ ### Validation
25
+
26
+ - The PR's regression tests plus two added on landing: bulk counts cannot
27
+ mask a failed readback, and readback evidence is what establishes a pass on
28
+ a bulk result. Full offline suite, the drift guards and the advanced Node
29
+ suite are green. No Resolve behavior changed; live test not required.
30
+
5
31
  ## What's New in v2.212.1 — the networked transport's generated bearer token no longer lands in server.log
6
32
 
7
33
  ### Fixed
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.212.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.212.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#server-modes)
package/README.zh-CN.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.212.1-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.212.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
6
6
  [![npm](https://img.shields.io/npm/v/davinci-resolve-mcp.svg?label=npm&color=CB3837)](https://www.npmjs.com/package/davinci-resolve-mcp)
7
7
  [![API Coverage](https://img.shields.io/badge/API%20Coverage-100%25-brightgreen.svg)](docs/reference/api-coverage.md)
8
8
  [![Tools](https://img.shields.io/badge/MCP%20Tools-36%20(353%20full)-blue.svg)](#服务器模式)
@@ -12,7 +12,7 @@
12
12
  [![Python](https://img.shields.io/badge/python-3.10+-green.svg)](https://www.python.org/downloads/)
13
13
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
14
14
 
15
- > 本翻译对应 v2.212.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.212.2 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
package/install.py CHANGED
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.212.1"
40
+ VERSION = "2.212.2"
41
41
  # Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
42
42
  # Resolve's scripting bridge loads into newer interpreters on recent builds
43
43
  # (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.212.1",
3
+ "version": "2.212.2",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.212.1"
90
+ VERSION = "2.212.2"
91
91
  logger = logging.getLogger("davinci-resolve-mcp")
92
92
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
93
93
  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 353-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.212.1"
14
+ VERSION = "2.212.2"
15
15
 
16
16
  import base64
17
17
  import os
@@ -166,75 +166,57 @@ def extract_verification(raw: Any) -> Dict[str, Any]:
166
166
  if not isinstance(raw, dict):
167
167
  return unverified
168
168
 
169
- # An impl that already speaks this shape wins outright.
170
169
  existing = raw.get("verification")
171
- if isinstance(existing, dict) and existing:
172
- merged = dict(existing)
173
- merged.setdefault(
174
- "status", "passed" if existing.get("verified") else "unverified")
175
- merged.setdefault("checks", [])
176
- merged.setdefault("contradiction", False)
177
- return merged
178
-
179
- checks: List[Dict[str, Any]] = []
180
- status = "unverified"
181
- contradiction = False
170
+ existing = existing if isinstance(existing, dict) else {}
171
+ checks = list(existing.get("checks") or [])
172
+ statuses = []
173
+ existing_status = existing.get("status")
174
+ if existing_status in {"passed", "failed", "partial", "contradiction"}:
175
+ statuses.append(existing_status)
176
+ elif existing.get("verified") is True:
177
+ statuses.append("passed")
178
+ elif existing.get("verified") is False:
179
+ statuses.append("failed")
180
+ contradiction = existing.get("contradiction") is True or raw.get("contradiction") is True
182
181
 
183
182
  readback = raw.get("readback")
184
- if isinstance(readback, dict):
185
- missing = readback.get("missing")
186
- if isinstance(missing, list):
187
- checks.append({
188
- "check": "readback_verification",
189
- "passed": not missing,
190
- "missing_items": len(missing),
191
- })
192
- status = "passed" if not missing else "failed"
193
-
194
- # verify_by_readback's own shape: a mutation that reported success while the
195
- # post-state disagrees is a contradiction, this repo's single most valuable
196
- # reliability signal — it must not be flattened into a plain failure.
197
- if "verified" in raw:
198
- verified = bool(raw["verified"])
199
- contradiction = bool(raw.get("contradiction"))
200
- checks.append({
201
- "check": "readback_post_state",
202
- "passed": verified,
203
- "contradiction": contradiction,
204
- "observed": raw.get("observed"),
205
- })
206
- status = "contradiction" if contradiction else ("passed" if verified else "failed")
183
+ if isinstance(readback, dict) and isinstance(readback.get("missing"), list):
184
+ missing = readback["missing"]
185
+ checks.append({"check": "readback_verification", "passed": not missing,
186
+ "missing_items": len(missing)})
187
+ statuses.append("failed" if missing else "passed")
188
+
189
+ if type(raw.get("verified")) is bool:
190
+ checks.append({"check": "readback_post_state", "passed": raw["verified"],
191
+ "contradiction": contradiction, "observed": raw.get("observed")})
192
+ statuses.append("passed" if raw["verified"] else "failed")
207
193
 
208
194
  if "property_restore_failures" in raw:
209
195
  failures = _as_int(raw.get("property_restore_failures"))
210
- checks.append({
211
- "check": "property_restore",
212
- "passed": failures == 0,
213
- "restored_items": _as_int(raw.get("properties_restored_items")),
214
- "failures": failures,
215
- })
216
- if failures and status in ("passed", "unverified"):
217
- status = "partial"
218
-
219
- succeeded, failed = raw.get("succeeded"), raw.get("failed")
220
- if isinstance(succeeded, int) and isinstance(failed, int):
221
- checks.append({
222
- "check": "bulk_operations",
223
- "passed": failed == 0,
224
- "succeeded": succeeded,
225
- "failed": failed,
226
- })
227
- if status == "unverified":
228
- if failed == 0 and succeeded > 0:
229
- status = "passed"
230
- elif succeeded > 0:
231
- status = "partial"
232
- elif failed > 0:
233
- status = "failed"
234
-
235
- if not checks:
236
- return unverified
237
- return {"status": status, "checks": checks, "contradiction": contradiction}
196
+ checks.append({"check": "property_restore", "passed": failures == 0,
197
+ "restored_items": _as_int(raw.get("properties_restored_items")),
198
+ "failures": failures})
199
+ if failures:
200
+ statuses.append("partial")
201
+
202
+ for check in checks:
203
+ if isinstance(check, dict):
204
+ contradiction = contradiction or check.get("contradiction") is True
205
+ if check.get("passed") is False:
206
+ statuses.append("partial" if check.get("check") == "property_restore" else "failed")
207
+ # Command counts are not evidence that Resolve honored those commands.
208
+ if contradiction or "contradiction" in statuses:
209
+ status = "contradiction"
210
+ contradiction = True
211
+ elif "failed" in statuses:
212
+ status = "failed"
213
+ elif "partial" in statuses:
214
+ status = "partial"
215
+ elif "passed" in statuses:
216
+ status = "passed"
217
+ else:
218
+ status = "unverified"
219
+ return {**existing, "status": status, "checks": checks, "contradiction": contradiction}
238
220
 
239
221
 
240
222
  # ─── Changes ─────────────────────────────────────────────────────────────────