davinci-resolve-mcp 4.7.0 → 4.7.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,27 @@
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 v4.7.1 — a token handed back as `confirmToken` can be redeemed
6
+
7
+ ### Fixed
8
+
9
+ - **A camelCase client could never execute a confirm-gated action.** ([#238](https://github.com/samuelgursky/davinci-resolve-mcp/pull/238), @Dev-next-gen)
10
+ `ConfirmTokenStore.consume` accepts the token as `confirm_token` or `confirmToken`,
11
+ and the twenty-eight gated compound actions treat either spelling as "the caller
12
+ holds a token" — but `fingerprint()` stripped only the snake_case key. A token
13
+ echoed back as `confirmToken` stayed in the params, the request hashed differently
14
+ after issuance than before, and redemption failed with
15
+ `CONFIRM_TOKEN_FINGERPRINT_MISMATCH`. Because the token is popped before that check
16
+ it was already spent, so the retry the remediation asks for reported
17
+ `CONFIRM_TOKEN_INVALID` instead, and re-issuing looped back to the same mismatch.
18
+ That was a closed loop in front of `TimelineItem.CopyGrades` and project deletion
19
+ on both servers, which share the store. Reproduced on `main` before merging.
20
+ Both spellings now live in one `TOKEN_PARAM_KEYS` tuple that `fingerprint` strips
21
+ and `consume` reads, so the pair cannot drift apart again. A camelCase token
22
+ presented against different params is still refused — stripping the key does not
23
+ loosen the gate. Guard tests: `tests/test_confirm_token_camel_case.py`, 5 of 6
24
+ failing on the previous code.
25
+
5
26
  ## What's New in v4.7.0 — the Resolve 21.1 transcription and timeline-item type reads are complete
6
27
 
7
28
  The read-only pick from the 21.1 tracker ([#207](https://github.com/samuelgursky/davinci-resolve-mcp/issues/207)), by @legionsound in [#237](https://github.com/samuelgursky/davinci-resolve-mcp/pull/237). Granular 387 → 389; compound unchanged at 37.
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-4.7.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.7.1-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-37%20(389%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-4.7.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.7.1-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-37%20(389%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
- > 本翻译对应 v4.7.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v4.7.1 版 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 = "4.7.0"
40
+ VERSION = "4.7.1"
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": "4.7.0",
3
+ "version": "4.7.1",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -93,7 +93,7 @@ if not logging.getLogger().handlers:
93
93
  handlers=[logging.StreamHandler()],
94
94
  )
95
95
 
96
- VERSION = "4.7.0"
96
+ VERSION = "4.7.1"
97
97
  logger = logging.getLogger("davinci-resolve-mcp")
98
98
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
99
99
  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 377-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "4.7.0"
14
+ VERSION = "4.7.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -39,6 +39,13 @@ DEFAULT_TTL_SECONDS = 300
39
39
  PREFERENCE_KEY = "require_confirm_token"
40
40
  PREFERENCE_SECTION = "destructive"
41
41
 
42
+ #: Both spellings a caller may hand the token back under. `consume` reads either,
43
+ #: and every gated action in the compound server treats either as "a token is
44
+ #: present", so `fingerprint` has to ignore both: a spelling the fingerprint does
45
+ #: not strip is an ordinary param, and the request looks different after issuance
46
+ #: than it did before.
47
+ TOKEN_PARAM_KEYS = ("confirm_token", "confirmToken")
48
+
42
49
 
43
50
  def gate_required_from(preferences: Optional[Dict[str, Any]]) -> bool:
44
51
  """Read `destructive.require_confirm_token` out of a preferences payload.
@@ -109,9 +116,10 @@ class ConfirmTokenStore:
109
116
  payload = {"action": action, "params": params or {}}
110
117
  # Strip the token itself if the caller is echoing it back to us, so the
111
118
  # fingerprint of "the request" is the same before and after issuance.
112
- if isinstance(payload["params"], dict) and "confirm_token" in payload["params"]:
119
+ # Both spellings, not just the snake_case one: `consume` accepts either.
120
+ if isinstance(payload["params"], dict):
113
121
  payload["params"] = {
114
- k: v for k, v in payload["params"].items() if k != "confirm_token"
122
+ k: v for k, v in payload["params"].items() if k not in TOKEN_PARAM_KEYS
115
123
  }
116
124
  try:
117
125
  blob = json.dumps(payload, sort_keys=True, default=str)
@@ -181,7 +189,8 @@ class ConfirmTokenStore:
181
189
  """
182
190
  if not self.required():
183
191
  return None
184
- token = (params or {}).get("confirm_token") or (params or {}).get("confirmToken")
192
+ supplied = params or {}
193
+ token = next((supplied.get(key) for key in TOKEN_PARAM_KEYS if supplied.get(key)), None)
185
194
  if not token:
186
195
  return None # Caller is expected to call issue() in this case.
187
196
  with self.lock: