#
tokens: 10947/50000 8/8 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual environments
venv/
env/
ENV/

# IDE specific files
.idea/
.vscode/
*.swp
*.swo

# OS specific files
.DS_Store
Thumbs.db

# Logs
*.log

```

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

```markdown
# DaVinci Resolve MCP Server

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.

## Features

- Two-way communication: Connect Claude AI to DaVinci Resolve through the MCP protocol
- Project management: Create, open, and manage DaVinci Resolve projects
- Timeline manipulation: Create, modify, and navigate timelines
- Media management: Import, organize, and manage media in the Media Pool
- Fusion integration: Create and modify Fusion compositions
- Scene inspection: Get detailed information about the current DaVinci Resolve project
- Code execution: Run arbitrary Python code in DaVinci Resolve from Claude

## Installation

### Prerequisites

- DaVinci Resolve Studio (version 17 or higher recommended)
- Python 3.8 or higher
- Claude Desktop (for AI integration)

### Setup

1. Clone this repository:
   ```
   git clone https://github.com/apvlv/davinci-resolve-mcp.git
   cd davinci-resolve-mcp
   ```

2. Install the required dependencies:
   ```
   pip install -r requirements.txt
   ```

3. Install the MCP server in Claude Desktop:
   ```
   mcp install src/resolve_mcp/server.py
   ```

   Alternatively, you can install with the editable flag for development:
   ```
   mcp install src/resolve_mcp/server.py --with-editable .
   ```

## Usage

### With Claude Desktop

1. Start DaVinci Resolve
2. In Claude Desktop, connect to the "DaVinci Resolve MCP" server
3. You can now interact with DaVinci Resolve through Claude

### With 5ire

