davinci-resolve-mcp 2.68.0 → 2.68.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 +48 -0
- package/README.md +1 -1
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/granular/common.py +1 -1
- package/src/server.py +186 -36
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,54 @@
|
|
|
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.68.1
|
|
6
|
+
|
|
7
|
+
Bug fix. A missing tool argument now returns the structured error envelope the
|
|
8
|
+
rest of the surface uses, instead of escaping as a raw `KeyError`.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **A missing argument reached the client as an unroutable crash.** Actions read
|
|
13
|
+
`p["track_type"]` directly; when the key was absent the resulting `KeyError`
|
|
14
|
+
escaped the action, escaped the tool, and arrived as
|
|
15
|
+
`ToolError: Error executing tool timeline: 'track_type'` — no code, no
|
|
16
|
+
category, no remediation. Walking the whole surface found **99 of 512 declared
|
|
17
|
+
actions** failing this way across 16 tools (`timeline`, `graph`, `render`,
|
|
18
|
+
`media_pool`, `media_storage`, `fusion_comp`, `folder`, `gallery_stills`,
|
|
19
|
+
`layout_presets`, `project_settings`, `project_manager_*`, `render_presets`,
|
|
20
|
+
`resolve_control`, `timeline_markers`). They now return, for example:
|
|
21
|
+
|
|
22
|
+
{"error": {"message": "'track_type' is required",
|
|
23
|
+
"code": "MISSING_TRACK_TYPE", "category": "invalid_input",
|
|
24
|
+
"retryable": false, "remediation": "...", "state": {...}}}
|
|
25
|
+
|
|
26
|
+
`retryable: false` matters as much as the code: an input error the caller must
|
|
27
|
+
fix was previously reported through a path that defaulted to retryable, which
|
|
28
|
+
sends an agent into a loop it cannot win.
|
|
29
|
+
|
|
30
|
+
The mechanism is a params dict whose missing keys raise a dedicated
|
|
31
|
+
`KeyError` subclass, caught at the tool boundary. Catching plain `KeyError`
|
|
32
|
+
there would have been simpler and wrong — it cannot tell a missing argument
|
|
33
|
+
from an internal lookup failure on some other dict, so our own bugs would have
|
|
34
|
+
been relabelled as the caller's mistake. `contracts.validate` remains the
|
|
35
|
+
preferred tool where a type, range or enum also needs checking, and the actions
|
|
36
|
+
already using it are unchanged.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `tests/test_tool_argument_validation.py` walks **every declared action** with an
|
|
41
|
+
empty params dict and asserts none leaks a `KeyError`. Reading the source cannot
|
|
42
|
+
find this class — `p[...]` after a guard is correct and `p[...]` without one is a
|
|
43
|
+
bug — so the walk executes each branch against a stub Resolve. It fails on the
|
|
44
|
+
pre-fix tree and runs in 2 s.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- `test_doc_tool_counts` counts `@mcp.tool()` decorators from the parsed syntax
|
|
49
|
+
tree rather than by matching the text. A docstring explaining where the
|
|
50
|
+
decorator has to sit was counted as a 35th tool; prose that mentions a
|
|
51
|
+
decorator is not a tool.
|
|
52
|
+
|
|
5
53
|
## What's New in v2.68.0
|
|
6
54
|
|
|
7
55
|
Six engines that let an agent judge its own output before shipping it — audio
|
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/install.py
CHANGED
|
@@ -36,7 +36,7 @@ from src.utils.update_check import (
|
|
|
36
36
|
|
|
37
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
38
38
|
|
|
39
|
-
VERSION = "2.68.
|
|
39
|
+
VERSION = "2.68.1"
|
|
40
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
41
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
42
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
package/package.json
CHANGED
package/src/granular/common.py
CHANGED
|
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
|
|
|
85
85
|
handlers=[logging.StreamHandler()],
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
VERSION = "2.68.
|
|
88
|
+
VERSION = "2.68.1"
|
|
89
89
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
90
90
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
91
91
|
logger.info(f"Detected platform: {get_platform()}")
|
package/src/server.py
CHANGED
|
@@ -11,11 +11,13 @@ Usage:
|
|
|
11
11
|
python src/server.py --full # Start the 341-tool granular server instead
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
VERSION = "2.68.
|
|
14
|
+
VERSION = "2.68.1"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
18
18
|
import sys
|
|
19
|
+
import functools
|
|
20
|
+
import inspect
|
|
19
21
|
import json
|
|
20
22
|
import logging
|
|
21
23
|
import math
|
|
@@ -1225,6 +1227,120 @@ _CATEGORY_RETRYABLE_DEFAULT: Dict[str, bool] = {
|
|
|
1225
1227
|
_RETRYABLE_UNSET = object()
|
|
1226
1228
|
|
|
1227
1229
|
|
|
1230
|
+
class _MissingParam(KeyError):
|
|
1231
|
+
"""A tool action subscripted a parameter the caller did not supply.
|
|
1232
|
+
|
|
1233
|
+
Subclasses `KeyError` so any existing `except KeyError` in the codebase keeps
|
|
1234
|
+
behaving as it did; what it adds is the ability to tell a *missing argument*
|
|
1235
|
+
apart from a KeyError on some other dict — a Resolve settings map, a parsed
|
|
1236
|
+
JSON body — which would otherwise be reported to the caller as "you forgot an
|
|
1237
|
+
argument" when the real fault is ours.
|
|
1238
|
+
"""
|
|
1239
|
+
|
|
1240
|
+
|
|
1241
|
+
class _Params(dict):
|
|
1242
|
+
"""The params dict, wired so a missing key is a diagnosable refusal.
|
|
1243
|
+
|
|
1244
|
+
Actions have historically reached into `p["track_type"]` directly. When the
|
|
1245
|
+
key is absent that raises a bare `KeyError`, which escapes the action, escapes
|
|
1246
|
+
the tool, and reaches the client as
|
|
1247
|
+
`ToolError: Error executing tool timeline: 'track_type'` — no code, no
|
|
1248
|
+
category, no remediation, and nothing an agent can route on. Measured across
|
|
1249
|
+
the whole surface: **99 of 512 declared actions** failed that way.
|
|
1250
|
+
|
|
1251
|
+
Two ways to fix that were available. Rewriting all 99 sites to use
|
|
1252
|
+
`contracts.validate` gives the best individual errors but is 99 chances to get
|
|
1253
|
+
a spec wrong, and leaves the 100th site to be written next month. Catching
|
|
1254
|
+
`KeyError` at the tool boundary closes the class in one place but cannot tell
|
|
1255
|
+
which dict raised, so an unrelated internal KeyError would be mislabelled as
|
|
1256
|
+
the caller's mistake.
|
|
1257
|
+
|
|
1258
|
+
This is the third option: keep the boundary catch, but make the *parameters*
|
|
1259
|
+
raise something only they can raise. False positives become impossible by
|
|
1260
|
+
construction.
|
|
1261
|
+
|
|
1262
|
+
`contracts.validate` remains the better tool wherever a type, a range or an
|
|
1263
|
+
enum also needs checking, and the actions that already use it keep doing so.
|
|
1264
|
+
This is deliberately **not** a migration of the 99 — rewriting them would add
|
|
1265
|
+
99 opportunities to write a spec wrong without improving the defect being
|
|
1266
|
+
fixed here, and it would still leave the next new action unguarded. What is
|
|
1267
|
+
lost by not migrating is bounded: a *wrong* value still reaches Resolve, which
|
|
1268
|
+
answers None or False, so the result is a truthful failure rather than a
|
|
1269
|
+
misleading success.
|
|
1270
|
+
"""
|
|
1271
|
+
|
|
1272
|
+
def __missing__(self, key):
|
|
1273
|
+
raise _MissingParam(key)
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
def _params(params: Optional[Dict[str, Any]]) -> "_Params":
|
|
1277
|
+
"""Every tool body starts here, so every action gets the diagnosable dict.
|
|
1278
|
+
|
|
1279
|
+
Note `_Params(params or {})` rather than a truthiness dance: an empty
|
|
1280
|
+
`_Params` is falsy like any empty dict, so `p = params or {}` would have
|
|
1281
|
+
quietly handed back a *plain* dict in exactly the no-arguments case this
|
|
1282
|
+
exists to serve.
|
|
1283
|
+
"""
|
|
1284
|
+
return _Params(params or {})
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def _missing_param_error(exc: _MissingParam, action: str) -> Dict[str, Any]:
|
|
1288
|
+
"""Turn a missing parameter into the envelope the rest of the surface uses."""
|
|
1289
|
+
name = exc.args[0] if exc.args else "parameter"
|
|
1290
|
+
slug = re.sub(r"[^A-Z0-9]+", "_", str(name).upper()).strip("_") or "PARAMETER"
|
|
1291
|
+
return _err(
|
|
1292
|
+
f"'{name}' is required",
|
|
1293
|
+
code=f"MISSING_{slug}",
|
|
1294
|
+
category="invalid_input",
|
|
1295
|
+
remediation=f"pass {name} in params, e.g. params={{\"{name}\": ...}}",
|
|
1296
|
+
state={"action": action, "missing": str(name)},
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
def _guarded_action_name(args, kwargs) -> str:
|
|
1301
|
+
"""The `action` this call was for, wherever it was passed."""
|
|
1302
|
+
if "action" in kwargs:
|
|
1303
|
+
return str(kwargs["action"])
|
|
1304
|
+
return str(args[0]) if args else "?"
|
|
1305
|
+
|
|
1306
|
+
|
|
1307
|
+
def _guard_missing_params(fn):
|
|
1308
|
+
"""Tool decorator: report a missing parameter instead of leaking a KeyError.
|
|
1309
|
+
|
|
1310
|
+
Sits *under* `@mcp.tool()` so FastMCP registers the guarded callable.
|
|
1311
|
+
`functools.wraps` keeps the name, docstring and signature FastMCP builds the
|
|
1312
|
+
tool schema from.
|
|
1313
|
+
|
|
1314
|
+
Two things this has to preserve, both learned by breaking them:
|
|
1315
|
+
|
|
1316
|
+
- **Async tools must stay coroutine functions.** `media_analysis` is `async`,
|
|
1317
|
+
and a synchronous wrapper around it returned an un-awaited coroutine that
|
|
1318
|
+
FastMCP could not use — the tool stopped answering entirely, taking its 71
|
|
1319
|
+
actions with it. `inspect.iscoroutinefunction` decides which wrapper to
|
|
1320
|
+
build, and a test now pins that async tools are still async afterwards.
|
|
1321
|
+
- **The signature is not (action, params).** `media_analysis` also takes
|
|
1322
|
+
`ctx: Optional[Context]`, so the wrapper forwards `*args, **kwargs` rather
|
|
1323
|
+
than naming parameters it does not know about.
|
|
1324
|
+
"""
|
|
1325
|
+
if inspect.iscoroutinefunction(fn):
|
|
1326
|
+
@functools.wraps(fn)
|
|
1327
|
+
async def wrapper(*args, **kwargs):
|
|
1328
|
+
try:
|
|
1329
|
+
return await fn(*args, **kwargs)
|
|
1330
|
+
except _MissingParam as exc:
|
|
1331
|
+
return _missing_param_error(exc, _guarded_action_name(args, kwargs))
|
|
1332
|
+
else:
|
|
1333
|
+
@functools.wraps(fn)
|
|
1334
|
+
def wrapper(*args, **kwargs):
|
|
1335
|
+
try:
|
|
1336
|
+
return fn(*args, **kwargs)
|
|
1337
|
+
except _MissingParam as exc:
|
|
1338
|
+
return _missing_param_error(exc, _guarded_action_name(args, kwargs))
|
|
1339
|
+
|
|
1340
|
+
wrapper.__wrapped_by_missing_param_guard__ = True
|
|
1341
|
+
return wrapper
|
|
1342
|
+
|
|
1343
|
+
|
|
1228
1344
|
def _err(message, *, code=None, category=None, retryable=_RETRYABLE_UNSET,
|
|
1229
1345
|
remediation=None, reason=None, state=None):
|
|
1230
1346
|
"""Return a structured error envelope.
|
|
@@ -12687,6 +12803,7 @@ def _setup_clear_defaults(keys: Any, dry_run: bool) -> Dict[str, Any]:
|
|
|
12687
12803
|
|
|
12688
12804
|
|
|
12689
12805
|
@mcp.tool()
|
|
12806
|
+
@_guard_missing_params
|
|
12690
12807
|
def setup(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
12691
12808
|
"""Configure MCP conversational defaults and setup preferences.
|
|
12692
12809
|
|
|
@@ -12700,7 +12817,7 @@ def setup(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any
|
|
|
12700
12817
|
media_analysis.*: analysis, metadata, marker, reporting, and workflow defaults
|
|
12701
12818
|
updates.*: MCP update policy, interval, and snooze defaults
|
|
12702
12819
|
"""
|
|
12703
|
-
p = params
|
|
12820
|
+
p = _params(params)
|
|
12704
12821
|
if action in {"schema", "capabilities", "options"}:
|
|
12705
12822
|
return {
|
|
12706
12823
|
"actions": ["schema", "get_defaults", "set_defaults", "clear_defaults"],
|
|
@@ -12840,6 +12957,7 @@ def setup(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any
|
|
|
12840
12957
|
|
|
12841
12958
|
|
|
12842
12959
|
@mcp.tool()
|
|
12960
|
+
@_guard_missing_params
|
|
12843
12961
|
def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
12844
12962
|
"""App-level DaVinci Resolve operations.
|
|
12845
12963
|
|
|
@@ -12882,7 +13000,7 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
12882
13000
|
restore_state(state_token) -> {success, restored: {...}}
|
|
12883
13001
|
— Returns Resolve to a previously-saved state.
|
|
12884
13002
|
"""
|
|
12885
|
-
p = params
|
|
13003
|
+
p = _params(params)
|
|
12886
13004
|
|
|
12887
13005
|
# api_truth is a static knowledge lookup — no Resolve connection needed.
|
|
12888
13006
|
if action == "api_truth":
|
|
@@ -13859,6 +13977,7 @@ def _close_control_panel() -> Dict[str, Any]:
|
|
|
13859
13977
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
13860
13978
|
|
|
13861
13979
|
@mcp.tool()
|
|
13980
|
+
@_guard_missing_params
|
|
13862
13981
|
def layout_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
13863
13982
|
"""Manage DaVinci Resolve UI layout presets.
|
|
13864
13983
|
|
|
@@ -13870,7 +13989,7 @@ def layout_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
|
|
|
13870
13989
|
import_preset(path, name?) -> {success}
|
|
13871
13990
|
delete(name) -> {success}
|
|
13872
13991
|
"""
|
|
13873
|
-
p = params
|
|
13992
|
+
p = _params(params)
|
|
13874
13993
|
r = get_resolve()
|
|
13875
13994
|
if r is None:
|
|
13876
13995
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -13905,6 +14024,7 @@ def layout_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
|
|
|
13905
14024
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
13906
14025
|
|
|
13907
14026
|
@mcp.tool()
|
|
14027
|
+
@_guard_missing_params
|
|
13908
14028
|
def render_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
13909
14029
|
"""Import/export render and burn-in presets.
|
|
13910
14030
|
|
|
@@ -13914,7 +14034,7 @@ def render_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
|
|
|
13914
14034
|
import_burnin(path) -> {success}
|
|
13915
14035
|
export_burnin(name, path) -> {success}
|
|
13916
14036
|
"""
|
|
13917
|
-
p = params
|
|
14037
|
+
p = _params(params)
|
|
13918
14038
|
r = get_resolve()
|
|
13919
14039
|
if r is None:
|
|
13920
14040
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -14815,6 +14935,7 @@ def _project_lint_live(r, pm) -> Dict[str, Any]:
|
|
|
14815
14935
|
|
|
14816
14936
|
|
|
14817
14937
|
@mcp.tool()
|
|
14938
|
+
@_guard_missing_params
|
|
14818
14939
|
def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
14819
14940
|
"""Manage DaVinci Resolve projects.
|
|
14820
14941
|
|
|
@@ -14856,7 +14977,7 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
14856
14977
|
settings are applied in dependency order; markers only added if absent. Hooks run only
|
|
14857
14978
|
when run_hooks=true (executes shell from the spec — opt-in).
|
|
14858
14979
|
"""
|
|
14859
|
-
p = params
|
|
14980
|
+
p = _params(params)
|
|
14860
14981
|
r = get_resolve()
|
|
14861
14982
|
if r is None:
|
|
14862
14983
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -14963,6 +15084,7 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
14963
15084
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
14964
15085
|
|
|
14965
15086
|
@mcp.tool()
|
|
15087
|
+
@_guard_missing_params
|
|
14966
15088
|
def project_manager_folders(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
14967
15089
|
"""Navigate and manage project folders in the Project Manager.
|
|
14968
15090
|
|
|
@@ -14975,7 +15097,7 @@ def project_manager_folders(action: str, params: Optional[Dict[str, Any]] = None
|
|
|
14975
15097
|
goto_root() -> {success}
|
|
14976
15098
|
goto_parent() -> {success}
|
|
14977
15099
|
"""
|
|
14978
|
-
p = params
|
|
15100
|
+
p = _params(params)
|
|
14979
15101
|
r = get_resolve()
|
|
14980
15102
|
if r is None:
|
|
14981
15103
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -15007,6 +15129,7 @@ def project_manager_folders(action: str, params: Optional[Dict[str, Any]] = None
|
|
|
15007
15129
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
15008
15130
|
|
|
15009
15131
|
@mcp.tool()
|
|
15132
|
+
@_guard_missing_params
|
|
15010
15133
|
def project_manager_cloud(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
15011
15134
|
"""Cloud project operations (requires DaVinci Resolve cloud infrastructure).
|
|
15012
15135
|
|
|
@@ -15016,7 +15139,7 @@ def project_manager_cloud(action: str, params: Optional[Dict[str, Any]] = None)
|
|
|
15016
15139
|
import_project(path, settings) -> {success}
|
|
15017
15140
|
restore(folder_path, settings) -> {success}
|
|
15018
15141
|
"""
|
|
15019
|
-
p = params
|
|
15142
|
+
p = _params(params)
|
|
15020
15143
|
r = get_resolve()
|
|
15021
15144
|
if r is None:
|
|
15022
15145
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -15045,6 +15168,7 @@ def project_manager_cloud(action: str, params: Optional[Dict[str, Any]] = None)
|
|
|
15045
15168
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
15046
15169
|
|
|
15047
15170
|
@mcp.tool()
|
|
15171
|
+
@_guard_missing_params
|
|
15048
15172
|
def project_manager_database(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
15049
15173
|
"""Manage DaVinci Resolve project databases.
|
|
15050
15174
|
|
|
@@ -15053,7 +15177,7 @@ def project_manager_database(action: str, params: Optional[Dict[str, Any]] = Non
|
|
|
15053
15177
|
list() -> {databases}
|
|
15054
15178
|
set_current(db_info) -> {success} — db_info: {DbType, DbName}
|
|
15055
15179
|
"""
|
|
15056
|
-
p = params
|
|
15180
|
+
p = _params(params)
|
|
15057
15181
|
r = get_resolve()
|
|
15058
15182
|
if r is None:
|
|
15059
15183
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -15076,6 +15200,7 @@ def project_manager_database(action: str, params: Optional[Dict[str, Any]] = Non
|
|
|
15076
15200
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
15077
15201
|
|
|
15078
15202
|
@mcp.tool()
|
|
15203
|
+
@_guard_missing_params
|
|
15079
15204
|
def project_settings(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
15080
15205
|
"""Project metadata, settings, and color groups.
|
|
15081
15206
|
|
|
@@ -15100,7 +15225,7 @@ def project_settings(action: str, params: Optional[Dict[str, Any]] = None) -> Di
|
|
|
15100
15225
|
apply_fairlight_preset(preset_name) -> {success}
|
|
15101
15226
|
generate_speech(speech_generation_settings, timecode?) -> {success, new, new_id} — Resolve 21+, AI Speech Generator; creates new audio media (confirm-gated)
|
|
15102
15227
|
"""
|
|
15103
|
-
p = params
|
|
15228
|
+
p = _params(params)
|
|
15104
15229
|
_, proj, err = _check()
|
|
15105
15230
|
if err:
|
|
15106
15231
|
return err
|
|
@@ -15853,6 +15978,7 @@ def _export_render_boundary_report(proj, p: Dict[str, Any]):
|
|
|
15853
15978
|
|
|
15854
15979
|
|
|
15855
15980
|
@mcp.tool()
|
|
15981
|
+
@_guard_missing_params
|
|
15856
15982
|
def render(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
15857
15983
|
"""Render pipeline: jobs, presets, formats, codecs, and rendering.
|
|
15858
15984
|
|
|
@@ -15901,7 +16027,7 @@ def render(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
|
|
|
15901
16027
|
resolved against the live matrix, so an intent unavailable on this
|
|
15902
16028
|
machine/license fails with the actual available lists.
|
|
15903
16029
|
"""
|
|
15904
|
-
p = params
|
|
16030
|
+
p = _params(params)
|
|
15905
16031
|
_, proj, err = _check()
|
|
15906
16032
|
if err:
|
|
15907
16033
|
return err
|
|
@@ -16044,6 +16170,7 @@ def render(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
|
|
|
16044
16170
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
16045
16171
|
|
|
16046
16172
|
@mcp.tool()
|
|
16173
|
+
@_guard_missing_params
|
|
16047
16174
|
def media_storage(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
16048
16175
|
"""Browse storage volumes and import media into the Media Pool.
|
|
16049
16176
|
|
|
@@ -16060,7 +16187,7 @@ def media_storage(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
16060
16187
|
add_clip_mattes(clip_id, paths, stereo_eye?) -> {success}
|
|
16061
16188
|
add_timeline_mattes(paths) -> {items}
|
|
16062
16189
|
"""
|
|
16063
|
-
p = params
|
|
16190
|
+
p = _params(params)
|
|
16064
16191
|
r = get_resolve()
|
|
16065
16192
|
if r is None:
|
|
16066
16193
|
return _err("Could not connect to DaVinci Resolve. It was not running and auto-launch failed. Check that Resolve Studio is installed.")
|
|
@@ -16111,6 +16238,7 @@ def media_storage(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
16111
16238
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
16112
16239
|
|
|
16113
16240
|
@mcp.tool()
|
|
16241
|
+
@_guard_missing_params
|
|
16114
16242
|
@_destructive_op("media_pool")
|
|
16115
16243
|
def media_pool(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
16116
16244
|
"""Manage the Media Pool: folders, clips, timelines, import/export.
|
|
@@ -16198,7 +16326,7 @@ def media_pool(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str
|
|
|
16198
16326
|
copy_clip_annotations(source_clip_id, target_clip_ids, include_markers?, include_flags?, include_clip_color?, dry_run?) -> {success, results}
|
|
16199
16327
|
media_pool_boundary_report(depth?, clip_ids?, selected?) -> {capabilities, media_pool, items?}
|
|
16200
16328
|
"""
|
|
16201
|
-
p = params
|
|
16329
|
+
p = _params(params)
|
|
16202
16330
|
_, proj, mp, err = _get_mp()
|
|
16203
16331
|
if err:
|
|
16204
16332
|
return err
|
|
@@ -16575,6 +16703,7 @@ def media_pool(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str
|
|
|
16575
16703
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
16576
16704
|
|
|
16577
16705
|
@mcp.tool()
|
|
16706
|
+
@_guard_missing_params
|
|
16578
16707
|
def folder(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
16579
16708
|
"""Operations on Media Pool folders.
|
|
16580
16709
|
|
|
@@ -16593,7 +16722,7 @@ def folder(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
|
|
|
16593
16722
|
analyze_for_slate(path?, marker_color?) -> {success} — Resolve 21+, AI Slate ID Extra
|
|
16594
16723
|
remove_motion_blur(path?, deblur_option?) -> {success, created} — Resolve 21+; renders NEW media (confirm-gated)
|
|
16595
16724
|
"""
|
|
16596
|
-
p = params
|
|
16725
|
+
p = _params(params)
|
|
16597
16726
|
_, _, mp, err = _get_mp()
|
|
16598
16727
|
if err:
|
|
16599
16728
|
return err
|
|
@@ -16716,6 +16845,7 @@ def folder(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, An
|
|
|
16716
16845
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
16717
16846
|
|
|
16718
16847
|
@mcp.tool()
|
|
16848
|
+
@_guard_missing_params
|
|
16719
16849
|
def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
16720
16850
|
"""Operations on a media pool clip. Identify clip by clip_id.
|
|
16721
16851
|
|
|
@@ -16767,7 +16897,7 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
16767
16897
|
verified empirically on Resolve Studio 20.3.2.9. If BMD changes the
|
|
16768
16898
|
behavior the clip will still be selected — editor double-clicks to load.
|
|
16769
16899
|
"""
|
|
16770
|
-
p = params
|
|
16900
|
+
p = _params(params)
|
|
16771
16901
|
_, _, mp, err = _get_mp()
|
|
16772
16902
|
if err:
|
|
16773
16903
|
return err
|
|
@@ -17080,6 +17210,7 @@ def media_pool_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
|
|
|
17080
17210
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
17081
17211
|
|
|
17082
17212
|
@mcp.tool()
|
|
17213
|
+
@_guard_missing_params
|
|
17083
17214
|
def media_pool_item_markers(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
17084
17215
|
"""Markers and flags on media pool clips. Identify clip by clip_id.
|
|
17085
17216
|
|
|
@@ -17100,7 +17231,7 @@ def media_pool_item_markers(action: str, params: Optional[Dict[str, Any]] = None
|
|
|
17100
17231
|
monitor_growing_file(clip_id) -> {success}
|
|
17101
17232
|
replace_clip_preserve_sub_clip(clip_id, path) -> {success}
|
|
17102
17233
|
"""
|
|
17103
|
-
p = params
|
|
17234
|
+
p = _params(params)
|
|
17104
17235
|
_, _, mp, err = _get_mp()
|
|
17105
17236
|
if err:
|
|
17106
17237
|
return err
|
|
@@ -17212,6 +17343,7 @@ def _opt_number(value: Any) -> Optional[float]:
|
|
|
17212
17343
|
|
|
17213
17344
|
|
|
17214
17345
|
@mcp.tool()
|
|
17346
|
+
@_guard_missing_params
|
|
17215
17347
|
async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, ctx: Optional[Context] = None) -> Dict[str, Any]:
|
|
17216
17348
|
"""Project-scoped media analysis and guarded metadata publishing.
|
|
17217
17349
|
|
|
@@ -17308,7 +17440,7 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
17308
17440
|
vision-dependent metadata to Resolve. Works with any MCP client whose chat
|
|
17309
17441
|
model is vision-capable; no sampling/createMessage support required.
|
|
17310
17442
|
"""
|
|
17311
|
-
p = _media_analysis_apply_setup_defaults(action, dict(params or {}))
|
|
17443
|
+
p = _params(_media_analysis_apply_setup_defaults(action, dict(params or {})))
|
|
17312
17444
|
|
|
17313
17445
|
# First-run frame-sampling prompt: if the user has never chosen a sampling
|
|
17314
17446
|
# mode (and didn't pass one this call), ask before spending any vision
|
|
@@ -18384,6 +18516,7 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
|
|
|
18384
18516
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
18385
18517
|
|
|
18386
18518
|
@mcp.tool()
|
|
18519
|
+
@_guard_missing_params
|
|
18387
18520
|
def timeline_versioning(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
18388
18521
|
"""Timeline version-on-mutate, archive, rollback, and brain-edit history (C6).
|
|
18389
18522
|
|
|
@@ -18431,7 +18564,7 @@ def timeline_versioning(action: str, params: Optional[Dict[str, Any]] = None) ->
|
|
|
18431
18564
|
if ctx is None:
|
|
18432
18565
|
return _err("No current project / can't resolve project root")
|
|
18433
18566
|
resolve_h, project_h, project_root, project_name = ctx
|
|
18434
|
-
p = params
|
|
18567
|
+
p = _params(params)
|
|
18435
18568
|
|
|
18436
18569
|
if action == "begin_run":
|
|
18437
18570
|
return _analysis_runs.begin_run(
|
|
@@ -18756,6 +18889,7 @@ def _edit_engine_linked_audio_tracks(
|
|
|
18756
18889
|
|
|
18757
18890
|
|
|
18758
18891
|
@mcp.tool()
|
|
18892
|
+
@_guard_missing_params
|
|
18759
18893
|
@_destructive_op("edit_engine")
|
|
18760
18894
|
def edit_engine(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
18761
18895
|
"""Evidence-driven edit loops (Phase E): selects assembly, tighten, swap.
|
|
@@ -18797,7 +18931,7 @@ def edit_engine(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
18797
18931
|
timeline (version-archived first).
|
|
18798
18932
|
- list_plans(limit?) / get_plan(plan_id)
|
|
18799
18933
|
"""
|
|
18800
|
-
p = params
|
|
18934
|
+
p = _params(params)
|
|
18801
18935
|
|
|
18802
18936
|
def _project_context(*, need_resolve: bool) -> Tuple[Optional[Any], Optional[Any], Optional[str], Optional[Dict[str, Any]]]:
|
|
18803
18937
|
# An explicit analysis_root always wins — the evidence DB the caller
|
|
@@ -19562,6 +19696,7 @@ _TIMELINE_ACTIONS = [
|
|
|
19562
19696
|
|
|
19563
19697
|
|
|
19564
19698
|
@mcp.tool()
|
|
19699
|
+
@_guard_missing_params
|
|
19565
19700
|
@_destructive_op("timeline")
|
|
19566
19701
|
def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
19567
19702
|
"""Timeline operations: tracks, clips, import/export, generators, titles.
|
|
@@ -19747,7 +19882,7 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
|
|
|
19747
19882
|
For long-form per-action guidance and a worked example, call:
|
|
19748
19883
|
timeline(action="action_help", params={"name": "<action>"})
|
|
19749
19884
|
"""
|
|
19750
|
-
p = params
|
|
19885
|
+
p = _params(params)
|
|
19751
19886
|
# action_help is pull-on-demand metadata; no Resolve connection needed.
|
|
19752
19887
|
if action == "action_help":
|
|
19753
19888
|
return _action_help("timeline", p)
|
|
@@ -20148,7 +20283,7 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
|
|
|
20148
20283
|
result["ignored_state_keys"] = ignored_state
|
|
20149
20284
|
return result
|
|
20150
20285
|
elif action == "extract_source_frame_ranges":
|
|
20151
|
-
p = params
|
|
20286
|
+
p = _params(params)
|
|
20152
20287
|
handles = int(p.get("handles", 24))
|
|
20153
20288
|
gap_max = int(p.get("gap_max", 30))
|
|
20154
20289
|
audio_ext = tuple(
|
|
@@ -20335,6 +20470,7 @@ def timeline(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str,
|
|
|
20335
20470
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20336
20471
|
|
|
20337
20472
|
@mcp.tool()
|
|
20473
|
+
@_guard_missing_params
|
|
20338
20474
|
@_destructive_op("timeline_markers")
|
|
20339
20475
|
def timeline_markers(action: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
20340
20476
|
"""Markers and playhead operations on the current timeline.
|
|
@@ -20370,7 +20506,7 @@ def timeline_markers(action: str, params: Optional[Dict[str, Any]] = None) -> An
|
|
|
20370
20506
|
export_review_report(scope?, include_capabilities?) -> {title, annotations, capabilities?}
|
|
20371
20507
|
annotation_boundary_report(scope?) -> {capabilities, annotations}
|
|
20372
20508
|
"""
|
|
20373
|
-
p = params
|
|
20509
|
+
p = _params(params)
|
|
20374
20510
|
_, tl, err = _get_tl()
|
|
20375
20511
|
if err:
|
|
20376
20512
|
return err
|
|
@@ -20455,6 +20591,7 @@ def timeline_markers(action: str, params: Optional[Dict[str, Any]] = None) -> An
|
|
|
20455
20591
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20456
20592
|
|
|
20457
20593
|
@mcp.tool()
|
|
20594
|
+
@_guard_missing_params
|
|
20458
20595
|
@_destructive_op("timeline_ai")
|
|
20459
20596
|
def timeline_ai(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
20460
20597
|
"""AI and analysis operations on the current timeline.
|
|
@@ -20468,7 +20605,7 @@ def timeline_ai(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
20468
20605
|
grab_still() -> {success}
|
|
20469
20606
|
grab_all_stills(source?) -> {count}
|
|
20470
20607
|
"""
|
|
20471
|
-
p = params
|
|
20608
|
+
p = _params(params)
|
|
20472
20609
|
_, tl, err = _get_tl()
|
|
20473
20610
|
if err:
|
|
20474
20611
|
return err
|
|
@@ -20509,6 +20646,7 @@ def timeline_ai(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
20509
20646
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20510
20647
|
|
|
20511
20648
|
@mcp.tool()
|
|
20649
|
+
@_guard_missing_params
|
|
20512
20650
|
@_destructive_op("timeline_item")
|
|
20513
20651
|
def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
20514
20652
|
"""Properties, transforms, speed, keyframes, and metadata for a timeline item.
|
|
@@ -20560,7 +20698,7 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
20560
20698
|
|
|
20561
20699
|
Default: track_type="video", track_index=1, item_index=0
|
|
20562
20700
|
"""
|
|
20563
|
-
p = params
|
|
20701
|
+
p = _params(params)
|
|
20564
20702
|
tl, item, err = _get_item(p)
|
|
20565
20703
|
if err:
|
|
20566
20704
|
return err
|
|
@@ -20748,6 +20886,7 @@ def timeline_item(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
20748
20886
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20749
20887
|
|
|
20750
20888
|
@mcp.tool()
|
|
20889
|
+
@_guard_missing_params
|
|
20751
20890
|
@_destructive_op("timeline_item_markers")
|
|
20752
20891
|
def timeline_item_markers(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
20753
20892
|
"""Markers, flags, and clip color on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
|
|
@@ -20770,7 +20909,7 @@ def timeline_item_markers(action: str, params: Optional[Dict[str, Any]] = None)
|
|
|
20770
20909
|
|
|
20771
20910
|
Default: track_type="video", track_index=1, item_index=0
|
|
20772
20911
|
"""
|
|
20773
|
-
p = params
|
|
20912
|
+
p = _params(params)
|
|
20774
20913
|
_, item, err = _get_item(p)
|
|
20775
20914
|
if err:
|
|
20776
20915
|
return err
|
|
@@ -20825,6 +20964,7 @@ def timeline_item_markers(action: str, params: Optional[Dict[str, Any]] = None)
|
|
|
20825
20964
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
20826
20965
|
|
|
20827
20966
|
@mcp.tool()
|
|
20967
|
+
@_guard_missing_params
|
|
20828
20968
|
@_destructive_op("timeline_item_fusion")
|
|
20829
20969
|
def timeline_item_fusion(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
20830
20970
|
"""Fusion composition operations on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
|
|
@@ -20845,7 +20985,7 @@ def timeline_item_fusion(action: str, params: Optional[Dict[str, Any]] = None) -
|
|
|
20845
20985
|
|
|
20846
20986
|
Default: track_type="video", track_index=1, item_index=0
|
|
20847
20987
|
"""
|
|
20848
|
-
p = params
|
|
20988
|
+
p = _params(params)
|
|
20849
20989
|
_, item, err = _get_item(p)
|
|
20850
20990
|
if err:
|
|
20851
20991
|
return err
|
|
@@ -22126,6 +22266,7 @@ def _grade_evidence_base(proj, item, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
22126
22266
|
}
|
|
22127
22267
|
|
|
22128
22268
|
@mcp.tool()
|
|
22269
|
+
@_guard_missing_params
|
|
22129
22270
|
@_destructive_op("timeline_item_color")
|
|
22130
22271
|
def timeline_item_color(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
22131
22272
|
"""Color grading, versions, LUTs, cache, and AI tools on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
|
|
@@ -22216,7 +22357,7 @@ def timeline_item_color(action: str, params: Optional[Dict[str, Any]] = None) ->
|
|
|
22216
22357
|
For long-form per-action guidance and a worked example, call:
|
|
22217
22358
|
timeline_item_color(action="action_help", params={"name": "<action>"})
|
|
22218
22359
|
"""
|
|
22219
|
-
p = params
|
|
22360
|
+
p = _params(params)
|
|
22220
22361
|
if action == "action_help":
|
|
22221
22362
|
return _action_help("timeline_item_color", p)
|
|
22222
22363
|
_, item, err = _get_item(p)
|
|
@@ -22331,6 +22472,7 @@ def timeline_item_color(action: str, params: Optional[Dict[str, Any]] = None) ->
|
|
|
22331
22472
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
22332
22473
|
|
|
22333
22474
|
@mcp.tool()
|
|
22475
|
+
@_guard_missing_params
|
|
22334
22476
|
@_destructive_op("timeline_item_takes")
|
|
22335
22477
|
def timeline_item_takes(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
22336
22478
|
"""Take management on timeline items. Identify by track_type, track_index, item_index (item_index is 0-BASED: 0 = first clip; track_index is 1-based).
|
|
@@ -22346,7 +22488,7 @@ def timeline_item_takes(action: str, params: Optional[Dict[str, Any]] = None) ->
|
|
|
22346
22488
|
|
|
22347
22489
|
Default: track_type="video", track_index=1, item_index=0
|
|
22348
22490
|
"""
|
|
22349
|
-
p = params
|
|
22491
|
+
p = _params(params)
|
|
22350
22492
|
_, item, err = _get_item(p)
|
|
22351
22493
|
if err:
|
|
22352
22494
|
return err
|
|
@@ -22379,6 +22521,7 @@ def timeline_item_takes(action: str, params: Optional[Dict[str, Any]] = None) ->
|
|
|
22379
22521
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
22380
22522
|
|
|
22381
22523
|
@mcp.tool()
|
|
22524
|
+
@_guard_missing_params
|
|
22382
22525
|
def gallery(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
22383
22526
|
"""Gallery album management.
|
|
22384
22527
|
|
|
@@ -22394,7 +22537,7 @@ def gallery(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, A
|
|
|
22394
22537
|
|
|
22395
22538
|
album_index is 0-based into the still albums list.
|
|
22396
22539
|
"""
|
|
22397
|
-
p = params
|
|
22540
|
+
p = _params(params)
|
|
22398
22541
|
_, proj, err = _check()
|
|
22399
22542
|
if err:
|
|
22400
22543
|
return err
|
|
@@ -22444,6 +22587,7 @@ def gallery(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, A
|
|
|
22444
22587
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
22445
22588
|
|
|
22446
22589
|
@mcp.tool()
|
|
22590
|
+
@_guard_missing_params
|
|
22447
22591
|
def gallery_stills(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
22448
22592
|
"""Manage stills in gallery albums (best results on Color page).
|
|
22449
22593
|
|
|
@@ -22464,7 +22608,7 @@ def gallery_stills(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
|
|
|
22464
22608
|
File data is inlined in the response (DRX as text, images as base64).
|
|
22465
22609
|
cleanup (default true) deletes exported files from disk after inlining.
|
|
22466
22610
|
"""
|
|
22467
|
-
p = params
|
|
22611
|
+
p = _params(params)
|
|
22468
22612
|
_, proj, err = _check()
|
|
22469
22613
|
if err:
|
|
22470
22614
|
return err
|
|
@@ -22597,6 +22741,7 @@ def gallery_stills(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
|
|
|
22597
22741
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
22598
22742
|
|
|
22599
22743
|
@mcp.tool()
|
|
22744
|
+
@_guard_missing_params
|
|
22600
22745
|
@_destructive_op("graph")
|
|
22601
22746
|
def graph(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
22602
22747
|
"""Node graph operations (color grading nodes). Source can be timeline, timeline item, or color group.
|
|
@@ -22636,7 +22781,7 @@ def graph(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any
|
|
|
22636
22781
|
For long-form per-action guidance and a worked example, call:
|
|
22637
22782
|
graph(action="action_help", params={"name": "<action>"})
|
|
22638
22783
|
"""
|
|
22639
|
-
p = params
|
|
22784
|
+
p = _params(params)
|
|
22640
22785
|
if action == "action_help":
|
|
22641
22786
|
return _action_help("graph", p)
|
|
22642
22787
|
source = p.get("source", "timeline")
|
|
@@ -22739,6 +22884,7 @@ def graph(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any
|
|
|
22739
22884
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
22740
22885
|
|
|
22741
22886
|
@mcp.tool()
|
|
22887
|
+
@_guard_missing_params
|
|
22742
22888
|
def color_group(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
22743
22889
|
"""Manage color groups and their node graphs.
|
|
22744
22890
|
|
|
@@ -22750,7 +22896,7 @@ def color_group(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
22750
22896
|
get_pre_clip_graph(group_name) -> {available, num_nodes}
|
|
22751
22897
|
get_post_clip_graph(group_name) -> {available, num_nodes}
|
|
22752
22898
|
"""
|
|
22753
|
-
p = params
|
|
22899
|
+
p = _params(params)
|
|
22754
22900
|
_, proj, err = _check()
|
|
22755
22901
|
if err:
|
|
22756
22902
|
return err
|
|
@@ -23661,6 +23807,7 @@ def _fusion_get_text_plus(comp, p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
23661
23807
|
|
|
23662
23808
|
|
|
23663
23809
|
@mcp.tool()
|
|
23810
|
+
@_guard_missing_params
|
|
23664
23811
|
def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
23665
23812
|
"""Fusion composition node graph operations.
|
|
23666
23813
|
|
|
@@ -23731,7 +23878,7 @@ def fusion_comp(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
23731
23878
|
ColorCorrector, RectangleMask, EllipseMask, Tracker, MediaIn, MediaOut,
|
|
23732
23879
|
Loader, Saver, Glow, FilmGrain, CornerPositioner, DeltaKeyer, UltraKeyer
|
|
23733
23880
|
"""
|
|
23734
|
-
p = params
|
|
23881
|
+
p = _params(params)
|
|
23735
23882
|
|
|
23736
23883
|
if action == "bulk_set_inputs":
|
|
23737
23884
|
return _fusion_comp_bulk_set_inputs(p)
|
|
@@ -24231,6 +24378,7 @@ def _validate_glsl_minimal(source: str) -> Dict[str, Any]:
|
|
|
24231
24378
|
|
|
24232
24379
|
|
|
24233
24380
|
@mcp.tool()
|
|
24381
|
+
@_guard_missing_params
|
|
24234
24382
|
def fuse_plugin(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
24235
24383
|
"""Author and install Fusion Fuse plugins (.fuse files).
|
|
24236
24384
|
|
|
@@ -24257,7 +24405,7 @@ def fuse_plugin(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[st
|
|
|
24257
24405
|
docs/authoring/fuse-dctl-authoring.md for the per-kind option spec.
|
|
24258
24406
|
list_templates() -> {kinds}
|
|
24259
24407
|
"""
|
|
24260
|
-
p = params
|
|
24408
|
+
p = _params(params)
|
|
24261
24409
|
|
|
24262
24410
|
if action == "path":
|
|
24263
24411
|
return {"fuses_dir": _fuses_dir()}
|
|
@@ -24474,6 +24622,7 @@ _DCTL_VALID_CATEGORIES = ("lut", "aces_idt", "aces_odt")
|
|
|
24474
24622
|
|
|
24475
24623
|
|
|
24476
24624
|
@mcp.tool()
|
|
24625
|
+
@_guard_missing_params
|
|
24477
24626
|
def dctl(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
24478
24627
|
"""Author and install DCTL files (Color page custom shaders + ACES transforms).
|
|
24479
24628
|
|
|
@@ -24510,7 +24659,7 @@ def dctl(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]
|
|
|
24510
24659
|
pass that as the `category` argument to install().
|
|
24511
24660
|
list_templates() -> {kinds, kind_categories}
|
|
24512
24661
|
"""
|
|
24513
|
-
p = params
|
|
24662
|
+
p = _params(params)
|
|
24514
24663
|
|
|
24515
24664
|
def _category(default: str = "lut") -> Tuple[Optional[Dict[str, Any]], str]:
|
|
24516
24665
|
cat = p.get("category", default)
|
|
@@ -25403,6 +25552,7 @@ def _extension_boundary_report(p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
25403
25552
|
|
|
25404
25553
|
|
|
25405
25554
|
@mcp.tool()
|
|
25555
|
+
@_guard_missing_params
|
|
25406
25556
|
def script_plugin(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
25407
25557
|
"""Author and install Resolve-page Lua/Python scripts (Workspace → Scripts menu).
|
|
25408
25558
|
|
|
@@ -25458,7 +25608,7 @@ def script_plugin(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
|
|
|
25458
25608
|
refresh_or_restart_required(extension_type, category?) -> {refresh_luts, restart_required}
|
|
25459
25609
|
extension_boundary_report(include_template_matrix?) -> {capabilities, template_matrix, dry_run_probes}
|
|
25460
25610
|
"""
|
|
25461
|
-
p = params
|
|
25611
|
+
p = _params(params)
|
|
25462
25612
|
|
|
25463
25613
|
if action == "extension_capabilities":
|
|
25464
25614
|
return _extension_capabilities()
|