davinci-resolve-mcp 2.87.2 → 2.88.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 CHANGED
@@ -2,6 +2,56 @@
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.88.0
6
+
7
+ The twelve Resolve 21.0.4 surfaces that only existed on the compound server now
8
+ exist on the granular one too. Issue #140, PR #142 by @legionsound.
9
+
10
+ ### Added
11
+
12
+ - **Twelve granular tools closing the 21.0.4 delta.** `get_layout_preset_list`,
13
+ `get_burn_in_preset_list` and `delete_burn_in_preset`; the six
14
+ `*_user_preferences_preset` tools; `get_project_attributes_in_current_folder`;
15
+ `get_clip_timeline`; and `get_selected_timeline_items`. Each is guarded with
16
+ `_requires_method` at 21.0.4, so an older build gets a named version error
17
+ rather than an attribute crash, and each returns the same shape its compound
18
+ counterpart does. `get_selected_timeline_items` is deliberately not called
19
+ `get_selected_clips` — that name belongs to the Media Pool selection tool, and
20
+ the collision was the confusion the issue reported. The granular server is now
21
+ 353 tools.
22
+ - **`load_user_preferences_preset` carries the SESSION-WIDE warning** in its
23
+ docstring, matching the compound action. It swaps the user's global Resolve
24
+ preferences, not a project setting.
25
+
26
+ ### Fixed
27
+
28
+ - **Four places still said 341 after the count moved to 353, and the guard that
29
+ exists to catch exactly that was not looking at any of them.** The literal in
30
+ `tests/test_import.py` was the one that bit: that file is pytest-style, so
31
+ `unittest discover` never collects it, and a green 2560-test run said nothing
32
+ while `python tests/test_import.py` — the smoke step in the publish workflow
33
+ and step one of the release process — failed. The other three were the startup
34
+ log line in `src/resolve_mcp_server.py`, `docs/install.md` (which had also been
35
+ quoting 32 compound tools since the compound server reached 34), and the badge,
36
+ server-modes table and metrics table in `README.zh-CN.md`. All six files are in
37
+ `test_doc_tool_counts` now, along with the English README's Tools badge, so the
38
+ next count change fails offline instead of at publish time.
39
+
40
+ ### Validation
41
+
42
+ - 2560 offline tests OK; `python tests/test_import.py` exits 0; agent-rules
43
+ drift check in sync.
44
+ - The twelve 21.0.4 methods were exercised live on Studio 21.0.4.5 by the
45
+ contributor — full round-trips for the layout, burn-in and user-preferences
46
+ preset families, project attributes across 8 projects, and both branches of
47
+ `get_clip_timeline`. `LoadUserPreferencesPreset` was deliberately not executed,
48
+ by them or here. The validation machine for this repo runs 19.1.3, so the live
49
+ results stay attributed to the reporter in `docs/reference/api-coverage.md`.
50
+ - What could be checked live here was: against a running Studio 19.1.3.7, the
51
+ new tools return their named `requires DaVinci Resolve 21.0.4+` error rather
52
+ than crashing on a missing attribute — the guard path exercised against a real
53
+ Resolve object, not a stub.
54
+
5
55
  ## What's New in v2.87.2
6
56
 
7
57
  A refused `SetSetting` now says why, when the ledger already knows. Issue #141,