[5ire](https://5ire.app/) is an open-source cross-platform desktop AI assistant and MCP client that's compatible with this server.

1. Install 5ire from [GitHub](https://github.com/nanbingxyz/5ire) or using Homebrew on macOS:
   ```
   brew tap brewforge/extras
   brew install --cask 5ire
   ```
2. Start DaVinci Resolve
3. In 5ire, add the DaVinci Resolve MCP server
4. Connect to the server using your preferred AI model (OpenAI, Claude, etc.)
5. You can now interact with DaVinci Resolve through 5ire

## Available Commands

### Resources (Information Retrieval)

- `project://current` - Get information about the current project
- `project://timelines` - Get a list of timelines in the current project
- `timeline://current` - Get information about the current timeline
- `mediapool://folders` - Get a list of folders in the media pool
- `mediapool://current` - Get information about the current media pool folder
- `storage://volumes` - Get a list of mounted volumes in the media storage
- `system://status` - Get the current status of the DaVinci Resolve connection

### Project Management

- `create_project(name)` - Create a new DaVinci Resolve project
- `load_project(name)` - Load an existing DaVinci Resolve project
- `save_project()` - Save the current DaVinci Resolve project

### Timeline Management

- `create_timeline(name)` - Create a new timeline in the current project
- `set_current_timeline(index)` - Set the current timeline by index (1-based)

### Media Management

- `import_media(file_paths)` - Import media files into the current media pool folder
- `create_folder(name)` - Create a new folder in the current media pool folder
- `create_timeline_from_clips(name, clip_indices)` - Create a new timeline from clips in the current media pool folder

### Fusion Integration

- `add_fusion_comp_to_clip(timeline_index, track_type, track_index, item_index)` - Add a Fusion composition to a clip in the timeline
- `create_fusion_node(node_type, parameters)` - Create a specific Fusion node in the current composition
- `create_fusion_node_chain(node_chain)` - Create a chain of connected Fusion nodes in the current composition

### Page Navigation

- `open_page(page_name)` - Open a specific page in DaVinci Resolve (media, edit, fusion, color, fairlight, deliver)

### Advanced Operations

- `execute_python(code)` - Execute arbitrary Python code in DaVinci Resolve
- `execute_lua(script)` - Execute a Lua script in DaVinci Resolve's Fusion

## Examples

- "Create a new project named 'My Documentary'"
- "Import all video files from the Downloads folder"
- "Create a new timeline with the selected clips"
- "Apply a Fusion effect to the selected clip"
- "Get information about the current project"
- "Switch to the Color page"
- "Save the current project"
- "Create a folder named 'Raw Footage' in the media pool"
- "Create a Blur node in the current Fusion composition"
- "Create a Text node with the content 'Hello World'"
- "Create a chain of nodes: MediaIn -> Blur -> ColorCorrector -> MediaOut"

## Technical Details

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.

## License

MIT

```

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

```
mcp>=0.3.0
pydantic>=2.0.0
typing-extensions>=4.0.0

```

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

```python
"""
DaVinci Resolve MCP Server - Model Context Protocol integration for DaVinci Resolve
"""

__version__ = "0.1.0"

```

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

```python
from setuptools import setup, find_packages

setup(
    name="resolve-mcp",
    version="0.1.0",
    description="Model Context Protocol (MCP) server for DaVinci Resolve",
    author="Your Name",
    author_email="[email protected]",
    packages=find_packages(where="src"),
    package_dir={"": "src"},
    install_requires=[
        "mcp>=0.3.0",
        "pydantic>=2.0.0",
        "typing-extensions>=4.0.0",
    ],
    entry_points={
        "console_scripts": [
            "resolve-mcp=resolve_mcp.server:main",
        ],
    },
    python_requires=">=3.8",
    classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Developers",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
    ],
)

```

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

```python
#!/usr/bin/env python
"""
Basic example of using the DaVinci Resolve MCP Server.

This script demonstrates how to manually connect to the MCP server
and execute some basic operations with DaVinci Resolve.
"""

import sys
import os
import time
import json
from typing import Dict, Any

# Add the parent directory to the path so we can import the resolve_mcp package
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from src.resolve_mcp.resolve_api import ResolveAPI

def main():
    """Main function to demonstrate basic usage of the DaVinci Resolve API."""
    print("Connecting to DaVinci Resolve...")
    resolve_api = ResolveAPI()
    
    if not resolve_api.is_connected():
        print("Failed to connect to DaVinci Resolve. Make sure DaVinci Resolve is running.")
        return
    
    print("Successfully connected to DaVinci Resolve.")
    
    # Get the current project
    project = resolve_api.get_current_project()
    if project:
        print(f"Current project: {project.GetName()}")
    else:
        print("No project is currently open.")
        
        # Create a new project
        project_name = f"Example Project {int(time.time())}"
        print(f"Creating a new project: {project_name}")
        if resolve_api.create_project(project_name):
            print(f"Successfully created project: {project_name}")
            project = resolve_api.get_current_project()
        else:
            print(f"Failed to create project: {project_name}")
            return
    
    # Create a new timeline
    timeline_name = f"Example Timeline {int(time.time())}"
    print(f"Creating a new timeline: {timeline_name}")
    if resolve_api.create_timeline(timeline_name):
        print(f"Successfully created timeline: {timeline_name}")
    else:
        print(f"Failed to create timeline: {timeline_name}")
    
    # Get the current timeline
    timeline = resolve_api.get_current_timeline()
    if timeline:
        print(f"Current timeline: {timeline.GetName()}")
        print(f"Timeline duration: {timeline.GetEndFrame() - timeline.GetStartFrame() + 1} frames")
        print(f"Video tracks: {timeline.GetTrackCount('video')}")
        print(f"Audio tracks: {timeline.GetTrackCount('audio')}")
    else:
        print("No timeline is currently open.")
    
    # Get media storage information
    print("\nMedia Storage Information:")
    volumes = resolve_api.get_mounted_volumes()
    if volumes:
        print("Mounted volumes:")
        for i, volume in enumerate(volumes, 1):
            print(f"  {i}. {volume}")
    else:
        print("No mounted volumes available.")
    
    # Get media pool information
    print("\nMedia Pool Information:")
    media_pool = resolve_api.get_media_pool()
    if media_pool:
        root_folder = media_pool.GetRootFolder()
        if root_folder:
            print(f"Root folder: {root_folder.GetName()}")
            
            # Print folder structure
            def print_folder_structure(folder, indent=""):
                name = folder.GetName()
                print(f"{indent}- {name}")
                
                subfolders = folder.GetSubFolders()
                for subfolder in subfolders:
                    print_folder_structure(subfolder, indent + "  ")
            
            print("Folder structure:")
            print_folder_structure(root_folder)
        else:
            print("No root folder available.")
    else:
        print("No media pool available.")
    
    # Save the project
    if resolve_api.save_project():
        print(f"Successfully saved project: {project.GetName()}")
    else:
        print(f"Failed to save project: {project.GetName()}")

if __name__ == "__main__":
    main()

```

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

```python
"""
DaVinci Resolve API connector module.

This module provides functions to connect to DaVinci Resolve's Python API
and interact with its various components.
"""

import sys
import os
import platform
from typing import Optional, Dict, List, Any, Union, Tuple

class ResolveAPI:
    """Class to handle connection and interaction with DaVinci Resolve API."""
    
    def __init__(self):
        """Initialize the ResolveAPI class and connect to DaVinci Resolve."""
        self.resolve = None
        self.fusion = None
        self.project_manager = None
        self.current_project = None
        self.media_storage = None
        self.media_pool = None
        
        self._connect_to_resolve()
    
    def _connect_to_resolve(self) -> None:
        """
        Connect to DaVinci Resolve based on the current operating system.
        
        This function adds the appropriate paths to sys.path and imports the
        DaVinciResolveScript module to establish a connection to Resolve.
        """
        # Determine the appropriate path based on the operating system
        if platform.system() == "Windows":
            resolve_script_dir = os.path.join(
                os.environ.get("PROGRAMDATA", "C:\\ProgramData"),
                "Blackmagic Design", "DaVinci Resolve", "Support", "Developer", "Scripting"
            )
            script_api_path = os.path.join(resolve_script_dir, "Modules")
            
            # Add the API directory to the system path
            if script_api_path not in sys.path:
                sys.path.append(script_api_path)
            
            # Import the DaVinciResolveScript module
            try:
                import DaVinciResolveScript as dvr_script
                self.resolve = dvr_script.scriptapp("Resolve")
            except ImportError:
                print("Error: Could not find DaVinciResolveScript module on Windows.")
                self.resolve = None
                
        elif platform.system() == "Darwin":
            # macOS path
            resolve_script_dir = "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting"
            script_api_path = os.path.join(resolve_script_dir, "Modules")
            
            # Add the API directory to the system path
            if script_api_path not in sys.path:
                sys.path.append(script_api_path)
            
            # Import the DaVinciResolveScript module
            try:
                import DaVinciResolveScript as dvr_script
                self.resolve = dvr_script.scriptapp("Resolve")
            except ImportError:
                # Try the user-specific path if the system-wide path fails
                user_script_api_path = os.path.join(
                    os.path.expanduser("~"),
                    "Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting/Modules"
                )
                if user_script_api_path not in sys.path:
                    sys.path.append(user_script_api_path)
                
                try:
                    import DaVinciResolveScript as dvr_script
                    self.resolve = dvr_script.scriptapp("Resolve")
                except ImportError:
                    print("Error: Could not find DaVinciResolveScript module on macOS.")
                    self.resolve = None
                    
        elif platform.system() == "Linux":
            # Linux path
            resolve_script_dir = "/opt/resolve/Developer/Scripting"
            script_api_path = os.path.join(resolve_script_dir, "Modules")
            
            # Add the API directory to the system path
            if script_api_path not in sys.path:
                sys.path.append(script_api_path)
            
            # Import the DaVinciResolveScript module
            try:
                import DaVinciResolveScript as dvr_script
                self.resolve = dvr_script.scriptapp("Resolve")
            except ImportError:
                print("Error: Could not find DaVinciResolveScript module on Linux.")
                self.resolve = None
        
        # Initialize other components if Resolve is connected
        if self.resolve:
            self.project_manager = self.resolve.GetProjectManager()
            self.current_project = self.project_manager.GetCurrentProject()
            self.media_storage = self.resolve.GetMediaStorage()
            self.fusion = self.resolve.Fusion()
            
            # Initialize media pool if a project is open
            if self.current_project:
                self.media_pool = self.current_project.GetMediaPool()
    
    def is_connected(self) -> bool:
        """Check if connected to DaVinci Resolve."""
        return self.resolve is not None
    
    def get_project_manager(self):
        """Get the project manager object."""
        return self.project_manager
    
    def get_current_project(self):
        """Get the current project object."""
        if self.project_manager:
            self.current_project = self.project_manager.GetCurrentProject()
        return self.current_project
    
    def get_media_storage(self):
        """Get the media storage object."""
        return self.media_storage
    
    def get_media_pool(self):
        """Get the media pool object for the current project."""
        if self.current_project:
            self.media_pool = self.current_project.GetMediaPool()
        return self.media_pool
    
    def get_fusion(self):
        """Get the Fusion object."""
        return self.fusion
    
    def open_page(self, page_name: str) -> bool:
        """
        Open a specific page in DaVinci Resolve.
        
        Args:
            page_name (str): The name of the page to open.
                             Valid values: "media", "edit", "fusion", "color", "fairlight", "deliver"
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.resolve:
            return False
        
        valid_pages = ["media", "edit", "fusion", "color", "fairlight", "deliver"]
        if page_name.lower() not in valid_pages:
            return False
        
        self.resolve.OpenPage(page_name.lower())
        return True
    
    def create_project(self, project_name: str) -> bool:
        """
        Create a new project with the given name.
        
        Args:
            project_name (str): The name of the project to create.
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.project_manager:
            return False
        
        new_project = self.project_manager.CreateProject(project_name)
        if new_project:
            self.current_project = new_project
            self.media_pool = self.current_project.GetMediaPool()
            return True
        
        return False
    
    def load_project(self, project_name: str) -> bool:
        """
        Load an existing project with the given name.
        
        Args:
            project_name (str): The name of the project to load.
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.project_manager:
            return False
        
        loaded_project = self.project_manager.LoadProject(project_name)
        if loaded_project:
            self.current_project = loaded_project
            self.media_pool = self.current_project.GetMediaPool()
            return True
        
        return False
    
    def save_project(self) -> bool:
        """
        Save the current project.
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.current_project:
            return False
        
        return self.current_project.SaveProject()
    
    def get_project_name(self) -> Optional[str]:
        """
        Get the name of the current project.
        
        Returns:
            Optional[str]: The project name, or None if no project is open.
        """
        if not self.current_project:
            return None
        
        return self.current_project.GetName()
    
    def create_timeline(self, timeline_name: str) -> bool:
        """
        Create a new timeline with the given name.
        
        Args:
            timeline_name (str): The name of the timeline to create.
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.media_pool:
            return False
        
        new_timeline = self.media_pool.CreateEmptyTimeline(timeline_name)
        return new_timeline is not None
    
    def get_current_timeline(self):
        """
        Get the current timeline.
        
        Returns:
            The current timeline object, or None if no timeline is open.
        """
        if not self.current_project:
            return None
        
        return self.current_project.GetCurrentTimeline()
    
    def get_timeline_count(self) -> int:
        """
        Get the number of timelines in the current project.
        
        Returns:
            int: The number of timelines, or 0 if no project is open.
        """
        if not self.current_project:
            return 0
        
        return self.current_project.GetTimelineCount()
    
    def get_timeline_by_index(self, index: int):
        """
        Get a timeline by its index.
        
        Args:
            index (int): The index of the timeline (1-based).
        
        Returns:
            The timeline object, or None if the index is invalid.
        """
        if not self.current_project:
            return None
        
        return self.current_project.GetTimelineByIndex(index)
    
    def set_current_timeline(self, timeline) -> bool:
        """
        Set the current timeline.
        
        Args:
            timeline: The timeline object to set as current.
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.current_project:
            return False
        
        return self.current_project.SetCurrentTimeline(timeline)
    
    def get_mounted_volumes(self) -> List[str]:
        """
        Get a list of mounted volumes.
        
        Returns:
            List[str]: A list of mounted volume paths.
        """
        if not self.media_storage:
            return []
        
        return self.media_storage.GetMountedVolumes()
    
    def get_sub_folders(self, folder_path: str) -> List[str]:
        """
        Get a list of subfolders in the given folder path.
        
        Args:
            folder_path (str): The path of the folder to get subfolders from.
        
        Returns:
            List[str]: A list of subfolder paths.
        """
        if not self.media_storage:
            return []
        
        return self.media_storage.GetSubFolders(folder_path)
    
    def get_files(self, folder_path: str) -> List[str]:
        """
        Get a list of files in the given folder path.
        
        Args:
            folder_path (str): The path of the folder to get files from.
        
        Returns:
            List[str]: A list of file paths.
        """
        if not self.media_storage:
            return []
        
        return self.media_storage.GetFiles(folder_path)
    
    def add_items_to_media_pool(self, file_paths: List[str]) -> List[Any]:
        """
        Add items from the media storage to the media pool.
        
        Args:
            file_paths (List[str]): A list of file paths to add.
        
        Returns:
            List[Any]: A list of added media pool items.
        """
        if not self.media_storage or not self.media_pool:
            return []
        
        return self.media_storage.AddItemsToMediaPool(file_paths)
    
    def get_root_folder(self):
        """
        Get the root folder of the media pool.
        
        Returns:
            The root folder object, or None if no media pool is available.
        """
        if not self.media_pool:
            return None
        
        return self.media_pool.GetRootFolder()
    
    def get_current_folder(self):
        """
        Get the current folder of the media pool.
        
        Returns:
            The current folder object, or None if no media pool is available.
        """
        if not self.media_pool:
            return None
        
        return self.media_pool.GetCurrentFolder()
    
    def add_sub_folder(self, parent_folder, folder_name: str):
        """
        Add a subfolder to the given parent folder.
        
        Args:
            parent_folder: The parent folder object.
            folder_name (str): The name of the subfolder to create.
        
        Returns:
            The created subfolder object, or None if unsuccessful.
        """
        if not self.media_pool:
            return None
        
        return self.media_pool.AddSubFolder(parent_folder, folder_name)
    
    def get_folder_clips(self, folder) -> List[Any]:
        """
        Get a list of clips in the given folder.
        
        Args:
            folder: The folder object.
        
        Returns:
            List[Any]: A list of media pool items in the folder.
        """
        if not folder:
            return []
        
        return folder.GetClips()
    
    def get_folder_name(self, folder) -> Optional[str]:
        """
        Get the name of the given folder.
        
        Args:
            folder: The folder object.
        
        Returns:
            Optional[str]: The folder name, or None if the folder is invalid.
        """
        if not folder:
            return None
        
        return folder.GetName()
    
    def get_folder_sub_folders(self, folder) -> List[Any]:
        """
        Get a list of subfolders in the given folder.
        
        Args:
            folder: The folder object.
        
        Returns:
            List[Any]: A list of subfolder objects.
        """
        if not folder:
            return []
        
        return folder.GetSubFolders()
    
    def append_to_timeline(self, clips: List[Any]) -> bool:
        """
        Append the given clips to the current timeline.
        
        Args:
            clips (List[Any]): A list of media pool items to append.
        
        Returns:
            bool: True if successful, False otherwise.
        """
        if not self.media_pool:
            return False
        
        return self.media_pool.AppendToTimeline(clips)
    
    def create_timeline_from_clips(self, timeline_name: str, clips: List[Any]):
        """
        Create a new timeline with the given name and append the given clips.
        
        Args:
            timeline_name (str): The name of the timeline to create.
            clips (List[Any]): A list of media pool items to append.
        
        Returns:
            The created timeline object, or None if unsuccessful.
        """
        if not self.media_pool:
            return None
        
        return self.media_pool.CreateTimelineFromClips(timeline_name, clips)
    
    def import_timeline_from_file(self, file_path: str):
        """
        Import a timeline from a file.
        
        Args:
            file_path (str): The path of the file to import.
        
        Returns:
            The imported timeline object, or None if unsuccessful.
        """
        if not self.media_pool:
            return None
        
        return self.media_pool.ImportTimelineFromFile(file_path)
    
    def execute_lua(self, script: str) -> Any:
        """
        Execute a Lua script in DaVinci Resolve.
        
        Args:
            script (str): The Lua script to execute.
        
        Returns:
            Any: The result of the script execution.
        """
        if not self.fusion:
            return None
        
        return self.fusion.Execute(script)
    
    def create_fusion_node(self, comp, node_type: str, inputs: Dict[str, Any] = None) -> Any:
        """
        Create a Fusion node in the given composition.
        
        Args:
            comp: The Fusion composition to add the node to.
            node_type (str): The type of node to create (e.g., 'Blur', 'ColorCorrector').
            inputs (Dict[str, Any], optional): Dictionary of input parameters for the node.
        
        Returns:
            Any: The created node, or None if unsuccessful.
        """
        if not self.fusion or not comp:
            return None
        
        try:
            # Create the node in the composition
            node = comp.AddTool(node_type)
            
            # Set input parameters if provided
            if inputs and node:
                for key, value in inputs.items():
                    node[key] = value
            
            return node
        except Exception as e:
            print(f"Error creating Fusion node: {e}")
            return None
    
    def get_current_comp(self) -> Any:
        """
        Get the current Fusion composition.
        
        Returns:
            Any: The current Fusion composition, or None if not available.
        """
        if not self.fusion:
            return None
        
        try:
            return self.fusion.CurrentComp
        except Exception as e:
            print(f"Error getting current composition: {e}")
            return None

```

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

```python
"""
DaVinci Resolve MCP Server

This module implements a Model Context Protocol (MCP) server for DaVinci Resolve,
allowing AI assistants like Claude to interact with DaVinci Resolve through the MCP protocol.
"""

import os
import sys
import json
import logging
import traceback
from typing import Dict, List, Any, Optional, Union, Tuple

# Print debugging information
print(f"Python version: {sys.version}", file=sys.stderr)
print(f"Python executable: {sys.executable}", file=sys.stderr)
print(f"Python path: {sys.path}", file=sys.stderr)
print(f"Current working directory: {os.getcwd()}", file=sys.stderr)

try:
    from mcp.server.fastmcp import FastMCP, Context, Image
    from pydantic import BaseModel
    print(f"Successfully imported MCP", file=sys.stderr)
except ImportError as e:
    print(f"Error importing MCP: {e}", file=sys.stderr)
    traceback.print_exc(file=sys.stderr)
    raise

try:
    # Try absolute import first (when installed as a package)
    from resolve_mcp.resolve_api import ResolveAPI
    print(f"Successfully imported ResolveAPI using absolute import", file=sys.stderr)
except ImportError as e:
    print(f"Error with absolute import: {e}", file=sys.stderr)
    try:
        # Fall back to relative import (when running from source)
        from .resolve_api import ResolveAPI
        print(f"Successfully imported ResolveAPI using relative import", file=sys.stderr)
    except ImportError as e2:
        print(f"Error with relative import: {e2}", file=sys.stderr)
        traceback.print_exc(file=sys.stderr)
        raise

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("resolve_mcp")

# Create the MCP server
mcp = FastMCP("DaVinci Resolve")

# Initialize the Resolve API
resolve_api = ResolveAPI()

# Check if connected to Resolve
if not resolve_api.is_connected():
    logger.error("Failed to connect to DaVinci Resolve. Make sure DaVinci Resolve is running.")
else:
    logger.info("Successfully connected to DaVinci Resolve.")

# Define resource and tool functions

# System Information Resources

@mcp.resource("system://status")
def get_system_status() -> str:
    """Get the current status of the DaVinci Resolve connection."""
    if resolve_api.is_connected():
        project_name = resolve_api.get_project_name() or "No project open"
        timeline = resolve_api.get_current_timeline()
        timeline_name = timeline.GetName() if timeline else "No timeline open"
        
        return f"""
        DaVinci Resolve Status:
        - Connection: Connected
        - Current Project: {project_name}
        - Current Timeline: {timeline_name}
        """
    else:
        return """
        DaVinci Resolve Status:
        - Connection: Not connected
        - Error: DaVinci Resolve is not running or not accessible
        """

# Project Resources

@mcp.resource("project://current")
def get_current_project() -> str:
    """Get information about the current project."""
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    project_name = project.GetName()
    timeline_count = project.GetTimelineCount()
    
    current_timeline = project.GetCurrentTimeline()
    timeline_name = current_timeline.GetName() if current_timeline else "None"
    
    return f"""
    Current Project: {project_name}
    Timeline Count: {timeline_count}
    Current Timeline: {timeline_name}
    """

@mcp.resource("project://timelines")
def get_project_timelines() -> str:
    """Get a list of timelines in the current project."""
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    timeline_count = project.GetTimelineCount()
    if timeline_count == 0:
        return "No timelines in the current project."
    
    timelines = []
    for i in range(1, timeline_count + 1):
        timeline = project.GetTimelineByIndex(i)
        if timeline:
            timelines.append(f"{i}. {timeline.GetName()}")
    
    return "\n".join(timelines)

@mcp.resource("timeline://current")
def get_current_timeline() -> str:
    """Get information about the current timeline."""
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    timeline = project.GetCurrentTimeline()
    if not timeline:
        return "No timeline is currently open."
    
    timeline_name = timeline.GetName()
    start_frame = timeline.GetStartFrame()
    end_frame = timeline.GetEndFrame()
    duration = end_frame - start_frame + 1
    
    video_track_count = timeline.GetTrackCount("video")
    audio_track_count = timeline.GetTrackCount("audio")
    subtitle_track_count = timeline.GetTrackCount("subtitle")
    
    return f"""
    Timeline: {timeline_name}
    Duration: {duration} frames ({start_frame} - {end_frame})
    Video Tracks: {video_track_count}
    Audio Tracks: {audio_track_count}
    Subtitle Tracks: {subtitle_track_count}
    """

@mcp.resource("mediapool://folders")
def get_media_pool_folders() -> str:
    """Get a list of folders in the media pool."""
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    media_pool = resolve_api.get_media_pool()
    if not media_pool:
        return "No media pool available."
    
    root_folder = media_pool.GetRootFolder()
    if not root_folder:
        return "No root folder available."
    
    def get_folder_structure(folder, indent=""):
        result = []
        name = folder.GetName()
        result.append(f"{indent}- {name}")
        
        subfolders = folder.GetSubFolders()
        for subfolder in subfolders:
            result.extend(get_folder_structure(subfolder, indent + "  "))
        
        return result
    
    folder_structure = get_folder_structure(root_folder)
    return "\n".join(folder_structure)

@mcp.resource("mediapool://current")
def get_current_media_pool_folder() -> str:
    """Get information about the current media pool folder."""
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    media_pool = resolve_api.get_media_pool()
    if not media_pool:
        return "No media pool available."
    
    current_folder = media_pool.GetCurrentFolder()
    if not current_folder:
        return "No current folder available."
    
    folder_name = current_folder.GetName()
    clips = current_folder.GetClips()
    clip_count = len(clips) if clips else 0
    
    clip_info = []
    if clips:
        for i, clip in enumerate(clips, 1):
            if i > 10:  # Limit to 10 clips to avoid overwhelming response
                clip_info.append(f"... and {clip_count - 10} more clips")
                break
            clip_info.append(f"{i}. {clip.GetName()}")
    
    return f"""
    Current Folder: {folder_name}
    Clip Count: {clip_count}
    Clips:
    {"No clips in this folder." if clip_count == 0 else "\n".join(clip_info)}
    """

@mcp.resource("storage://volumes")
def get_mounted_volumes() -> str:
    """Get a list of mounted volumes in the media storage."""
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    media_storage = resolve_api.get_media_storage()
    if not media_storage:
        return "No media storage available."
    
    volumes = media_storage.GetMountedVolumes()
    if not volumes:
        return "No mounted volumes available."
    
    volume_list = []
    for i, volume in enumerate(volumes, 1):
        volume_list.append(f"{i}. {volume}")
    
    return "\n".join(volume_list)

# Tools for Project Management

@mcp.tool()
def create_project(name: str) -> str:
    """
    Create a new DaVinci Resolve project.
    
    Args:
        name: The name of the project to create
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    success = resolve_api.create_project(name)
    if success:
        return f"Successfully created project '{name}'."
    else:
        return f"Failed to create project '{name}'. The project may already exist."

@mcp.tool()
def load_project(name: str) -> str:
    """
    Load an existing DaVinci Resolve project.
    
    Args:
        name: The name of the project to load
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    success = resolve_api.load_project(name)
    if success:
        return f"Successfully loaded project '{name}'."
    else:
        return f"Failed to load project '{name}'. The project may not exist."

@mcp.tool()
def save_project() -> str:
    """
    Save the current DaVinci Resolve project.
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    success = resolve_api.save_project()
    if success:
        return f"Successfully saved project '{project.GetName()}'."
    else:
        return f"Failed to save project '{project.GetName()}'."

# Tools for Timeline Management

@mcp.tool()
def create_timeline(name: str) -> str:
    """
    Create a new timeline in the current project.
    
    Args:
        name: The name of the timeline to create
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    media_pool = resolve_api.get_media_pool()
    if not media_pool:
        return "No media pool available."
    
    timeline = media_pool.CreateEmptyTimeline(name)
    if timeline:
        project.SetCurrentTimeline(timeline)
        return f"Successfully created timeline '{name}'."
    else:
        return f"Failed to create timeline '{name}'. The timeline may already exist."

@mcp.tool()
def set_current_timeline(index: int) -> str:
    """
    Set the current timeline by index.
    
    Args:
        index: The index of the timeline (1-based)
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    timeline_count = project.GetTimelineCount()
    if index < 1 or index > timeline_count:
        return f"Invalid timeline index. Valid range is 1-{timeline_count}."
    
    timeline = project.GetTimelineByIndex(index)
    if not timeline:
        return f"Failed to get timeline at index {index}."
    
    success = project.SetCurrentTimeline(timeline)
    if success:
        return f"Successfully set current timeline to '{timeline.GetName()}'."
    else:
        return f"Failed to set current timeline to '{timeline.GetName()}'."

# Tools for Media Management

@mcp.tool()
def import_media(file_paths: List[str]) -> str:
    """
    Import media files into the current media pool folder.
    
    Args:
        file_paths: A list of file paths to import
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    media_storage = resolve_api.get_media_storage()
    if not media_storage:
        return "No media storage available."
    
    media_pool = resolve_api.get_media_pool()
    if not media_pool:
        return "No media pool available."
    
    clips = media_storage.AddItemsToMediaPool(file_paths)
    if clips:
        return f"Successfully imported {len(clips)} media files."
    else:
        return "Failed to import media files. Check that the file paths are valid."

@mcp.tool()
def create_folder(name: str) -> str:
    """
    Create a new folder in the current media pool folder.
    
    Args:
        name: The name of the folder to create
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    media_pool = resolve_api.get_media_pool()
    if not media_pool:
        return "No media pool available."
    
    current_folder = media_pool.GetCurrentFolder()
    if not current_folder:
        return "No current folder available."
    
    new_folder = media_pool.AddSubFolder(current_folder, name)
    if new_folder:
        return f"Successfully created folder '{name}'."
    else:
        return f"Failed to create folder '{name}'. The folder may already exist."

@mcp.tool()
def create_timeline_from_clips(name: str, clip_indices: List[int]) -> str:
    """
    Create a new timeline from clips in the current media pool folder.
    
    Args:
        name: The name of the timeline to create
        clip_indices: A list of clip indices (1-based) to include in the timeline
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    media_pool = resolve_api.get_media_pool()
    if not media_pool:
        return "No media pool available."
    
    current_folder = media_pool.GetCurrentFolder()
    if not current_folder:
        return "No current folder available."
    
    clips = current_folder.GetClips()
    if not clips:
        return "No clips in the current folder."
    
    clips_list = list(clips.values())
    selected_clips = []
    
    for index in clip_indices:
        if index < 1 or index > len(clips_list):
            return f"Invalid clip index {index}. Valid range is 1-{len(clips_list)}."
        selected_clips.append(clips_list[index - 1])
    
    timeline = media_pool.CreateTimelineFromClips(name, selected_clips)
    if timeline:
        return f"Successfully created timeline '{name}' with {len(selected_clips)} clips."
    else:
        return f"Failed to create timeline '{name}'."

# Tools for Fusion Integration

@mcp.tool()
def add_fusion_comp_to_clip(timeline_index: int, track_type: str, track_index: int, item_index: int) -> str:
    """
    Add a Fusion composition to a clip in the timeline.
    
    Args:
        timeline_index: The index of the timeline (1-based)
        track_type: The type of track ("video", "audio", or "subtitle")
        track_index: The index of the track (1-based)
        item_index: The index of the item in the track (1-based)
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    project = resolve_api.get_current_project()
    if not project:
        return "No project is currently open."
    
    timeline_count = project.GetTimelineCount()
    if timeline_index < 1 or timeline_index > timeline_count:
        return f"Invalid timeline index. Valid range is 1-{timeline_count}."
    
    timeline = project.GetTimelineByIndex(timeline_index)
    if not timeline:
        return f"Failed to get timeline at index {timeline_index}."
    
    if track_type not in ["video", "audio", "subtitle"]:
        return "Invalid track type. Valid types are 'video', 'audio', or 'subtitle'."
    
    track_count = timeline.GetTrackCount(track_type)
    if track_index < 1 or track_index > track_count:
        return f"Invalid track index. Valid range is 1-{track_count}."
    
    items = timeline.GetItemsInTrack(track_type, track_index)
    if not items:
        return f"No items in {track_type} track {track_index}."
    
    if item_index < 1 or item_index > len(items):
        return f"Invalid item index. Valid range is 1-{len(items)}."
    
    item = items[item_index - 1]
    fusion_comp = item.AddFusionComp()
    
    if fusion_comp:
        # Switch to the Fusion page to edit the composition
        resolve_api.open_page("fusion")
        return f"Successfully added Fusion composition to {track_type} track {track_index}, item {item_index}."
    else:
        return f"Failed to add Fusion composition to {track_type} track {track_index}, item {item_index}."

@mcp.tool()
def create_fusion_node(node_type: str, parameters: Dict[str, Any] = None) -> str:
    """
    Create a Fusion node in the current composition.
    
    Args:
        node_type: The type of node to create (e.g., 'Blur', 'ColorCorrector', 'Text')
        parameters: Optional dictionary of parameters to set on the node
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    # Get the current Fusion composition
    comp = resolve_api.get_current_comp()
    if not comp:
        return "No active Fusion composition. Please open the Fusion page and select a composition first."
    
    # Create the node
    node = resolve_api.create_fusion_node(comp, node_type, parameters)
    if node:
        return f"Successfully created {node_type} node in the Fusion composition."
    else:
        return f"Failed to create {node_type} node. Check that the node type is valid."

@mcp.tool()
def create_fusion_node_chain(node_chain: List[Dict[str, Any]]) -> str:
    """
    Create a chain of connected Fusion nodes in the current composition.
    
    Args:
        node_chain: A list of dictionaries, each containing:
                    - 'type': The type of node to create
                    - 'name': Optional name for the node
                    - 'params': Optional dictionary of parameters to set on the node
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    # Get the current Fusion composition
    comp = resolve_api.get_current_comp()
    if not comp:
        return "No active Fusion composition. Please open the Fusion page and select a composition first."
    
    if not node_chain or len(node_chain) == 0:
        return "No nodes specified in the chain."
    
    try:
        # Create the first node
        prev_node = None
        nodes_created = []
        
        for node_info in node_chain:
            node_type = node_info.get('type')
            if not node_type:
                continue
                
            # Create the node
            node = resolve_api.create_fusion_node(comp, node_type, node_info.get('params'))
            
            if not node:
                continue
                
            # Set the node name if provided
            if 'name' in node_info and node_info['name']:
                node.SetAttrs({'TOOLS_Name': node_info['name']})
            
            # Connect to previous node if this isn't the first node
            if prev_node:
                # Connect the main output of the previous node to the main input of this node
                node.ConnectInput('Input', prev_node)
            
            prev_node = node
            nodes_created.append(node_type)
        
        if not nodes_created:
            return "Failed to create any nodes in the chain."
            
        return f"Successfully created node chain: {' -> '.join(nodes_created)}"
    except Exception as e:
        return f"Error creating node chain: {str(e)}"

# Tools for Page Navigation

@mcp.tool()
def open_page(page_name: str) -> str:
    """
    Open a specific page in DaVinci Resolve.
    
    Args:
        page_name: The name of the page to open (media, edit, fusion, color, fairlight, deliver)
    
    Returns:
        A message indicating success or failure
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    valid_pages = ["media", "edit", "fusion", "color", "fairlight", "deliver"]
    if page_name.lower() not in valid_pages:
        return f"Invalid page name. Valid pages are: {', '.join(valid_pages)}."
    
    success = resolve_api.open_page(page_name.lower())
    if success:
        return f"Successfully opened the {page_name.capitalize()} page."
    else:
        return f"Failed to open the {page_name.capitalize()} page."

# Tools for Advanced Operations

@mcp.tool()
def execute_python(code: str) -> str:
    """
    Execute arbitrary Python code in DaVinci Resolve.
    
    Args:
        code: The Python code to execute
    
    Returns:
        The result of the code execution
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    # Create a local namespace with access to the Resolve API
    local_namespace = {
        "resolve_api": resolve_api,
        "resolve": resolve_api.resolve,
        "fusion": resolve_api.fusion,
        "project_manager": resolve_api.project_manager,
        "current_project": resolve_api.current_project,
        "media_storage": resolve_api.media_storage,
        "media_pool": resolve_api.media_pool,
    }
    
    try:
        # Execute the code in the local namespace
        exec_result = {}
        exec(code, globals(), local_namespace)
        
        # Update the Resolve API objects with any changes made in the code
        resolve_api.resolve = local_namespace.get("resolve", resolve_api.resolve)
        resolve_api.fusion = local_namespace.get("fusion", resolve_api.fusion)
        resolve_api.project_manager = local_namespace.get("project_manager", resolve_api.project_manager)
        resolve_api.current_project = local_namespace.get("current_project", resolve_api.current_project)
        resolve_api.media_storage = local_namespace.get("media_storage", resolve_api.media_storage)
        resolve_api.media_pool = local_namespace.get("media_pool", resolve_api.media_pool)
        
        # Check for a result variable
        if "result" in local_namespace:
            result = local_namespace["result"]
            return str(result)
        
        return "Code executed successfully."
    except Exception as e:
        return f"Error executing code: {str(e)}"

@mcp.tool()
def execute_lua(script: str) -> str:
    """
    Execute a Lua script in DaVinci Resolve's Fusion.
    
    Args:
        script: The Lua script to execute
    
    Returns:
        The result of the script execution
    """
    if not resolve_api.is_connected():
        return "Error: Not connected to DaVinci Resolve."
    
    if not resolve_api.fusion:
        return "Fusion is not available."
    
    try:
        result = resolve_api.execute_lua(script)
        return str(result) if result is not None else "Script executed successfully."
    except Exception as e:
        return f"Error executing Lua script: {str(e)}"

# Main entry point function for the MCP server
def main():
    """Main entry point for the MCP server."""
    return mcp.run()

if __name__ == "__main__":
    main()

```