davinci-resolve-mcp 2.63.2 → 2.64.0
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 +39 -0
- package/README.md +1 -1
- package/docs/SKILL.md +8 -2
- package/docs/install.md +23 -1
- package/install.py +30 -3
- package/package.json +1 -1
- package/scripts/doctor.py +73 -15
- package/src/analysis_dashboard.py +2 -1
- package/src/granular/common.py +4 -3
- package/src/server.py +3 -2
- package/src/utils/resolve_connection.py +46 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
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.64.0
|
|
6
|
+
|
|
7
|
+
Adds opt-in support for DaVinci Resolve's **Network** external-scripting mode
|
|
8
|
+
(PR #91… correction: PR #94, by @double2tea), so the MCP can drive a Resolve
|
|
9
|
+
instance addressed by IP — including a Resolve running on the same machine with
|
|
10
|
+
`External scripting using = Network`. Contributed in PR #94 by @double2tea,
|
|
11
|
+
with two additional connection sites hardened during adoption. Local mode is
|
|
12
|
+
unchanged and remains the default.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Resolve Network scripting mode** — set `RESOLVE_SCRIPT_HOST` to the Resolve
|
|
17
|
+
host IP (`127.0.0.1` on the same machine) to route connections through
|
|
18
|
+
Resolve's explicit IP-targeted `scriptapp("Resolve", host, timeout)` overload.
|
|
19
|
+
`RESOLVE_SCRIPT_TIMEOUT` (positive finite seconds, default 5) bounds the
|
|
20
|
+
connection wait. When `RESOLVE_SCRIPT_HOST` is absent, the server uses the
|
|
21
|
+
one-argument Local discovery exactly as before. A shared `connect_resolve`
|
|
22
|
+
helper centralizes this behavior; the compound server, granular server,
|
|
23
|
+
analysis dashboard, `scripts/doctor.py`, and the installer's post-install
|
|
24
|
+
connection probe all route through it. The installer copies
|
|
25
|
+
`RESOLVE_SCRIPT_HOST`/`RESOLVE_SCRIPT_TIMEOUT` into generated client configs
|
|
26
|
+
when present in its environment.
|
|
27
|
+
- **`doctor.py` Network flags** — `python3 scripts/doctor.py --resolve-host
|
|
28
|
+
<ip> [--resolve-timeout <seconds>]` runs the read-only connection check
|
|
29
|
+
against a Network-mode host.
|
|
30
|
+
|
|
31
|
+
### Security
|
|
32
|
+
|
|
33
|
+
- Network scripting permits remote control of Resolve. Documentation (SKILL.md,
|
|
34
|
+
install.md) advises using Local mode when remote access is unnecessary, and
|
|
35
|
+
otherwise restricting access with host firewall and network controls.
|
|
36
|
+
|
|
37
|
+
### Validation
|
|
38
|
+
|
|
39
|
+
- Live-validated on DaVinci Resolve Studio 19.1.3.7: Local mode (one-argument
|
|
40
|
+
discovery) and Network mode (`--resolve-host 127.0.0.1 --resolve-timeout 8`)
|
|
41
|
+
both connect through the shared `connect_resolve` helper. PR author validated
|
|
42
|
+
against Studio 20.3.2.9 in Network mode. Full offline suite: 1514 tests.
|
|
43
|
+
|
|
5
44
|
## What's New in v2.63.2
|
|
6
45
|
|
|
7
46
|
Installer fix for Windows MSIX builds of Claude Desktop (issue #93, reported by
|
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/docs/SKILL.md
CHANGED
|
@@ -14,8 +14,14 @@ session — projects, timelines, clips, color grading, Fusion compositions, audi
|
|
|
14
14
|
render queues, and more — through natural language.
|
|
15
15
|
|
|
16
16
|
DaVinci Resolve must be running with **Preferences > General > "External scripting
|
|
17
|
-
using"** set to **Local
|
|
18
|
-
|
|
17
|
+
using"** set to **Local**, or set to **Network** with `RESOLVE_SCRIPT_HOST`
|
|
18
|
+
configured to the Resolve host IP (use `127.0.0.1` on the same machine). The
|
|
19
|
+
server auto-launches Resolve if it is not running, but that first connection can
|
|
20
|
+
take up to 60 seconds.
|
|
21
|
+
|
|
22
|
+
Network scripting permits remote control of Resolve. Use Local mode when remote
|
|
23
|
+
access is unnecessary; otherwise restrict access with host firewall and network
|
|
24
|
+
controls.
|
|
19
25
|
|
|
20
26
|
**Session-start update note.** The first `resolve_control(action="get_version")`
|
|
21
27
|
of a session returns an `mcp` block with the cached update check
|
package/docs/install.md
CHANGED
|
@@ -7,7 +7,9 @@ This guide covers Resolve requirements, the universal installer, supported MCP c
|
|
|
7
7
|
- **DaVinci Resolve Studio** 18.5+ (macOS, Windows, or Linux) — the free edition does not support external scripting
|
|
8
8
|
- **Python 3.10+** (the MCP SDK requires 3.10). **3.10–3.12 is the lowest-risk
|
|
9
9
|
choice**; 3.13/3.14 also work on recent Resolve builds — see below
|
|
10
|
-
- DaVinci Resolve running with **Preferences > General > "External scripting using"**
|
|
10
|
+
- DaVinci Resolve running with **Preferences > General > "External scripting using"**
|
|
11
|
+
set to **Local**, or **Network** with `RESOLVE_SCRIPT_HOST` set to the Resolve
|
|
12
|
+
host IP (`127.0.0.1` when Resolve and the MCP run on the same machine)
|
|
11
13
|
|
|
12
14
|
> **Python 3.13 / 3.14:** these are **allowed** — setup will use them and warn.
|
|
13
15
|
> Python 3.14 is verified working against DaVinci Resolve Studio 20.3.2. On
|
|
@@ -170,6 +172,26 @@ If you prefer to set things up yourself, add to your MCP client config:
|
|
|
170
172
|
|
|
171
173
|
On Windows, installer-generated configs also include `PYTHONHOME`. That scopes Resolve's Python binding to the selected interpreter and avoids the Resolve 20.3 multi-Python crash reported in [Issue #26](https://github.com/samuelgursky/davinci-resolve-mcp/issues/26).
|
|
172
174
|
|
|
175
|
+
For Resolve's **Network** scripting mode, add
|
|
176
|
+
`"RESOLVE_SCRIPT_HOST": "127.0.0.1"` to the `env` object above, replacing the
|
|
177
|
+
address when Resolve runs on another host. When this variable is present, the
|
|
178
|
+
server uses Resolve's explicit IP-targeted `scriptapp` call with a bounded
|
|
179
|
+
connection timeout. Set `RESOLVE_SCRIPT_TIMEOUT` to override the default
|
|
180
|
+
five-second timeout with a positive finite number of seconds. The installer
|
|
181
|
+
includes both values in generated client configs when they are present in its
|
|
182
|
+
environment.
|
|
183
|
+
|
|
184
|
+
Network scripting permits remote control of Resolve. Prefer Local mode when
|
|
185
|
+
remote access is unnecessary; otherwise restrict access with host firewall and
|
|
186
|
+
network controls.
|
|
187
|
+
|
|
188
|
+
Run the read-only doctor against Network mode explicitly:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
python3 scripts/doctor.py --resolve-host 127.0.0.1
|
|
192
|
+
python3 scripts/doctor.py --resolve-host resolve.example.test --resolve-timeout 12.5
|
|
193
|
+
```
|
|
194
|
+
|
|
173
195
|
When the compound server is running, `resolve_control(action="get_version")`
|
|
174
196
|
includes the local MCP version, the last update-check status, and the current
|
|
175
197
|
update decision under the `mcp` key. `resolve_control(action="mcp_update_status",
|
package/install.py
CHANGED
|
@@ -15,6 +15,7 @@ Usage:
|
|
|
15
15
|
|
|
16
16
|
import argparse
|
|
17
17
|
import json
|
|
18
|
+
import math
|
|
18
19
|
import os
|
|
19
20
|
import platform
|
|
20
21
|
import shutil
|
|
@@ -35,7 +36,7 @@ from src.utils.update_check import (
|
|
|
35
36
|
|
|
36
37
|
# ─── Version ──────────────────────────────────────────────────────────────────
|
|
37
38
|
|
|
38
|
-
VERSION = "2.
|
|
39
|
+
VERSION = "2.64.0"
|
|
39
40
|
# Only hard floor: mcp[cli] requires Python 3.10+. There is no upper bound —
|
|
40
41
|
# Resolve's scripting bridge loads into newer interpreters on recent builds
|
|
41
42
|
# (Python 3.14 verified against Resolve Studio 20.3.2). Older Resolve builds
|
|
@@ -474,6 +475,13 @@ def build_server_env(python_path, api_path, lib_path, system=SYSTEM, python_home
|
|
|
474
475
|
if system == "Windows":
|
|
475
476
|
env["PYTHONHOME"] = str(python_home or get_python_base_install(python_path))
|
|
476
477
|
|
|
478
|
+
host = os.environ.get("RESOLVE_SCRIPT_HOST")
|
|
479
|
+
if host:
|
|
480
|
+
env["RESOLVE_SCRIPT_HOST"] = host
|
|
481
|
+
timeout = os.environ.get("RESOLVE_SCRIPT_TIMEOUT")
|
|
482
|
+
if timeout:
|
|
483
|
+
env["RESOLVE_SCRIPT_TIMEOUT"] = timeout
|
|
484
|
+
|
|
477
485
|
return env
|
|
478
486
|
|
|
479
487
|
|
|
@@ -810,12 +818,21 @@ def verify_resolve_connection(python_path, api_path, lib_path):
|
|
|
810
818
|
|
|
811
819
|
env = {**os.environ, **build_server_env(python_path, api_path, lib_path)}
|
|
812
820
|
modules_path = env["PYTHONPATH"]
|
|
821
|
+
repo_root = str(Path(__file__).resolve().parent)
|
|
822
|
+
# Route through connect_resolve so Network mode (RESOLVE_SCRIPT_HOST, propagated
|
|
823
|
+
# into env by build_server_env) uses the explicit IP-targeted overload. Fall
|
|
824
|
+
# back to Local-mode discovery if the helper cannot be imported.
|
|
813
825
|
test_script = textwrap.dedent(f"""\
|
|
814
826
|
import sys
|
|
815
827
|
sys.path.insert(0, {modules_path!r})
|
|
828
|
+
sys.path.insert(0, {repo_root!r})
|
|
816
829
|
try:
|
|
817
830
|
import DaVinciResolveScript as dvr
|
|
818
|
-
|
|
831
|
+
try:
|
|
832
|
+
from src.utils.resolve_connection import connect_resolve
|
|
833
|
+
except Exception:
|
|
834
|
+
connect_resolve = lambda mod: mod.scriptapp('Resolve')
|
|
835
|
+
resolve = connect_resolve(dvr)
|
|
819
836
|
if resolve:
|
|
820
837
|
name = resolve.GetProductName()
|
|
821
838
|
ver = resolve.GetVersionString()
|
|
@@ -828,12 +845,22 @@ def verify_resolve_connection(python_path, api_path, lib_path):
|
|
|
828
845
|
print(f"ERROR: {{e}}")
|
|
829
846
|
""")
|
|
830
847
|
|
|
848
|
+
process_timeout = 10.0
|
|
849
|
+
configured_timeout = env.get("RESOLVE_SCRIPT_TIMEOUT")
|
|
850
|
+
if configured_timeout:
|
|
851
|
+
try:
|
|
852
|
+
network_timeout = float(configured_timeout)
|
|
853
|
+
if math.isfinite(network_timeout) and network_timeout > 0:
|
|
854
|
+
process_timeout = max(process_timeout, network_timeout + 2)
|
|
855
|
+
except ValueError:
|
|
856
|
+
pass
|
|
857
|
+
|
|
831
858
|
try:
|
|
832
859
|
result = subprocess.run(
|
|
833
860
|
[str(python_path), "-c", test_script],
|
|
834
861
|
capture_output=True,
|
|
835
862
|
text=True,
|
|
836
|
-
timeout=
|
|
863
|
+
timeout=process_timeout,
|
|
837
864
|
env=env,
|
|
838
865
|
)
|
|
839
866
|
output = result.stdout.strip() or result.stderr.strip()
|
package/package.json
CHANGED
package/scripts/doctor.py
CHANGED
|
@@ -7,16 +7,19 @@ mutating anything. Prints OK/WARN/FAIL lines (or JSON with --json) and exits
|
|
|
7
7
|
nonzero only when a FAIL is present.
|
|
8
8
|
|
|
9
9
|
Run: `python3 scripts/doctor.py`
|
|
10
|
+
Network mode: `python3 scripts/doctor.py --resolve-host 127.0.0.1`
|
|
10
11
|
|
|
11
12
|
Overridable via environment: DAVINCI_MCP_REPO, DAVINCI_MCP_PYTHON,
|
|
12
13
|
CODEX_HOME, CLAUDE_DESKTOP_CONFIG, RESOLVE_APP, RESOLVE_SCRIPT_API,
|
|
13
|
-
RESOLVE_SCRIPT_MODULES, RESOLVE_SCRIPT_LIB
|
|
14
|
+
RESOLVE_SCRIPT_MODULES, RESOLVE_SCRIPT_LIB, RESOLVE_SCRIPT_HOST,
|
|
15
|
+
RESOLVE_SCRIPT_TIMEOUT.
|
|
14
16
|
"""
|
|
15
17
|
|
|
16
18
|
from __future__ import annotations
|
|
17
19
|
|
|
18
20
|
import argparse
|
|
19
21
|
import json
|
|
22
|
+
import math
|
|
20
23
|
import os
|
|
21
24
|
import re
|
|
22
25
|
import subprocess
|
|
@@ -128,26 +131,41 @@ def git_head() -> str:
|
|
|
128
131
|
return head["stdout"] or head["stderr"] or "git describe produced no output"
|
|
129
132
|
|
|
130
133
|
|
|
131
|
-
def resolve_probe(
|
|
134
|
+
def resolve_probe(
|
|
135
|
+
resolve_host: str | None = None,
|
|
136
|
+
resolve_timeout: float | None = None,
|
|
137
|
+
) -> dict[str, Any]:
|
|
132
138
|
if not PYTHON.exists():
|
|
133
139
|
return {"import_ok": False, "error": f"{PYTHON} is missing"}
|
|
134
140
|
|
|
135
141
|
code = f"""
|
|
136
142
|
import json
|
|
137
143
|
import sys
|
|
144
|
+
sys.path.insert(0, {str(REPO)!r})
|
|
138
145
|
sys.path.insert(0, {str(RESOLVE_MODULES)!r})
|
|
139
146
|
try:
|
|
140
147
|
import DaVinciResolveScript as dvr
|
|
141
|
-
|
|
142
|
-
payload = {{
|
|
143
|
-
"import_ok": True,
|
|
144
|
-
"module": getattr(dvr, "__file__", None),
|
|
145
|
-
"resolve_connected": bool(resolve),
|
|
146
|
-
"product": resolve.GetProductName() if resolve else None,
|
|
147
|
-
"version": resolve.GetVersionString() if resolve else None,
|
|
148
|
-
}}
|
|
148
|
+
from src.utils.resolve_connection import connect_resolve
|
|
149
149
|
except Exception as exc:
|
|
150
150
|
payload = {{"import_ok": False, "error": repr(exc)}}
|
|
151
|
+
else:
|
|
152
|
+
try:
|
|
153
|
+
resolve = connect_resolve(dvr)
|
|
154
|
+
except Exception as exc:
|
|
155
|
+
payload = {{
|
|
156
|
+
"import_ok": True,
|
|
157
|
+
"module": getattr(dvr, "__file__", None),
|
|
158
|
+
"resolve_connected": False,
|
|
159
|
+
"connection_error": repr(exc),
|
|
160
|
+
}}
|
|
161
|
+
else:
|
|
162
|
+
payload = {{
|
|
163
|
+
"import_ok": True,
|
|
164
|
+
"module": getattr(dvr, "__file__", None),
|
|
165
|
+
"resolve_connected": bool(resolve),
|
|
166
|
+
"product": resolve.GetProductName() if resolve else None,
|
|
167
|
+
"version": resolve.GetVersionString() if resolve else None,
|
|
168
|
+
}}
|
|
151
169
|
print(json.dumps(payload))
|
|
152
170
|
"""
|
|
153
171
|
env = {
|
|
@@ -156,7 +174,26 @@ print(json.dumps(payload))
|
|
|
156
174
|
"RESOLVE_SCRIPT_LIB": str(RESOLVE_LIB),
|
|
157
175
|
"PYTHONPATH": str(RESOLVE_MODULES),
|
|
158
176
|
}
|
|
159
|
-
|
|
177
|
+
if resolve_host is not None:
|
|
178
|
+
env["RESOLVE_SCRIPT_HOST"] = resolve_host
|
|
179
|
+
if resolve_timeout is not None:
|
|
180
|
+
env["RESOLVE_SCRIPT_TIMEOUT"] = str(resolve_timeout)
|
|
181
|
+
process_timeout = 12.0
|
|
182
|
+
configured_timeout = env.get("RESOLVE_SCRIPT_TIMEOUT")
|
|
183
|
+
if configured_timeout:
|
|
184
|
+
try:
|
|
185
|
+
network_timeout = float(configured_timeout)
|
|
186
|
+
if math.isfinite(network_timeout) and network_timeout > 0:
|
|
187
|
+
process_timeout = max(process_timeout, network_timeout + 2)
|
|
188
|
+
except ValueError:
|
|
189
|
+
pass
|
|
190
|
+
proc = subprocess.run(
|
|
191
|
+
[str(PYTHON), "-c", code],
|
|
192
|
+
capture_output=True,
|
|
193
|
+
text=True,
|
|
194
|
+
timeout=process_timeout,
|
|
195
|
+
env=env,
|
|
196
|
+
)
|
|
160
197
|
output = (proc.stdout or proc.stderr).strip()
|
|
161
198
|
try:
|
|
162
199
|
payload = json.loads(output)
|
|
@@ -166,7 +203,10 @@ print(json.dumps(payload))
|
|
|
166
203
|
return payload
|
|
167
204
|
|
|
168
205
|
|
|
169
|
-
def collect(
|
|
206
|
+
def collect(
|
|
207
|
+
resolve_host: str | None = None,
|
|
208
|
+
resolve_timeout: float | None = None,
|
|
209
|
+
) -> list[dict[str, str]]:
|
|
170
210
|
results: list[dict[str, str]] = []
|
|
171
211
|
|
|
172
212
|
check(results, "OK" if REPO.exists() else "FAIL", "MCP checkout", str(REPO))
|
|
@@ -186,18 +226,27 @@ def collect() -> list[dict[str, str]]:
|
|
|
186
226
|
pyver = run([str(PYTHON), "--version"]) if PYTHON.exists() else {"ok": False, "stdout": "", "stderr": "missing"}
|
|
187
227
|
check(results, "OK" if pyver["ok"] else "FAIL", "Python version", pyver["stdout"] or pyver["stderr"])
|
|
188
228
|
|
|
189
|
-
probe = resolve_probe()
|
|
229
|
+
probe = resolve_probe(resolve_host, resolve_timeout)
|
|
190
230
|
if probe.get("import_ok"):
|
|
191
231
|
check(results, "OK", "DaVinciResolveScript import", str(probe.get("module")))
|
|
192
232
|
if probe.get("resolve_connected"):
|
|
193
233
|
detail = f"{probe.get('product')} {probe.get('version')}"
|
|
194
234
|
check(results, "OK", "Resolve scripting connection", detail)
|
|
235
|
+
elif probe.get("connection_error"):
|
|
236
|
+
check(
|
|
237
|
+
results,
|
|
238
|
+
"FAIL",
|
|
239
|
+
"Resolve scripting connection",
|
|
240
|
+
str(probe["connection_error"]),
|
|
241
|
+
)
|
|
195
242
|
else:
|
|
196
243
|
check(
|
|
197
244
|
results,
|
|
198
245
|
"WARN",
|
|
199
246
|
"Resolve scripting connection",
|
|
200
|
-
|
|
247
|
+
"Module import worked, but scriptapp returned no object. Use "
|
|
248
|
+
"External scripting = Local, or Network with --resolve-host "
|
|
249
|
+
"set to the Resolve host IP, then restart Resolve.",
|
|
201
250
|
)
|
|
202
251
|
else:
|
|
203
252
|
check(results, "FAIL", "DaVinciResolveScript import", str(probe.get("error")))
|
|
@@ -211,9 +260,18 @@ def collect() -> list[dict[str, str]]:
|
|
|
211
260
|
def main() -> int:
|
|
212
261
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
213
262
|
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON")
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
"--resolve-host",
|
|
265
|
+
help="Resolve host IP for Network scripting mode",
|
|
266
|
+
)
|
|
267
|
+
parser.add_argument(
|
|
268
|
+
"--resolve-timeout",
|
|
269
|
+
type=float,
|
|
270
|
+
help="Network connection timeout in seconds (default: 5)",
|
|
271
|
+
)
|
|
214
272
|
args = parser.parse_args()
|
|
215
273
|
|
|
216
|
-
results = collect()
|
|
274
|
+
results = collect(args.resolve_host, args.resolve_timeout)
|
|
217
275
|
if args.json:
|
|
218
276
|
print(json.dumps({"checks": results}, indent=2))
|
|
219
277
|
else:
|
|
@@ -44,6 +44,7 @@ from src.utils.media_analysis_jobs import (
|
|
|
44
44
|
run_batch_job_slice,
|
|
45
45
|
)
|
|
46
46
|
from src.utils.platform import setup_environment
|
|
47
|
+
from src.utils.resolve_connection import connect_resolve
|
|
47
48
|
from src.utils.analysis_memory import read_panel_state, write_panel_state
|
|
48
49
|
from src.utils import brain_edits as _brain_edits
|
|
49
50
|
from src.utils import timeline_versioning as _timeline_versioning
|
|
@@ -11838,7 +11839,7 @@ def _connect_resolve_read_only() -> Tuple[Any, Optional[str]]:
|
|
|
11838
11839
|
except Exception as exc:
|
|
11839
11840
|
return None, f"Resolve scripting API unavailable: {exc}"
|
|
11840
11841
|
try:
|
|
11841
|
-
resolve = dvr_script
|
|
11842
|
+
resolve = connect_resolve(dvr_script)
|
|
11842
11843
|
except Exception as exc:
|
|
11843
11844
|
return None, f"Resolve connection failed: {exc}"
|
|
11844
11845
|
if resolve is None:
|
package/src/granular/common.py
CHANGED
|
@@ -44,6 +44,7 @@ from src.utils.layout_presets import (
|
|
|
44
44
|
)
|
|
45
45
|
from src.utils.object_inspection import inspect_object, print_object_help
|
|
46
46
|
from src.utils.platform import get_platform, get_resolve_paths
|
|
47
|
+
from src.utils.resolve_connection import connect_resolve
|
|
47
48
|
from src.utils.project_properties import (
|
|
48
49
|
get_all_project_properties,
|
|
49
50
|
get_color_settings,
|
|
@@ -80,7 +81,7 @@ if not logging.getLogger().handlers:
|
|
|
80
81
|
handlers=[logging.StreamHandler()],
|
|
81
82
|
)
|
|
82
83
|
|
|
83
|
-
VERSION = "2.
|
|
84
|
+
VERSION = "2.64.0"
|
|
84
85
|
logger = logging.getLogger("davinci-resolve-mcp")
|
|
85
86
|
logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
|
|
86
87
|
logger.info(f"Detected platform: {get_platform()}")
|
|
@@ -233,7 +234,7 @@ dvr_script = None
|
|
|
233
234
|
try:
|
|
234
235
|
import DaVinciResolveScript as dvr_script # type: ignore
|
|
235
236
|
|
|
236
|
-
resolve = dvr_script
|
|
237
|
+
resolve = connect_resolve(dvr_script)
|
|
237
238
|
if resolve:
|
|
238
239
|
logger.info(
|
|
239
240
|
f"Connected to DaVinci Resolve: {resolve.GetProductName()} {resolve.GetVersionString()}"
|
|
@@ -311,7 +312,7 @@ def _try_connect():
|
|
|
311
312
|
"""Attempt to connect to Resolve once. Returns resolve object or None."""
|
|
312
313
|
global resolve
|
|
313
314
|
try:
|
|
314
|
-
candidate = dvr_script
|
|
315
|
+
candidate = connect_resolve(dvr_script)
|
|
315
316
|
if candidate and _is_resolve_handle_live(candidate):
|
|
316
317
|
resolve = candidate
|
|
317
318
|
logger.info(f"Connected: {resolve.GetProductName()} {resolve.GetVersionString()}")
|
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.
|
|
14
|
+
VERSION = "2.64.0"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -97,6 +97,7 @@ from src.utils.media_analysis_jobs import (
|
|
|
97
97
|
run_batch_job_slice as run_media_analysis_batch_job_slice,
|
|
98
98
|
)
|
|
99
99
|
from src.utils.platform import get_resolve_paths, get_resolve_plugin_paths
|
|
100
|
+
from src.utils.resolve_connection import connect_resolve
|
|
100
101
|
from src.utils.lut_paths import master_lut_dir, ensure_lut_in_master
|
|
101
102
|
from src.utils import fuse_templates, dctl_templates, script_templates
|
|
102
103
|
from src.utils.timeline_title_text import (
|
|
@@ -804,7 +805,7 @@ def _try_connect():
|
|
|
804
805
|
if dvr_script is None:
|
|
805
806
|
return None
|
|
806
807
|
try:
|
|
807
|
-
candidate = dvr_script
|
|
808
|
+
candidate = connect_resolve(dvr_script)
|
|
808
809
|
if candidate and _is_resolve_handle_live(candidate):
|
|
809
810
|
resolve = candidate
|
|
810
811
|
logger.info(f"Connected: {resolve.GetProductName()} {resolve.GetVersionString()}")
|
|
@@ -3,35 +3,66 @@
|
|
|
3
3
|
DaVinci Resolve Connection Utilities
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import os
|
|
7
6
|
import logging
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
|
|
8
10
|
from .platform import get_platform, get_resolve_paths, setup_environment
|
|
9
11
|
|
|
10
12
|
logger = logging.getLogger("davinci-resolve-mcp.connection")
|
|
11
13
|
|
|
14
|
+
DEFAULT_RESOLVE_SCRIPT_TIMEOUT = 5.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _network_timeout():
|
|
18
|
+
value = os.environ.get(
|
|
19
|
+
"RESOLVE_SCRIPT_TIMEOUT",
|
|
20
|
+
str(DEFAULT_RESOLVE_SCRIPT_TIMEOUT),
|
|
21
|
+
)
|
|
22
|
+
try:
|
|
23
|
+
timeout = float(value)
|
|
24
|
+
except ValueError as exc:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"RESOLVE_SCRIPT_TIMEOUT must be a positive finite number"
|
|
27
|
+
) from exc
|
|
28
|
+
if not math.isfinite(timeout) or timeout <= 0:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
"RESOLVE_SCRIPT_TIMEOUT must be a positive finite number"
|
|
31
|
+
)
|
|
32
|
+
return timeout
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def connect_resolve(dvr_script):
|
|
36
|
+
"""Create a Resolve handle, optionally targeting an explicit network host."""
|
|
37
|
+
host = os.environ.get("RESOLVE_SCRIPT_HOST")
|
|
38
|
+
if host:
|
|
39
|
+
return dvr_script.scriptapp("Resolve", host, _network_timeout())
|
|
40
|
+
return dvr_script.scriptapp("Resolve")
|
|
41
|
+
|
|
42
|
+
|
|
12
43
|
def initialize_resolve():
|
|
13
44
|
"""Initialize connection to DaVinci Resolve application."""
|
|
14
45
|
try:
|
|
15
46
|
# Import the DaVinci Resolve scripting module
|
|
16
47
|
import DaVinciResolveScript as dvr_script
|
|
17
|
-
|
|
48
|
+
|
|
18
49
|
# Get the resolve object
|
|
19
|
-
resolve = dvr_script
|
|
20
|
-
|
|
50
|
+
resolve = connect_resolve(dvr_script)
|
|
51
|
+
|
|
21
52
|
if resolve is None:
|
|
22
53
|
logger.error("Failed to get Resolve object. Is DaVinci Resolve running?")
|
|
23
54
|
return None
|
|
24
|
-
|
|
55
|
+
|
|
25
56
|
logger.info(f"Connected to DaVinci Resolve: {resolve.GetProductName()} {resolve.GetVersionString()}")
|
|
26
57
|
return resolve
|
|
27
|
-
|
|
58
|
+
|
|
28
59
|
except ImportError:
|
|
29
60
|
platform_name = get_platform()
|
|
30
61
|
paths = get_resolve_paths()
|
|
31
|
-
|
|
62
|
+
|
|
32
63
|
logger.error("Failed to import DaVinciResolveScript. Check environment variables.")
|
|
33
64
|
logger.error("RESOLVE_SCRIPT_API, RESOLVE_SCRIPT_LIB, and PYTHONPATH must be set correctly.")
|
|
34
|
-
|
|
65
|
+
|
|
35
66
|
if platform_name == 'darwin':
|
|
36
67
|
logger.error("On macOS, typically:")
|
|
37
68
|
logger.error(f'export RESOLVE_SCRIPT_API="{paths["api_path"]}"')
|
|
@@ -47,24 +78,25 @@ def initialize_resolve():
|
|
|
47
78
|
logger.error(f'export RESOLVE_SCRIPT_API="{paths["api_path"]}"')
|
|
48
79
|
logger.error(f'export RESOLVE_SCRIPT_LIB="{paths["lib_path"]}"')
|
|
49
80
|
logger.error(f'export PYTHONPATH="$PYTHONPATH:{paths["modules_path"]}"')
|
|
50
|
-
|
|
81
|
+
|
|
51
82
|
return None
|
|
52
|
-
|
|
83
|
+
|
|
53
84
|
except Exception as e:
|
|
54
85
|
logger.error(f"Unexpected error initializing Resolve: {str(e)}")
|
|
55
86
|
return None
|
|
56
87
|
|
|
88
|
+
|
|
57
89
|
def check_environment_variables():
|
|
58
90
|
"""Check if the required environment variables are set."""
|
|
59
91
|
resolve_script_api = os.environ.get("RESOLVE_SCRIPT_API")
|
|
60
92
|
resolve_script_lib = os.environ.get("RESOLVE_SCRIPT_LIB")
|
|
61
|
-
|
|
93
|
+
|
|
62
94
|
missing_vars = []
|
|
63
95
|
if not resolve_script_api:
|
|
64
96
|
missing_vars.append("RESOLVE_SCRIPT_API")
|
|
65
97
|
if not resolve_script_lib:
|
|
66
98
|
missing_vars.append("RESOLVE_SCRIPT_LIB")
|
|
67
|
-
|
|
99
|
+
|
|
68
100
|
return {
|
|
69
101
|
"all_set": len(missing_vars) == 0,
|
|
70
102
|
"missing": missing_vars,
|
|
@@ -72,6 +104,7 @@ def check_environment_variables():
|
|
|
72
104
|
"resolve_script_lib": resolve_script_lib
|
|
73
105
|
}
|
|
74
106
|
|
|
107
|
+
|
|
75
108
|
def set_default_environment_variables():
|
|
76
109
|
"""Set the default environment variables based on platform."""
|
|
77
|
-
return setup_environment()
|
|
110
|
+
return setup_environment()
|