davinci-resolve-mcp 4.5.0 → 4.5.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,49 @@
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.5.1 — the safe-mode refusal reaches the caller on 27 more tools
6
+
7
+ v4.5.0 gave the granular server a working safe-mode gate. On 27 tools it then threw
8
+ the answer away.
9
+
10
+ ### Fixed
11
+
12
+ - **A blocked call raised `ToolError` instead of returning the refusal.** FastMCP
13
+ builds an output schema from a tool's return annotation and validates against it,
14
+ so handing the block dict to a tool annotated `-> str` failed validation:
15
+
16
+ ```
17
+ ToolError: ...Output / result / Input should be a valid string
18
+ ```
19
+
20
+ The caller received a generic execution error carrying none of the
21
+ `SAFE_MODE_BLOCKED` code, reason or remediation — the gate fired correctly and its
22
+ answer was destroyed on the way out. Measured on the real `--full` entry path
23
+ against shipped v4.5.0: `clear_folder_transcription` raised rather than refusing.
24
+
25
+ It lands hardest exactly where it matters. The HIGH-rated string-returning tools
26
+ are the calls safe mode exists to stop: `clear_folder_transcription`,
27
+ `unlink_proxy_media`, `replace_clip`, `delete_keyframe`, `quit_app`, `restart_app`.
28
+
29
+ A `-> str` tool is now refused with the message and its remediation as text,
30
+ prefixed with the code. That loses the machine-readable field, which is a real
31
+ cost and worth stating plainly — but a refusal the client can read beats a
32
+ `ToolError` that discards it, and it is the only shape that tool's own schema will
33
+ accept. The 105 tools annotated `-> Dict[str, Any]` or `-> dict` keep the
34
+ structured envelope unchanged.
35
+
36
+ ### Validation
37
+
38
+ - Full offline suite: **3,684 passed, 1 skipped, 0 failed**, 1,412 subtests.
39
+ - Both new assertions were confirmed to **fail with the fix reverted**, then pass on
40
+ restore — the guard is not vacuous.
41
+ - Verified end-to-end through the real registry with `destructive.safe_mode` on: a
42
+ `-> str` tool returns readable refusal text, a `-> dict` tool returns the full
43
+ envelope, and `allow_risky_operation=true` still lets a permitted call through on
44
+ both paths.
45
+ - A static check now walks every destructive-decorated tool annotated `-> str` and
46
+ asserts the hook would hand it a string, so tool number 28 cannot reintroduce this.
47
+
5
48
  ## What's New in v4.5.0 — safe mode and the audit log reach the granular server
6
49
 
7
50
  v4.4.1 froze 131 destructive-hinted granular tools in a backlog and said plainly
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.5.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.5.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(387%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.5.0-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.5.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(387%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.5.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v4.5.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.5.0"
40
+ VERSION = "4.5.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.5.0",
3
+ "version": "4.5.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.5.0"
96
+ VERSION = "4.5.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.5.0"
14
+ VERSION = "4.5.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -991,6 +991,39 @@ def _with_override_param(fn: Callable[..., Any]) -> Tuple[inspect.Signature, boo
991
991
  return sig.replace(parameters=head + [extra] + tail), True
992
992
 
993
993
 
994
+ def _returns_plain_string(fn: Callable[..., Any]) -> bool:
995
+ """Does this tool declare `-> str`?
996
+
997
+ FastMCP builds an output schema from the return annotation and validates
998
+ against it, so handing a dict to a tool annotated `-> str` raises ToolError
999
+ and the caller never sees the refusal — only a generic execution error with
1000
+ none of its code or remediation. 27 granular tools declare `-> str`, and the
1001
+ HIGH-risk ones among them are exactly the calls safe mode exists to stop.
1002
+ """
1003
+ annotation = getattr(fn, "__annotations__", {}).get("return")
1004
+ return annotation is str or annotation in ("str", "'str'")
1005
+
1006
+
1007
+ def _block_for(fn: Callable[..., Any], block: Dict[str, Any]) -> Any:
1008
+ """The refusal, shaped to the contract the tool declares.
1009
+
1010
+ A string-returning tool gets the message and its remediation as text. That
1011
+ loses the machine-readable `code`, which is a real cost — but a refusal the
1012
+ client can read beats a ToolError that discards it entirely, and it is the
1013
+ only shape that tool's own schema will accept.
1014
+ """
1015
+ if not _returns_plain_string(fn):
1016
+ return block
1017
+ error = block.get("error") or {}
1018
+ message = error.get("message") or "Blocked by destructive.safe_mode."
1019
+ remediation = error.get("remediation")
1020
+ code = error.get("code")
1021
+ parts = [f"{code}: {message}" if code else message]
1022
+ if remediation:
1023
+ parts.append(remediation)
1024
+ return " ".join(parts)
1025
+
1026
+
994
1027
  def granular_destructive_op(
995
1028
  tool_name: Optional[str] = None,
996
1029
  *,
@@ -1043,13 +1076,13 @@ def granular_destructive_op(
1043
1076
  params=params,
1044
1077
  reason="safe_mode",
1045
1078
  )
1046
- return _security_block_response(
1079
+ return _block_for(fn, _security_block_response(
1047
1080
  operation_id=operation_id,
1048
1081
  tool_name="granular",
1049
1082
  action=action,
1050
1083
  risk_level=level,
1051
1084
  override_hint=f"{GRANULAR_OVERRIDE_PARAM}=true",
1052
- )
1085
+ ))
1053
1086
 
1054
1087
  result = fn(*args, **kwargs)
1055
1088
  _audit_security_event(