davinci-resolve-mcp 4.7.4 → 4.7.6
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 +35 -0
- package/README.md +1 -1
- package/README.zh-CN.md +2 -2
- package/install.py +1 -1
- package/package.json +1 -1
- package/src/analysis_dashboard.py +20 -2
- package/src/granular/common.py +3 -1
- package/src/server.py +10 -3
- package/src/utils/mcp_import_stubs.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
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.6 — `serverInfo.version` reports this project's version
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **The MCP `initialize` handshake advertised the installed SDK's version (1.30.0)
|
|
10
|
+
as the server's, not the project's.** ([#243](https://github.com/samuelgursky/davinci-resolve-mcp/issues/243) reported with the diagnosis by @Eniot666; fixed in [#244](https://github.com/samuelgursky/davinci-resolve-mcp/pull/244) by @DYNOSuprovo)
|
|
11
|
+
`FastMCP.__init__` in the 1.x SDK has no `version` parameter, so it builds the
|
|
12
|
+
low-level `Server` without one and `create_initialization_options()` falls back to
|
|
13
|
+
`pkg_version("mcp")`. Every client displayed the SDK's number, so the version a user
|
|
14
|
+
quoted in a bug report was not this project's. Both FastMCP instances (compound and
|
|
15
|
+
granular) now set `_mcp_server.version = VERSION` right after construction, and the
|
|
16
|
+
test-mode stub carries the attribute. This reaches through a private SDK attribute
|
|
17
|
+
on purpose — the SDK exposes no public route — and
|
|
18
|
+
`tests/test_server_info_version.py` pins `create_initialization_options().server_version`
|
|
19
|
+
on both servers so a future SDK that adds the parameter can replace it cleanly.
|
|
20
|
+
|
|
21
|
+
## What's New in v4.7.5 — the control panel serves on the `::1` loopback it accepts
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`open_control_panel(host="::1")` was accepted by both loopback guards and could
|
|
26
|
+
never start.** ([#242](https://github.com/samuelgursky/davinci-resolve-mcp/pull/242), @Dev-next-gen)
|
|
27
|
+
The panel's `ThreadingHTTPServer` is AF_INET, so binding `("::1", port)` raised
|
|
28
|
+
`socket.gaierror` and the tool reported "Control panel child exited (rc=1) before
|
|
29
|
+
serving" — while the panel's own `--host` refusal message listed `::1` as allowed.
|
|
30
|
+
Behind it, the launch URL, the pidfile URL and the `/api/boot` probe URL were all
|
|
31
|
+
written `http://::1:<port>/`, which neither a browser nor urllib parses.
|
|
32
|
+
`make_panel_server()` now uses an AF_INET6 subclass for an IPv6 literal, and the
|
|
33
|
+
three URLs bracket the host. The Host/Origin gate already accepted `[::1]`; IPv4
|
|
34
|
+
and `localhost` take the same paths as before. Guard test:
|
|
35
|
+
`tests/test_control_panel_ipv6_loopback.py` launches the real panel on `::1`
|
|
36
|
+
through `_open_control_panel`, GETs `/` over IPv6, parses the issued URL, and probes
|
|
37
|
+
`/api/boot` with the issued token; it skips on a host with no IPv6 loopback and ran
|
|
38
|
+
(did not skip) on the macOS landing machine.
|
|
39
|
+
|
|
5
40
|
## What's New in v4.7.4 — the networked transport can serve a client on another machine
|
|
6
41
|
|
|
7
42
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [简体中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#server-modes)
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
|
|
6
6
|
[](https://www.npmjs.com/package/davinci-resolve-mcp)
|
|
7
7
|
[](docs/reference/api-coverage.md)
|
|
8
8
|
[-blue.svg)](#服务器模式)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.python.org/downloads/)
|
|
13
13
|
[](https://opensource.org/licenses/MIT)
|
|
14
14
|
|
|
15
|
-
> 本翻译对应 v4.7.
|
|
15
|
+
> 本翻译对应 v4.7.6 版 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.
|
|
40
|
+
VERSION = "4.7.6"
|
|
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
|
@@ -10,6 +10,7 @@ import json
|
|
|
10
10
|
import os
|
|
11
11
|
import re
|
|
12
12
|
import secrets
|
|
13
|
+
import socket
|
|
13
14
|
import sqlite3
|
|
14
15
|
import sys
|
|
15
16
|
import threading
|
|
@@ -16289,6 +16290,23 @@ def _loopback_host(value: str) -> str:
|
|
|
16289
16290
|
return value
|
|
16290
16291
|
|
|
16291
16292
|
|
|
16293
|
+
class _IPv6ThreadingHTTPServer(ThreadingHTTPServer):
|
|
16294
|
+
address_family = socket.AF_INET6
|
|
16295
|
+
|
|
16296
|
+
|
|
16297
|
+
def make_panel_server(host: str, port: int, handler: type) -> ThreadingHTTPServer:
|
|
16298
|
+
"""Bind the panel. ThreadingHTTPServer is AF_INET, so `::1` needs the v6 class."""
|
|
16299
|
+
bind = host.strip("[]")
|
|
16300
|
+
server_cls = _IPv6ThreadingHTTPServer if ":" in bind else ThreadingHTTPServer
|
|
16301
|
+
return server_cls((bind, port), handler)
|
|
16302
|
+
|
|
16303
|
+
|
|
16304
|
+
def panel_url_host(host: str) -> str:
|
|
16305
|
+
"""Host as it goes in a URL: an IPv6 literal must be bracketed."""
|
|
16306
|
+
bind = host.strip("[]")
|
|
16307
|
+
return f"[{bind}]" if ":" in bind else bind
|
|
16308
|
+
|
|
16309
|
+
|
|
16292
16310
|
def parse_args() -> argparse.Namespace:
|
|
16293
16311
|
parser = argparse.ArgumentParser(description="Run the local Resolve MCP control panel.")
|
|
16294
16312
|
parser.add_argument("--host", default="127.0.0.1", type=_loopback_host)
|
|
@@ -16325,10 +16343,10 @@ def main() -> None:
|
|
|
16325
16343
|
state = DashboardState(args.project_name, args.project_id, args.analysis_root)
|
|
16326
16344
|
Handler.state = state
|
|
16327
16345
|
Handler.token = resolve_panel_token()
|
|
16328
|
-
server =
|
|
16346
|
+
server = make_panel_server(args.host, args.port, Handler)
|
|
16329
16347
|
# The token rides in the URL fragment: browsers keep fragments client-side,
|
|
16330
16348
|
# so it never appears in a request line, proxy log, or Referer.
|
|
16331
|
-
url = f"http://{args.host}:{args.port}/#token={Handler.token}"
|
|
16349
|
+
url = f"http://{panel_url_host(args.host)}:{args.port}/#token={Handler.token}"
|
|
16332
16350
|
# flush: under --no-open the URL (with its token) is the only handle the
|
|
16333
16351
|
# operator gets, and a piped/redirected stdout would otherwise hold it back.
|
|
16334
16352
|
print(f"DaVinci Resolve MCP: {url}", flush=True)
|
package/src/granular/common.py
CHANGED
|
@@ -93,7 +93,7 @@ if not logging.getLogger().handlers:
|
|
|
93
93
|
handlers=[logging.StreamHandler()],
|
|
94
94
|
)
|
|
95
95
|
|
|
96
|
-
VERSION = "4.7.
|
|
96
|
+
VERSION = "4.7.6"
|
|
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()}")
|
|
@@ -101,6 +101,8 @@ logger.info(f"Using Resolve API path: {RESOLVE_API_PATH}")
|
|
|
101
101
|
logger.info(f"Using Resolve library path: {RESOLVE_LIB_PATH}")
|
|
102
102
|
|
|
103
103
|
mcp = FastMCP("DaVinciResolveMCP")
|
|
104
|
+
if hasattr(mcp, "_mcp_server"):
|
|
105
|
+
mcp._mcp_server.version = VERSION
|
|
104
106
|
|
|
105
107
|
READ_ONLY_TOOL = ToolAnnotations(
|
|
106
108
|
readOnlyHint=True,
|
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.
|
|
14
|
+
VERSION = "4.7.6"
|
|
15
15
|
|
|
16
16
|
import base64
|
|
17
17
|
import os
|
|
@@ -263,6 +263,8 @@ mcp = FastMCP(
|
|
|
263
263
|
"issue link for the user to review and submit; nothing is filed for them."
|
|
264
264
|
),
|
|
265
265
|
)
|
|
266
|
+
if hasattr(mcp, "_mcp_server"):
|
|
267
|
+
mcp._mcp_server.version = VERSION
|
|
266
268
|
|
|
267
269
|
READ_ONLY_TOOL = mcp_types.ToolAnnotations(
|
|
268
270
|
readOnlyHint=True,
|
|
@@ -17491,6 +17493,11 @@ def _control_panel_read_token() -> Optional[str]:
|
|
|
17491
17493
|
_CONTROL_PANEL_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "::1"})
|
|
17492
17494
|
|
|
17493
17495
|
|
|
17496
|
+
def _control_panel_url_host(host: str) -> str:
|
|
17497
|
+
"""Host as it goes in a URL: `::1` must be written `[::1]`."""
|
|
17498
|
+
return f"[{host}]" if ":" in host else host
|
|
17499
|
+
|
|
17500
|
+
|
|
17494
17501
|
def _control_panel_pid_alive(pid: int) -> bool:
|
|
17495
17502
|
"""Check whether a PID is alive on this OS without killing it."""
|
|
17496
17503
|
if not pid or pid <= 0:
|
|
@@ -17578,7 +17585,7 @@ def _control_panel_probe(host: str, port: int, timeout: float = 1.5,
|
|
|
17578
17585
|
"""
|
|
17579
17586
|
import urllib.error
|
|
17580
17587
|
import urllib.request
|
|
17581
|
-
url = f"http://{host}:{port}/api/boot"
|
|
17588
|
+
url = f"http://{_control_panel_url_host(host)}:{port}/api/boot"
|
|
17582
17589
|
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
17583
17590
|
try:
|
|
17584
17591
|
req = urllib.request.Request(url, headers=headers)
|
|
@@ -17838,7 +17845,7 @@ def _open_control_panel(p: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
17838
17845
|
# Write the pidfile so subsequent calls find it. The token travels in the
|
|
17839
17846
|
# URL fragment — browsers never send fragments, so it stays out of every
|
|
17840
17847
|
# request line and log.
|
|
17841
|
-
url = f"http://{host}:{port}/#token={panel_token}"
|
|
17848
|
+
url = f"http://{_control_panel_url_host(host)}:{port}/#token={panel_token}"
|
|
17842
17849
|
state = {
|
|
17843
17850
|
"pid": proc.pid,
|
|
17844
17851
|
"port": port,
|
|
@@ -51,7 +51,7 @@ def _real_mcp_is_importable() -> bool:
|
|
|
51
51
|
def _build_stub_modules(*, stdio_note: str) -> Dict[str, types.ModuleType]:
|
|
52
52
|
class FastMCP:
|
|
53
53
|
def __init__(self, *args, **kwargs):
|
|
54
|
-
|
|
54
|
+
self._mcp_server = types.SimpleNamespace(version=None)
|
|
55
55
|
|
|
56
56
|
def _decorator(self, *args, **kwargs):
|
|
57
57
|
def decorate(func):
|