Content
# DaVinci Resolve MCP Server
[](https://github.com/samuelgursky/davinci-resolve-mcp/releases)
[](#api-coverage)
[-blue.svg)](#server-modes)
[](#test-results)
[](https://www.blackmagicdesign.com/products/davinciresolve)
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
A Model Context Protocol (MCP) server providing **complete coverage** of the DaVinci Resolve Scripting API. Connect AI assistants (Claude, Cursor, Windsurf) to DaVinci Resolve and control every aspect of your post-production workflow through natural language.
### What's New in v2.1.0
- **New `fusion_comp` tool** — 20-action tool exposing the full Fusion composition node graph API. Add/delete/find nodes, wire connections, set/get parameters, manage keyframes, control undo grouping, set render ranges, and trigger renders — all on the currently active Fusion page composition
- **`timeline_item_fusion` cache actions** — added `get_cache_enabled` and `set_cache` actions for Fusion output cache control directly on timeline items
- **Fusion node graph reference** — docstring includes common tool IDs (Merge, TextPlus, Background, Transform, ColorCorrector, DeltaKeyer, etc.) for discoverability
### v2.0.9
- **Cross-platform sandbox path redirect** — `_resolve_safe_dir()` now handles macOS (`/var/folders`, `/private/var`), Linux (`/tmp`, `/var/tmp`), and Windows (`AppData\Local\Temp`) sandbox paths that Resolve can't write to. Redirects to `~/Documents/resolve-stills` instead of Desktop
- **Auto-cleanup for `grab_and_export`** — exported files are read into the response (DRX as inline text, images as base64) then deleted from disk automatically. Zero file accumulation. Pass `cleanup: false` to keep files on disk
- **Both servers in sync** — `server.py` and `resolve_mcp_server.py` now share the same version and both use `_resolve_safe_dir()` for all Resolve-facing temp paths (project export, LUT export, still export)
### v2.0.8
- **New `grab_and_export` action on `gallery_stills`** — combines `GrabStill()` + `ExportStills()` in a single atomic call, keeping the live GalleryStill reference for reliable export. Returns a file manifest with exported image + companion `.drx` grade file
- **Format fallback chain** — if the requested format fails, automatically retries with tif then dpx
- **macOS sandbox path redirect** — `/var/folders` and `/private/var` paths are redirected to `~/Desktop/resolve-stills` since Resolve's process can't write to sandboxed temp directories
- **Key finding documented** — `ExportStills` requires the Gallery panel to be visible on the Color page. All 9 supported formats (dpx, cin, tif, jpg, png, ppm, bmp, xpm, drx) produce a companion `.drx` grade file alongside the image
### v2.0.7
- **Security: path traversal protection for layout preset tools** — `export_layout_preset`, `import_layout_preset`, and `delete_layout_preset` now validate that resolved file paths stay within the expected Resolve presets directory, preventing path traversal via crafted preset names
- **Security: document destructive tool risk** — added Security Considerations section noting that `quit_app`/`restart_app` tools can terminate Resolve; MCP clients should require user confirmation before invoking
### v2.0.6
- **Fix color group operations crash** — `timeline_item_color` unpacked `_check()` as `(proj, _, _)` but `_check()` returns `(pm, proj, err)`, so `proj` got the ProjectManager instead of the Project, crashing `assign_color_group` and `remove_from_color_group`
### v2.0.5
- **Lazy connection recovery** — full server (`--full` mode) now auto-reconnects and auto-launches Resolve, matching the compound server behavior
- **Null guards on all chained API calls** — `GetProjectManager()`, `GetCurrentProject()`, `GetCurrentTimeline()` failures now return clear errors instead of `NoneType` crashes
- **Helper functions** — `get_resolve()`, `get_project_manager()`, `get_current_project()` replace 178 boilerplate blocks
### v2.0.4
- **Fix apply_grade_from_drx parameter** — renamed `mode` to `grade_mode` to match Resolve API; corrected documentation from replace/append to actual keyframe alignment modes (0=No keyframes, 1=Source Timecode aligned, 2=Start Frames aligned)
- **Backward compatible** — still accepts `mode` for existing clients, `grade_mode` takes precedence
### v2.0.3
- **Fix GetNodeGraph crash** — `GetNodeGraph(0)` returns `False` in Resolve; now calls without args unless `layer_index` is explicitly provided
- **Falsy node graph check** — guard checks `not g` instead of `g is None` to catch `False` returns
### v2.0.2
- **Antigravity support** — Google's agentic AI coding assistant added as 10th MCP client
- **Alphabetical client ordering** — MCP_CLIENTS list sorted for easier maintenance
### v2.0.1
- **26-tool compound server** — all 324 API methods grouped into 26 context-efficient tools (default)
- **Universal installer** — single `python install.py` for macOS/Windows/Linux, 10 MCP clients
- **Dedicated timeline_item actions** — retime/speed, transform, crop, composite, audio, keyframes with validation
- **Lazy Resolve connection** — server starts instantly, connects when first tool is called
- **Bug fixes** — CreateMagicMask param type, GetCurrentClipThumbnailImage args, Python 3.13+ warning
## Key Stats
| Metric | Value |
|--------|-------|
| MCP Tools | **26** compound (default) / **342** granular |
| API Methods Covered | **324/324** (100%) |
| Methods Live Tested | **319/324** (98.5%) |
| Live Test Pass Rate | **319/319** (100%) |
| API Object Classes | 13 |
| Tested Against | DaVinci Resolve 19.1.3 Studio |
## API Coverage
Every non-deprecated method in the DaVinci Resolve Scripting API is covered. The default compound server exposes **27 tools** that group related operations by action parameter, keeping LLM context windows lean. The full granular server provides **342 individual tools** for power users. Both modes cover all 13 API object classes:
| Class | Methods | Tools | Description |
|-------|---------|-------|-------------|
| Resolve | 21 | 21 | App control, pages, layout presets, render/burn-in presets, keyframe mode |
| ProjectManager | 25 | 25 | Project CRUD, folders, databases, cloud projects, archive/restore |
| Project | 42 | 42 | Timelines, render pipeline, settings, LUTs, color groups |
| MediaStorage | 9 | 9 | Volumes, file browsing, media import, mattes |
| MediaPool | 27 | 27 | Folders, clips, timelines, metadata, stereo, sync |
| Folder | 8 | 8 | Clip listing, export, transcription |
| MediaPoolItem | 32 | 32 | Metadata, markers, flags, properties, proxy, transcription |
| Timeline | 56 | 56 | Tracks, markers, items, export, generators, titles, stills, stereo |
| TimelineItem | 76 | 76 | Properties, markers, Fusion comps, versions, takes, CDL, AI tools |
| Gallery | 8 | 8 | Albums, stills, power grades |
| GalleryStillAlbum | 6 | 6 | Stills management, import/export, labels |
| Graph | 11 | 22 | Node operations, LUTs, cache, grades (timeline + clip graph variants) |
| ColorGroup | 5 | 10 | Group management, pre/post clip node graphs |
## Requirements
- **DaVinci Resolve Studio** 18.5+ (macOS, Windows, or Linux) — the free edition does not support external scripting
- **Python 3.10–3.12** recommended (3.13+ may have ABI incompatibilities with Resolve's scripting library)
- DaVinci Resolve running with **Preferences > General > "External scripting using"** set to **Local**
## Quick Start
```bash
# Clone the repository
git clone https://github.com/samuelgursky/davinci-resolve-mcp.git
cd davinci-resolve-mcp
# Make sure DaVinci Resolve is running, then:
python install.py
```
The universal installer auto-detects your platform, finds your DaVinci Resolve installation, creates a virtual environment, and configures your MCP client — all in one step.
### Supported MCP Clients
The installer can automatically configure any of these clients:
| Client | Config Written To |
|--------|-------------------|
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) |
| Claude Code | `.mcp.json` (project root) |
| Cursor | `~/.cursor/mcp.json` |
| VS Code (Copilot) | `.vscode/mcp.json` (workspace) |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
| Cline | VS Code global storage |
| Roo Code | VS Code global storage |
| Zed | `~/.config/zed/settings.json` |
| Continue | `~/.continue/config.json` |
| JetBrains IDEs | Manual (Settings > Tools > AI Assistant > MCP) |
You can configure multiple clients at once, or use `--clients manual` to get copy-paste config snippets.
### Installer Options
```bash
python install.py # Interactive mode
python install.py --clients all # Configure all clients
python install.py --clients cursor,claude-desktop # Specific clients
python install.py --clients manual # Just print the config
python install.py --dry-run --clients all # Preview without writing
python install.py --no-venv --clients cursor # Skip venv creation
```
### Server Modes
The MCP server comes in two modes:
| Mode | File | Tools | Best For |
|------|------|-------|----------|
| **Compound** (default) | `src/server.py` | 26 | Most users — fast, clean, low context usage |
| **Full** | `src/resolve_mcp_server.py` | 342 | Power users who want one tool per API method |
The compound server's `timeline_item` tool includes dedicated actions for common workflows:
| Category | Actions | Parameters |
|----------|---------|------------|
| **Retime** | `get_retime`, `set_retime` | process (nearest, frame_blend, optical_flow), motion_estimation (0-6) |
| **Transform** | `get_transform`, `set_transform` | Pan, Tilt, ZoomX/Y, RotationAngle, AnchorPointX/Y, Pitch, Yaw, FlipX/Y |
| **Crop** | `get_crop`, `set_crop` | CropLeft, CropRight, CropTop, CropBottom, CropSoftness, CropRetain |
| **Composite** | `get_composite`, `set_composite` | Opacity, CompositeMode |
| **Audio** | `get_audio`, `set_audio` | Volume, Pan, AudioSyncOffset |
| **Keyframes** | `get_keyframes`, `add_keyframe`, `modify_keyframe`, `delete_keyframe`, `set_keyframe_interpolation` | property, frame, value, interpolation (Linear, Bezier, EaseIn, EaseOut, EaseInOut) |
The installer uses the compound server by default. To use the full server:
```bash
python src/server.py --full # Launch full 342-tool server
# Or point your MCP config directly at src/resolve_mcp_server.py
```
### Manual Configuration
If you prefer to set things up yourself, add to your MCP client config:
```json
{
"mcpServers": {
"davinci-resolve": {
"command": "/path/to/venv/bin/python",
"args": ["/path/to/davinci-resolve-mcp/src/server.py"]
}
}
}
```
Platform-specific paths:
| Platform | API Path | Library Path |
|----------|----------|-------------|
| macOS | `/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting` | `fusionscript.so` in DaVinci Resolve.app |
| Windows | `C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Scripting` | `fusionscript.dll` in Resolve install dir |
| Linux | `/opt/resolve/Developer/Scripting` | `/opt/resolve/libs/Fusion/fusionscript.so` |
## Usage Examples
Once connected, you can control DaVinci Resolve through natural language:
```
"What version of DaVinci Resolve is running?"
"List all projects and open the one called 'My Film'"
"Create a new timeline called 'Assembly Cut' and add all clips from the media pool"
"Add a blue marker at the current playhead position with note 'Review this'"
"Set up a ProRes 422 HQ render for the current timeline"
"Export the timeline as an EDL"
"Switch to the Color page and grab a still"
"Create a Fusion composition on the selected clip"
```
## Test Results
All testing performed against **DaVinci Resolve 19.1.3 Studio** on macOS with live API calls (no mocks).
| Phase | Tests | Pass Rate | Scope |
|-------|-------|-----------|-------|
| Phase 1 | 204/204 | 100% | Safe read-only operations across all classes |
| Phase 2 | 79/79 | 100% | Destructive operations with create-test-cleanup patterns |
| Phase 3 | 20/20 | 100% | Real media import, sync, transcription, database switching, Resolve.Quit |
| Phase 4 | 10/10 | 100% | AI/ML methods, Fusion clips, stereo, gallery stills |
| Phase 5 | 6/6 | 100% | Scene cuts, subtitles from audio, graph node cache/tools/enable |
| **Total** | **319/319** | **100%** | **98.5% of all API methods tested live** |
### Untested Methods (5 of 324)
| Method | Reason | Help Wanted |
|--------|--------|-------------|
| `PM.CreateCloudProject` | Requires DaVinci Resolve cloud infrastructure | Yes |
| `PM.LoadCloudProject` | Requires DaVinci Resolve cloud infrastructure | Yes |
| `PM.ImportCloudProject` | Requires DaVinci Resolve cloud infrastructure | Yes |
| `PM.RestoreCloudProject` | Requires DaVinci Resolve cloud infrastructure | Yes |
| `TL.AnalyzeDolbyVision` | Requires HDR/Dolby Vision content | Yes |
---
## Complete API Reference
Every method in the DaVinci Resolve Scripting API and its test status. Methods are listed by object class.
**Status Key:**
- ✅ = Tested live, returned expected result
- ⚠️ = Tested live, API accepted call (returned `False` — needs specific context to fully execute)
- ☁️ = Requires cloud infrastructure (untested)
- 🔬 = Requires specific content/hardware (untested — PRs welcome)
### Resolve
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `Fusion()` | ✅ | Returns Fusion object |
| 2 | `GetMediaStorage()` | ✅ | Returns MediaStorage object |
| 3 | `GetProjectManager()` | ✅ | Returns ProjectManager object |
| 4 | `OpenPage(pageName)` | ✅ | Switches Resolve page |
| 5 | `GetCurrentPage()` | ✅ | Returns current page name (e.g. `"edit"`) |
| 6 | `GetProductName()` | ✅ | Returns `"DaVinci Resolve Studio"` |
| 7 | `GetVersion()` | ✅ | Returns `[19, 1, 3, 7, '']` |
| 8 | `GetVersionString()` | ✅ | Returns `"19.1.3.7"` |
| 9 | `LoadLayoutPreset(presetName)` | ✅ | Loads saved layout |
| 10 | `UpdateLayoutPreset(presetName)` | ✅ | Updates existing preset |
| 11 | `ExportLayoutPreset(presetName, presetFilePath)` | ✅ | Exports preset to file |
| 12 | `DeleteLayoutPreset(presetName)` | ✅ | Deletes preset |
| 13 | `SaveLayoutPreset(presetName)` | ⚠️ | API accepts; returns `False` when preset name conflicts |
| 14 | `ImportLayoutPreset(presetFilePath, presetName)` | ✅ | Imports preset from file |
| 15 | `Quit()` | ✅ | Quits DaVinci Resolve |
| 16 | `ImportRenderPreset(presetPath)` | ⚠️ | API accepts; needs valid preset file |
| 17 | `ExportRenderPreset(presetName, exportPath)` | ⚠️ | API accepts; needs valid preset name |
| 18 | `ImportBurnInPreset(presetPath)` | ⚠️ | API accepts; needs valid preset file |
| 19 | `ExportBurnInPreset(presetName, exportPath)` | ⚠️ | API accepts; needs valid preset name |
| 20 | `GetKeyframeMode()` | ✅ | Returns keyframe mode |
| 21 | `SetKeyframeMode(keyframeMode)` | ⚠️ | API accepts; mode must match valid enum |
### ProjectManager
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `ArchiveProject(projectName, filePath, ...)` | ⚠️ | API accepts; archiving is slow |
| 2 | `CreateProject(projectName)` | ✅ | Creates new project |
| 3 | `DeleteProject(projectName)` | ⚠️ | Returns `False` if project is open |
| 4 | `LoadProject(projectName)` | ✅ | Returns Project object |
| 5 | `GetCurrentProject()` | ✅ | Returns current Project |
| 6 | `SaveProject()` | ✅ | Saves current project |
| 7 | `CloseProject(project)` | ✅ | Closes project |
| 8 | `CreateFolder(folderName)` | ✅ | Creates project folder |
| 9 | `DeleteFolder(folderName)` | ✅ | Deletes project folder |
| 10 | `GetProjectListInCurrentFolder()` | ✅ | Returns project name list |
| 11 | `GetFolderListInCurrentFolder()` | ✅ | Returns folder name list |
| 12 | `GotoRootFolder()` | ✅ | Navigates to root |
| 13 | `GotoParentFolder()` | ✅ | Returns `False` at root (expected) |
| 14 | `GetCurrentFolder()` | ✅ | Returns current folder name |
| 15 | `OpenFolder(folderName)` | ✅ | Opens folder |
| 16 | `ImportProject(filePath, projectName)` | ✅ | Imports .drp file |
| 17 | `ExportProject(projectName, filePath, ...)` | ✅ | Exports .drp file |
| 18 | `RestoreProject(filePath, projectName)` | ⚠️ | API accepts; needs backup archive |
| 19 | `GetCurrentDatabase()` | ✅ | Returns `{DbType, DbName}` |
| 20 | `GetDatabaseList()` | ✅ | Returns list of databases |
| 21 | `SetCurrentDatabase({dbInfo})` | ✅ | Switches database |
| 22 | `CreateCloudProject({cloudSettings})` | ☁️ | Requires cloud infrastructure |
| 23 | `LoadCloudProject({cloudSettings})` | ☁️ | Requires cloud infrastructure |
| 24 | `ImportCloudProject(filePath, {cloudSettings})` | ☁️ | Requires cloud infrastructure |
| 25 | `RestoreCloudProject(folderPath, {cloudSettings})` | ☁️ | Requires cloud infrastructure |
### Project
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetMediaPool()` | ✅ | Returns MediaPool object |
| 2 | `GetTimelineCount()` | ✅ | Returns integer count |
| 3 | `GetTimelineByIndex(idx)` | ✅ | Returns Timeline object |
| 4 | `GetCurrentTimeline()` | ✅ | Returns current Timeline |
| 5 | `SetCurrentTimeline(timeline)` | ✅ | Sets active timeline |
| 6 | `GetGallery()` | ✅ | Returns Gallery object |
| 7 | `GetName()` | ✅ | Returns project name |
| 8 | `SetName(projectName)` | ⚠️ | Returns `False` on open project |
| 9 | `GetPresetList()` | ✅ | Returns preset list with dimensions |
| 10 | `SetPreset(presetName)` | ⚠️ | API accepts; preset must exist |
| 11 | `AddRenderJob()` | ✅ | Returns job ID string |
| 12 | `DeleteRenderJob(jobId)` | ✅ | Deletes render job |
| 13 | `DeleteAllRenderJobs()` | ✅ | Clears render queue |
| 14 | `GetRenderJobList()` | ✅ | Returns job list |
| 15 | `GetRenderPresetList()` | ✅ | Returns preset names |
| 16 | `StartRendering(...)` | ✅ | Starts render |
| 17 | `StopRendering()` | ✅ | Stops render |
| 18 | `IsRenderingInProgress()` | ✅ | Returns `False` when idle |
| 19 | `LoadRenderPreset(presetName)` | ✅ | Loads render preset |
| 20 | `SaveAsNewRenderPreset(presetName)` | ✅ | Creates render preset |
| 21 | `DeleteRenderPreset(presetName)` | ✅ | Deletes render preset |
| 22 | `SetRenderSettings({settings})` | ✅ | Applies render settings |
| 23 | `GetRenderJobStatus(jobId)` | ✅ | Returns `{JobStatus, CompletionPercentage}` |
| 24 | `GetQuickExportRenderPresets()` | ✅ | Returns preset names |
| 25 | `RenderWithQuickExport(preset, {params})` | ✅ | Initiates quick export |
| 26 | `GetSetting(settingName)` | ✅ | Returns project settings dict |
| 27 | `SetSetting(settingName, settingValue)` | ✅ | Sets project setting |
| 28 | `GetRenderFormats()` | ✅ | Returns format map |
| 29 | `GetRenderCodecs(renderFormat)` | ✅ | Returns codec map |
| 30 | `GetCurrentRenderFormatAndCodec()` | ✅ | Returns `{format, codec}` |
| 31 | `SetCurrentRenderFormatAndCodec(format, codec)` | ✅ | Sets format and codec |
| 32 | `GetCurrentRenderMode()` | ✅ | Returns mode integer |
| 33 | `SetCurrentRenderMode(renderMode)` | ✅ | Sets render mode |
| 34 | `GetRenderResolutions(format, codec)` | ✅ | Returns resolution list |
| 35 | `RefreshLUTList()` | ✅ | Refreshes LUT list |
| 36 | `GetUniqueId()` | ✅ | Returns UUID string |
| 37 | `InsertAudioToCurrentTrackAtPlayhead(...)` | ⚠️ | Tested; needs Fairlight page context |
| 38 | `LoadBurnInPreset(presetName)` | ⚠️ | API accepts; preset must exist |
| 39 | `ExportCurrentFrameAsStill(filePath)` | ⚠️ | API accepts; needs valid playhead position |
| 40 | `GetColorGroupsList()` | ✅ | Returns color group list |
| 41 | `AddColorGroup(groupName)` | ✅ | Returns ColorGroup object |
| 42 | `DeleteColorGroup(colorGroup)` | ✅ | Deletes color group |
### MediaStorage
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetMountedVolumeList()` | ✅ | Returns mounted volume paths |
| 2 | `GetSubFolderList(folderPath)` | ✅ | Returns subfolder paths |
| 3 | `GetFileList(folderPath)` | ✅ | Returns file paths |
| 4 | `RevealInStorage(path)` | ✅ | Reveals path in Media Storage |
| 5 | `AddItemListToMediaPool(...)` | ✅ | Imports media, returns clips |
| 6 | `AddClipMattesToMediaPool(item, [paths], eye)` | ✅ | Adds clip mattes |
| 7 | `AddTimelineMattesToMediaPool([paths])` | ✅ | Returns MediaPoolItem list |
### MediaPool
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetRootFolder()` | ✅ | Returns root Folder |
| 2 | `AddSubFolder(folder, name)` | ✅ | Creates subfolder |
| 3 | `RefreshFolders()` | ✅ | Refreshes folder list |
| 4 | `CreateEmptyTimeline(name)` | ✅ | Creates timeline |
| 5 | `AppendToTimeline(...)` | ✅ | Appends clips, returns TimelineItems |
| 6 | `CreateTimelineFromClips(name, ...)` | ✅ | Creates timeline from clips |
| 7 | `ImportTimelineFromFile(filePath, {options})` | ✅ | Imports AAF/EDL/XML |
| 8 | `DeleteTimelines([timeline])` | ✅ | Deletes timelines |
| 9 | `GetCurrentFolder()` | ✅ | Returns current Folder |
| 10 | `SetCurrentFolder(folder)` | ✅ | Sets current folder |
| 11 | `DeleteClips([clips])` | ✅ | Deletes clips |
| 12 | `ImportFolderFromFile(filePath)` | ✅ | Imports DRB folder |
| 13 | `DeleteFolders([subfolders])` | ✅ | Deletes folders |
| 14 | `MoveClips([clips], targetFolder)` | ✅ | Moves clips |
| 15 | `MoveFolders([folders], targetFolder)` | ✅ | Moves folders |
| 16 | `GetClipMatteList(item)` | ✅ | Returns matte paths |
| 17 | `GetTimelineMatteList(folder)` | ✅ | Returns matte items |
| 18 | `DeleteClipMattes(item, [paths])` | ✅ | Deletes clip mattes |
| 19 | `RelinkClips([items], folderPath)` | ⚠️ | API accepts; needs offline clips |
| 20 | `UnlinkClips([items])` | ✅ | Unlinks clips |
| 21 | `ImportMedia([items])` | ✅ | Imports media files |
| 22 | `ExportMetadata(fileName, [clips])` | ✅ | Exports metadata CSV |
| 23 | `GetUniqueId()` | ✅ | Returns UUID string |
| 24 | `CreateStereoClip(left, right)` | ✅ | Creates stereo pair |
| 25 | `AutoSyncAudio([items], {settings})` | ⚠️ | Tested; needs matching A/V clips |
| 26 | `GetSelectedClips()` | ✅ | Returns selected clips |
| 27 | `SetSelectedClip(item)` | ✅ | Selects clip |
### Folder
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetClipList()` | ✅ | Returns clip list |
| 2 | `GetName()` | ✅ | Returns folder name |
| 3 | `GetSubFolderList()` | ✅ | Returns subfolder list |
| 4 | `GetIsFolderStale()` | ✅ | Returns `False` |
| 5 | `GetUniqueId()` | ✅ | Returns UUID string |
| 6 | `Export(filePath)` | ✅ | Exports DRB file |
| 7 | `TranscribeAudio()` | ✅ | Starts audio transcription |
| 8 | `ClearTranscription()` | ✅ | Clears transcription |
### MediaPoolItem
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetName()` | ✅ | Returns clip name |
| 2 | `GetMetadata(metadataType)` | ✅ | Returns metadata dict |
| 3 | `SetMetadata(type, value)` | ✅ | Sets metadata |
| 4 | `GetThirdPartyMetadata(type)` | ✅ | Returns third-party metadata |
| 5 | `SetThirdPartyMetadata(type, value)` | ✅ | Sets third-party metadata |
| 6 | `GetMediaId()` | ✅ | Returns media UUID |
| 7 | `AddMarker(frameId, color, name, note, duration, customData)` | ✅ | Adds marker |
| 8 | `GetMarkers()` | ✅ | Returns marker dict |
| 9 | `GetMarkerByCustomData(customData)` | ✅ | Finds marker by data |
| 10 | `UpdateMarkerCustomData(frameId, customData)` | ✅ | Updates marker data |
| 11 | `GetMarkerCustomData(frameId)` | ✅ | Returns custom data string |
| 12 | `DeleteMarkersByColor(color)` | ✅ | Deletes markers by color |
| 13 | `DeleteMarkerAtFrame(frameNum)` | ⚠️ | Returns `False` if no marker at frame |
| 14 | `DeleteMarkerByCustomData(customData)` | ⚠️ | Returns `False` if no match |
| 15 | `AddFlag(color)` | ✅ | Adds flag |
| 16 | `GetFlagList()` | ✅ | Returns flag colors |
| 17 | `ClearFlags(color)` | ✅ | Clears flags |
| 18 | `GetClipColor()` | ✅ | Returns clip color |
| 19 | `SetClipColor(colorName)` | ✅ | Sets clip color |
| 20 | `ClearClipColor()` | ✅ | Clears clip color |
| 21 | `GetClipProperty(propertyName)` | ✅ | Returns property dict |
| 22 | `SetClipProperty(propertyName, value)` | ⚠️ | API accepts; some properties read-only |
| 23 | `LinkProxyMedia(proxyMediaFilePath)` | ✅ | Links proxy media |
| 24 | `UnlinkProxyMedia()` | ✅ | Unlinks proxy media |
| 25 | `ReplaceClip(filePath)` | ✅ | Replaces clip source |
| 26 | `GetUniqueId()` | ✅ | Returns UUID string |
| 27 | `TranscribeAudio()` | ✅ | Starts audio transcription |
| 28 | `ClearTranscription()` | ✅ | Clears transcription |
| 29 | `GetAudioMapping()` | ✅ | Returns JSON audio mapping |
| 30 | `GetMarkInOut()` | ✅ | Returns mark in/out dict |
| 31 | `SetMarkInOut(in, out, type)` | ✅ | Sets mark in/out |
| 32 | `ClearMarkInOut(type)` | ✅ | Clears mark in/out |
### Timeline
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetName()` | ✅ | Returns timeline name |
| 2 | `SetName(timelineName)` | ⚠️ | Returns `False` on active timeline |
| 3 | `GetStartFrame()` | ✅ | Returns start frame |
| 4 | `GetEndFrame()` | ✅ | Returns end frame |
| 5 | `SetStartTimecode(timecode)` | ✅ | Sets start timecode |
| 6 | `GetStartTimecode()` | ✅ | Returns `"01:00:00:00"` |
| 7 | `GetTrackCount(trackType)` | ✅ | Returns track count |
| 8 | `AddTrack(trackType, subTrackType)` | ✅ | Adds track |
| 9 | `DeleteTrack(trackType, trackIndex)` | ✅ | Deletes track |
| 10 | `GetTrackSubType(trackType, trackIndex)` | ✅ | Returns sub-type (e.g. `"stereo"`) |
| 11 | `SetTrackEnable(trackType, trackIndex, enabled)` | ✅ | Enables/disables track |
| 12 | `GetIsTrackEnabled(trackType, trackIndex)` | ✅ | Returns enabled state |
| 13 | `SetTrackLock(trackType, trackIndex, locked)` | ✅ | Locks/unlocks track |
| 14 | `GetIsTrackLocked(trackType, trackIndex)` | ✅ | Returns lock state |
| 15 | `DeleteClips([timelineItems], ripple)` | ✅ | Deletes clips from timeline |
| 16 | `SetClipsLinked([timelineItems], linked)` | ✅ | Links/unlinks clips |
| 17 | `GetItemListInTrack(trackType, index)` | ✅ | Returns items on track |
| 18 | `AddMarker(frameId, color, name, note, duration, customData)` | ✅ | Adds timeline marker |
| 19 | `GetMarkers()` | ✅ | Returns marker dict |
| 20 | `GetMarkerByCustomData(customData)` | ✅ | Finds marker by data |
| 21 | `UpdateMarkerCustomData(frameId, customData)` | ✅ | Updates marker data |
| 22 | `GetMarkerCustomData(frameId)` | ✅ | Returns custom data |
| 23 | `DeleteMarkersByColor(color)` | ✅ | Deletes markers by color |
| 24 | `DeleteMarkerAtFrame(frameNum)` | ⚠️ | Returns `False` if no marker at frame |
| 25 | `DeleteMarkerByCustomData(customData)` | ⚠️ | Returns `False` if no match |
| 26 | `GetCurrentTimecode()` | ✅ | Returns timecode string |
| 27 | `SetCurrentTimecode(timecode)` | ⚠️ | Returns `False` if playback not active |
| 28 | `GetCurrentVideoItem()` | ✅ | Returns item at playhead |
| 29 | `GetCurrentClipThumbnailImage()` | ✅ | Returns thumbnail data |
| 30 | `GetTrackName(trackType, trackIndex)` | ✅ | Returns track name |
| 31 | `SetTrackName(trackType, trackIndex, name)` | ✅ | Sets track name |
| 32 | `DuplicateTimeline(timelineName)` | ✅ | Duplicates timeline |
| 33 | `CreateCompoundClip([items], {clipInfo})` | ✅ | Returns compound clip item |
| 34 | `CreateFusionClip([timelineItems])` | ✅ | Returns Fusion clip item |
| 35 | `ImportIntoTimeline(filePath, {options})` | ⚠️ | Tested; result depends on file format |
| 36 | `Export(fileName, exportType, exportSubtype)` | ✅ | Exports EDL/XML/AAF |
| 37 | `GetSetting(settingName)` | ✅ | Returns settings dict |
| 38 | `SetSetting(settingName, settingValue)` | ⚠️ | API accepts; some settings read-only |
| 39 | `InsertGeneratorIntoTimeline(name)` | ✅ | Inserts generator |
| 40 | `InsertFusionGeneratorIntoTimeline(name)` | ✅ | Inserts Fusion generator |
| 41 | `InsertFusionCompositionIntoTimeline()` | ✅ | Inserts Fusion composition |
| 42 | `InsertOFXGeneratorIntoTimeline(name)` | ⚠️ | API accepts; needs valid OFX plugin |
| 43 | `InsertTitleIntoTimeline(name)` | ✅ | Inserts title |
| 44 | `InsertFusionTitleIntoTimeline(name)` | ✅ | Inserts Fusion title |
| 45 | `GrabStill()` | ✅ | Returns GalleryStill object |
| 46 | `GrabAllStills(stillFrameSource)` | ✅ | Returns list of GalleryStill objects |
| 47 | `GetUniqueId()` | ✅ | Returns UUID string |
| 48 | `CreateSubtitlesFromAudio({settings})` | ✅ | Returns `True` — creates subtitles from audio |
| 49 | `DetectSceneCuts()` | ✅ | Returns `True` — detects scene cuts in timeline |
| 50 | `ConvertTimelineToStereo()` | ✅ | Converts timeline to stereo 3D |
| 51 | `GetNodeGraph()` | ✅ | Returns Graph object |
| 52 | `AnalyzeDolbyVision([items], analysisType)` | 🔬 | Requires HDR/Dolby Vision content |
| 53 | `GetMediaPoolItem()` | ✅ | Returns MediaPoolItem for timeline |
| 54 | `GetMarkInOut()` | ✅ | Returns mark in/out dict |
| 55 | `SetMarkInOut(in, out, type)` | ✅ | Sets mark in/out |
| 56 | `ClearMarkInOut(type)` | ✅ | Clears mark in/out |
### TimelineItem
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetName()` | ✅ | Returns item name |
| 2 | `GetDuration(subframe_precision)` | ✅ | Returns duration |
| 3 | `GetEnd(subframe_precision)` | ✅ | Returns end frame |
| 4 | `GetSourceEndFrame()` | ✅ | Returns source end frame |
| 5 | `GetSourceEndTime()` | ✅ | Returns source end time |
| 6 | `GetFusionCompCount()` | ✅ | Returns comp count |
| 7 | `GetFusionCompByIndex(compIndex)` | ✅ | Returns Fusion composition |
| 8 | `GetFusionCompNameList()` | ✅ | Returns comp names |
| 9 | `GetFusionCompByName(compName)` | ✅ | Returns Fusion composition |
| 10 | `GetLeftOffset(subframe_precision)` | ✅ | Returns left offset |
| 11 | `GetRightOffset(subframe_precision)` | ✅ | Returns right offset |
| 12 | `GetStart(subframe_precision)` | ✅ | Returns start frame |
| 13 | `GetSourceStartFrame()` | ✅ | Returns source start |
| 14 | `GetSourceStartTime()` | ✅ | Returns source start time |
| 15 | `SetProperty(propertyKey, propertyValue)` | ✅ | Sets item property |
| 16 | `GetProperty(propertyKey)` | ✅ | Returns property dict |
| 17 | `AddMarker(frameId, color, name, note, duration, customData)` | ✅ | Adds marker to item |
| 18 | `GetMarkers()` | ✅ | Returns marker dict |
| 19 | `GetMarkerByCustomData(customData)` | ✅ | Finds marker by data |
| 20 | `UpdateMarkerCustomData(frameId, customData)` | ✅ | Updates marker data |
| 21 | `GetMarkerCustomData(frameId)` | ✅ | Returns custom data |
| 22 | `DeleteMarkersByColor(color)` | ✅ | Deletes markers by color |
| 23 | `DeleteMarkerAtFrame(frameNum)` | ⚠️ | Returns `False` if no marker at frame |
| 24 | `DeleteMarkerByCustomData(customData)` | ⚠️ | Returns `False` if no match |
| 25 | `AddFlag(color)` | ✅ | Adds flag |
| 26 | `GetFlagList()` | ✅ | Returns flag colors |
| 27 | `ClearFlags(color)` | ✅ | Clears flags |
| 28 | `GetClipColor()` | ✅ | Returns clip color |
| 29 | `SetClipColor(colorName)` | ✅ | Sets clip color |
| 30 | `ClearClipColor()` | ✅ | Clears clip color |
| 31 | `AddFusionComp()` | ✅ | Creates Fusion composition |
| 32 | `ImportFusionComp(path)` | ✅ | Imports .comp file |
| 33 | `ExportFusionComp(path, compIndex)` | ✅ | Exports .comp file |
| 34 | `DeleteFusionCompByName(compName)` | ⚠️ | Returns `False` if comp not found |
| 35 | `LoadFusionCompByName(compName)` | ✅ | Loads composition |
| 36 | `RenameFusionCompByName(oldName, newName)` | ✅ | Renames composition |
| 37 | `AddVersion(versionName, versionType)` | ✅ | Adds grade version |
| 38 | `GetCurrentVersion()` | ✅ | Returns version info |
| 39 | `DeleteVersionByName(versionName, versionType)` | ⚠️ | Returns `False` if version not found |
| 40 | `LoadVersionByName(versionName, versionType)` | ✅ | Loads grade version |
| 41 | `RenameVersionByName(oldName, newName, type)` | ✅ | Renames version |
| 42 | `GetVersionNameList(versionType)` | ✅ | Returns version names |
| 43 | `GetMediaPoolItem()` | ✅ | Returns source MediaPoolItem |
| 44 | `GetStereoConvergenceValues()` | ✅ | Returns stereo keyframes |
| 45 | `GetStereoLeftFloatingWindowParams()` | ✅ | Returns stereo params |
| 46 | `GetStereoRightFloatingWindowParams()` | ✅ | Returns stereo params |
| 47 | `SetCDL([CDL map])` | ✅ | Sets CDL values |
| 48 | `AddTake(mediaPoolItem, startFrame, endFrame)` | ✅ | Adds take |
| 49 | `GetSelectedTakeIndex()` | ✅ | Returns selected take index |
| 50 | `GetTakesCount()` | ✅ | Returns take count |
| 51 | `GetTakeByIndex(idx)` | ✅ | Returns take info |
| 52 | `DeleteTakeByIndex(idx)` | ✅ | Deletes take |
| 53 | `SelectTakeByIndex(idx)` | ✅ | Selects take |
| 54 | `FinalizeTake()` | ⚠️ | Returns `False` when no take selected |
| 55 | `CopyGrades([tgtTimelineItems])` | ⚠️ | API accepts; needs matching items |
| 56 | `SetClipEnabled(enabled)` | ✅ | Enables/disables clip |
| 57 | `GetClipEnabled()` | ✅ | Returns enabled state |
| 58 | `UpdateSidecar()` | ⚠️ | Returns `False` for non-BRAW clips |
| 59 | `GetUniqueId()` | ✅ | Returns UUID string |
| 60 | `LoadBurnInPreset(presetName)` | ⚠️ | API accepts; preset must exist |
| 61 | `CreateMagicMask(mode)` | ⚠️ | Tested; needs DaVinci Neural Engine + Color page context |
| 62 | `RegenerateMagicMask()` | ⚠️ | Tested; needs existing mask |
| 63 | `Stabilize()` | ✅ | Returns `True` on supported clips |
| 64 | `SmartReframe()` | ⚠️ | Tested; needs specific aspect ratio setup |
| 65 | `GetNodeGraph(layerIdx)` | ✅ | Returns Graph object |
| 66 | `GetColorGroup()` | ✅ | Returns ColorGroup |
| 67 | `AssignToColorGroup(colorGroup)` | ✅ | Assigns to group |
| 68 | `RemoveFromColorGroup()` | ⚠️ | Returns `False` if not in group |
| 69 | `ExportLUT(exportType, path)` | ✅ | Exports LUT file |
| 70 | `GetLinkedItems()` | ✅ | Returns linked items |
| 71 | `GetTrackTypeAndIndex()` | ✅ | Returns `[trackType, trackIndex]` |
| 72 | `GetSourceAudioChannelMapping()` | ✅ | Returns audio mapping |
| 73 | `GetIsColorOutputCacheEnabled()` | ✅ | Returns cache state |
| 74 | `GetIsFusionOutputCacheEnabled()` | ✅ | Returns cache state |
| 75 | `SetColorOutputCache(cache_value)` | ⚠️ | Tested; needs active color pipeline |
| 76 | `SetFusionOutputCache(cache_value)` | ⚠️ | Tested; needs active Fusion pipeline |
### Gallery
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetAlbumName(galleryStillAlbum)` | ✅ | Returns album name |
| 2 | `SetAlbumName(galleryStillAlbum, albumName)` | ✅ | Sets album name |
| 3 | `GetCurrentStillAlbum()` | ✅ | Returns GalleryStillAlbum |
| 4 | `SetCurrentStillAlbum(galleryStillAlbum)` | ✅ | Sets current album |
| 5 | `GetGalleryStillAlbums()` | ✅ | Returns album list |
| 6 | `GetGalleryPowerGradeAlbums()` | ✅ | Returns power grade albums |
| 7 | `CreateGalleryStillAlbum()` | ✅ | Creates still album |
| 8 | `CreateGalleryPowerGradeAlbum()` | ✅ | Creates power grade album |
### GalleryStillAlbum
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetStills()` | ✅ | Returns list of GalleryStill objects |
| 2 | `GetLabel(galleryStill)` | ✅ | Returns label string |
| 3 | `SetLabel(galleryStill, label)` | ⚠️ | API accepts; may not persist in all versions |
| 4 | `ImportStills([filePaths])` | ✅ | Imports DRX still files (requires Color page) |
| 5 | `ExportStills([stills], folderPath, prefix, format)` | ✅ | Exports stills as image + companion .drx grade file. Requires Color page with Gallery panel visible. Supported formats: dpx, cin, tif, jpg, png, ppm, bmp, xpm, drx. |
| 6 | `DeleteStills([galleryStill])` | ✅ | Deletes stills from album |
> **Note (v2.0.8+):** The compound server's `gallery_stills` tool includes a `grab_and_export` action that combines `GrabStill()` + `ExportStills()` in a single call — more reliable than calling them separately since it keeps the live GalleryStill reference. Returns the list of exported files (image + .drx grade data). Requires the Color page with the Gallery panel open.
### Graph
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetNumNodes()` | ✅ | Returns node count (via ColorGroup pre/post graphs) |
| 2 | `SetLUT(nodeIndex, lutPath)` | ✅ | Sets LUT on node |
| 3 | `GetLUT(nodeIndex)` | ✅ | Returns LUT path |
| 4 | `SetNodeCacheMode(nodeIndex, cache_value)` | ✅ | Returns `True` |
| 5 | `GetNodeCacheMode(nodeIndex)` | ✅ | Returns `-1` (no cache mode set) |
| 6 | `GetNodeLabel(nodeIndex)` | ✅ | Returns node label string |
| 7 | `GetToolsInNode(nodeIndex)` | ✅ | Returns `None` (no OFX tools in node) |
| 8 | `SetNodeEnabled(nodeIndex, isEnabled)` | ✅ | Returns `True` |
| 9 | `ApplyGradeFromDRX(path, gradeMode)` | ✅ | Applies grade from DRX file |
| 10 | `ApplyArriCdlLut()` | ✅ | Applies ARRI CDL LUT |
| 11 | `ResetAllGrades()` | ✅ | Resets all grades |
### ColorGroup
| # | Method | Status | Test Result / Notes |
|---|--------|--------|---------------------|
| 1 | `GetName()` | ✅ | Returns group name |
| 2 | `SetName(groupName)` | ✅ | Sets group name |
| 3 | `GetClipsInTimeline(timeline)` | ✅ | Returns clips in group |
| 4 | `GetPreClipNodeGraph()` | ✅ | Returns Graph object |
| 5 | `GetPostClipNodeGraph()` | ✅ | Returns Graph object |
---
## Contributing
We welcome contributions! The following areas especially need help:
### Help Wanted: Untested API Methods
**5 methods** (1.5%) remain untested against a live DaVinci Resolve instance. If you have access to the required infrastructure or content, we'd love a PR with test confirmation:
1. **Cloud Project Methods** (4 methods) — Need DaVinci Resolve cloud infrastructure:
- `ProjectManager.CreateCloudProject`
- `ProjectManager.LoadCloudProject`
- `ProjectManager.ImportCloudProject`
- `ProjectManager.RestoreCloudProject`
2. **HDR Analysis** (1 method) — Needs specific content:
- `Timeline.AnalyzeDolbyVision` — needs HDR/Dolby Vision content
### How to Contribute
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/my-contribution`)
3. Run the existing test suite to ensure nothing breaks
4. Add your test results or fixes
5. Submit a pull request
### Other Contribution Ideas
- **Windows testing** — All tests were run on macOS; Windows verification welcome
- **Linux testing** — DaVinci Resolve supports Linux; test coverage needed
- **Resolve version compatibility** — Test against Resolve 18.x, 19.0, or newer versions
- **Bug reports** — If a tool returns unexpected results on your setup, file an issue
- **Documentation** — Improve examples, add tutorials, translate docs
## Platform Support
| Platform | Status | Resolve Paths Auto-Detected | Notes |
|----------|--------|----------------------------|-------|
| macOS | ✅ Tested | `/Library/Application Support/Blackmagic Design/...` | Primary development and test platform |
| Windows | ✅ Supported | `C:\ProgramData\Blackmagic Design\...` | Community-tested; PRs welcome |
| Linux | ⚠️ Experimental | `/opt/resolve/...` | Should work — testing and feedback welcome |
## Security Considerations
This MCP server controls DaVinci Resolve via its Scripting API. Some tools perform actions that are destructive or interact with the host filesystem:
| Tool | Risk | Mitigation |
|------|------|------------|
| `quit_app` / `restart_app` | Terminates the Resolve process — can cause data loss if unsaved changes exist or a render is in progress | MCP clients should require explicit user confirmation before calling these tools. Subprocess calls use hardcoded command lists (no shell injection possible). |
| `export_layout_preset` / `import_layout_preset` / `delete_layout_preset` | Read/write/delete files in the Resolve layout presets directory | Path traversal protection validates all resolved paths stay within the expected presets directory (v2.0.7+). |
| `save_project` | Creates and removes a temporary `.drp` file in the system temp directory | Path is constructed server-side with no LLM-controlled input. |
**Recommendations for MCP client developers:**
- Enable tool-call confirmation prompts for destructive tools (`quit_app`, `restart_app`, `delete_layout_preset`)
- Do not grant blanket auto-approval to all tools in this server
## Project Structure
```
davinci-resolve-mcp/
├── install.py # Universal installer (macOS/Windows/Linux)
├── src/
│ ├── server.py # Compound MCP server — 27 tools (default)
│ ├── resolve_mcp_server.py # Full MCP server — 342 tools (power users)
│ └── utils/ # Platform detection, Resolve connection helpers
├── tests/ # 5-phase live API test suite (319/319 pass)
├── docs/
│ └── resolve_scripting_api.txt # Official Resolve Scripting API reference
└── examples/ # Getting started, markers, media, timeline examples
```
## License
MIT
## Author
Samuel Gursky (samgursky@gmail.com)
- GitHub: [github.com/samuelgursky](https://github.com/samuelgursky)
## Acknowledgments
- Blackmagic Design for DaVinci Resolve and its scripting API
- The Model Context Protocol team for enabling AI assistant integration
- Anthropic for Claude Code, used extensively in development and testing
Connection Info
You Might Also Like
OpenAI Whisper
OpenAI Whisper MCP Server - 基于本地 Whisper CLI 的离线语音识别与翻译,无需 API Key,支持...
markitdown
Python tool for converting files and office documents to Markdown.
oh-my-opencode
Background agents · Curated agents like oracle, librarians, frontend...
chatbox
User-friendly Desktop Client App for AI Models/LLMs (GPT, Claude, Gemini, Ollama...)
continue
Continue is an open-source project for seamless server management.
claude-flow
Claude-Flow v2.7.0 is an enterprise AI orchestration platform.