davinci-resolve-mcp 2.99.3 → 2.101.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/AGENTS.md CHANGED
@@ -66,6 +66,10 @@ semantics.
66
66
  - Public overview, current stats, and docs map: `README.md`
67
67
  - Historical release notes: `CHANGELOG.md`
68
68
  - AI assistant operating reference: `docs/SKILL.md`
69
+ - Craft guidance for non-Claude-Code clients: the `knowledge` tool serves
70
+ `.claude/skills/`, `docs/guides/`, and `docs/kernels/` as resolved prose over
71
+ MCP. When you add a skill, guide, or kernel it is indexed automatically — a
72
+ drift guard fails the suite if anything in those directories is unreachable
69
73
  - Release checklist and validation rules: `docs/process/release-process.md`
70
74
  - Kernel workflow support maps: `docs/kernels/`
71
75
  - API coverage and live-test status: `docs/reference/api-coverage.md`
package/CHANGELOG.md CHANGED
@@ -2,6 +2,128 @@
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.101.0
6
+
7
+ **A grade can now reject itself.** `assess_grade` has measured grade damage since
8
+ v2.68.0 — banding in a sky, highlight levels collapsing, shadow grain amplified into
9
+ noise — and every flag it raises carries a remedy. Nothing consumed that report. The
10
+ measurement existed; the loop did not, so the remedy "reduce the strength" was advice an
11
+ agent had no way to act on.
12
+
13
+ ### Added
14
+
15
+ - **`media_analysis grade_loop`** — the retry ladder. Applies a look LUT, measures the
16
+ real decoded frame, and on any flag retries with the same look attenuated toward
17
+ identity (strength x 0.8 per rung, floored at 0.5, three tries by default). The first
18
+ strength that clears every sampled frame wins.
19
+ - **A flagged result is never reported acceptable.** An exhausted ladder returns
20
+ `needs_human` with the best attempt and its remaining flags — never a quiet success
21
+ at a strength that still bands.
22
+ - **Every sampled frame must pass.** `times=[...]` samples several timestamps and the
23
+ report names the one that failed; a grade clean on the frame you happened to check
24
+ is not a grade that passed.
25
+ - **The best attempt is the gentlest.** When nothing converges, attempts rank by flag
26
+ count with ties broken by the smallest colour shift — equal damage means taking the
27
+ one a human has less to undo.
28
+ - **It does not touch the project.** The result is an apply manifest with
29
+ `safe_to_apply`, and a flagged result carries the reason it is blocked.
30
+ - `dry_run` defaults to true and reports the ffmpeg decode budget before anyone
31
+ commits to it. `cost_tier` defaults to `numeric`, because escalating every rung to
32
+ vision would spend host turns on attempts that exist to be rejected.
33
+ - **`media_analysis grade_loop_capabilities`** — dependency state, ladder constants, and
34
+ an explicit statement of which modes exist.
35
+ - **`src/utils/cube_lut.py`** — read, write, and attenuate 3D `.cube` LUTs. Attenuation
36
+ is a blend toward identity, the same operation a LUT mix control performs. Exact at
37
+ both endpoints: strength 1.0 returns the table unchanged and 0.0 returns true
38
+ identity. 1D LUTs are refused by name, and attenuation on a non-unit
39
+ `DOMAIN_MIN`/`DOMAIN_MAX` is refused because identity is only identity on 0..1.
40
+
41
+ ### Not built, and said so
42
+
43
+ The in-loop **live** mode — apply in Resolve, render a frame, assess, repeat — is not
44
+ implemented. It needs a single-frame render per rung, and shipping it unvalidated would
45
+ put a "verified live" claim behind something no runnable command has produced.
46
+ `grade_loop_capabilities()` says this in the response rather than only in the docs. The
47
+ offline LUT ladder is complete and validated.
48
+
49
+ ### Documentation
50
+
51
+ - `docs/guides/color-decision-guide.md` — a new "Rejecting Your Own Grade" section on
52
+ when measurement beats eyeballing a compressed preview.
53
+ - `docs/kernels/color-grade-kernel.md` — the numeric grade-QC actions and their
54
+ display-referred-only contract.
55
+
56
+ ### Validation
57
+
58
+ - Offline suite: 2889 passed, 1 skipped, 711 subtests, 0 failures.
59
+ - End-to-end through real ffmpeg on generated media: a look that converges only after
60
+ backing off, and one that never converges and says so.
61
+ - Two deliberate mutations — `acceptable` hard-coded true, and a rung passing on its
62
+ first clean frame — were each caught by the new tests.
63
+ - No Resolve behavior changed; live test not required. A test asserts no Resolve
64
+ connection is attempted.
65
+
66
+ ## What's New in v2.100.0
67
+
68
+ **The craft guidance is now readable by any MCP client.** This repository carries a
69
+ real body of editorial, colour, and audio guidance — how to tighten a take without
70
+ cutting the breath out of it, what frames to look at before applying a grade, which
71
+ API calls silently lie. It lived in `.claude/skills/`, `docs/guides/`, and
72
+ `docs/kernels/`, and it was reachable only by an agent with this checkout on disk.
73
+
74
+ Over MCP there is no checkout. A skill that says "open
75
+ `docs/guides/color-decision-guide.md`" is a dead end on Codex, Cursor, or a bare SDK
76
+ loop: the pointer resolves to nothing, and the agent operates the tools without ever
77
+ seeing the reasoning that makes the operation correct.
78
+
79
+ ### Added
80
+
81
+ - **`knowledge` tool** (36th compound tool) — the corpus served as prose, with no
82
+ Resolve connection involved:
83
+ - `topics(category?)` — the index: id, summary, size, sections, related topics.
84
+ 35 topics across `workflow`, `guide`, `kernel`, `reference`, and `repo`.
85
+ - `get(topic, section?, inline?)` — resolved prose. Natural aliases (`"tighten"`,
86
+ `"dead air"`, `"grading"`) resolve to real topics, and referenced guides and
87
+ kernels arrive **inlined**, so what comes back is the manual rather than a path
88
+ to it. `section` returns one heading's subtree.
89
+ - `search(query, limit?)` — ranked topics with excerpts.
90
+ - `capabilities()` — topic counts by category and the corpus directories.
91
+ - **`knowledge://topics` MCP resource** — the same index, so hosts that consume
92
+ resources can see what guidance exists without spending a turn on it.
93
+ - `setup(action="schema")` now names the guidance, because an agent's orientation
94
+ call is where it will actually be noticed.
95
+
96
+ ### Design notes
97
+
98
+ - **Inlining stops at one level.** Following references transitively would turn a
99
+ 150-line answer into the whole `docs/` tree.
100
+ - **Oversized references are summarised, not truncated.** Over the inline budget an
101
+ agent gets the title, summary, section list, and the topic id to fetch — a
102
+ truncated prefix is the first N lines, which is rarely the part that answers the
103
+ question.
104
+ - **`reference` topics are terminal.** The 2250-line operating reference and the
105
+ generated API ledgers cross-link each other freely; inlining from them doubles a
106
+ document that was already complete.
107
+ - **An unknown section is an error that lists the real ones**, never a quiet return
108
+ of the whole document.
109
+ - **Search matches whole words.** Substring counting ranked `resolve-audio` top for
110
+ "dead air", because "air" is inside "F-air-light". Body hits are also normalised by
111
+ document length, so the longest document cannot win on mass alone.
112
+
113
+ ### Guarded against drift
114
+
115
+ A test asserts every skill, guide, and kernel in the corpus reaches the index, and
116
+ that every alias points at a topic that exists. Knowledge added to this repository
117
+ later cannot go silently unserved — the failure mode a hand-kept list has every time.
118
+
119
+ ### Validation
120
+
121
+ - Offline suite: 2849 passed, 1 skipped, 711 subtests, 0 failures.
122
+ - Three deliberate mutations (substring search, inlining disabled, unknown section
123
+ returning the whole document) were each caught by the new tests.
124
+ - No Resolve behavior changed; live test not required. A test asserts the tool never
125
+ reaches for a Resolve connection.
126
+
5
127
  ## What's New in v2.99.3
