davinci-resolve-mcp 4.7.4 → 4.7.5

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,25 @@
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.5 — the control panel serves on the `::1` loopback it accepts
6
+
7
+ ### Fixed
8
+
9
+ - **`open_control_panel(host="::1")` was accepted by both loopback guards and could
10
+ never start.** ([#242](https://github.com/samuelgursky/davinci-resolve-mcp/pull/242), @Dev-next-gen)
11
+ The panel's `ThreadingHTTPServer` is AF_INET, so binding `("::1", port)` raised
12
+ `socket.gaierror` and the tool reported "Control panel child exited (rc=1) before
13
+ serving" — while the panel's own `--host` refusal message listed `::1` as allowed.
14
+ Behind it, the launch URL, the pidfile URL and the `/api/boot` probe URL were all
15
+ written `http://::1:<port>/`, which neither a browser nor urllib parses.
16
+ `make_panel_server()` now uses an AF_INET6 subclass for an IPv6 literal, and the
17
+ three URLs bracket the host. The Host/Origin gate already accepted `[::1]`; IPv4
18
+ and `localhost` take the same paths as before. Guard test:
19
+ `tests/test_control_panel_ipv6_loopback.py` launches the real panel on `::1`
20
+ through `_open_control_panel`, GETs `/` over IPv6, parses the issued URL, and probes
21
+ `/api/boot` with the issued token; it skips on a host with no IPv6 loopback and ran
22
+ (did not skip) on the macOS landing machine.
23
+
5
24
  ## What's New in v4.7.4 — the networked transport can serve a client on another machine
6
25
 
7
26
  ### Fixed
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.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.7.5-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.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-4.7.5-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.4 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v4.7.5 版 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.4"
40
+ VERSION = "4.7.5"
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.4",
3
+ "version": "4.7.5",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -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 = ThreadingHTTPServer((args.host, args.port), Handler)
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)
@@ -93,7 +93,7 @@ if not logging.getLogger().handlers:
93
93
  handlers=[logging.StreamHandler()],
94
94
  )
95
95
 
96
- VERSION = "4.7.4"
96
+ VERSION = "4.7.5"
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.4"
14
+ VERSION = "4.7.5"
15
15
 
16
16
  import base64
17
17
  import os
@@ -17491,6 +17491,11 @@ def _control_panel_read_token() -> Optional[str]:
17491
17491
  _CONTROL_PANEL_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "::1"})
17492
17492
 
17493
17493
 
17494
+ def _control_panel_url_host(host: str) -> str:
17495
+ """Host as it goes in a URL: `::1` must be written `[::1]`."""
17496
+ return f"[{host}]" if ":" in host else host
17497
+
17498
+
17494
17499
  def _control_panel_pid_alive(pid: int) -> bool:
17495
17500
  """Check whether a PID is alive on this OS without killing it."""
17496
17501
  if not pid or pid <= 0:
@@ -17578,7 +17583,7 @@ def _control_panel_probe(host: str, port: int, timeout: float = 1.5,
17578
17583
  """
17579
17584
  import urllib.error
17580
17585
  import urllib.request
17581
- url = f"http://{host}:{port}/api/boot"
17586
+ url = f"http://{_control_panel_url_host(host)}:{port}/api/boot"
17582
17587
  headers = {"Authorization": f"Bearer {token}"} if token else {}
17583
17588
  try:
17584
17589
  req = urllib.request.Request(url, headers=headers)
@@ -17838,7 +17843,7 @@ def _open_control_panel(p: Dict[str, Any]) -> Dict[str, Any]:
17838
17843
  # Write the pidfile so subsequent calls find it. The token travels in the
17839
17844
  # URL fragment — browsers never send fragments, so it stays out of every
17840
17845
  # request line and log.
17841
- url = f"http://{host}:{port}/#token={panel_token}"
17846
+ url = f"http://{_control_panel_url_host(host)}:{port}/#token={panel_token}"
17842
17847
  state = {
17843
17848
  "pid": proc.pid,
17844
17849
  "port": port,