davinci-resolve-mcp 2.86.4 → 2.87.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/AGENTS.md CHANGED
@@ -73,7 +73,13 @@ semantics.
73
73
  `docs/reference/api-limitations.md` — when you document a new Resolve API
74
74
  limitation, add a `submit`-tagged entry to `src/utils/api_truth.py` and
75
75
  regenerate with `scripts/gen_api_limitations.py` (a drift guard enforces it)
76
- - Bundled Resolve API text: `docs/reference/resolve_scripting_api.txt`
76
+ - Bundled Resolve API text: `docs/reference/resolve_scripting_api.txt` — refresh
77
+ it by copying the shipped `Developer/Scripting/README.txt` over it wholesale,
78
+ never by hand-adding the lines you already know about. The file's only value
79
+ is that a diff against it is trustworthy; a hand-patch carries a newer
80
+ `Last Updated:` header while still hiding everything you did not know to look
81
+ for. That is exactly how it sat eight weeks stale at 26 May 2026 while ten
82
+ documented 21.0.4 methods went unwired (PR #139)
77
83
 
78
84
  ## Key Paths
79
85
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,87 @@
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.87.1
6
+
7
+ Follow-up evidence from @legionsound on PR #139, plus the process fix for the
8
+ tooling failure that PR exposed.
9
+
10
+ ### Changed
11
+
12
+ - **`import_user_preferences_preset` now says what the preset ended up called.**
13
+ The no-name path was the one branch in v2.87.0 with nothing behind it — it
14
+ calls the single-argument binding, which would have been a `TypeError` rather
15
+ than a graceful failure if the binding wanted two positionals. Round-tripped
16
+ on Studio 21.0.4.5 (export → delete → import both ways): the single-arg form
17
+ returns `True`, and the imported preset is **named after the file**. The
18
+ answer now carries that, so a caller who passed no name knows to read the
19
+ name back with `list_user_preferences_presets` instead of guessing.
20
+
21
+ ### Documentation
22
+
23
+ - **`api-coverage.md`: the import/export rows carry the round-trip**, not
24
+ `dir()` membership. Still 🔬 and still 23 untested of 361 — the counting
25
+ convention is unchanged, because none of this was executed here. What changed
26
+ is the strength of the contributor's evidence behind two of the rows.
27
+ - **`AGENTS.md` now states how the API snapshot must be refreshed:** copy the
28
+ shipped `Developer/Scripting/README.txt` over it wholesale, never hand-add the
29
+ lines you already know about. A hand-patch carries a newer `Last Updated:`
30
+ header while hiding everything you did not know to look for — which is
31
+ precisely how the file sat eight weeks stale at 26 May 2026 while ten
32
+ documented 21.0.4 methods went unwired. The v2.87.0 snapshot was verified as a
33
+ wholesale replace (md5 `d732b3f6c1da08dc516bc8f80c5acd92`, byte-identical to
34
+ the 21.0.4.5 shipped file per the contributor).
35
+
36
+ ## What's New in v2.87.0
37
+
38
+ The ten Resolve 21.0.4 scripting surfaces that the 24 Jul 2026 README refresh
39
+ documented beyond the three wired in v2.86.0. Contributed by @legionsound
40
+ (PR #139), who reported them as issues #135–#138 from Studio 21.0.4.5.
41
+
42
+ ### Added
43
+
44
+ - **`layout_presets` gains `list`** (`Resolve.GetLayoutPresetList`). The tool
45
+ had six actions and every one of them took a preset name the caller had no
46
+ way to enumerate.
47
+ - **`render_presets` gains `list_burnin` and `delete_burnin`**
48
+ (`Resolve.GetBurnInPresetList` / `DeleteBurnInPreset`). `list_burnin` is the
49
+ missing half of the burn-in surface: the `DataBurnIn` render setting and both
50
+ `load_burnin_preset` actions take a name nothing could list.
51
+ - **`resolve_control` gains the six `*_user_preferences_preset` actions** —
52
+ `list`, `save`, `load`, `delete`, `import`, `export`. Two caveats are carried
53
+ in the docstrings and answers rather than left to be discovered:
54
+ `load_user_preferences_preset` is **session-wide** (it swaps the user's global
55
+ Resolve preferences, not a project setting), and
56
+ `import_user_preferences_preset` answers with the README's own caveat that the
57
+ imported preset is *not* auto-loaded, so a caller follows with `load` instead
58
+ of stopping early.
59
+ - **`project_manager` gains `list_attributes`**
60
+ (`ProjectManager.GetProjectAttributesInCurrentFolder`) — `lastModifiedDate`,
61
+ `creationDate`, `notes`, and `liveCollaborationMode` per project in the
62
+ current folder, without loading any project.
63
+
64
+ All ten are `_requires_method`-guarded at 21.0.4, advertised in the capability
65
+ and preset-lifecycle probes, and covered by 21 stub contract cases in
66
+ `tests/test_resolve2104_preset_actions.py` (answers, name-required errors,
67
+ pre-21.0.4 guards, `_unknown` listings, capability advertisement on both a
68
+ 21.0.4 and a legacy stub).
69
+
70
+ ### Documentation
71
+
72
+ - **`docs/reference/resolve_scripting_api.txt` was the 26 May 2026 README** —
73
+ which is exactly why this delta went unnoticed. It is now the 24 Jul 2026
74
+ text these wrappers came from.
75
+ - **`api-coverage.md`: 351 → 361 methods**, with the ten new rows marked 🔬 and
76
+ carrying the contributor's Studio 21.0.4.5 results as a contributor report,
77
+ not as validation of ours — this machine runs Studio 19.1.3 and free 21.0.3,
78
+ neither of which is 21.0.4. `LoadUserPreferencesPreset` is recorded as
79
+ deliberately unexecuted even by the reporter, same class as
80
+ `DisableBackgroundTasksForCurrentResolveSession`.
81
+ - **The README key stats were stale at 349** since the v2.86.0 surfaces landed;
82
+ both READMEs and the live-tested badge now read 361 covered / 338 live-tested
83
+ (93.6%), and the English tested-against row picks up the free 21.0.3 build the
84
+ coverage doc already listed.
85
+
5
86
  ## What's New in v2.86.4
6
87
 
7
88
  The issue #132 follow-up, which turned out not to be a tool bug at all. No
package/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  English | [简体中文](README.zh-CN.md)
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.86.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.87.1-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-34%20(341%20full)-blue.svg)](#server-modes)
9
9
  [![Advanced](https://img.shields.io/badge/Advanced%20(offline)-18%20tools-blueviolet.svg)](#server-modes)
10
- [![Tested](https://img.shields.io/badge/Live%20Tested-96.8%25-green.svg)](docs/reference/api-coverage.md#test-results)
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)
@@ -265,10 +265,10 @@ The default server is a local stdio process launched by your MCP client; it does
265
265
  | MCP Tools | **34** compound / **341** 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
- | API Methods Covered | **349/349** (100%) |
269
- | Methods Live Tested | **338/349** (96.8%) |
268
+ | API Methods Covered | **361/361** (100%) |
269
+ | Methods Live Tested | **338/361** (93.6%) |
270
270
  | Live Test Pass Rate | **338/338** (100%) |
271
- | Tested Against | DaVinci Resolve 19.1.3 Studio + Resolve 20.3.2 Studio + Resolve 21.0.2 Studio |
271
+ | Tested Against | DaVinci Resolve 19.1.3 Studio + Resolve 20.3.2 Studio + Resolve 21.0.2 Studio + Resolve 21.0.3 **free** (via the in-app bridge) |
272
272
 
273
273
  For method-by-method status, see [API Coverage and Test Results](docs/reference/api-coverage.md). For current workflow support, see [Kernel Action Coverage](docs/kernels/README.md).
274
274
 
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.86.4-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.87.1-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-34%20(341%20full)-blue.svg)](#服务器模式)
9
9
  [![Advanced](https://img.shields.io/badge/Advanced%20(offline)-18%20tools-blueviolet.svg)](#服务器模式)
10
- [![Tested](https://img.shields.io/badge/Live%20Tested-96.8%25-green.svg)](docs/reference/api-coverage.md#test-results)
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.86.4 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.87.1 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -193,10 +193,10 @@ DRX 调色写入**针对 Resolve Studio 做过实机校准**:调色参数默
193
193
  | MCP 工具 | **34** 复合 / **341** 细粒度(实时服务器) |
194
194
  | Advanced(离线)工具 | **18**——.drp/.drt/.drx + 数据库创作,无需 Resolve 运行 |
195
195
  | 内核 action | 9 个复合工具下 **136** 个带护栏的工作流 action |
196
- | API 方法覆盖 | **349/349**(100%) |
197
- | 实机测试方法数 | **338/349**(96.8%) |
196
+ | API 方法覆盖 | **361/361**(100%) |
197
+ | 实机测试方法数 | **338/361**(93.6%) |
198
198
  | 实机测试通过率 | **338/338**(100%) |
199
- | 测试环境 | DaVinci Resolve 19.1.3 Studio + 20.3.2 Studio + 21.0.2 Studio |
199
+ | 测试环境 | DaVinci Resolve 19.1.3 Studio + 20.3.2 Studio + 21.0.2 Studio + 21.0.3 **免费版**(经内置桥接) |
200
200
 
201
201
  逐方法状态见 [API 覆盖与测试结果](docs/reference/api-coverage.md)。当前工作流支持见 [内核 action 覆盖](docs/kernels/README.md)。
202
202
 
package/docs/SKILL.md CHANGED
@@ -498,11 +498,21 @@ Key actions:
498
498
  - `get_keyframe_mode` / `set_keyframe_mode(mode)`
499
499
  - `get_fairlight_presets` — Resolve 20.2.2+; returns available Fairlight
500
500
  preset names
501
+ - `list/save/load/delete/import/export_user_preferences_preset` — Resolve
502
+ 21.0.4+; user-preferences presets. `load_...` is SESSION-WIDE: it swaps the
503
+ user's global Resolve preferences, so only call it when the user asked for
504
+ the switch. `import_...` does not activate the imported preset — follow with
505
+ `load_user_preferences_preset`
501
506
  - `quit` — terminates Resolve (destructive; confirm with user first)
502
507
 
503
508
  **`layout_presets`** — Save, load, export, import, delete UI layout presets.
509
+ `list` (Resolve 21.0.4+) enumerates the saved preset names the other actions
510
+ take.
504
511
 
505
512
  **`render_presets`** — Import and export render and burn-in presets.
513
+ `list_burnin` / `delete_burnin` (Resolve 21.0.4+) enumerate and remove burn-in
514
+ presets — `list_burnin` is the only way to discover the names the `DataBurnIn`
515
+ render setting and the `load_burnin_preset` actions expect.
506
516
 
507
517
  ---
508
518
 
@@ -510,11 +520,16 @@ Key actions:
510
520
 
511
521
  **`project_manager`** — CRUD on projects.
512
522
 
513
- Key actions: `list`, `get_current`, `create(name, media_location_path?)`,
523
+ Key actions: `list`, `list_attributes`, `get_current`,
524
+ `create(name, media_location_path?)`,
514
525
  `load(name)`, `save`, `close`,
515
526
  `delete(name)`, `import_project(path)`, `export_project(name, path)`, `archive`,
516
527
  `restore`
517
528
 
529
+ `list_attributes` (Resolve 21.0.4+) returns `lastModifiedDate`, `creationDate`,
530
+ `notes`, and `liveCollaborationMode` per project in the current folder without
531
+ loading any of them.
532
+
518
533
  Project / Database / Archive kernel actions (v2.15.0+) add guarded project
519
534
  lifecycle, settings, database, preset, and archive boundary helpers:
520
535
 
@@ -8,8 +8,8 @@ Complete Resolve scripting API coverage, live-test status, and method-by-method
8
8
  |--------|-------|
9
9
  | MCP Tools | **34** compound (default) / **341** granular |
10
10
  | Kernel Actions | **136** guarded MCP workflow actions across 9 compound tools |
11
- | API Methods Covered | **351/351** (100%) |
12
- | Methods Live Tested | **338/351** (96.3%) |
11
+ | API Methods Covered | **361/361** (100%) |
12
+ | Methods Live Tested | **338/361** (93.6%) |
13
13
  | Live Test Pass Rate | **338/338** (100%) |
14
14
  | API Object Classes | 13 |
15
15
  | Tested Against | DaVinci Resolve 19.1.3 Studio + Resolve 20.3.2 Studio + Resolve 21.0.2 Studio + Resolve 21.0.3 **free** (via the in-app bridge) |
@@ -109,7 +109,7 @@ source, these summaries are downstream of it:
109
109
  | Phase 5 | 6/6 | 100% | Scene cuts, subtitles from audio, graph node cache/tools/enable |
110
110
  | Resolve 20 delta | 12/12 | 100% | Resolve 20.0-20.2.2 scripting additions live-tested on 20.3.2 |
111
111
  | Resolve 21 delta | 7/7 | 100% | Resolve 21.0 additions that could be executed, live-tested on Studio 21.0.2.4 (`tests/live_resolve21_validation.py`). The other 6 need an AI Extras pack or are unsafe to run — counted as untested, not as passes |
112
- | **Total** | **338/338** | **100%** | **96.3% of the 351 covered methods tested live** |
112
+ | **Total** | **338/338** | **100%** | **93.6% of the 361 covered methods tested live** |
113
113
 
114
114
  #### Resolve 21 delta detail
115
115
 
@@ -130,7 +130,7 @@ marked 🔬 rather than ⚠️.
130
130
  | `Project.GenerateSpeech` | 🔬 | Requires AI Speech Generator; returned an error **string**, not a MediaPoolItem |
131
131
  | `Resolve.DisableBackgroundTasksForCurrentResolveSession` | 🔬 | Present in `dir()`; **not executed** — session-wide, returns None, and has no `Enable...` counterpart, so there is no undo short of restarting Resolve |
132
132
 
133
- ### Untested Methods (13 of 351)
133
+ ### Untested Methods (23 of 361)
134
134
 
135
135
  Every ☁️ and 🔬 row from the reference tables, listed here so the count is
136
136
  checkable rather than asserted.
@@ -150,12 +150,24 @@ checkable rather than asserted.
150
150
  | `Resolve.DisableBackgroundTasksForCurrentResolveSession` | Deliberately not executed: session-wide, returns `None`, and has no `Enable...` counterpart, so there is no undo short of restarting Resolve | No |
151
151
  | `MPI.GetTimeline` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
152
152
  | `TL.GetSelectedClips` | Requires a Resolve 21.0.4 build; the validation machine runs 19.1.3 | Yes |
153
+ | `Resolve.GetLayoutPresetList` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
154
+ | `Resolve.GetBurnInPresetList` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
155
+ | `Resolve.DeleteBurnInPreset` | Requires a Resolve 21.0.4 build; also, no scripting API creates a burn-in preset to round-trip against | Yes |
156
+ | `Resolve.GetUserPreferencesPresetList` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
157
+ | `Resolve.SaveUserPreferencesPreset` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
158
+ | `Resolve.LoadUserPreferencesPreset` | Deliberately not executed even by the reporter: swaps the user's global preferences session-wide | No |
159
+ | `Resolve.DeleteUserPreferencesPreset` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
160
+ | `Resolve.ImportUserPreferencesPreset` | Requires a Resolve 21.0.4 build; round-tripped by the reporter on 21.0.4.5 in both call shapes (PR #139), not here | Yes |
161
+ | `Resolve.ExportUserPreferencesPreset` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
162
+ | `PM.GetProjectAttributesInCurrentFolder` | Requires a Resolve 21.0.4 build; the validation machine runs Studio 19.1.3 and free 21.0.3 | Yes |
153
163
 
154
164
  The five AI Extras rows are untested for want of a downloadable pack, not because
155
165
  the wrappers are suspect — a report from anyone who has the packs installed would
156
- close them. The two 21.0.4 rows are untested for want of the build: this machine
166
+ close them. The twelve 21.0.4 rows are untested for want of the build: this machine
157
167
  runs 19.1.3, so they are guarded and unit-tested against stubs and carry a
158
- contributor's report rather than a result of ours. The remaining one is a
168
+ contributor's report rather than a result of ours (issue #131 for the first two;
169
+ issues #135–#138 for the ten from the 24 Jul 2026 README refresh, several with
170
+ save→list→delete round-trips on Studio 21.0.4.5). The remaining one is a
159
171
  decision rather than a gap: it is reachable, and
160
172
  running it during a validation sweep would disable background tasks for every
161
173
  project open in that Resolve instance.
@@ -199,6 +211,15 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
199
211
  | 21 | `SetKeyframeMode(keyframeMode)` | ⚠️ | API accepts; mode must match valid enum |
200
212
  | 22 | `GetFairlightPresets()` | ✅ | Returns a preset map. Live on 20.3.2 Studio and 21.0.3 free; **absent below 20.2.2** — confirmed missing on 19.1.3, so the version floor is verified from both sides |
201
213
  | 23 | `DisableBackgroundTasksForCurrentResolveSession()` | 🔬 | Resolve 21.0. Present in `dir()`; **not executed** — session-wide, returns `None`, no `Enable...` counterpart, so no undo short of restarting Resolve |
214
+ | 24 | `GetLayoutPresetList()` | 🔬 | Resolve 21.0.4 (24 Jul 2026 README). Not executed here — no 21.0.4 build; reporter's save→list→delete round-trip on Studio 21.0.4.5 in issue #137 |
215
+ | 25 | `GetBurnInPresetList()` | 🔬 | Resolve 21.0.4. Not executed here; reported returning a list on Studio 21.0.4.5 in issue #136 |
216
+ | 26 | `DeleteBurnInPreset(presetName)` | 🔬 | Resolve 21.0.4. `dir()`-present on 21.0.4.5 (issue #136); not executed anywhere — no scripting API creates a burn-in preset to round-trip against |
217
+ | 27 | `GetUserPreferencesPresetList()` | 🔬 | Resolve 21.0.4. Not executed here; reporter's save→list→delete round-trip on Studio 21.0.4.5 in issue #138 |
218
+ | 28 | `SaveUserPreferencesPreset(presetName)` | 🔬 | Resolve 21.0.4. Not executed here; part of the issue #138 round-trip on 21.0.4.5 |
219
+ | 29 | `LoadUserPreferencesPreset(presetName)` | 🔬 | Resolve 21.0.4. `dir()`-present on 21.0.4.5; **not executed** even by the reporter — swaps the user's global preferences session-wide (issue #138) |
220
+ | 30 | `DeleteUserPreferencesPreset(presetName)` | 🔬 | Resolve 21.0.4. Not executed here; part of the issue #138 round-trip on 21.0.4.5 |
221
+ | 31 | `ImportUserPreferencesPreset(presetFilePath, presetName)` | 🔬 | Resolve 21.0.4. Not executed here; reporter's export→delete→import round-trip on 21.0.4.5 (PR #139) covers **both** call shapes — single-arg returns `True` rather than a `TypeError`, and the imported preset is then named after the file; the two-arg form takes the given name. Per the README the imported preset is **not** auto-loaded, and the round-trip did not load either import |
222
+ | 32 | `ExportUserPreferencesPreset(presetName, exportPath)` | 🔬 | Resolve 21.0.4. Not executed here; reported writing the file at the given path with no extension appended, on 21.0.4.5 (PR #139) |
202
223
 
203
224
  ### ProjectManager
204
225
 
@@ -229,6 +250,7 @@ Every method in the DaVinci Resolve Scripting API and its test status. Methods a
229
250
  | 23 | `LoadCloudProject({cloudSettings})` | ☁️ | Requires cloud infrastructure |
230
251
  | 24 | `ImportCloudProject(filePath, {cloudSettings})` | ☁️ | Requires cloud infrastructure |
231
252
  | 25 | `RestoreCloudProject(folderPath, {cloudSettings})` | ☁️ | Requires cloud infrastructure |
253
+ | 26 | `GetProjectAttributesInCurrentFolder()` | 🔬 | Resolve 21.0.4 (24 Jul 2026 README). Not executed here — no 21.0.4 build; reported live on Studio 21.0.4.5 in issue #135, returning `lastModifiedDate` / `creationDate` / `notes` / `liveCollaborationMode` for all 8 projects in the current folder |
232
254
 
233
255
  ### Project
234
256
 
@@ -1,4 +1,4 @@
1
- Last Updated: 26 May 2026
1
+ Last Updated: 24 Jul 2026
2
2
  -------------------------
3
3
  In this package, you will find a brief introduction to the Scripting API for DaVinci Resolve Studio. Apart from this README.txt file, this package contains folders containing the basic import
4
4
  modules for scripting access (DaVinciResolve.py) and some representative examples.
@@ -91,6 +91,7 @@ Resolve
91
91
  GetProductName() --> string # Returns product name.
92
92
  GetVersion() --> [version fields] # Returns list of product version fields in [major, minor, patch, build, suffix] format.
93
93
  GetVersionString() --> string # Returns product version in "major.minor.patch[suffix].build" format.
94
+ GetLayoutPresetList() --> [presetNames...] # Returns list of layout preset names.
94
95
  LoadLayoutPreset(presetName) --> Bool # Loads UI layout from saved preset named 'presetName'.
95
96
  UpdateLayoutPreset(presetName) --> Bool # Overwrites preset named 'presetName' with current UI layout.
96
97
  ExportLayoutPreset(presetName, presetFilePath) --> Bool # Exports preset named 'presetName' to path 'presetFilePath'.
@@ -100,8 +101,16 @@ Resolve
100
101
  Quit() --> None # Quits the Resolve App.
101
102
  ImportRenderPreset(presetPath) --> Bool # Import a preset from presetPath (string) and set it as current preset for rendering.
102
103
  ExportRenderPreset(presetName, exportPath) --> Bool # Export a preset to a given path (string) if presetName(string) exists.
104
+ GetBurnInPresetList() --> [presetName...] # Returns a list of data burn in preset names.
105
+ DeleteBurnInPreset(presetName) --> Bool # Deletes the data burn in preset.
103
106
  ImportBurnInPreset(presetPath) --> Bool # Import a data burn in preset from a given presetPath (string)
104
107
  ExportBurnInPreset(presetName, exportPath) --> Bool # Export a data burn in preset to a given path (string) if presetName (string) exists.
108
+ GetUserPreferencesPresetList() --> [presetNames...] # Returns a list of user preferences preset names.
109
+ LoadUserPreferencesPreset(presetName) --> Bool # Loads the user preferences preset named 'presetName'.
110
+ SaveUserPreferencesPreset(presetName) --> Bool # Saves the current user preferences as a new preset named 'presetName'.
111
+ DeleteUserPreferencesPreset(presetName) --> Bool # Deletes the user preferences preset named 'presetName'.
112
+ ImportUserPreferencesPreset(presetFilePath, presetName) --> Bool # Imports user preferences preset from presetFilePath. Optional presetName argument to name the imported preset. The imported preset is not automatically loaded, use LoadUserPreferencesPreset(..) to load it.
113
+ ExportUserPreferencesPreset(presetName, exportPath) --> Bool # Exports the user preferences preset to exportPath.
105
114
  GetKeyframeMode() --> keyframeMode # Returns the currently set keyframe mode (int). Refer to section 'Keyframe Mode information' below for details.
106
115
  SetKeyframeMode(keyframeMode) --> Bool # Returns True when 'keyframeMode'(enum) is successfully set. Refer to section 'Keyframe Mode information' below for details.
107
116
  GetFairlightPresets() --> [presetNames...]. # Returns a list of Fairlight presets by name
@@ -122,6 +131,8 @@ ProjectManager
122
131
  CreateFolder(folderName) --> Bool # Creates a folder if folderName (string) is unique.
123
132
  DeleteFolder(folderName) --> Bool # Deletes the specified folder if it exists. Returns True in case of success.
124
133
  GetProjectListInCurrentFolder() --> [project names...] # Returns a list of project names in current folder.
134
+ GetProjectAttributesInCurrentFolder() --> {project name: {attributes}, ...} # Returns a dictionary of project names in the current folder to project attributes.
135
+ # Attributes include "lastModifiedDate", "creationDate", "notes", and "liveCollaborationMode".
125
136
  GetFolderListInCurrentFolder() --> [folder names...] # Returns a list of folder names in current folder.
126
137
  GotoRootFolder() --> Bool # Opens root folder in database.
127
138
  GotoParentFolder() --> Bool # Opens parent folder of current folder in database if current folder has parent.
@@ -295,6 +306,7 @@ MediaPoolItem
295
306
  # If no argument is specified, a dict of all set metadata properties is returned.
296
307
  SetMetadata(metadataType, metadataValue) --> Bool # Sets the given metadata to metadataValue (string). Returns True if successful.
297
308
  SetMetadata({metadata}) --> Bool # Sets the item metadata with specified 'metadata' dict. Returns True if successful.
309
+ GetTimeline() --> TimelineItem # Returns the timeline object if the mpItem is a timeline clip, or None if not.
298
310
  GetThirdPartyMetadata(metadataType=None) --> string|dict # Returns the third party metadata value for the key 'metadataType'.
299
311
  # If no argument is specified, a dict of all set third party metadata properties is returned.
300
312
  SetThirdPartyMetadata(metadataType, metadataValue) --> Bool # Sets/Add the given third party metadata to metadataValue (string). Returns True if successful.
@@ -385,6 +397,7 @@ Timeline
385
397
  DeleteClips([timelineItems], Bool) --> Bool # Deletes specified TimelineItems from the timeline, performing ripple delete if the second argument is True. Second argument is optional (The default for this is False)
386
398
  SetClipsLinked([timelineItems], Bool) --> Bool # Links or unlinks the specified TimelineItems depending on second argument.
387
399
  GetItemListInTrack(trackType, index) --> [items...] # Returns a list of timeline items on that track (based on trackType and index). 1 <= index <= GetTrackCount(trackType).
400
+ GetSelectedClips() --> [items...] # Returns the currently selected timeline items.
388
401
  AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker.
389
402
  customData)
390
403
  GetMarkers() --> {markers...} # Returns a dict (frameId -> {information}) of all markers and dicts with their information.
@@ -693,6 +706,10 @@ Affects:
693
706
  Affects:
694
707
  • x = Project:GetSetting('timelineFrameRate') and Project:SetSetting('timelineFrameRate', x)
695
708
 
709
+ "timelineSampleRate" - the property value is one of the sample rates available to the user in project settings under "Audio sample rate" option in the Fairlight tab.
710
+ Affects:
711
+ • x = Project:GetSetting('timelineSampleRate') and Project:SetSetting('timelineSampleRate', x)
712
+
696
713
  The following Clip properties have specifically enumerated values:
697
714
  "Super Scale" - the property value is an enumerated integer between 1 and 4 with these meanings: 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x.
698
715
  for super scale multiplier '2x Enhanced', exactly 4 arguments must be passed as outlined below. If less than 4 arguments are passed, it will default to 2x.
@@ -844,6 +861,9 @@ The parameter setting is a dictionary containing the following keys:
844
861
  - "ReplaceExistingFilesInPlace": Bool
845
862
  - "ExportSubtitle": Bool
846
863
  - "SubtitleFormat": string (options: "BurnIn", "EmbeddedCaptions", "SeparateFile")
864
+ - "UseFullExtents": Bool
865
+ - "AddFrameHandles": int (>= 0). Ignored if use full extents is enabled.
866
+ - "DataBurnIn": string. (example: "Same as project", "None")
847
867
 
848
868
  Looking up timeline export properties
849
869
  -------------------------------------
package/install.py CHANGED
@@ -36,7 +36,7 @@ from src.utils.update_check import (
36
36
 
37
37
  # ─── Version ──────────────────────────────────────────────────────────────────
38
38
 
39
- VERSION = "2.86.4"
39
+ VERSION = "2.87.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "davinci-resolve-mcp",
3
- "version": "2.86.4",
3
+ "version": "2.87.1",
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.86.4"
88
+ VERSION = "2.87.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,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.86.4"
14
+ VERSION = "2.87.1"
15
15
 
16
16
  import base64
17
17
  import os
@@ -13581,6 +13581,19 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
13581
13581
  get_fairlight_presets() -> {presets}
13582
13582
  set_high_priority() -> {success}
13583
13583
  disable_background_tasks_for_current_session() -> {success} — Resolve 21+
13584
+ list_user_preferences_presets() -> {presets} — Resolve 21.0.4+
13585
+ save_user_preferences_preset(name) -> {success} — Resolve 21.0.4+
13586
+ load_user_preferences_preset(name) -> {success} — Resolve 21.0.4+.
13587
+ SESSION-WIDE: swaps the user's global Resolve preferences, not a
13588
+ project setting. Only call when the user asked for the switch.
13589
+ delete_user_preferences_preset(name) -> {success} — Resolve 21.0.4+
13590
+ import_user_preferences_preset(path, name?) -> {success} — Resolve 21.0.4+.
13591
+ The imported preset is NOT auto-loaded; follow with
13592
+ load_user_preferences_preset to activate it. Omitting name is safe
13593
+ (reported on 21.0.4.5: single-arg returns True, not a TypeError) and
13594
+ the preset is then named after the file, so pass name when the preset
13595
+ should be called something the filename does not say.
13596
+ export_user_preferences_preset(name, path) -> {success} — Resolve 21.0.4+
13584
13597
  open_control_panel(port?, host?, open_browser?) -> {success, url, pid, port, status}
13585
13598
  — Launches the analysis control panel (src/analysis_dashboard.py) as a background process.
13586
13599
  Idempotent: returns the existing URL if already running.
@@ -13816,7 +13829,59 @@ def resolve_control(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
13816
13829
  return missing
13817
13830
  r.DisableBackgroundTasksForCurrentResolveSession()
13818
13831
  return _ok()
13819
- return _unknown(action, ["launch","runtime_mode","get_version","api_truth","check_version_support","verification_stats","job_status","list_jobs","mcp_update_status","set_mcp_update_policy","ignore_mcp_update","snooze_mcp_update","clear_mcp_update_preferences","get_page","open_page","get_keyframe_mode","set_keyframe_mode","quit","get_fairlight_presets","set_high_priority","disable_background_tasks_for_current_session","open_control_panel","control_panel_status","close_control_panel","save_state","restore_state"])
13832
+ elif action == "list_user_preferences_presets":
13833
+ missing = _requires_method(r, "GetUserPreferencesPresetList", "21.0.4")
13834
+ if missing:
13835
+ return missing
13836
+ return {"presets": _ser(r.GetUserPreferencesPresetList() or [])}
13837
+ elif action == "save_user_preferences_preset":
13838
+ missing = _requires_method(r, "SaveUserPreferencesPreset", "21.0.4")
13839
+ if missing:
13840
+ return missing
13841
+ if not p.get("name"):
13842
+ return _err("save_user_preferences_preset requires name")
13843
+ return {"success": bool(r.SaveUserPreferencesPreset(p["name"]))}
13844
+ elif action == "load_user_preferences_preset":
13845
+ missing = _requires_method(r, "LoadUserPreferencesPreset", "21.0.4")
13846
+ if missing:
13847
+ return missing
13848
+ if not p.get("name"):
13849
+ return _err("load_user_preferences_preset requires name")
13850
+ return {"success": bool(r.LoadUserPreferencesPreset(p["name"]))}
13851
+ elif action == "delete_user_preferences_preset":
13852
+ missing = _requires_method(r, "DeleteUserPreferencesPreset", "21.0.4")
13853
+ if missing:
13854
+ return missing
13855
+ if not p.get("name"):
13856
+ return _err("delete_user_preferences_preset requires name")
13857
+ return {"success": bool(r.DeleteUserPreferencesPreset(p["name"]))}
13858
+ elif action == "import_user_preferences_preset":
13859
+ missing = _requires_method(r, "ImportUserPreferencesPreset", "21.0.4")
13860
+ if missing:
13861
+ return missing
13862
+ if not p.get("path"):
13863
+ return _err("import_user_preferences_preset requires path")
13864
+ if p.get("name"):
13865
+ ok = bool(r.ImportUserPreferencesPreset(p["path"], p["name"]))
13866
+ else:
13867
+ ok = bool(r.ImportUserPreferencesPreset(p["path"]))
13868
+ note = "The imported preset is not auto-loaded; use load_user_preferences_preset to activate it."
13869
+ if not p.get("name"):
13870
+ note += (" No name was given, so the preset takes its name from the file — "
13871
+ "list_user_preferences_presets to read it back.")
13872
+ return {"success": ok, "note": note}
13873
+ elif action == "export_user_preferences_preset":
13874
+ missing = _requires_method(r, "ExportUserPreferencesPreset", "21.0.4")
13875
+ if missing:
13876
+ return missing
13877
+ err, clean = _validate_params(p, {
13878
+ "name": {"type": str, "required": True, "non_empty": True},
13879
+ "path": {"type": str, "required": True, "non_empty": True},
13880
+ })
13881
+ if err:
13882
+ return _err(err)
13883
+ return {"success": bool(r.ExportUserPreferencesPreset(clean["name"], clean["path"]))}
13884
+ return _unknown(action, ["launch","runtime_mode","get_version","api_truth","check_version_support","verification_stats","job_status","list_jobs","mcp_update_status","set_mcp_update_policy","ignore_mcp_update","snooze_mcp_update","clear_mcp_update_preferences","get_page","open_page","get_keyframe_mode","set_keyframe_mode","quit","get_fairlight_presets","set_high_priority","disable_background_tasks_for_current_session","list_user_preferences_presets","save_user_preferences_preset","load_user_preferences_preset","delete_user_preferences_preset","import_user_preferences_preset","export_user_preferences_preset","open_control_panel","control_panel_status","close_control_panel","save_state","restore_state"])
13820
13885
 
13821
13886
 
13822
13887
  # ─── V2 C4: Per-field corrections with provenance + changelog ────────────────
@@ -14682,6 +14747,7 @@ def layout_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
14682
14747
  """Manage DaVinci Resolve UI layout presets.
14683
14748
 
14684
14749
  Actions:
14750
+ list() -> {presets} — Resolve 21.0.4+; saved layout preset names
14685
14751
  save(name) -> {success}
14686
14752
  load(name) -> {success}
14687
14753
  update(name) -> {success}
@@ -14694,7 +14760,12 @@ def layout_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
14694
14760
  if r is None:
14695
14761
  return _not_connected_error()
14696
14762
 
14697
- if action == "save":
14763
+ if action == "list":
14764
+ missing = _requires_method(r, "GetLayoutPresetList", "21.0.4")
14765
+ if missing:
14766
+ return missing
14767
+ return {"presets": _ser(r.GetLayoutPresetList() or [])}
14768
+ elif action == "save":
14698
14769
  if not p.get("name"):
14699
14770
  return _err("save requires name")
14700
14771
  return {"success": bool(r.SaveLayoutPreset(p["name"]))}
@@ -14716,7 +14787,7 @@ def layout_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
14716
14787
  return {"success": bool(r.ImportLayoutPreset(p["path"]))}
14717
14788
  elif action == "delete":
14718
14789
  return {"success": bool(r.DeleteLayoutPreset(p["name"]))}
14719
- return _unknown(action, ["save","load","update","export","import_preset","delete"])
14790
+ return _unknown(action, ["list","save","load","update","export","import_preset","delete"])
14720
14791
 
14721
14792
 
14722
14793
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -14733,6 +14804,10 @@ def render_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
14733
14804
  export_render(name, path) -> {success}
14734
14805
  import_burnin(path) -> {success}
14735
14806
  export_burnin(name, path) -> {success}
14807
+ list_burnin() -> {presets} — Resolve 21.0.4+; burn-in preset names usable
14808
+ with the render tool's DataBurnIn setting and load_burnin_preset on
14809
+ project_settings / timeline_item
14810
+ delete_burnin(name) -> {success} — Resolve 21.0.4+
14736
14811
  """
14737
14812
  p = _params(params)
14738
14813
  r = get_resolve()
@@ -14747,7 +14822,19 @@ def render_presets(action: str, params: Optional[Dict[str, Any]] = None) -> Dict
14747
14822
  return {"success": bool(r.ImportBurnInPreset(p["path"]))}
14748
14823
  elif action == "export_burnin":
14749
14824
  return {"success": bool(r.ExportBurnInPreset(p["name"], p["path"]))}
14750
- return _unknown(action, ["import_render","export_render","import_burnin","export_burnin"])
14825
+ elif action == "list_burnin":
14826
+ missing = _requires_method(r, "GetBurnInPresetList", "21.0.4")
14827
+ if missing:
14828
+ return missing
14829
+ return {"presets": _ser(r.GetBurnInPresetList() or [])}
14830
+ elif action == "delete_burnin":
14831
+ missing = _requires_method(r, "DeleteBurnInPreset", "21.0.4")
14832
+ if missing:
14833
+ return missing
14834
+ if not p.get("name"):
14835
+ return _err("delete_burnin requires name")
14836
+ return {"success": bool(r.DeleteBurnInPreset(p["name"]))}
14837
+ return _unknown(action, ["import_render","export_render","import_burnin","export_burnin","list_burnin","delete_burnin"])
14751
14838
 
14752
14839
 
14753
14840
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -14783,6 +14870,7 @@ _PROJECT_MANAGER_METHODS = [
14783
14870
  "CreateFolder",
14784
14871
  "DeleteFolder",
14785
14872
  "GetProjectListInCurrentFolder",
14873
+ "GetProjectAttributesInCurrentFolder",
14786
14874
  "GetFolderListInCurrentFolder",
14787
14875
  "GotoRootFolder",
14788
14876
  "GotoParentFolder",
@@ -14938,6 +15026,7 @@ def _project_capabilities(pm=None, project=None, resolve_obj=None) -> Dict[str,
14938
15026
  "kernel_actions": list(_PROJECT_KERNEL_ACTIONS),
14939
15027
  "resolve": {
14940
15028
  "layout_presets": {
15029
+ "list": _has_method(resolve_obj, "GetLayoutPresetList") if resolve_obj else True,
14941
15030
  "save": _has_method(resolve_obj, "SaveLayoutPreset") if resolve_obj else True,
14942
15031
  "load": _has_method(resolve_obj, "LoadLayoutPreset") if resolve_obj else True,
14943
15032
  "update": _has_method(resolve_obj, "UpdateLayoutPreset") if resolve_obj else True,
@@ -14950,6 +15039,16 @@ def _project_capabilities(pm=None, project=None, resolve_obj=None) -> Dict[str,
14950
15039
  "export_render": _has_method(resolve_obj, "ExportRenderPreset") if resolve_obj else True,
14951
15040
  "import_burnin": _has_method(resolve_obj, "ImportBurnInPreset") if resolve_obj else True,
14952
15041
  "export_burnin": _has_method(resolve_obj, "ExportBurnInPreset") if resolve_obj else True,
15042
+ "list_burnin": _has_method(resolve_obj, "GetBurnInPresetList") if resolve_obj else True,
15043
+ "delete_burnin": _has_method(resolve_obj, "DeleteBurnInPreset") if resolve_obj else True,
15044
+ },
15045
+ "user_preferences_presets": {
15046
+ "list": _has_method(resolve_obj, "GetUserPreferencesPresetList") if resolve_obj else True,
15047
+ "save": _has_method(resolve_obj, "SaveUserPreferencesPreset") if resolve_obj else True,
15048
+ "load": _has_method(resolve_obj, "LoadUserPreferencesPreset") if resolve_obj else True,
15049
+ "delete": _has_method(resolve_obj, "DeleteUserPreferencesPreset") if resolve_obj else True,
15050
+ "import": _has_method(resolve_obj, "ImportUserPreferencesPreset") if resolve_obj else True,
15051
+ "export": _has_method(resolve_obj, "ExportUserPreferencesPreset") if resolve_obj else True,
14953
15052
  },
14954
15053
  },
14955
15054
  }
@@ -15248,6 +15347,7 @@ def _preset_lifecycle_probe(resolve_obj, project, p: Dict[str, Any]) -> Dict[str
15248
15347
  "quick_export_presets": {"available": _has_method(project, "GetQuickExportRenderPresets")},
15249
15348
  "fairlight_presets": {"available": _has_method(resolve_obj, "GetFairlightPresets")},
15250
15349
  "layout_presets": {
15350
+ "list": _has_method(resolve_obj, "GetLayoutPresetList"),
15251
15351
  "save": _has_method(resolve_obj, "SaveLayoutPreset"),
15252
15352
  "load": _has_method(resolve_obj, "LoadLayoutPreset"),
15253
15353
  "update": _has_method(resolve_obj, "UpdateLayoutPreset"),
@@ -15260,6 +15360,16 @@ def _preset_lifecycle_probe(resolve_obj, project, p: Dict[str, Any]) -> Dict[str
15260
15360
  "export_render": _has_method(resolve_obj, "ExportRenderPreset"),
15261
15361
  "import_burnin": _has_method(resolve_obj, "ImportBurnInPreset"),
15262
15362
  "export_burnin": _has_method(resolve_obj, "ExportBurnInPreset"),
15363
+ "list_burnin": _has_method(resolve_obj, "GetBurnInPresetList"),
15364
+ "delete_burnin": _has_method(resolve_obj, "DeleteBurnInPreset"),
15365
+ },
15366
+ "user_preferences_presets": {
15367
+ "list": _has_method(resolve_obj, "GetUserPreferencesPresetList"),
15368
+ "save": _has_method(resolve_obj, "SaveUserPreferencesPreset"),
15369
+ "load": _has_method(resolve_obj, "LoadUserPreferencesPreset"),
15370
+ "delete": _has_method(resolve_obj, "DeleteUserPreferencesPreset"),
15371
+ "import": _has_method(resolve_obj, "ImportUserPreferencesPreset"),
15372
+ "export": _has_method(resolve_obj, "ExportUserPreferencesPreset"),
15263
15373
  },
15264
15374
  }
15265
15375
  try:
@@ -15641,6 +15751,9 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15641
15751
 
15642
15752
  Actions:
15643
15753
  list() -> {projects}
15754
+ list_attributes() -> {projects: {name: {lastModifiedDate, creationDate, notes, liveCollaborationMode}}}
15755
+ — Resolve 21.0.4+. Per-project attributes for the current folder without
15756
+ loading any project.
15644
15757
  get_current() -> {name, id}
15645
15758
  create(name, media_location_path?) -> {success, name}
15646
15759
  load(name) -> {success}
@@ -15722,6 +15835,11 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15722
15835
  return _project_boundary_report(r, pm, proj, p)
15723
15836
  elif action == "list":
15724
15837
  return {"projects": pm.GetProjectListInCurrentFolder()}
15838
+ elif action == "list_attributes":
15839
+ missing = _requires_method(pm, "GetProjectAttributesInCurrentFolder", "21.0.4")
15840
+ if missing:
15841
+ return missing
15842
+ return {"projects": _ser(pm.GetProjectAttributesInCurrentFolder() or {})}
15725
15843
  elif action == "get_current":
15726
15844
  proj = pm.GetCurrentProject()
15727
15845
  return {"name": proj.GetName(), "id": proj.GetUniqueId()} if proj else _err("No project open")
@@ -15776,7 +15894,7 @@ def project_manager(action: str, params: Optional[Dict[str, Any]] = None) -> Dic
15776
15894
  if not p.get("path"):
15777
15895
  return _err("restore requires path")
15778
15896
  return {"success": bool(pm.RestoreProject(p["path"], p.get("name")))}
15779
- return _unknown(action, ["list","get_current","create","load","save","close","delete","import_project","export_project","archive","restore","lint","diff_to_spec","plan_spec","apply_spec", *_PROJECT_KERNEL_ACTIONS])
15897
+ return _unknown(action, ["list","list_attributes","get_current","create","load","save","close","delete","import_project","export_project","archive","restore","lint","diff_to_spec","plan_spec","apply_spec", *_PROJECT_KERNEL_ACTIONS])
15780
15898
 
15781
15899
 
15782
15900
  # ═══════════════════════════════════════════════════════════════════════════════