6
128
 
7
129
  **Fusion authoring now works on the free edition.** v2.99.2 documented that
@@ -58,6 +180,11 @@ render -> PSNR 23.32 dB vs baseline — RENDERED
58
180
 
59
181
  ## What's New in v2.99.2
60
182
 
183
+ > **Cause corrected in v2.99.3.** The "`fusion_comp add_tool` cannot run on the
184
+ > free edition" note below has the wrong cause: `GetAttrs`/`SetAttrs` are present
185
+ > on a Fusion tool and work when called — the bridge client reported them absent
186
+ > because `dir()` on a Fusion Tool omits them. `add_tool` needed no change.
187
+
61
188
  **The Fusion comp-lock question is closed on Resolve 21.** The v2.98.5–v2.98.8
62
189
  work isolated the bug on Studio 19.1.3.7 only, and the open caveat was whether
63
190
  the "renders on 19.1.3.7, ignored on 21.0.4.5" split reported in
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.99.3-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.101.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-35%20(353%20full)-blue.svg)](#server-modes)
8
+ [![Tools](https://img.shields.io/badge/MCP%20Tools-36%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)
@@ -109,7 +109,7 @@ The command starts a loopback-only server and opens the control panel in your br
109
109
 
110
110
  | Mode | Entry point | Tools | Best for |
111
111
  |------|-------------|-------|----------|
112
- | Compound | `src/server.py` | 35 | Default mode for most assistants. Related Resolve operations are grouped behind action parameters to keep context usage low. |
112
+ | Compound | `src/server.py` | 36 | Default mode for most assistants. Related Resolve operations are grouped behind action parameters to keep context usage low. |
113
113
  | Full / granular | `src/server.py --full` or `src/resolve_mcp_server.py` | 353 | Power users who want one MCP tool per Resolve API method. |
114
114
 
115
115
  The compound server is recommended unless you specifically need the granular one-tool-per-method surface.
@@ -218,6 +218,7 @@ The open-source servers are complete and fully functional on their own.
218
218
  | Audio and Fairlight | Track/item probes, source mapping, guarded audio property writes, voice isolation, auto-sync planning, transcription/subtitle probes |
219
219
  | Render and deliver | Format/codec matrix probing, render settings validation, queued job lifecycle checks, guarded Quick Export |
220
220
  | Extension authoring | Fuse, DCTL, ACES DCTL, and Resolve-page Lua/Python script lifecycle helpers with safe MCP-marked install/remove |
221
+ | Craft guidance | The bundled editorial, colour, audio, and workflow guidance served as prose over MCP — indexed, searchable, and readable by any client, not just ones with this repository on disk |
221
222
 
222
223
  ## Optional Extras
223
224
 
@@ -272,7 +273,7 @@ The default server is a local stdio process launched by your MCP client; it does
272
273
 
273
274
  | Metric | Value |
274
275
  |--------|-------|
275
- | MCP Tools | **35** compound / **353** granular (live server) |
276
+ | MCP Tools | **36** compound / **353** granular (live server) |
276
277
  | Advanced (offline) tools | **18** — .drp/.drt/.drx + DB authoring, no Resolve running |
277
278
  | Kernel Actions | **136** guarded workflow actions across 9 compound tools |
278
279
  | 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.99.3-blue.svg)](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
5
+ [![Version](https://img.shields.io/badge/version-2.101.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-35%20(353%20full)-blue.svg)](#服务器模式)
8
+ [![Tools](https://img.shields.io/badge/MCP%20Tools-36%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.99.3 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
15
+ > 本翻译对应 v2.101.0 版 README。如与英文原版有出入,以 [英文原版](README.md) 为准。
16
16
 
17
17
  一个 Model Context Protocol (MCP) 服务器,让 AI 助手通过官方脚本 API 控制 DaVinci Resolve Studio(达芬奇)。它提供完整的 API 覆盖,外加带护栏的工作流助手,涵盖剪辑、媒体池整理、渲染设置、审阅标记、调色、Fusion、Fairlight、项目生命周期任务、扩展开发,以及不碰源媒体的媒体分析。
18
18
 
@@ -81,7 +81,7 @@ venv/bin/python -m src.control_panel
81
81
 
82
82
  | 模式 | 入口 | 工具数 | 适合谁 |
83
83
  |------|------|--------|--------|
84
- | Compound(复合) | `src/server.py` | 35 | 大多数助手的默认模式。相关的 Resolve 操作按 action 参数分组,压低上下文占用。 |
84
+ | Compound(复合) | `src/server.py` | 36 | 大多数助手的默认模式。相关的 Resolve 操作按 action 参数分组,压低上下文占用。 |
85
85
  | Full / granular(细粒度) | `src/server.py --full` 或 `src/resolve_mcp_server.py` | 353 | 想要"一个 Resolve API 方法 = 一个 MCP 工具"的重度用户。 |
86
86
 
87
87
  除非你明确需要一方法一工具的细粒度界面,否则推荐复合模式。
@@ -196,7 +196,7 @@ DRX 调色写入**针对 Resolve Studio 做过实机校准**:调色参数默
196
196
 
197
197
  | 指标 | 数值 |
198
198
  |------|------|
199
- | MCP 工具 | **35** 复合 / **353** 细粒度(实时服务器) |
199
+ | MCP 工具 | **36** 复合 / **353** 细粒度(实时服务器) |
200
200
  | Advanced(离线)工具 | **18**——.drp/.drt/.drx + 数据库创作,无需 Resolve 运行 |
201
201
  | 内核 action | 9 个复合工具下 **136** 个带护栏的工作流 action |
202
202
  | API 方法覆盖 | **361/361**(100%) |
package/docs/SKILL.md CHANGED
@@ -162,7 +162,7 @@ before mutating Resolve state.
162
162
 
163
163
  | Mode | Entry point | Tool count | Use when |
164
164
  |---|---|---|---|
165
- | Compound (default) | `src/server.py` | 35 tools | Most workflows — keeps context lean |
165
+ | Compound (default) | `src/server.py` | 36 tools | Most workflows — keeps context lean |
166
166
  | Granular (full) | `src/server.py --full` | 353 tools | Power users needing one tool per API method |
167
167
 
168
168
  This skill document covers the **compound server** (the default). Each compound
@@ -494,6 +494,32 @@ you are on the correct page first.
494
494
 
495
495
  ## Tool Map
496
496
 
497
+ ### Craft Guidance
498
+
499
+ **`knowledge`** — The editorial, colour, audio, and workflow guidance bundled with
500
+ this server, served as prose. No Resolve connection required.
501
+
502
+ Read a topic **before** a creative or destructive operation, not after. The tools will
503
+ happily execute an editorially wrong decision; this is where the reasoning lives —
504
+ measured numbers, known traps, and what each move costs to undo.
505
+
506
+ Key actions:
507
+ - `topics(category?)` — the index: topic id, one-line summary, size, sections, and
508
+ related topics. Categories: `workflow` (task playbooks: tighten a recording, build a
509
+ rough cut, match a grade), `guide`, `kernel` (per-surface tool maps), `reference`
510
+ (exhaustive ledgers including this document), `repo` (contributing here)
511
+ - `get(topic, section?, inline?)` — the resolved prose. Natural aliases work
512
+ (`"tighten"`, `"dead air"`, `"grading"`, `"conform"`). Referenced guides and kernels
513
+ arrive inlined, so a client with no checkout of this repository still gets the
514
+ manual, not a path to it. `section` returns one heading's subtree
515
+ - `search(query, limit?)` — ranked topics with excerpts
516
+ - `capabilities()` — topic count by category, and the corpus directories
517
+
518
+ The same index is published as the `knowledge://topics` MCP resource, so hosts that
519
+ consume resources can see what guidance exists without spending a turn.
520
+
521
+ ---
522
+
497
523
  ### App Control
498
524
 
499
525
  **`resolve_control`** — App-level operations.
@@ -63,7 +63,7 @@ This MCP server controls DaVinci Resolve via its Scripting API. Some tools perfo
63
63
  davinci-resolve-mcp/
64
64
  ├── install.py # Universal installer (macOS/Windows/Linux)
65
65
  ├── src/
66
- │ ├── server.py # Compound MCP server — 35 tools (default)
66
+ │ ├── server.py # Compound MCP server — 36 tools (default)
67
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
@@ -211,6 +211,36 @@ If untreated/current/after comparison is not available through the API in the
211
211
  moment, say which part is unavailable and whether the user wants a blind/global
212
212
  pass. Do not imply that a grade was reviewed if no rendered frames were checked.
213
213
 
214
+ ## Rejecting Your Own Grade
215
+
216
+ Looking at frames tells you whether a grade reads right. It does not reliably tell you
217
+ whether it *damaged* the picture — banding in a sky, highlight levels collapsing, shadow
218
+ grain amplified into visible noise. Those are measurable, and measuring them is cheaper
219
+ and steadier than eyeballing a compressed preview.
220
+
221
+ `media_analysis assess_grade` measures one graded frame against its source and returns
222
+ flags with remedies. `media_analysis grade_loop` closes the loop: it applies a look LUT,
223
+ measures the real decoded result, and on any flag retries with the same look attenuated
224
+ toward identity (strength × 0.8 per rung, floored at 0.5, three tries by default). The
225
+ first strength that clears every sampled frame wins.
226
+
227
+ Three properties matter more than the convenience:
228
+
229
+ - **A flagged result is never reported acceptable.** When the ladder is exhausted the
230
+ answer is `needs_human`, carrying the best attempt and what it still fails on. There
231
+ is no strength at which the loop shrugs and ships.
232
+ - **Every sampled frame must pass.** Pass several timestamps — a grade that is clean on
233
+ the frame you happened to check and bands two hundred frames later has not passed
234
+ anything. The report names the frame that failed.
235
+ - **It does not touch the project.** The loop returns an apply manifest and stops.
236
+ Applying a grade is still a deliberate, version-protected step, and a result carrying
237
+ unresolved flags should reach a human before it reaches a timeline.
238
+
239
+ Reach for it when applying an unfamiliar look LUT to unfamiliar footage, which is
240
+ exactly where "it looked fine on the first shot" goes wrong. The flags are advisory
241
+ thresholds, not standards — the raw measurements come back too, so a colorist can
242
+ disagree with the flag rather than only with the verdict.
243
+
214
244
  ## Safe Color Workflow
215
245
 
216
246
  Before changing color:
package/docs/install.md CHANGED
@@ -141,7 +141,7 @@ The MCP server comes in two modes:
141
141
 
142
142
  | Mode | File | Tools | Best For |
143
143
  |------|------|-------|----------|
144
- | **Compound** (default) | `src/server.py` | 35 | Most users — fast, clean, low context usage |
144
+ | **Compound** (default) | `src/server.py` | 36 | Most users — fast, clean, low context usage |
145
145
  | **Full** | `src/resolve_mcp_server.py` | 353 | Power users who want one tool per API method |
146
146
 
147
147
  The compound server's `timeline_item` tool includes dedicated actions for common workflows:
@@ -67,6 +67,28 @@ All actions are exposed through `timeline_item_color`.
67
67
  and delete worked.
68
68
  - Gallery capability and album list/create calls worked.
69
69
 
70
+ ## Numeric grade QC (`media_analysis`)
71
+
72
+ Not part of the live kernel — no Resolve connection is involved — but it belongs to the
73
+ same decision. Both actions measure a decoded frame of the real result, never a
74
+ simulated transform, because LUT interpolation and encode rounding are where banding is
75
+ actually introduced.
76
+
77
+ - `assess_grade(source_path, time_seconds, graded_path|lut_path, working_space)` —
78
+ flags (`flat`, `washed_out`, `milky`, `noisy`, `clipped`, `posterized`, `banding`),
79
+ each with a remedy, plus the raw tonal/noise/damage measurements.
80
+ - `grade_loop(source_path, lut_path, times[], strength?, max_tries?, strength_floor?,
81
+ dry_run?)` — the retry ladder over `assess_grade`. Attenuates the look toward identity
82
+ until every sampled frame clears, or returns `needs_human` with the best attempt.
83
+ `dry_run` defaults to **true** and reports the ffmpeg decode budget first.
84
+ - `grade_loop_capabilities()` — dependency state, ladder constants, and which modes
85
+ exist. The in-loop **live** mode (apply in Resolve, render, assess) is **not built**;
86
+ the loop returns an apply manifest instead of driving the project.
87
+
88
+ Both are display-referred only. Log and scene-referred encodings run through the same
89
+ arithmetic happily and produce meaningless numbers, so `working_space` must be declared
90
+ and non-display-referred values are refused rather than guessed at.
91
+
70
92
  ## Boundaries
71
93
 
72
94
  - Node graph internals are intentionally limited by Resolve's public API. The
@@ -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 | **35** compound (default) / **353** granular |
9
+ | MCP Tools | **36** 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 **35 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).
20
+ Every non-deprecated method in the DaVinci Resolve Scripting API is covered. The default compound server exposes **36 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
@@ -37,7 +37,7 @@ from src.utils.update_check import (
37
37
 
38
38
  # ─── Version ──────────────────────────────────────────────────────────────────
39
39
 
40
- VERSION = "2.99.3"
40
+ VERSION = "2.101.0"
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": "2.99.3",
3
+ "version": "2.101.0",
4
4
  "description": "NPM bootstrapper for the DaVinci Resolve MCP Server.",
5
5
  "license": "MIT",
6
6
  "author": "Samuel Gursky <samgursky@gmail.com>",
@@ -87,7 +87,7 @@ if not logging.getLogger().handlers:
87
87
  handlers=[logging.StreamHandler()],
88
88
  )
89
89
 
90
- VERSION = "2.99.3"
90
+ VERSION = "2.101.0"
91
91
  logger = logging.getLogger("davinci-resolve-mcp")
92
92
  logger.info(f"Starting DaVinci Resolve MCP Server v{VERSION}")
93
93
  logger.info(f"Detected platform: {get_platform()}")
package/src/server.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """
3
3
  DaVinci Resolve MCP Server (Compound Tools)
4
4
 
5
- 35 compound tools covering 100% of the DaVinci Resolve Scripting API (336 methods)
5
+ 36 compound tools covering 100% of the DaVinci Resolve Scripting API (336 methods)
6
6
  plus Fusion Fuse, DCTL, and Resolve-page Script authoring tools.
7
7
  Each tool groups related operations via an 'action' parameter.
8
8
 
@@ -11,7 +11,7 @@ Usage:
11
11
  python src/server.py --full # Start the 353-tool granular server instead
12
12
  """
13
13
 
14
- VERSION = "2.99.3"
14
+ VERSION = "2.101.0"
15
15
 
16
16
  import base64
17
17
  import os
@@ -317,7 +317,7 @@ def davinci_resolve_workflow() -> str:
317
317
  return """Use this DaVinci Resolve MCP server as a guarded post-production control surface.
318
318
 
319
319
  Core pattern:
320
- - Prefer the 35 compound tools and their action names over raw scripting.
320
+ - Prefer the 36 compound tools and their action names over raw scripting.
321
321
  - Start by probing state: resolve_control.get_version/get_page, project_manager.get_current, timeline.get_current, and media_pool.probe_media_pool.
322
322
  - Before mutating timelines, media pools, render settings, grades, projects, databases, or extensions, prefer the matching probe, capabilities, boundary_report, safe_*, or dry_run action when one exists.
323
323
  - Preserve source media integrity. Never transcode, proxy, rewrite, move, rename, or create derivatives of source media unless the user explicitly asks. Analysis output belongs in sidecars or analysis directories.
@@ -14730,6 +14730,17 @@ def setup(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any
14730
14730
  if action in {"schema", "capabilities", "options"}:
14731
14731
  return {
14732
14732
  "actions": ["schema", "get_defaults", "set_defaults", "clear_defaults"],
14733
+ # Agents commonly call this first. The craft guidance is worth nothing if
14734
+ # nobody knows it is there, so the orientation call names it.
14735
+ "craft_guidance": {
14736
+ "tool": "knowledge",
14737
+ "start_with": "knowledge(action='topics')",
14738
+ "when": (
14739
+ "Before a creative or destructive operation — cutting, grading, "
14740
+ "conforming, tightening, delivering. The tools execute; this is "
14741
+ "where the reasoning and the measured numbers live."
14742
+ ),
14743
+ },
14733
14744
  "defaults": {
14734
14745
  "media_analysis.timed_markers_default": {
14735
14746
  "description": "Default answer for writing source-time analysis notes as Media Pool clip markers.",
@@ -20065,6 +20076,58 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
20065
20076
  "metrics are undefined on them and will not be guessed at."
20066
20077
  ),
20067
20078
  )
20079
+ if action == "grade_loop":
20080
+ # The retry ladder that consumes assess_grade's own verdict: apply the look,
20081
+ # measure the real decoded frame, and on any flag retry with the look
20082
+ # attenuated toward identity. Exhausting the ladder returns needs_human with
20083
+ # the best attempt — never a quiet success at a strength that still bands.
20084
+ from src.utils import grade_loop as _grade_loop_mod
20085
+
20086
+ source = str(p.get("source_path") or p.get("sourcePath") or "")
20087
+ lut = str(p.get("lut_path") or p.get("lutPath") or "")
20088
+ err, _clean = _validate_params(
20089
+ {"source_path": source, "lut_path": lut},
20090
+ {
20091
+ "source_path": {"type": str, "required": True, "non_empty": True},
20092
+ "lut_path": {"type": str, "required": True, "non_empty": True},
20093
+ },
20094
+ )
20095
+ if err:
20096
+ return _err(err)
20097
+ kwargs = dict(
20098
+ times=p.get("times"),
20099
+ time_seconds=p.get("time_seconds", p.get("timeSeconds")),
20100
+ strength=float(p.get("strength", 1.0) or 1.0),
20101
+ max_tries=int(p.get("max_tries", p.get("maxTries", _grade_loop_mod.DEFAULT_MAX_TRIES))),
20102
+ strength_floor=float(
20103
+ p.get("strength_floor", p.get("strengthFloor", _grade_loop_mod.DEFAULT_STRENGTH_FLOOR))
20104
+ ),
20105
+ working_space=str(p.get("working_space") or p.get("workingSpace") or "rec709"),
20106
+ cost_tier=str(p.get("cost_tier") or p.get("costTier") or _grade_loop_mod.DEFAULT_COST_TIER),
20107
+ )
20108
+ try:
20109
+ # Dry run by default: the ladder can spend a dozen ffmpeg decodes per clip,
20110
+ # and the plan names that budget before anyone commits to it.
20111
+ if p.get("dry_run", True):
20112
+ return _ok(**_grade_loop_mod.plan(source, lut, **kwargs))
20113
+ return _ok(**_grade_loop_mod.run(
20114
+ source, lut,
20115
+ output_dir=(p.get("output_dir") or p.get("outputDir")) or None,
20116
+ **kwargs,
20117
+ ))
20118
+ except (_grade_loop_mod.GradeLoopError, _grade_loop_mod.cube_lut.CubeLutError) as exc:
20119
+ return _err(str(exc), code="GRADE_LOOP_REFUSED", category="invalid_input",
20120
+ remediation=(
20121
+ "Supply an existing source_path and a 3D .cube lut_path, plus "
20122
+ "times=[seconds,...] to sample. A grade clean on one frame is "
20123
+ "not a grade that passed."
20124
+ ))
20125
+ except _grade_loop_mod.image_qc.ImageQcError as exc:
20126
+ return _err(str(exc), code="IMAGE_QC_REFUSED", category="invalid_input")
20127
+ if action == "grade_loop_capabilities":
20128
+ from src.utils import grade_loop as _grade_loop_mod
20129
+
20130
+ return _ok(**_grade_loop_mod.capabilities())
20068
20131
  if action == "image_qc_capabilities":
20069
20132
  from src.utils import image_qc as _image_qc_mod
20070
20133
 
@@ -21017,6 +21080,8 @@ async def media_analysis(action: str, params: Optional[Dict[str, Any]] = None, c
21017
21080
  "capabilities",
21018
21081
  "recheck_capabilities",
21019
21082
  "assess_grade",
21083
+ "grade_loop",
21084
+ "grade_loop_capabilities",
21020
21085
  "image_qc_capabilities",
21021
21086
  "install_guidance",
21022
21087
  "resolve_output_root",
@@ -29471,6 +29536,83 @@ def script_plugin(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[
29471
29536
  "execute", "run_inline", *_EXTENSION_KERNEL_ACTIONS])
29472
29537
 
29473
29538
 
29539
+ # ═══════════════════════════════════════════════════════════════════════════════
29540
+ # TOOL: knowledge
29541
+ #
29542
+ # The craft guidance in this repository — how to tighten a take without cutting
29543
+ # the breath out of it, what to look at before applying a grade, which API calls
29544
+ # silently lie — has always been readable only by an agent with this checkout on
29545
+ # disk. Over MCP there is no checkout, so a client on any other host operated the
29546
+ # tools without ever seeing the reasoning that makes the operation correct.
29547
+ #
29548
+ # This serves that corpus as content: the index, one resolved topic, or a search.
29549
+ # No Resolve connection is involved at any point.
29550
+ # ═══════════════════════════════════════════════════════════════════════════════
29551
+
29552
+ _KNOWLEDGE_ACTIONS = ["topics", "get", "search", "capabilities"]
29553
+
29554
+
29555
+ @mcp.tool()
29556
+ @_guard_missing_params
29557
+ def knowledge(action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
29558
+ """Editorial, colour, audio, and workflow guidance — readable without this checkout.
29559
+
29560
+ Read a topic BEFORE a creative or destructive operation, not after. The guidance
29561
+ carries measured numbers and known traps; operating the tools without it is how a
29562
+ technically-correct call produces an editorially wrong result.
29563
+
29564
+ Actions:
29565
+ topics(category?) -> {topics} — the index: id, summary, size, sections, related.
29566
+ Categories: workflow (task playbooks), guide, kernel (per-surface tool maps),
29567
+ reference (exhaustive ledgers), repo (contributing to this project).
29568
+ get(topic, section?, inline?) -> {content} — resolved prose. Accepts natural
29569
+ aliases ("tighten", "dead air", "grading"). `section` returns one heading's
29570
+ subtree; `inline=false` skips the referenced documents.
29571
+ search(query, limit?) -> {hits} — ranked topics with excerpts.
29572
+ capabilities() -> {topic_count, categories, corpus}
29573
+
29574
+ No Resolve connection required.
29575
+ """
29576
+ p = _params(params)
29577
+ from src.utils import knowledge as _knowledge_mod
29578
+
29579
+ try:
29580
+ if action == "topics":
29581
+ category = p.get("category")
29582
+ listing = _knowledge_mod.topics(category=str(category) if category else None)
29583
+ return _ok(topics=listing, count=len(listing),
29584
+ categories=list(_knowledge_mod.CATEGORIES))
29585
+ if action == "get":
29586
+ err, _clean = _validate_params(p, {
29587
+ "topic": {"type": str, "required": True, "non_empty": True},
29588
+ })
29589
+ if err:
29590
+ return _err(err)
29591
+ section = p.get("section")
29592
+ return _ok(**_knowledge_mod.get(
29593
+ str(p["topic"]),
29594
+ section=str(section) if section else None,
29595
+ inline=bool(p.get("inline", True)),
29596
+ ))
29597
+ if action == "search":
29598
+ err, _clean = _validate_params(p, {
29599
+ "query": {"type": str, "required": True, "non_empty": True},
29600
+ })
29601
+ if err:
29602
+ return _err(err)
29603
+ hits = _knowledge_mod.search(str(p["query"]), limit=int(p.get("limit", 5)))
29604
+ return _ok(hits=hits, count=len(hits))
29605
+ if action in {"capabilities", "schema"}:
29606
+ return _ok(**_knowledge_mod.capabilities(), actions=_KNOWLEDGE_ACTIONS)
29607
+ except _knowledge_mod.KnowledgeError as exc:
29608
+ # The message already names the real topics or sections, so an agent that
29609
+ # guessed wrong can correct itself without a second round-trip.
29610
+ return _err(str(exc), code="UNKNOWN_TOPIC", category="invalid_params",
29611
+ retryable=False)
29612
+
29613
+ return _unknown(action, _KNOWLEDGE_ACTIONS)
29614
+
29615
+
29474
29616
  # ═══════════════════════════════════════════════════════════════════════════════
29475
29617
  # MCP Resources — agentic-flow improvement E1
29476
29618
  #
@@ -29515,6 +29657,32 @@ def _resource_mcp_version() -> Dict[str, Any]:
29515
29657
  }
29516
29658
 
29517
29659
 
29660
+ @mcp.resource("knowledge://topics")
29661
+ @_safe_resource
29662
+ def _resource_knowledge_topics() -> Dict[str, Any]:
29663
+ """The knowledge index — id, summary, category, size. Pure read of bundled docs.
29664
+
29665
+ A host that consumes resources learns what guidance exists without spending a turn
29666
+ on it, which is the difference between the `knowledge` tool being available and it
29667
+ being used.
29668
+ """
29669
+ from src.utils import knowledge as _knowledge_mod
29670
+
29671
+ return {
29672
+ "topics": [
29673
+ {
29674
+ "topic": item["topic"],
29675
+ "title": item["title"],
29676
+ "category": item["category"],
29677
+ "summary": item["summary"],
29678
+ "length_lines": item["resolved_length_lines"],
29679
+ }
29680
+ for item in _knowledge_mod.topics()
29681
+ ],
29682
+ "fetch_with": "knowledge(action='get', params={'topic': '<id>'})",
29683
+ }
29684
+
29685
+
29518
29686
  @mcp.resource("status://resolve_connection")
29519
29687
  @_safe_resource
29520
29688
  def _resource_resolve_connection() -> Dict[str, Any]:
@@ -29734,5 +29902,5 @@ if __name__ == "__main__":
29734
29902
  logger.error(f"Unknown --transport {transport!r}; use stdio|sse|streamable-http")
29735
29903
  sys.exit(2)
29736
29904
 
29737
- logger.info("Starting DaVinci Resolve MCP Server (35 compound tools)")
29905
+ logger.info("Starting DaVinci Resolve MCP Server (36 compound tools)")
29738
29906
  run_fastmcp_stdio(mcp)