package/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.87.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.88.0-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
- [![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(341%20full)-blue.svg)](#server-modes)
8
+ [![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(353%20full)-blue.svg)](#server-modes)
9
9
  [![Advanced](https://img.shields.io/badge/Advanced%20(offline)-18%20tools-blueviolet.svg)](#server-modes)
10
10
  [![Tested](https://img.shields.io/badge/Live%20Tested-93.6%25-green.svg)](docs/reference/api-coverage.md#test-results)
11
11
  [![DaVinci Resolve](https://img.shields.io/badge/DaVinci%20Resolve-18.5+-darkred.svg)](https://www.blackmagicdesign.com/products/davinciresolve)
@@ -100,7 +100,7 @@ The command starts a localhost server and opens the control panel in your browse
100
100
  | Mode | Entry point | Tools | Best for |
101
101
  |------|-------------|-------|----------|
102
102
  | Compound | `src/server.py` | 34 | Default mode for most assistants. Related Resolve operations are grouped behind action parameters to keep context usage low. |
103
- | Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 341 | Power users who want one MCP tool per Resolve API method. |
103
+ | Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 353 | Power users who want one MCP tool per Resolve API method. |
104
104
 
105
105
  The compound server is recommended unless you specifically need the granular one-tool-per-method surface.
106
106
 
@@ -262,7 +262,7 @@ The default server is a local stdio process launched by your MCP client; it does
262
262
 
263
263
  | Metric | Value |
264
264
  |--------|-------|
265
- | MCP Tools | **34** compound / **341** granular (live server) |
265
+ | MCP Tools | **34** compound / **353** granular (live server) |
266
266
  | Advanced (offline) tools | **18** — .drp/.drt/.drx + DB authoring, no Resolve running |
267
267
  | Kernel Actions | **136** guarded workflow actions across 9 compound tools |
268
268
  | API Methods Covered | **361/361** (100%) |
package/README.zh-CN.md CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  [English](README.md) | 简体中文
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.87.2-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.88.0-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
- [![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(341%20full)-blue.svg)](#服务器模式)
8
+ [![Tools](https://img.shields.io/badge/MCP%20Tools-34%20(353%20full)-blue.svg)](#服务器模式)
9
9
  [![Advanced](https://img.shields.io/badge/Advanced%20(offline)-18%20tools-blueviolet.svg)](#服务器模式)
10
10
  [![Tested](https://img.shields.io/badge/Live%20Tested-93.6%25-green.svg)](docs/reference/api-coverage.md#test-results)
11
11
  [![DaVinci Resolve](https://img.shields.io/badge/DaVinci%20Resolve-18.5+-darkred.svg)](https://www.blackmagicdesign.com/products/davinciresolve)
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
- > 本翻译对应 v2.87.2 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.88.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -76,7 +76,7 @@ venv/bin/python -m src.control_panel
76
76
  | 模式 | 入口 | 工具数 | 适合谁 |
77
77
  |------|------|--------|--------|
78
78
  | Compound(复合) | `src/server.py` | 34 | 大多数助手的默认模式。相关的 Resolve 操作按 action 参数分组,压低上下文占用。 |
79
- | Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 341 | 想要"一个 Resolve API 方法 = 一个 MCP 工具"的重度用户。 |
79
+ | Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 353 | 想要"一个 Resolve API 方法 = 一个 MCP 工具"的重度用户。 |
80
80
 
81
81
  除非你明确需要一方法一工具的细粒度界面,否则推荐复合模式。
82
82
 
@@ -190,7 +190,7 @@ DRX 调色写入**针对 Resolve Studio 做过实机校准**:调色参数默
190
190
 
191
191
  | 指标 | 数值 |
192
192
  |------|------|
193
- | MCP 工具 | **34** 复合 / **341** 细粒度(实时服务器) |
193
+ | MCP 工具 | **34** 复合 / **353** 细粒度(实时服务器) |
194
194
  | Advanced(离线)工具 | **18**——.drp/.drt/.drx + 数据库创作,无需 Resolve 运行 |
195
195
  | 内核 action | 9 个复合工具下 **136** 个带护栏的工作流 action |
196
196
  | API 方法覆盖 | **361/361**(100%) |
package/docs/SKILL.md CHANGED
@@ -158,7 +158,7 @@ before mutating Resolve state.
158
158
  | Mode | Entry point | Tool count | Use when |
159
159
  |---|---|---|---|
160
160
  | Compound (default) | `src/server.py` | 34 tools | Most workflows — keeps context lean |
161
- | Granular (full) | `src/server.py --full` | 341 tools | Power users needing one tool per API method |
161
+ | Granular (full) | `src/server.py --full` | 353 tools | Power users needing one tool per API method |
162
162
 
163
163
  This skill document covers the **compound server** (the default). Each compound
164
164
  tool accepts an `action` string and an optional `params` object.
@@ -64,7 +64,7 @@ davinci-resolve-mcp/
64
64
  ├── install.py # Universal installer (macOS/Windows/Linux)
65
65
  ├── src/
66
66
  │ ├── server.py # Compound MCP server — 34 tools (default)
67
- │ ├── resolve_mcp_server.py # Thin full-server entrypoint — 341 tools
67
+ │ ├── resolve_mcp_server.py # Thin full-server entrypoint — 353 tools
68
68
  │ ├── granular/ # Modular full-server implementation
69
69
  │ └── utils/ # Platform detection, Resolve connection helpers
70
70
  ├── tests/ # offline suite (test_*.py) + live harnesses (live_*.py):
package/docs/install.md CHANGED
@@ -132,8 +132,8 @@ The MCP server comes in two modes:
132
132
 
133
133
  | Mode | File | Tools | Best For |
134
134
  |------|------|-------|----------|
135
- | **Compound** (default) | `src/server.py` | 32 | Most users — fast, clean, low context usage |
136
- | **Full** | `src/resolve_mcp_server.py` | 341 | Power users who want one tool per API method |
135
+ | **Compound** (default) | `src/server.py` | 34 | Most users — fast, clean, low context usage |
136
+ | **Full** | `src/resolve_mcp_server.py` | 353 | Power users who want one tool per API method |
137
137
 
138
138
  The compound server's `timeline_item` tool includes dedicated actions for common workflows:
139
139
 
@@ -148,7 +148,7 @@ The compound server's `timeline_item` tool includes dedicated actions for common
148
148
 
149
149
  The installer uses the compound server by default. To use the full server:
150
150
  ```bash
151
- python src/server.py --full # Launch full 341-tool server
151
+ python src/server.py --full # Launch full 353-tool server
152
152
  # Or point your MCP config directly at src/resolve_mcp_server.py
153
153
  ```
154
154
 
@@ -6,7 +6,7 @@ Complete Resolve scripting API coverage, live-test status, and method-by-method
6
6
 
7
7
  | Metric | Value |
8
8
  |--------|-------|
9
- | MCP Tools | **34** compound (default) / **341** granular |
9
+ | MCP Tools | **34** compound (default) / **353** granular |
10
10
  | Kernel Actions | **136** guarded MCP workflow actions across 9 compound tools |
11
11
  | API Methods Covered | **361/361** (100%) |
12
12
  | Methods Live Tested | **338/361** (93.6%) |
@@ -17,7 +17,7 @@ Complete Resolve scripting API coverage, live-test status, and method-by-method
17
17
 
18
18
  ## API Coverage
19
19
 
20
- Every non-deprecated method in the DaVinci Resolve Scripting API is covered. The default compound server exposes **34 tools** that group related operations by action parameter, keeping LLM context windows lean. The full granular server provides **341 individual tools** for power users. Both modes cover all 13 API object classes. MCP-level kernel actions are tracked separately in [Kernel Action Coverage](../kernels/README.md).
20
+ Every non-deprecated method in the DaVinci Resolve Scripting API is covered. The default compound server exposes **34 tools** that group related operations by action parameter, keeping LLM context windows lean. The full granular server provides **353 individual tools** for power users. Both modes cover all 13 API object classes. MCP-level kernel actions are tracked separately in [Kernel Action Coverage](../kernels/README.md).
21
21
 
22
22
  The 34th compound tool is `timeline_versioning` (C6) — an MCP-level workflow
23
23
  tool, not a wrapper around a Resolve API method. It surfaces the
package/install.py CHANGED
@@ -36,7 +36,7 @@ from src.utils.update_check import (
36
36
 
37
37
  # ─── Version ──────────────────────────────────────────────────────────────────
38
38
 
39
- VERSION = "2.87.2"
39
+ VERSION = "2.88.0"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.87.2",
3
+ "version": "2.88.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -85,7 +85,7 @@ if not logging.getLogger().handlers:
85
85
  handlers=[logging.StreamHandler()],
86
86
  )
87
87
 
88
- VERSION = "2.87.2"
88
+ VERSION = "2.88.0"
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()}")
@@ -1018,3 +1018,41 @@ def remove_clip_motion_blur(clip_id: str, deblur_option: Optional[Dict[str, Any]
1018
1018
  if not new_clip:
1019
1019
  return {"success": False}
1020
1020
  return {"success": True, "new": new_clip.GetName(), "new_id": new_clip.GetUniqueId()}
1021
+
1022
+
1023
+ @mcp.tool()
1024
+ def get_clip_timeline(clip_id: str) -> Dict[str, Any]:
1025
+ """Resolve a Media Pool timeline entry to its timeline summary (Resolve 21.0.4+).
1026
+
1027
+ Calls MediaPoolItem.GetTimeline(). Returns is_timeline=false for ordinary
1028
+ clips — that is an answer, not a failure.
1029
+
1030
+ Args:
1031
+ clip_id: Unique ID of the Media Pool item.
1032
+ """
1033
+ _, mp, err = _get_mp()
1034
+ if err:
1035
+ return err
1036
+ clip = _find_clip_by_id(mp.GetRootFolder(), clip_id)
1037
+ if not clip:
1038
+ return {"error": f"Clip {clip_id} not found"}
1039
+ missing = _requires_method(clip, "GetTimeline", "21.0.4")
1040
+ if missing:
1041
+ return missing
1042
+ try:
1043
+ tl_obj = clip.GetTimeline()
1044
+ except Exception as exc:
1045
+ return {"error": f"GetTimeline failed: {exc}"}
1046
+ if not tl_obj:
1047
+ return {"is_timeline": False, "timeline": None,
1048
+ "note": "This media pool item is not a timeline entry."}
1049
+ summary = {}
1050
+ for getter, key in (("GetName", "name"), ("GetUniqueId", "unique_id"),
1051
+ ("GetStartFrame", "start_frame"), ("GetEndFrame", "end_frame")):
1052
+ method = getattr(tl_obj, getter, None)
1053
+ if callable(method):
1054
+ try:
1055
+ summary[key] = method()
1056
+ except Exception:
1057
+ pass
1058
+ return {"is_timeline": True, "timeline": summary}
@@ -1667,3 +1667,21 @@ def generate_speech(text_input: str, voice_model: str = "", timecode: str = "",
1667
1667
  if not new_item:
1668
1668
  return {"success": False, "error": "GenerateSpeech returned no media item"}
1669
1669
  return {"success": True, "new": new_item.GetName(), "new_id": new_item.GetUniqueId()}
1670
+
1671
+
1672
+ @mcp.tool()
1673
+ def get_project_attributes_in_current_folder() -> Dict[str, Any]:
1674
+ """Get per-project attributes for every project in the current folder (Resolve 21.0.4+).
1675
+
1676
+ Calls ProjectManager.GetProjectAttributesInCurrentFolder(). Returns a dict
1677
+ keyed by project name with 'lastModifiedDate', 'creationDate', 'notes' and
1678
+ 'liveCollaborationMode' — without loading any project.
1679
+ """
1680
+ project_manager = get_project_manager()
1681
+ if not project_manager:
1682
+ return {"error": "Failed to get Project Manager"}
1683
+ missing = _requires_method(project_manager, "GetProjectAttributesInCurrentFolder", "21.0.4")
1684
+ if missing:
1685
+ return missing
1686
+ attributes = project_manager.GetProjectAttributesInCurrentFolder()
1687
+ return {"projects": attributes if attributes else {}}
@@ -536,3 +536,172 @@ def quit_resolve() -> Dict[str, Any]:
536
536
  return {"error": "Not connected to DaVinci Resolve"}
537
537
  resolve.Quit()
538
538
  return {"success": True, "message": "DaVinci Resolve is quitting"}
539
+
540
+
541
+ @mcp.tool()
542
+ def get_layout_preset_list() -> Dict[str, Any]:
543
+ """Get the names of all saved UI layout presets (Resolve 21.0.4+).
544
+
545
+ Calls Resolve.GetLayoutPresetList(). These are the names accepted by
546
+ load_layout_preset_tool, update_layout_preset, export_layout_preset_tool
547
+ and delete_layout_preset_tool.
548
+ """
549
+ resolve = get_resolve()
550
+ if resolve is None:
551
+ return {"error": "Not connected to DaVinci Resolve"}
552
+ missing = _requires_method(resolve, "GetLayoutPresetList", "21.0.4")
553
+ if missing:
554
+ return missing
555
+ presets = resolve.GetLayoutPresetList()
556
+ return {"presets": presets if presets else []}
557
+
558
+
559
+ @mcp.tool()
560
+ def get_burn_in_preset_list() -> Dict[str, Any]:
561
+ """Get the names of all saved data burn-in presets (Resolve 21.0.4+).
562
+
563
+ Calls Resolve.GetBurnInPresetList(). These are the names accepted by the
564
+ 'DataBurnIn' render setting, project load_burn_in_preset, and
565
+ export_burn_in_preset.
566
+ """
567
+ resolve = get_resolve()
568
+ if resolve is None:
569
+ return {"error": "Not connected to DaVinci Resolve"}
570
+ missing = _requires_method(resolve, "GetBurnInPresetList", "21.0.4")
571
+ if missing:
572
+ return missing
573
+ presets = resolve.GetBurnInPresetList()
574
+ return {"presets": presets if presets else []}
575
+
576
+
577
+ @mcp.tool()
578
+ def delete_burn_in_preset(preset_name: str) -> Dict[str, Any]:
579
+ """Delete a data burn-in preset by name (Resolve 21.0.4+).
580
+
581
+ Args:
582
+ preset_name: Name of the burn-in preset to delete.
583
+ """
584
+ resolve = get_resolve()
585
+ if resolve is None:
586
+ return {"error": "Not connected to DaVinci Resolve"}
587
+ missing = _requires_method(resolve, "DeleteBurnInPreset", "21.0.4")
588
+ if missing:
589
+ return missing
590
+ result = resolve.DeleteBurnInPreset(preset_name)
591
+ return {"success": bool(result), "preset_name": preset_name}
592
+
593
+
594
+ @mcp.tool()
595
+ def get_user_preferences_preset_list() -> Dict[str, Any]:
596
+ """Get the names of all saved user-preferences presets (Resolve 21.0.4+).
597
+
598
+ Calls Resolve.GetUserPreferencesPresetList().
599
+ """
600
+ resolve = get_resolve()
601
+ if resolve is None:
602
+ return {"error": "Not connected to DaVinci Resolve"}
603
+ missing = _requires_method(resolve, "GetUserPreferencesPresetList", "21.0.4")
604
+ if missing:
605
+ return missing
606
+ presets = resolve.GetUserPreferencesPresetList()
607
+ return {"presets": presets if presets else []}
608
+
609
+
610
+ @mcp.tool()
611
+ def save_user_preferences_preset(preset_name: str) -> Dict[str, Any]:
612
+ """Save the current user preferences as a named preset (Resolve 21.0.4+).
613
+
614
+ Args:
615
+ preset_name: Name for the new user-preferences preset.
616
+ """
617
+ resolve = get_resolve()
618
+ if resolve is None:
619
+ return {"error": "Not connected to DaVinci Resolve"}
620
+ missing = _requires_method(resolve, "SaveUserPreferencesPreset", "21.0.4")
621
+ if missing:
622
+ return missing
623
+ result = resolve.SaveUserPreferencesPreset(preset_name)
624
+ return {"success": bool(result), "preset_name": preset_name}
625
+
626
+
627
+ @mcp.tool()
628
+ def load_user_preferences_preset(preset_name: str) -> Dict[str, Any]:
629
+ """Load a user-preferences preset (Resolve 21.0.4+).
630
+
631
+ SESSION-WIDE: this swaps the user's global Resolve preferences, not a
632
+ project setting. It affects every project open in this Resolve instance.
633
+ Only call when the user explicitly asked for the switch.
634
+
635
+ Args:
636
+ preset_name: Name of the user-preferences preset to load.
637
+ """
638
+ resolve = get_resolve()
639
+ if resolve is None:
640
+ return {"error": "Not connected to DaVinci Resolve"}
641
+ missing = _requires_method(resolve, "LoadUserPreferencesPreset", "21.0.4")
642
+ if missing:
643
+ return missing
644
+ result = resolve.LoadUserPreferencesPreset(preset_name)
645
+ return {"success": bool(result), "preset_name": preset_name}
646
+
647
+
648
+ @mcp.tool()
649
+ def delete_user_preferences_preset(preset_name: str) -> Dict[str, Any]:
650
+ """Delete a user-preferences preset by name (Resolve 21.0.4+).
651
+
652
+ Args:
653
+ preset_name: Name of the user-preferences preset to delete.
654
+ """
655
+ resolve = get_resolve()
656
+ if resolve is None:
657
+ return {"error": "Not connected to DaVinci Resolve"}
658
+ missing = _requires_method(resolve, "DeleteUserPreferencesPreset", "21.0.4")
659
+ if missing:
660
+ return missing
661
+ result = resolve.DeleteUserPreferencesPreset(preset_name)
662
+ return {"success": bool(result), "preset_name": preset_name}
663
+
664
+
665
+ @mcp.tool()
666
+ def import_user_preferences_preset(import_path: str, preset_name: str = None) -> Dict[str, Any]:
667
+ """Import a user-preferences preset from a file (Resolve 21.0.4+).
668
+
669
+ The imported preset is NOT auto-loaded; it takes its name from the file
670
+ when preset_name is omitted (measured on Studio 21.0.4.5). Follow with
671
+ load_user_preferences_preset to activate it.
672
+
673
+ Args:
674
+ import_path: Absolute path to the preset file to import.
675
+ preset_name: Name to save the imported preset as (filename if None).
676
+ """
677
+ resolve = get_resolve()
678
+ if resolve is None:
679
+ return {"error": "Not connected to DaVinci Resolve"}
680
+ missing = _requires_method(resolve, "ImportUserPreferencesPreset", "21.0.4")
681
+ if missing:
682
+ return missing
683
+ if preset_name:
684
+ result = resolve.ImportUserPreferencesPreset(import_path, preset_name)
685
+ else:
686
+ result = resolve.ImportUserPreferencesPreset(import_path)
687
+ preset_name = os.path.splitext(os.path.basename(import_path))[0]
688
+ return {"success": bool(result), "preset_name": preset_name, "import_path": import_path,
689
+ "note": "The imported preset is not auto-loaded; use load_user_preferences_preset to activate it."}
690
+
691
+
692
+ @mcp.tool()
693
+ def export_user_preferences_preset(preset_name: str, export_path: str) -> Dict[str, Any]:
694
+ """Export a user-preferences preset to a file (Resolve 21.0.4+).
695
+
696
+ Args:
697
+ preset_name: Name of the user-preferences preset to export.
698
+ export_path: Absolute path where the preset file will be saved.
699
+ """
700
+ resolve = get_resolve()
701
+ if resolve is None:
702
+ return {"error": "Not connected to DaVinci Resolve"}
703
+ missing = _requires_method(resolve, "ExportUserPreferencesPreset", "21.0.4")
704
+ if missing:
705
+ return missing
706
+ result = resolve.ExportUserPreferencesPreset(preset_name, export_path)
707
+ return {"success": bool(result), "preset_name": preset_name, "export_path": export_path}
@@ -1087,3 +1087,33 @@ def set_timeline_setting(setting_name: str, setting_value: str) -> Dict[str, Any
1087
1087
  return err
1088
1088
  result = tl.SetSetting(setting_name, setting_value)
1089
1089
  return {"success": bool(result), "setting_name": setting_name, "setting_value": setting_value}
1090
+
1091
+
1092
+ @mcp.tool()
1093
+ def get_selected_timeline_items() -> Dict[str, Any]:
1094
+ """Get the timeline items currently selected in the timeline (Resolve 21.0.4+).
1095
+
1096
+ Calls Timeline.GetSelectedClips() on the current timeline. An empty list
1097
+ means nothing is selected — that is an answer, not a failure. (Distinct
1098
+ from get_selected_clips, which reads the Media Pool selection.)
1099
+ """
1100
+ _, tl, err = _get_timeline()
1101
+ if err:
1102
+ return err
1103
+ missing = _requires_method(tl, "GetSelectedClips", "21.0.4")
1104
+ if missing:
1105
+ return missing
1106
+ items = tl.GetSelectedClips() or []
1107
+ summaries = []
1108
+ for item in items:
1109
+ entry = {}
1110
+ for getter, key in (("GetName", "name"), ("GetUniqueId", "unique_id"),
1111
+ ("GetStart", "start"), ("GetEnd", "end")):
1112
+ method = getattr(item, getter, None)
1113
+ if callable(method):
1114
+ try:
1115
+ entry[key] = method()
1116
+ except Exception:
1117
+ pass
1118
+ summaries.append(entry)
1119
+ return {"count": len(summaries), "items": summaries}
@@ -34,7 +34,7 @@ from src.utils.update_check import start_background_update_check
34
34
  if __name__ == "__main__":
35
35
  try:
36
36
  start_background_update_check(VERSION, project_dir, logger)
37
- logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (341 granular tools)")
37
+ logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION} (353 granular tools)")
38
38
  run_fastmcp_stdio(mcp)
39
39
  except KeyboardInterrupt:
40
40
  logger.info("Server shutdown requested")
package/src/server.py CHANGED
@@ -8,10 +8,10 @@ Each tool groups related operations via an 'action' parameter.
8
8
 
9
9
  Usage:
10
10
  python src/server.py # Start the MCP server
11
- python src/server.py --full # Start the 341-tool granular server instead
11
+ python src/server.py --full # Start the 353-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.87.2"
14
+ VERSION = "2.88.0"
15
15
 
16
16
  import base64
17
17
  import os
@@ -27796,9 +27796,9 @@ if __name__ == "__main__":
27796
27796
  start_background_update_check(VERSION, project_dir, logger, env=_setup_update_env())
27797
27797
  _install_threaded_tool_dispatch(mcp)
27798
27798
 
27799
- # Support --full flag to run the 341-tool granular server instead
27799
+ # Support --full flag to run the 353-tool granular server instead
27800
27800
  if "--full" in sys.argv:
27801
- logger.info("Starting full 341-tool granular server...")
27801
+ logger.info("Starting full 353-tool granular server...")
27802
27802
  sys.argv = [arg for arg in sys.argv if arg != "--full"]
27803
27803
  from src.granular import mcp as granular_mcp
27804
27804