#
tokens: 14748/50000 8/8 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── examples
│   └── basic_usage.py
├── README.md
├── requirements.txt
├── setup.py
└── src
    └── resolve_mcp
        ├── __init__.py
        ├── resolve_api.py
        └── server.py
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | *.so
 6 | .Python
 7 | build/
 8 | develop-eggs/
 9 | dist/
10 | downloads/
11 | eggs/
12 | .eggs/
13 | lib/
14 | lib64/
15 | parts/
16 | sdist/
17 | var/
18 | wheels/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 | 
23 | # Virtual environments
24 | venv/
25 | env/
26 | ENV/
27 | 
28 | # IDE specific files
29 | .idea/
30 | .vscode/
31 | *.swp
32 | *.swo
33 | 
34 | # OS specific files
35 | .DS_Store
36 | Thumbs.db
37 | 
38 | # Logs
39 | *.log
40 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # DaVinci Resolve MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server for interacting with DaVinci Resolve and Fusion. This server allows AI assistants like Claude to directly interact with and control DaVinci Resolve through the Model Context Protocol.
  4 | 
  5 | ## Features
  6 | 
  7 | - Two-way communication: Connect Claude AI to DaVinci Resolve through the MCP protocol
  8 | - Project management: Create, open, and manage DaVinci Resolve projects
  9 | - Timeline manipulation: Create, modify, and navigate timelines
 10 | - Media management: Import, organize, and manage media in the Media Pool
 11 | - Fusion integration: Create and modify Fusion compositions
 12 | - Scene inspection: Get detailed information about the current DaVinci Resolve project
 13 | - Code execution: Run arbitrary Python code in DaVinci Resolve from Claude
 14 | 
 15 | ## Installation
 16 | 
 17 | ### Prerequisites
 18 | 
 19 | - DaVinci Resolve Studio (version 17 or higher recommended)
 20 | - Python 3.8 or higher
 21 | - Claude Desktop (for AI integration)
 22 | 
 23 | ### Setup
 24 | 
 25 | 1. Clone this repository:
 26 |    ```
 27 |    git clone https://github.com/apvlv/davinci-resolve-mcp.git
 28 |    cd davinci-resolve-mcp
 29 |    ```
 30 | 
 31 | 2. Install the required dependencies:
 32 |    ```
 33 |    pip install -r requirements.txt
 34 |    ```
 35 | 
 36 | 3. Install the MCP server in Claude Desktop:
 37 |    ```
 38 |    mcp install src/resolve_mcp/server.py
 39 |    ```
 40 | 
 41 |    Alternatively, you can install with the editable flag for development:
 42 |    ```
 43 |    mcp install src/resolve_mcp/server.py --with-editable .
 44 |    ```
 45 | 
 46 | ## Usage
 47 | 
 48 | ### With Claude Desktop
 49 | 
 50 | 1. Start DaVinci Resolve
 51 | 2. In Claude Desktop, connect to the "DaVinci Resolve MCP" server
 52 | 3. You can now interact with DaVinci Resolve through Claude
 53 | 
 54 | ### With 5ire
 55 | 
 56 | [5ire](https://5ire.app/) is an open-source cross-platform desktop AI assistant and MCP client that's compatible with this server.
 57 | 
 58 | 1. Install 5ire from [GitHub](https://github.com/nanbingxyz/5ire) or using Homebrew on macOS:
 59 |    ```
 60 |    brew tap brewforge/extras
 61 |    brew install --cask 5ire
 62 |    ```
 63 | 2. Start DaVinci Resolve
 64 | 3. In 5ire, add the DaVinci Resolve MCP server
 65 | 4. Connect to the server using your preferred AI model (OpenAI, Claude, etc.)
 66 | 5. You can now interact with DaVinci Resolve through 5ire
 67 | 
 68 | ## Available Commands
 69 | 
 70 | ### Resources (Information Retrieval)
 71 | 
 72 | - `project://current` - Get information about the current project
 73 | - `project://timelines` - Get a list of timelines in the current project
 74 | - `timeline://current` - Get information about the current timeline
 75 | - `mediapool://folders` - Get a list of folders in the media pool
 76 | - `mediapool://current` - Get information about the current media pool folder
 77 | - `storage://volumes` - Get a list of mounted volumes in the media storage
 78 | - `system://status` - Get the current status of the DaVinci Resolve connection
 79 | 
 80 | ### Project Management
 81 | 
 82 | - `create_project(name)` - Create a new DaVinci Resolve project
 83 | - `load_project(name)` - Load an existing DaVinci Resolve project
 84 | - `save_project()` - Save the current DaVinci Resolve project
 85 | 
 86 | ### Timeline Management
 87 | 
 88 | - `create_timeline(name)` - Create a new timeline in the current project
 89 | - `set_current_timeline(index)` - Set the current timeline by index (1-based)
 90 | 
 91 | ### Media Management
 92 | 
 93 | - `import_media(file_paths)` - Import media files into the current media pool folder
 94 | - `create_folder(name)` - Create a new folder in the current media pool folder
 95 | - `create_timeline_from_clips(name, clip_indices)` - Create a new timeline from clips in the current media pool folder
 96 | 
 97 | ### Fusion Integration
 98 | 
 99 | - `add_fusion_comp_to_clip(timeline_index, track_type, track_index, item_index)` - Add a Fusion composition to a clip in the timeline
100 | - `create_fusion_node(node_type, parameters)` - Create a specific Fusion node in the current composition
101 | - `create_fusion_node_chain(node_chain)` - Create a chain of connected Fusion nodes in the current composition
102 | 
103 | ### Page Navigation
104 | 
105 | - `open_page(page_name)` - Open a specific page in DaVinci Resolve (media, edit, fusion, color, fairlight, deliver)
106 | 
107 | ### Advanced Operations
108 | 
109 | - `execute_python(code)` - Execute arbitrary Python code in DaVinci Resolve
110 | - `execute_lua(script)` - Execute a Lua script in DaVinci Resolve's Fusion
111 | 
112 | ## Examples
113 | 
114 | - "Create a new project named 'My Documentary'"
115 | - "Import all video files from the Downloads folder"
116 | - "Create a new timeline with the selected clips"
117 | - "Apply a Fusion effect to the selected clip"
118 | - "Get information about the current project"
119 | - "Switch to the Color page"
120 | - "Save the current project"
121 | - "Create a folder named 'Raw Footage' in the media pool"
122 | - "Create a Blur node in the current Fusion composition"
123 | - "Create a Text node with the content 'Hello World'"
124 | - "Create a chain of nodes: MediaIn -> Blur -> ColorCorrector -> MediaOut"
125 | 
126 | ## Technical Details
127 | 
128 | The server uses the Model Context Protocol to communicate between Claude and DaVinci Resolve. It leverages DaVinci Resolve's Python API to control the application.
129 | 
130 | ## License
131 | 
132 | MIT
133 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
1 | mcp>=0.3.0
2 | pydantic>=2.0.0
3 | typing-extensions>=4.0.0
4 | 
```

--------------------------------------------------------------------------------
/src/resolve_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """
2 | DaVinci Resolve MCP Server - Model Context Protocol integration for DaVinci Resolve
3 | """
4 | 
5 | __version__ = "0.1.0"
6 | 
```

--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------

```python
 1 | from setuptools import setup, find_packages
 2 | 
 3 | setup(
 4 |     name="resolve-mcp",
 5 |     version="0.1.0",
 6 |     description="Model Context Protocol (MCP) server for DaVinci Resolve",
 7 |     author="Your Name",
 8 |     author_email="[email protected]",
 9 |     packages=find_packages(where="src"),
10 |     package_dir={"": "src"},
11 |     install_requires=[
12 |         "mcp>=0.3.0",
13 |         "pydantic>=2.0.0",
14 |         "typing-extensions>=4.0.0",
15 |     ],
16 |     entry_points={
17 |         "console_scripts": [
18 |             "resolve-mcp=resolve_mcp.server:main",
19 |         ],
20 |     },
21 |     python_requires=">=3.8",
22 |     classifiers=[
23 |         "Development Status :: 3 - Alpha",
24 |         "Intended Audience :: Developers",
25 |         "Programming Language :: Python :: 3",
26 |         "Programming Language :: Python :: 3.8",
27 |         "Programming Language :: Python :: 3.9",
28 |         "Programming Language :: Python :: 3.10",
29 |     ],
30 | )
31 | 
```

--------------------------------------------------------------------------------
/examples/basic_usage.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python
  2 | """
  3 | Basic example of using the DaVinci Resolve MCP Server.
  4 | 
  5 | This script demonstrates how to manually connect to the MCP server
  6 | and execute some basic operations with DaVinci Resolve.
  7 | """
  8 | 
  9 | import sys
 10 | import os
 11 | import time
 12 | import json
 13 | from typing import Dict, Any
 14 | 
 15 | # Add the parent directory to the path so we can import the resolve_mcp package
 16 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
 17 | 
 18 | from src.resolve_mcp.resolve_api import ResolveAPI
 19 | 
 20 | def main():
 21 |     """Main function to demonstrate basic usage of the DaVinci Resolve API."""
 22 |     print("Connecting to DaVinci Resolve...")
 23 |     resolve_api = ResolveAPI()
 24 |     
 25 |     if not resolve_api.is_connected():
 26 |         print("Failed to connect to DaVinci Resolve. Make sure DaVinci Resolve is running.")
 27 |         return
 28 |     
 29 |     print("Successfully connected to DaVinci Resolve.")
 30 |     
 31 |     # Get the current project
 32 |     project = resolve_api.get_current_project()
 33 |     if project:
 34 |         print(f"Current project: {project.GetName()}")
 35 |     else:
 36 |         print("No project is currently open.")
 37 |         
 38 |         # Create a new project
 39 |         project_name = f"Example Project {int(time.time())}"
 40 |         print(f"Creating a new project: {project_name}")
 41 |         if resolve_api.create_project(project_name):
 42 |             print(f"Successfully created project: {project_name}")
 43 |             project = resolve_api.get_current_project()
 44 |         else:
 45 |             print(f"Failed to create project: {project_name}")
 46 |             return
 47 |     
 48 |     # Create a new timeline
 49 |     timeline_name = f"Example Timeline {int(time.time())}"
 50 |     print(f"Creating a new timeline: {timeline_name}")
 51 |     if resolve_api.create_timeline(timeline_name):
 52 |         print(f"Successfully created timeline: {timeline_name}")
 53 |     else:
 54 |         print(f"Failed to create timeline: {timeline_name}")
 55 |     
 56 |     # Get the current timeline
 57 |     timeline = resolve_api.get_current_timeline()
 58 |     if timeline:
 59 |         print(f"Current timeline: {timeline.GetName()}")
 60 |         print(f"Timeline duration: {timeline.GetEndFrame() - timeline.GetStartFrame() + 1} frames")
 61 |         print(f"Video tracks: {timeline.GetTrackCount('video')}")
 62 |         print(f"Audio tracks: {timeline.GetTrackCount('audio')}")
 63 |     else:
 64 |         print("No timeline is currently open.")
 65 |     
 66 |     # Get media storage information
 67 |     print("\nMedia Storage Information:")
 68 |     volumes = resolve_api.get_mounted_volumes()
 69 |     if volumes:
 70 |         print("Mounted volumes:")
 71 |         for i, volume in enumerate(volumes, 1):
 72 |             print(f"  {i}. {volume}")
 73 |     else:
 74 |         print("No mounted volumes available.")
 75 |     
 76 |     # Get media pool information
 77 |     print("\nMedia Pool Information:")
 78 |     media_pool = resolve_api.get_media_pool()
 79 |     if media_pool:
 80 |         root_folder = media_pool.GetRootFolder()
 81 |         if root_folder:
 82 |             print(f"Root folder: {root_folder.GetName()}")
 83 |             
 84 |             # Print folder structure
 85 |             def print_folder_structure(folder, indent=""):
 86 |                 name = folder.GetName()
 87 |                 print(f"{indent}- {name}")
 88 |                 
 89 |                 subfolders = folder.GetSubFolders()
 90 |                 for subfolder in subfolders:
 91 |                     print_folder_structure(subfolder, indent + "  ")
 92 |             
 93 |             print("Folder structure:")
 94 |             print_folder_structure(root_folder)
 95 |         else:
 96 |             print("No root folder available.")
 97 |     else:
 98 |         print("No media pool available.")
 99 |     
100 |     # Save the project
101 |     if resolve_api.save_project():
102 |         print(f"Successfully saved project: {project.GetName()}")
103 |     else:
104 |         print(f"Failed to save project: {project.GetName()}")
105 | 
106 | if __name__ == "__main__":
107 |     main()
108 | 
```

--------------------------------------------------------------------------------
/src/resolve_mcp/resolve_api.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | DaVinci Resolve API connector module.
  3 | 
  4 | This module provides functions to connect to DaVinci Resolve's Python API
  5 | and interact with its various components.
  6 | """
  7 | 
  8 | import sys
  9 | import os
 10 | import platform
 11 | from typing import Optional, Dict, List, Any, Union, Tuple
 12 | 
 13 | class ResolveAPI:
 14 |     """Class to handle connection and interaction with DaVinci Resolve API."""
 15 |     
 16 |     def __init__(self):
 17 |         """Initialize the ResolveAPI class and connect to DaVinci Resolve."""
 18 |         self.resolve = None
 19 |         self.fusion = None
 20 |         self.project_manager = None
 21 |         self.current_project = None
 22 |         self.media_storage = None
 23 |         self.media_pool = None
 24 |         
 25 |         self._connect_to_resolve()
 26 |     
 27 |     def _connect_to_resolve(self) -> None:
 28 |         """
 29 |         Connect to DaVinci Resolve based on the current operating system.
 30 |         
 31 |         This function adds the appropriate paths to sys.path and imports the
 32 |         DaVinciResolveScript module to establish a connection to Resolve.
 33 |         """
 34 |         # Determine the appropriate path based on the operating system
 35 |         if platform.system() == "Windows":
 36 |             resolve_script_dir = os.path.join(
 37 |                 os.environ.get("PROGRAMDATA", "C:\\ProgramData"),
 38 |                 "Blackmagic Design", "DaVinci Resolve", "Support", "Developer", "Scripting"
 39 |             )
 40 |             script_api_path = os.path.join(resolve_script_dir, "Modules")
 41 |             
 42 |             # Add the API directory to the system path
 43 |             if script_api_path not in sys.path:
 44 |                 sys.path.append(script_api_path)
 45 |             
 46 |             # Import the DaVinciResolveScript module
 47 |             try:
 48 |                 import DaVinciResolveScript as dvr_script
 49 |                 self.resolve = dvr_script.scriptapp("Resolve")
 50 |             except ImportError:
 51 |                 print("Error: Could not find DaVinciResolveScript module on Windows.")
 52 |                 self.resolve = None
 53 |                 
 54 |         elif platform.system() == "Darwin":
 55 |             # macOS path
 56 |             resolve_script_dir = "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting"
 57 |             script_api_path = os.path.join(resolve_script_dir, "Modules")
 58 |             
 59 |             # Add the API directory to the system path
 60 |             if script_api_path not in sys.path:
 61 |                 sys.path.append(script_api_path)
 62 |             
 63 |             # Import the DaVinciResolveScript module
 64 |             try:
 65 |                 import DaVinciResolveScript as dvr_script
 66 |                 self.resolve = dvr_script.scriptapp("Resolve")
 67 |             except ImportError:
 68 |                 # Try the user-specific path if the system-wide path fails
 69 |                 user_script_api_path = os.path.join(
 70 |                     os.path.expanduser("~"),
 71 |                     "Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/Modules"
 72 |                 )
 73 |                 if user_script_api_path not in sys.path:
 74 |                     sys.path.append(user_script_api_path)
 75 |                 
 76 |                 try:
 77 |                     import DaVinciResolveScript as dvr_script
 78 |                     self.resolve = dvr_script.scriptapp("Resolve")
 79 |                 except ImportError:
 80 |                     print("Error: Could not find DaVinciResolveScript module on macOS.")
 81 |                     self.resolve = None
 82 |                     
 83 |         elif platform.system() == "Linux":
 84 |             # Linux path
 85 |             resolve_script_dir = "/opt/resolve/Developer/Scripting"
 86 |             script_api_path = os.path.join(resolve_script_dir, "Modules")
 87 |             
 88 |             # Add the API directory to the system path
 89 |             if script_api_path not in sys.path:
 90 |                 sys.path.append(script_api_path)
 91 |             
 92 |             # Import the DaVinciResolveScript module
 93 |             try:
 94 |                 import DaVinciResolveScript as dvr_script
 95 |                 self.resolve = dvr_script.scriptapp("Resolve")
 96 |             except ImportError:
 97 |                 print("Error: Could not find DaVinciResolveScript module on Linux.")
 98 |                 self.resolve = None
 99 |         
100 |         # Initialize other components if Resolve is connected
101 |         if self.resolve:
102 |             self.project_manager = self.resolve.GetProjectManager()
103 |             self.current_project = self.project_manager.GetCurrentProject()
104 |             self.media_storage = self.resolve.GetMediaStorage()
105 |             self.fusion = self.resolve.Fusion()
106 |             
107 |             # Initialize media pool if a project is open
108 |             if self.current_project:
109 |                 self.media_pool = self.current_project.GetMediaPool()
110 |     
111 |     def is_connected(self) -> bool:
112 |         """Check if connected to DaVinci Resolve."""
113 |         return self.resolve is not None
114 |     
115 |     def get_project_manager(self):
116 |         """Get the project manager object."""
117 |         return self.project_manager
118 |     
119 |     def get_current_project(self):
120 |         """Get the current project object."""
121 |         if self.project_manager:
122 |             self.current_project = self.project_manager.GetCurrentProject()
123 |         return self.current_project
124 |     
125 |     def get_media_storage(self):
126 |         """Get the media storage object."""
127 |         return self.media_storage
128 |     
129 |     def get_media_pool(self):
130 |         """Get the media pool object for the current project."""
131 |         if self.current_project:
132 |             self.media_pool = self.current_project.GetMediaPool()
133 |         return self.media_pool
134 |     
135 |     def get_fusion(self):
136 |         """Get the Fusion object."""
137 |         return self.fusion
138 |     
139 |     def open_page(self, page_name: str) -> bool:
140 |         """
141 |         Open a specific page in DaVinci Resolve.
142 |         
143 |         Args:
144 |             page_name (str): The name of the page to open.
145 |                              Valid values: "media", "edit", "fusion", "color", "fairlight", "deliver"
146 |         
147 |         Returns:
148 |             bool: True if successful, False otherwise.
149 |         """
150 |         if not self.resolve:
151 |             return False
152 |         
153 |         valid_pages = ["media", "edit", "fusion", "color", "fairlight", "deliver"]
154 |         if page_name.lower() not in valid_pages:
155 |             return False
156 |         
157 |         self.resolve.OpenPage(page_name.lower())
158 |         return True
159 |     
160 |     def create_project(self, project_name: str) -> bool:
161 |         """
162 |         Create a new project with the given name.
163 |         
164 |         Args:
165 |             project_name (str): The name of the project to create.
166 |         
167 |         Returns:
168 |             bool: True if successful, False otherwise.
169 |         """
170 |         if not self.project_manager:
171 |             return False
172 |         
173 |         new_project = self.project_manager.CreateProject(project_name)
174 |         if new_project:
175 |             self.current_project = new_project
176 |             self.media_pool = self.current_project.GetMediaPool()
177 |             return True
178 |         
179 |         return False
180 |     
181 |     def load_project(self, project_name: str) -> bool:
182 |         """
183 |         Load an existing project with the given name.
184 |         
185 |         Args:
186 |             project_name (str): The name of the project to load.
187 |         
188 |         Returns:
189 |             bool: True if successful, False otherwise.
190 |         """
191 |         if not self.project_manager:
192 |             return False
193 |         
194 |         loaded_project = self.project_manager.LoadProject(project_name)
195 |         if loaded_project:
196 |             self.current_project = loaded_project
197 |             self.media_pool = self.current_project.GetMediaPool()
198 |             return True
199 |         
200 |         return False
201 |     
202 |     def save_project(self) -> bool:
203 |         """
204 |         Save the current project.
205 |         
206 |         Returns:
207 |             bool: True if successful, False otherwise.
208 |         """
209 |         if not self.current_project:
210 |             return False
211 |         
212 |         return self.current_project.SaveProject()
213 |     
214 |     def get_project_name(self) -> Optional[str]:
215 |         """
216 |         Get the name of the current project.
217 |         
218 |         Returns:
219 |             Optional[str]: The project name, or None if no project is open.
220 |         """
221 |         if not self.current_project:
222 |             return None
223 |         
224 |         return self.current_project.GetName()
225 |     
226 |     def create_timeline(self, timeline_name: str) -> bool:
227 |         """
228 |         Create a new timeline with the given name.
229 |         
230 |         Args:
231 |             timeline_name (str): The name of the timeline to create.
232 |         
233 |         Returns:
234 |             bool: True if successful, False otherwise.
235 |         """
236 |         if not self.media_pool:
237 |             return False
238 |         
239 |         new_timeline = self.media_pool.CreateEmptyTimeline(timeline_name)
240 |         return new_timeline is not None
241 |     
242 |     def get_current_timeline(self):
243 |         """
244 |         Get the current timeline.
245 |         
246 |         Returns:
247 |             The current timeline object, or None if no timeline is open.
248 |         """
249 |         if not self.current_project:
250 |             return None
251 |         
252 |         return self.current_project.GetCurrentTimeline()
253 |     
254 |     def get_timeline_count(self) -> int:
255 |         """
256 |         Get the number of timelines in the current project.
257 |         
258 |         Returns:
259 |             int: The number of timelines, or 0 if no project is open.
260 |         """
261 |         if not self.current_project:
262 |             return 0
263 |         
264 |         return self.current_project.GetTimelineCount()
265 |     
266 |     def get_timeline_by_index(self, index: int):
267 |         """
268 |         Get a timeline by its index.
269 |         
270 |         Args:
271 |             index (int): The index of the timeline (1-based).
272 |         
273 |         Returns:
274 |             The timeline object, or None if the index is invalid.
275 |         """
276 |         if not self.current_project:
277 |             return None
278 |         
279 |         return self.current_project.GetTimelineByIndex(index)
280 |     
281 |     def set_current_timeline(self, timeline) -> bool:
282 |         """
283 |         Set the current timeline.
284 |         
285 |         Args:
286 |             timeline: The timeline object to set as current.
287 |         
288 |         Returns:
289 |             bool: True if successful, False otherwise.
290 |         """
291 |         if not self.current_project:
292 |             return False
293 |         
294 |         return self.current_project.SetCurrentTimeline(timeline)
295 |     
296 |     def get_mounted_volumes(self) -> List[str]:
297 |         """
298 |         Get a list of mounted volumes.
299 |         
300 |         Returns:
301 |             List[str]: A list of mounted volume paths.
302 |         """
303 |         if not self.media_storage:
304 |             return []
305 |         
306 |         return self.media_storage.GetMountedVolumes()
307 |     
308 |     def get_sub_folders(self, folder_path: str) -> List[str]:
309 |         """
310 |         Get a list of subfolders in the given folder path.
311 |         
312 |         Args:
313 |             folder_path (str): The path of the folder to get subfolders from.
314 |         
315 |         Returns:
316 |             List[str]: A list of subfolder paths.
317 |         """
318 |         if not self.media_storage:
319 |             return []
320 |         
321 |         return self.media_storage.GetSubFolders(folder_path)
322 |     
323 |     def get_files(self, folder_path: str) -> List[str]:
324 |         """
325 |         Get a list of files in the given folder path.
326 |         
327 |         Args:
328 |             folder_path (str): The path of the folder to get files from.
329 |         
330 |         Returns:
331 |             List[str]: A list of file paths.
332 |         """
333 |         if not self.media_storage:
334 |             return []
335 |         
336 |         return self.media_storage.GetFiles(folder_path)
337 |     
338 |     def add_items_to_media_pool(self, file_paths: List[str]) -> List[Any]:
339 |         """
340 |         Add items from the media storage to the media pool.
341 |         
342 |         Args:
343 |             file_paths (List[str]): A list of file paths to add.
344 |         
345 |         Returns:
346 |             List[Any]: A list of added media pool items.
347 |         """
348 |         if not self.media_storage or not self.media_pool:
349 |             return []
350 |         
351 |         return self.media_storage.AddItemsToMediaPool(file_paths)
352 |     
353 |     def get_root_folder(self):
354 |         """
355 |         Get the root folder of the media pool.
356 |         
357 |         Returns:
358 |             The root folder object, or None if no media pool is available.
359 |         """
360 |         if not self.media_pool:
361 |             return None
362 |         
363 |         return self.media_pool.GetRootFolder()
364 |     
365 |     def get_current_folder(self):
366 |         """
367 |         Get the current folder of the media pool.
368 |         
369 |         Returns:
370 |             The current folder object, or None if no media pool is available.
371 |         """
372 |         if not self.media_pool:
373 |             return None
374 |         
375 |         return self.media_pool.GetCurrentFolder()
376 |     
377 |     def add_sub_folder(self, parent_folder, folder_name: str):
378 |         """
379 |         Add a subfolder to the given parent folder.
380 |         
381 |         Args:
382 |             parent_folder: The parent folder object.
383 |             folder_name (str): The name of the subfolder to create.
384 |         
385 |         Returns:
386 |             The created subfolder object, or None if unsuccessful.
387 |         """
388 |         if not self.media_pool:
389 |             return None
390 |         
391 |         return self.media_pool.AddSubFolder(parent_folder, folder_name)
392 |     
393 |     def get_folder_clips(self, folder) -> List[Any]:
394 |         """
395 |         Get a list of clips in the given folder.
396 |         
397 |         Args:
398 |             folder: The folder object.
399 |         
400 |         Returns:
401 |             List[Any]: A list of media pool items in the folder.
402 |         """
403 |         if not folder:
404 |             return []
405 |         
406 |         return folder.GetClips()
407 |     
408 |     def get_folder_name(self, folder) -> Optional[str]:
409 |         """
410 |         Get the name of the given folder.
411 |         
412 |         Args:
413 |             folder: The folder object.
414 |         
415 |         Returns:
416 |             Optional[str]: The folder name, or None if the folder is invalid.
417 |         """
418 |         if not folder:
419 |             return None
420 |         
421 |         return folder.GetName()
422 |     
423 |     def get_folder_sub_folders(self, folder) -> List[Any]:
424 |         """
425 |         Get a list of subfolders in the given folder.
426 |         
427 |         Args:
428 |             folder: The folder object.
429 |         
430 |         Returns:
431 |             List[Any]: A list of subfolder objects.
432 |         """
433 |         if not folder:
434 |             return []
435 |         
436 |         return folder.GetSubFolders()
437 |     
438 |     def append_to_timeline(self, clips: List[Any]) -> bool:
439 |         """
440 |         Append the given clips to the current timeline.
441 |         
442 |         Args:
443 |             clips (List[Any]): A list of media pool items to append.
444 |         
445 |         Returns:
446 |             bool: True if successful, False otherwise.
447 |         """
448 |         if not self.media_pool:
449 |             return False
450 |         
451 |         return self.media_pool.AppendToTimeline(clips)
452 |     
453 |     def create_timeline_from_clips(self, timeline_name: str, clips: List[Any]):
454 |         """
455 |         Create a new timeline with the given name and append the given clips.
456 |         
457 |         Args:
458 |             timeline_name (str): The name of the timeline to create.
459 |             clips (List[Any]): A list of media pool items to append.
460 |         
461 |         Returns:
462 |             The created timeline object, or None if unsuccessful.
463 |         """
464 |         if not self.media_pool:
465 |             return None
466 |         
467 |         return self.media_pool.CreateTimelineFromClips(timeline_name, clips)
468 |     
469 |     def import_timeline_from_file(self, file_path: str):
470 |         """
471 |         Import a timeline from a file.
472 |         
473 |         Args:
474 |             file_path (str): The path of the file to import.
475 |         
476 |         Returns:
477 |             The imported timeline object, or None if unsuccessful.
478 |         """
479 |         if not self.media_pool:
480 |             return None
481 |         
482 |         return self.media_pool.ImportTimelineFromFile(file_path)
483 |     
484 |     def execute_lua(self, script: str) -> Any:
485 |         """
486 |         Execute a Lua script in DaVinci Resolve.
487 |         
488 |         Args:
489 |             script (str): The Lua script to execute.
490 |         
491 |         Returns:
492 |             Any: The result of the script execution.
493 |         """
494 |         if not self.fusion:
495 |             return None
496 |         
497 |         return self.fusion.Execute(script)
498 |     
499 |     def create_fusion_node(self, comp, node_type: str, inputs: Dict[str, Any] = None) -> Any:
500 |         """
501 |         Create a Fusion node in the given composition.
502 |         
503 |         Args:
504 |             comp: The Fusion composition to add the node to.
505 |             node_type (str): The type of node to create (e.g., 'Blur', 'ColorCorrector').
506 |             inputs (Dict[str, Any], optional): Dictionary of input parameters for the node.
507 |         
508 |         Returns:
509 |             Any: The created node, or None if unsuccessful.
510 |         """
511 |         if not self.fusion or not comp:
512 |             return None
513 |         
514 |         try:
515 |             # Create the node in the composition
516 |             node = comp.AddTool(node_type)
517 |             
518 |             # Set input parameters if provided
519 |             if inputs and node:
520 |                 for key, value in inputs.items():
521 |                     node[key] = value
522 |             
523 |             return node
524 |         except Exception as e:
525 |             print(f"Error creating Fusion node: {e}")
526 |             return None
527 |     
528 |     def get_current_comp(self) -> Any:
529 |         """
530 |         Get the current Fusion composition.
531 |         
532 |         Returns:
533 |             Any: The current Fusion composition, or None if not available.
534 |         """
535 |         if not self.fusion:
536 |             return None
537 |         
538 |         try:
539 |             return self.fusion.CurrentComp
540 |         except Exception as e:
541 |             print(f"Error getting current composition: {e}")
542 |             return None
543 | 
```

--------------------------------------------------------------------------------
/src/resolve_mcp/server.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | DaVinci Resolve MCP Server
  3 | 
  4 | This module implements a Model Context Protocol (MCP) server for DaVinci Resolve,
  5 | allowing AI assistants like Claude to interact with DaVinci Resolve through the MCP protocol.
  6 | """
  7 | 
  8 | import os
  9 | import sys
 10 | import json
 11 | import logging
 12 | import traceback
 13 | from typing import Dict, List, Any, Optional, Union, Tuple
 14 | 
 15 | # Print debugging information
 16 | print(f"Python version: {sys.version}", file=sys.stderr)
 17 | print(f"Python executable: {sys.executable}", file=sys.stderr)
 18 | print(f"Python path: {sys.path}", file=sys.stderr)
 19 | print(f"Current working directory: {os.getcwd()}", file=sys.stderr)
 20 | 
 21 | try:
 22 |     from mcp.server.fastmcp import FastMCP, Context, Image
 23 |     from pydantic import BaseModel
 24 |     print(f"Successfully imported MCP", file=sys.stderr)
 25 | except ImportError as e:
 26 |     print(f"Error importing MCP: {e}", file=sys.stderr)
 27 |     traceback.print_exc(file=sys.stderr)
 28 |     raise
 29 | 
 30 | try:
 31 |     # Try absolute import first (when installed as a package)
 32 |     from resolve_mcp.resolve_api import ResolveAPI
 33 |     print(f"Successfully imported ResolveAPI using absolute import", file=sys.stderr)
 34 | except ImportError as e:
 35 |     print(f"Error with absolute import: {e}", file=sys.stderr)
 36 |     try:
 37 |         # Fall back to relative import (when running from source)
 38 |         from .resolve_api import ResolveAPI
 39 |         print(f"Successfully imported ResolveAPI using relative import", file=sys.stderr)
 40 |     except ImportError as e2:
 41 |         print(f"Error with relative import: {e2}", file=sys.stderr)
 42 |         traceback.print_exc(file=sys.stderr)
 43 |         raise
 44 | 
 45 | # Configure logging
 46 | logging.basicConfig(
 47 |     level=logging.INFO,
 48 |     format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 49 | )
 50 | logger = logging.getLogger("resolve_mcp")
 51 | 
 52 | # Create the MCP server
 53 | mcp = FastMCP("DaVinci Resolve")
 54 | 
 55 | # Initialize the Resolve API
 56 | resolve_api = ResolveAPI()
 57 | 
 58 | # Check if connected to Resolve
 59 | if not resolve_api.is_connected():
 60 |     logger.error("Failed to connect to DaVinci Resolve. Make sure DaVinci Resolve is running.")
 61 | else:
 62 |     logger.info("Successfully connected to DaVinci Resolve.")
 63 | 
 64 | # Define resource and tool functions
 65 | 
 66 | # System Information Resources
 67 | 
 68 | @mcp.resource("system://status")
 69 | def get_system_status() -> str:
 70 |     """Get the current status of the DaVinci Resolve connection."""
 71 |     if resolve_api.is_connected():
 72 |         project_name = resolve_api.get_project_name() or "No project open"
 73 |         timeline = resolve_api.get_current_timeline()
 74 |         timeline_name = timeline.GetName() if timeline else "No timeline open"
 75 |         
 76 |         return f"""
 77 |         DaVinci Resolve Status:
 78 |         - Connection: Connected
 79 |         - Current Project: {project_name}
 80 |         - Current Timeline: {timeline_name}
 81 |         """
 82 |     else:
 83 |         return """
 84 |         DaVinci Resolve Status:
 85 |         - Connection: Not connected
 86 |         - Error: DaVinci Resolve is not running or not accessible
 87 |         """
 88 | 
 89 | # Project Resources
 90 | 
 91 | @mcp.resource("project://current")
 92 | def get_current_project() -> str:
 93 |     """Get information about the current project."""
 94 |     if not resolve_api.is_connected():
 95 |         return "Error: Not connected to DaVinci Resolve."
 96 |     
 97 |     project = resolve_api.get_current_project()
 98 |     if not project:
 99 |         return "No project is currently open."
100 |     
101 |     project_name = project.GetName()
102 |     timeline_count = project.GetTimelineCount()
103 |     
104 |     current_timeline = project.GetCurrentTimeline()
105 |     timeline_name = current_timeline.GetName() if current_timeline else "None"
106 |     
107 |     return f"""
108 |     Current Project: {project_name}
109 |     Timeline Count: {timeline_count}
110 |     Current Timeline: {timeline_name}
111 |     """
112 | 
113 | @mcp.resource("project://timelines")
114 | def get_project_timelines() -> str:
115 |     """Get a list of timelines in the current project."""
116 |     if not resolve_api.is_connected():
117 |         return "Error: Not connected to DaVinci Resolve."
118 |     
119 |     project = resolve_api.get_current_project()
120 |     if not project:
121 |         return "No project is currently open."
122 |     
123 |     timeline_count = project.GetTimelineCount()
124 |     if timeline_count == 0:
125 |         return "No timelines in the current project."
126 |     
127 |     timelines = []
128 |     for i in range(1, timeline_count + 1):
129 |         timeline = project.GetTimelineByIndex(i)
130 |         if timeline:
131 |             timelines.append(f"{i}. {timeline.GetName()}")
132 |     
133 |     return "\n".join(timelines)
134 | 
135 | @mcp.resource("timeline://current")
136 | def get_current_timeline() -> str:
137 |     """Get information about the current timeline."""
138 |     if not resolve_api.is_connected():
139 |         return "Error: Not connected to DaVinci Resolve."
140 |     
141 |     project = resolve_api.get_current_project()
142 |     if not project:
143 |         return "No project is currently open."
144 |     
145 |     timeline = project.GetCurrentTimeline()
146 |     if not timeline:
147 |         return "No timeline is currently open."
148 |     
149 |     timeline_name = timeline.GetName()
150 |     start_frame = timeline.GetStartFrame()
151 |     end_frame = timeline.GetEndFrame()
152 |     duration = end_frame - start_frame + 1
153 |     
154 |     video_track_count = timeline.GetTrackCount("video")
155 |     audio_track_count = timeline.GetTrackCount("audio")
156 |     subtitle_track_count = timeline.GetTrackCount("subtitle")
157 |     
158 |     return f"""
159 |     Timeline: {timeline_name}
160 |     Duration: {duration} frames ({start_frame} - {end_frame})
161 |     Video Tracks: {video_track_count}
162 |     Audio Tracks: {audio_track_count}
163 |     Subtitle Tracks: {subtitle_track_count}
164 |     """
165 | 
166 | @mcp.resource("mediapool://folders")
167 | def get_media_pool_folders() -> str:
168 |     """Get a list of folders in the media pool."""
169 |     if not resolve_api.is_connected():
170 |         return "Error: Not connected to DaVinci Resolve."
171 |     
172 |     media_pool = resolve_api.get_media_pool()
173 |     if not media_pool:
174 |         return "No media pool available."
175 |     
176 |     root_folder = media_pool.GetRootFolder()
177 |     if not root_folder:
178 |         return "No root folder available."
179 |     
180 |     def get_folder_structure(folder, indent=""):
181 |         result = []
182 |         name = folder.GetName()
183 |         result.append(f"{indent}- {name}")
184 |         
185 |         subfolders = folder.GetSubFolders()
186 |         for subfolder in subfolders:
187 |             result.extend(get_folder_structure(subfolder, indent + "  "))
188 |         
189 |         return result
190 |     
191 |     folder_structure = get_folder_structure(root_folder)
192 |     return "\n".join(folder_structure)
193 | 
194 | @mcp.resource("mediapool://current")
195 | def get_current_media_pool_folder() -> str:
196 |     """Get information about the current media pool folder."""
197 |     if not resolve_api.is_connected():
198 |         return "Error: Not connected to DaVinci Resolve."
199 |     
200 |     media_pool = resolve_api.get_media_pool()
201 |     if not media_pool:
202 |         return "No media pool available."
203 |     
204 |     current_folder = media_pool.GetCurrentFolder()
205 |     if not current_folder:
206 |         return "No current folder available."
207 |     
208 |     folder_name = current_folder.GetName()
209 |     clips = current_folder.GetClips()
210 |     clip_count = len(clips) if clips else 0
211 |     
212 |     clip_info = []
213 |     if clips:
214 |         for i, clip in enumerate(clips, 1):
215 |             if i > 10:  # Limit to 10 clips to avoid overwhelming response
216 |                 clip_info.append(f"... and {clip_count - 10} more clips")
217 |                 break
218 |             clip_info.append(f"{i}. {clip.GetName()}")
219 |     
220 |     return f"""
221 |     Current Folder: {folder_name}
222 |     Clip Count: {clip_count}
223 |     Clips:
224 |     {"No clips in this folder." if clip_count == 0 else "\n".join(clip_info)}
225 |     """
226 | 
227 | @mcp.resource("storage://volumes")
228 | def get_mounted_volumes() -> str:
229 |     """Get a list of mounted volumes in the media storage."""
230 |     if not resolve_api.is_connected():
231 |         return "Error: Not connected to DaVinci Resolve."
232 |     
233 |     media_storage = resolve_api.get_media_storage()
234 |     if not media_storage:
235 |         return "No media storage available."
236 |     
237 |     volumes = media_storage.GetMountedVolumes()
238 |     if not volumes:
239 |         return "No mounted volumes available."
240 |     
241 |     volume_list = []
242 |     for i, volume in enumerate(volumes, 1):
243 |         volume_list.append(f"{i}. {volume}")
244 |     
245 |     return "\n".join(volume_list)
246 | 
247 | # Tools for Project Management
248 | 
249 | @mcp.tool()
250 | def create_project(name: str) -> str:
251 |     """
252 |     Create a new DaVinci Resolve project.
253 |     
254 |     Args:
255 |         name: The name of the project to create
256 |     
257 |     Returns:
258 |         A message indicating success or failure
259 |     """
260 |     if not resolve_api.is_connected():
261 |         return "Error: Not connected to DaVinci Resolve."
262 |     
263 |     success = resolve_api.create_project(name)
264 |     if success:
265 |         return f"Successfully created project '{name}'."
266 |     else:
267 |         return f"Failed to create project '{name}'. The project may already exist."
268 | 
269 | @mcp.tool()
270 | def load_project(name: str) -> str:
271 |     """
272 |     Load an existing DaVinci Resolve project.
273 |     
274 |     Args:
275 |         name: The name of the project to load
276 |     
277 |     Returns:
278 |         A message indicating success or failure
279 |     """
280 |     if not resolve_api.is_connected():
281 |         return "Error: Not connected to DaVinci Resolve."
282 |     
283 |     success = resolve_api.load_project(name)
284 |     if success:
285 |         return f"Successfully loaded project '{name}'."
286 |     else:
287 |         return f"Failed to load project '{name}'. The project may not exist."
288 | 
289 | @mcp.tool()
290 | def save_project() -> str:
291 |     """
292 |     Save the current DaVinci Resolve project.
293 |     
294 |     Returns:
295 |         A message indicating success or failure
296 |     """
297 |     if not resolve_api.is_connected():
298 |         return "Error: Not connected to DaVinci Resolve."
299 |     
300 |     project = resolve_api.get_current_project()
301 |     if not project:
302 |         return "No project is currently open."
303 |     
304 |     success = resolve_api.save_project()
305 |     if success:
306 |         return f"Successfully saved project '{project.GetName()}'."
307 |     else:
308 |         return f"Failed to save project '{project.GetName()}'."
309 | 
310 | # Tools for Timeline Management
311 | 
312 | @mcp.tool()
313 | def create_timeline(name: str) -> str:
314 |     """
315 |     Create a new timeline in the current project.
316 |     
317 |     Args:
318 |         name: The name of the timeline to create
319 |     
320 |     Returns:
321 |         A message indicating success or failure
322 |     """
323 |     if not resolve_api.is_connected():
324 |         return "Error: Not connected to DaVinci Resolve."
325 |     
326 |     project = resolve_api.get_current_project()
327 |     if not project:
328 |         return "No project is currently open."
329 |     
330 |     media_pool = resolve_api.get_media_pool()
331 |     if not media_pool:
332 |         return "No media pool available."
333 |     
334 |     timeline = media_pool.CreateEmptyTimeline(name)
335 |     if timeline:
336 |         project.SetCurrentTimeline(timeline)
337 |         return f"Successfully created timeline '{name}'."
338 |     else:
339 |         return f"Failed to create timeline '{name}'. The timeline may already exist."
340 | 
341 | @mcp.tool()
342 | def set_current_timeline(index: int) -> str:
343 |     """
344 |     Set the current timeline by index.
345 |     
346 |     Args:
347 |         index: The index of the timeline (1-based)
348 |     
349 |     Returns:
350 |         A message indicating success or failure
351 |     """
352 |     if not resolve_api.is_connected():
353 |         return "Error: Not connected to DaVinci Resolve."
354 |     
355 |     project = resolve_api.get_current_project()
356 |     if not project:
357 |         return "No project is currently open."
358 |     
359 |     timeline_count = project.GetTimelineCount()
360 |     if index < 1 or index > timeline_count:
361 |         return f"Invalid timeline index. Valid range is 1-{timeline_count}."
362 |     
363 |     timeline = project.GetTimelineByIndex(index)
364 |     if not timeline:
365 |         return f"Failed to get timeline at index {index}."
366 |     
367 |     success = project.SetCurrentTimeline(timeline)
368 |     if success:
369 |         return f"Successfully set current timeline to '{timeline.GetName()}'."
370 |     else:
371 |         return f"Failed to set current timeline to '{timeline.GetName()}'."
372 | 
373 | # Tools for Media Management
374 | 
375 | @mcp.tool()
376 | def import_media(file_paths: List[str]) -> str:
377 |     """
378 |     Import media files into the current media pool folder.
379 |     
380 |     Args:
381 |         file_paths: A list of file paths to import
382 |     
383 |     Returns:
384 |         A message indicating success or failure
385 |     """
386 |     if not resolve_api.is_connected():
387 |         return "Error: Not connected to DaVinci Resolve."
388 |     
389 |     media_storage = resolve_api.get_media_storage()
390 |     if not media_storage:
391 |         return "No media storage available."
392 |     
393 |     media_pool = resolve_api.get_media_pool()
394 |     if not media_pool:
395 |         return "No media pool available."
396 |     
397 |     clips = media_storage.AddItemsToMediaPool(file_paths)
398 |     if clips:
399 |         return f"Successfully imported {len(clips)} media files."
400 |     else:
401 |         return "Failed to import media files. Check that the file paths are valid."
402 | 
403 | @mcp.tool()
404 | def create_folder(name: str) -> str:
405 |     """
406 |     Create a new folder in the current media pool folder.
407 |     
408 |     Args:
409 |         name: The name of the folder to create
410 |     
411 |     Returns:
412 |         A message indicating success or failure
413 |     """
414 |     if not resolve_api.is_connected():
415 |         return "Error: Not connected to DaVinci Resolve."
416 |     
417 |     media_pool = resolve_api.get_media_pool()
418 |     if not media_pool:
419 |         return "No media pool available."
420 |     
421 |     current_folder = media_pool.GetCurrentFolder()
422 |     if not current_folder:
423 |         return "No current folder available."
424 |     
425 |     new_folder = media_pool.AddSubFolder(current_folder, name)
426 |     if new_folder:
427 |         return f"Successfully created folder '{name}'."
428 |     else:
429 |         return f"Failed to create folder '{name}'. The folder may already exist."
430 | 
431 | @mcp.tool()
432 | def create_timeline_from_clips(name: str, clip_indices: List[int]) -> str:
433 |     """
434 |     Create a new timeline from clips in the current media pool folder.
435 |     
436 |     Args:
437 |         name: The name of the timeline to create
438 |         clip_indices: A list of clip indices (1-based) to include in the timeline
439 |     
440 |     Returns:
441 |         A message indicating success or failure
442 |     """
443 |     if not resolve_api.is_connected():
444 |         return "Error: Not connected to DaVinci Resolve."
445 |     
446 |     media_pool = resolve_api.get_media_pool()
447 |     if not media_pool:
448 |         return "No media pool available."
449 |     
450 |     current_folder = media_pool.GetCurrentFolder()
451 |     if not current_folder:
452 |         return "No current folder available."
453 |     
454 |     clips = current_folder.GetClips()
455 |     if not clips:
456 |         return "No clips in the current folder."
457 |     
458 |     clips_list = list(clips.values())
459 |     selected_clips = []
460 |     
461 |     for index in clip_indices:
462 |         if index < 1 or index > len(clips_list):
463 |             return f"Invalid clip index {index}. Valid range is 1-{len(clips_list)}."
464 |         selected_clips.append(clips_list[index - 1])
465 |     
466 |     timeline = media_pool.CreateTimelineFromClips(name, selected_clips)
467 |     if timeline:
468 |         return f"Successfully created timeline '{name}' with {len(selected_clips)} clips."
469 |     else:
470 |         return f"Failed to create timeline '{name}'."
471 | 
472 | # Tools for Fusion Integration
473 | 
474 | @mcp.tool()
475 | def add_fusion_comp_to_clip(timeline_index: int, track_type: str, track_index: int, item_index: int) -> str:
476 |     """
477 |     Add a Fusion composition to a clip in the timeline.
478 |     
479 |     Args:
480 |         timeline_index: The index of the timeline (1-based)
481 |         track_type: The type of track ("video", "audio", or "subtitle")
482 |         track_index: The index of the track (1-based)
483 |         item_index: The index of the item in the track (1-based)
484 |     
485 |     Returns:
486 |         A message indicating success or failure
487 |     """
488 |     if not resolve_api.is_connected():
489 |         return "Error: Not connected to DaVinci Resolve."
490 |     
491 |     project = resolve_api.get_current_project()
492 |     if not project:
493 |         return "No project is currently open."
494 |     
495 |     timeline_count = project.GetTimelineCount()
496 |     if timeline_index < 1 or timeline_index > timeline_count:
497 |         return f"Invalid timeline index. Valid range is 1-{timeline_count}."
498 |     
499 |     timeline = project.GetTimelineByIndex(timeline_index)
500 |     if not timeline:
501 |         return f"Failed to get timeline at index {timeline_index}."
502 |     
503 |     if track_type not in ["video", "audio", "subtitle"]:
504 |         return "Invalid track type. Valid types are 'video', 'audio', or 'subtitle'."
505 |     
506 |     track_count = timeline.GetTrackCount(track_type)
507 |     if track_index < 1 or track_index > track_count:
508 |         return f"Invalid track index. Valid range is 1-{track_count}."
509 |     
510 |     items = timeline.GetItemsInTrack(track_type, track_index)
511 |     if not items:
512 |         return f"No items in {track_type} track {track_index}."
513 |     
514 |     if item_index < 1 or item_index > len(items):
515 |         return f"Invalid item index. Valid range is 1-{len(items)}."
516 |     
517 |     item = items[item_index - 1]
518 |     fusion_comp = item.AddFusionComp()
519 |     
520 |     if fusion_comp:
521 |         # Switch to the Fusion page to edit the composition
522 |         resolve_api.open_page("fusion")
523 |         return f"Successfully added Fusion composition to {track_type} track {track_index}, item {item_index}."
524 |     else:
525 |         return f"Failed to add Fusion composition to {track_type} track {track_index}, item {item_index}."
526 | 
527 | @mcp.tool()
528 | def create_fusion_node(node_type: str, parameters: Dict[str, Any] = None) -> str:
529 |     """
530 |     Create a Fusion node in the current composition.
531 |     
532 |     Args:
533 |         node_type: The type of node to create (e.g., 'Blur', 'ColorCorrector', 'Text')
534 |         parameters: Optional dictionary of parameters to set on the node
535 |     
536 |     Returns:
537 |         A message indicating success or failure
538 |     """
539 |     if not resolve_api.is_connected():
540 |         return "Error: Not connected to DaVinci Resolve."
541 |     
542 |     # Get the current Fusion composition
543 |     comp = resolve_api.get_current_comp()
544 |     if not comp:
545 |         return "No active Fusion composition. Please open the Fusion page and select a composition first."
546 |     
547 |     # Create the node
548 |     node = resolve_api.create_fusion_node(comp, node_type, parameters)
549 |     if node:
550 |         return f"Successfully created {node_type} node in the Fusion composition."
551 |     else:
552 |         return f"Failed to create {node_type} node. Check that the node type is valid."
553 | 
554 | @mcp.tool()
555 | def create_fusion_node_chain(node_chain: List[Dict[str, Any]]) -> str:
556 |     """
557 |     Create a chain of connected Fusion nodes in the current composition.
558 |     
559 |     Args:
560 |         node_chain: A list of dictionaries, each containing:
561 |                     - 'type': The type of node to create
562 |                     - 'name': Optional name for the node
563 |                     - 'params': Optional dictionary of parameters to set on the node
564 |     
565 |     Returns:
566 |         A message indicating success or failure
567 |     """
568 |     if not resolve_api.is_connected():
569 |         return "Error: Not connected to DaVinci Resolve."
570 |     
571 |     # Get the current Fusion composition
572 |     comp = resolve_api.get_current_comp()
573 |     if not comp:
574 |         return "No active Fusion composition. Please open the Fusion page and select a composition first."
575 |     
576 |     if not node_chain or len(node_chain) == 0:
577 |         return "No nodes specified in the chain."
578 |     
579 |     try:
580 |         # Create the first node
581 |         prev_node = None
582 |         nodes_created = []
583 |         
584 |         for node_info in node_chain:
585 |             node_type = node_info.get('type')
586 |             if not node_type:
587 |                 continue
588 |                 
589 |             # Create the node
590 |             node = resolve_api.create_fusion_node(comp, node_type, node_info.get('params'))
591 |             
592 |             if not node:
593 |                 continue
594 |                 
595 |             # Set the node name if provided
596 |             if 'name' in node_info and node_info['name']:
597 |                 node.SetAttrs({'TOOLS_Name': node_info['name']})
598 |             
599 |             # Connect to previous node if this isn't the first node
600 |             if prev_node:
601 |                 # Connect the main output of the previous node to the main input of this node
602 |                 node.ConnectInput('Input', prev_node)
603 |             
604 |             prev_node = node
605 |             nodes_created.append(node_type)
606 |         
607 |         if not nodes_created:
608 |             return "Failed to create any nodes in the chain."
609 |             
610 |         return f"Successfully created node chain: {' -> '.join(nodes_created)}"
611 |     except Exception as e:
612 |         return f"Error creating node chain: {str(e)}"
613 | 
614 | # Tools for Page Navigation
615 | 
616 | @mcp.tool()
617 | def open_page(page_name: str) -> str:
618 |     """
619 |     Open a specific page in DaVinci Resolve.
620 |     
621 |     Args:
622 |         page_name: The name of the page to open (media, edit, fusion, color, fairlight, deliver)
623 |     
624 |     Returns:
625 |         A message indicating success or failure
626 |     """
627 |     if not resolve_api.is_connected():
628 |         return "Error: Not connected to DaVinci Resolve."
629 |     
630 |     valid_pages = ["media", "edit", "fusion", "color", "fairlight", "deliver"]
631 |     if page_name.lower() not in valid_pages:
632 |         return f"Invalid page name. Valid pages are: {', '.join(valid_pages)}."
633 |     
634 |     success = resolve_api.open_page(page_name.lower())
635 |     if success:
636 |         return f"Successfully opened the {page_name.capitalize()} page."
637 |     else:
638 |         return f"Failed to open the {page_name.capitalize()} page."
639 | 
640 | # Tools for Advanced Operations
641 | 
642 | @mcp.tool()
643 | def execute_python(code: str) -> str:
644 |     """
645 |     Execute arbitrary Python code in DaVinci Resolve.
646 |     
647 |     Args:
648 |         code: The Python code to execute
649 |     
650 |     Returns:
651 |         The result of the code execution
652 |     """
653 |     if not resolve_api.is_connected():
654 |         return "Error: Not connected to DaVinci Resolve."
655 |     
656 |     # Create a local namespace with access to the Resolve API
657 |     local_namespace = {
658 |         "resolve_api": resolve_api,
659 |         "resolve": resolve_api.resolve,
660 |         "fusion": resolve_api.fusion,
661 |         "project_manager": resolve_api.project_manager,
662 |         "current_project": resolve_api.current_project,
663 |         "media_storage": resolve_api.media_storage,
664 |         "media_pool": resolve_api.media_pool,
665 |     }
666 |     
667 |     try:
668 |         # Execute the code in the local namespace
669 |         exec_result = {}
670 |         exec(code, globals(), local_namespace)
671 |         
672 |         # Update the Resolve API objects with any changes made in the code
673 |         resolve_api.resolve = local_namespace.get("resolve", resolve_api.resolve)
674 |         resolve_api.fusion = local_namespace.get("fusion", resolve_api.fusion)
675 |         resolve_api.project_manager = local_namespace.get("project_manager", resolve_api.project_manager)
676 |         resolve_api.current_project = local_namespace.get("current_project", resolve_api.current_project)
677 |         resolve_api.media_storage = local_namespace.get("media_storage", resolve_api.media_storage)
678 |         resolve_api.media_pool = local_namespace.get("media_pool", resolve_api.media_pool)
679 |         
680 |         # Check for a result variable
681 |         if "result" in local_namespace:
682 |             result = local_namespace["result"]
683 |             return str(result)
684 |         
685 |         return "Code executed successfully."
686 |     except Exception as e:
687 |         return f"Error executing code: {str(e)}"
688 | 
689 | @mcp.tool()
690 | def execute_lua(script: str) -> str:
691 |     """
692 |     Execute a Lua script in DaVinci Resolve's Fusion.
693 |     
694 |     Args:
695 |         script: The Lua script to execute
696 |     
697 |     Returns:
698 |         The result of the script execution
699 |     """
700 |     if not resolve_api.is_connected():
701 |         return "Error: Not connected to DaVinci Resolve."
702 |     
703 |     if not resolve_api.fusion:
704 |         return "Fusion is not available."
705 |     
706 |     try:
707 |         result = resolve_api.execute_lua(script)
708 |         return str(result) if result is not None else "Script executed successfully."
709 |     except Exception as e:
710 |         return f"Error executing Lua script: {str(e)}"
711 | 
712 | # Main entry point function for the MCP server
713 | def main():
714 |     """Main entry point for the MCP server."""
715 |     return mcp.run()
716 | 
717 | if __name__ == "__main__":
718 |     main()
719 | 
```