#!/usr/bin/env python3
"""
DaVinci Resolve MCP Server Test Script
--------------------------------------
This script tests the improvements made to the DaVinci Resolve MCP Server
by systematically checking each enhanced feature.
Usage:
python test_improvements.py
Requirements:
- DaVinci Resolve must be running with a project open
- DaVinci Resolve MCP Server must be running (after restart)
- requests module (pip install requests)
"""
import json
import time
import sys
import requests
import logging
from typing import Dict, Any, List, Tuple, Optional
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("mcp_test_results.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Server configuration
SERVER_URL = "http://localhost:8000/api"
def send_request(tool_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""Send a request to the MCP server."""
try:
payload = {
"tool": tool_name,
"params": params
}
response = requests.post(SERVER_URL, json=payload)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
logger.error(f"Request error: {e}")
return {"success": False, "error": str(e)}
def test_server_connection() -> bool:
"""Test basic connection to DaVinci Resolve via the server."""
logger.info("Testing server connection...")
# Try switching to media page as a basic connectivity test
result = send_request("mcp_davinci_resolve_switch_page", {"page": "media"})
if result.get("success", False) or "content" in result:
logger.info("✅ Server connection successful")
return True
else:
logger.error(f"❌ Server connection failed: {result.get('error', 'Unknown error')}")
return False
def test_project_settings() -> bool:
"""Test setting project settings with different parameter types."""
logger.info("Testing project settings parameter handling...")
# Test with numeric value
logger.info("Testing numeric parameter...")
result1 = send_request("mcp_davinci_resolve_set_project_setting",
{"setting_name": "timelineFrameRate", "setting_value": 24})
# Test with string value
logger.info("Testing string parameter...")
result2 = send_request("mcp_davinci_resolve_set_project_setting",
{"setting_name": "timelineFrameRate", "setting_value": "24"})
# Test with float value
logger.info("Testing float parameter...")
result3 = send_request("mcp_davinci_resolve_set_project_setting",
{"setting_name": "colorScienceMode", "setting_value": 0})
success1 = "error" not in result1 or not result1.get("error")
success2 = "error" not in result2 or not result2.get("error")
success3 = "error" not in result3 or not result3.get("error")
if success1 and success2 and success3:
logger.info("✅ Project settings parameter handling is working")
return True
else:
logger.error(f"❌ Project settings parameter handling failed")
logger.error(f" Numeric test: {'✅ Passed' if success1 else '❌ Failed'}")
logger.error(f" String test: {'✅ Passed' if success2 else '❌ Failed'}")
logger.error(f" Float test: {'✅ Passed' if success3 else '❌ Failed'}")
return False
def test_color_page_operations() -> bool:
"""Test color page operations with automatic clip selection."""
logger.info("Testing color page operations...")
# Switch to color page
result1 = send_request("mcp_davinci_resolve_switch_page", {"page": "color"})
# Try adding a serial node (should use automatic clip selection)
time.sleep(1) # Give it a moment to switch pages
result2 = send_request("mcp_davinci_resolve_add_node",
{"node_type": "serial", "label": "AutoTest"})
# Try setting color wheel parameter
result3 = send_request("mcp_davinci_resolve_set_color_wheel_param",
{"wheel": "gain", "param": "red", "value": 0.1})
success1 = "error" not in result1 or not result1.get("error")
success2 = "error" not in result2 or not result2.get("error")
success3 = "error" not in result3 or not result3.get("error")
# Check if automatic clip selection messages are present
auto_select_working = False
if not success2:
error_msg = str(result2.get("error", ""))
# Even if it failed, check if we see the right error message
if "ensure_clip_selected" in error_msg or "Selected first clip" in error_msg:
logger.info("✅ Automatic clip selection is being attempted")
auto_select_working = True
if success1 and (success2 or auto_select_working):
logger.info("✅ Color page operations are working or properly reporting selection issues")
return True
else:
logger.error(f"❌ Color page operations test failed")
logger.error(f" Switch to color page: {'✅ Passed' if success1 else '❌ Failed'}")
logger.error(f" Add node: {'✅ Passed' if success2 else '❌ Failed but proper error' if auto_select_working else '❌ Failed'}")
logger.error(f" Set color wheel: {'✅ Passed' if success3 else '❌ Failed'}")
return False
def test_render_queue_operations() -> bool:
"""Test render queue operations with improved helpers."""
logger.info("Testing render queue operations...")
# Switch to deliver page
result1 = send_request("mcp_davinci_resolve_switch_page", {"page": "deliver"})
# Clear render queue first (known to be working)
result2 = send_request("mcp_davinci_resolve_clear_render_queue", {"random_string": "test"})
# Try adding a timeline to the render queue
time.sleep(1) # Give it a moment to switch pages
result3 = send_request("mcp_davinci_resolve_add_to_render_queue",
{"preset_name": "YouTube 1080p", "timeline_name": None, "use_in_out_range": False})
success1 = "error" not in result1 or not result1.get("error")
success2 = "error" not in result2 or not result2.get("error")
success3 = "error" not in result3 or not result3.get("error")
# Check if our helpers are being used
helper_working = False
if not success3:
error_msg = str(result3.get("error", ""))
# Even if it failed, check if we see messages from our helpers
if "ensure_render_settings" in error_msg or "validate_render_preset" in error_msg:
logger.info("✅ Render queue helpers are being used")
helper_working = True
if success1 and success2 and (success3 or helper_working):
logger.info("✅ Render queue operations are working or properly using helpers")
return True
else:
logger.error(f"❌ Render queue operations test failed")
logger.error(f" Switch to deliver page: {'✅ Passed' if success1 else '❌ Failed'}")
logger.error(f" Clear render queue: {'✅ Passed' if success2 else '❌ Failed'}")
logger.error(f" Add to render queue: {'✅ Passed' if success3 else '❌ Failed but helpers working' if helper_working else '❌ Failed'}")
return False
def test_error_handling_with_empty_timeline() -> bool:
"""Test how the color operations handle an empty timeline."""
logger.info("Testing error handling with empty timeline...")
# First create a new empty timeline
empty_timeline_name = f"Empty_Test_Timeline_{int(time.time())}"
create_result = send_request("mcp_davinci_resolve_create_timeline",
{"name": empty_timeline_name})
# Set it as current
set_result = send_request("mcp_davinci_resolve_set_current_timeline",
{"name": empty_timeline_name})
# Try to perform color operations on empty timeline
result1 = send_request("mcp_davinci_resolve_switch_page", {"page": "color"})
time.sleep(1) # Give it a moment to switch pages
# Try adding a node - this should fail but with proper error message
result2 = send_request("mcp_davinci_resolve_add_node",
{"node_type": "serial", "label": "EmptyTest"})
success1 = "error" not in result1 or not result1.get("error")
# Check for improved error handling
improved_error = False
expected_phrases = ["no clip", "empty timeline", "select clip", "ensure_clip_selected"]
if "error" in result2 and result2.get("error"):
error_msg = str(result2.get("error", "")).lower()
for phrase in expected_phrases:
if phrase in error_msg:
improved_error = True
logger.info(f"✅ Proper error handling detected: '{phrase}' found in error message")
break
# Clean up - delete the test timeline
delete_result = send_request("mcp_davinci_resolve_delete_timeline",
{"name": empty_timeline_name})
if success1 and improved_error:
logger.info("✅ Error handling for empty timeline is working properly")
return True
else:
logger.error("❌ Error handling for empty timeline test failed")
logger.error(f" Switch to color page: {'✅ Passed' if success1 else '❌ Failed'}")
logger.error(f" Improved error message: {'✅ Passed' if improved_error else '❌ Failed'}")
return False
def test_parameter_validation() -> bool:
"""Test parameter validation with various types and edge cases."""
logger.info("Testing parameter validation with various types...")
# Test with different types
tests = [
{"type": "integer", "value": 24, "setting": "timelineFrameRate"},
{"type": "string", "value": "24", "setting": "timelineFrameRate"},
{"type": "float", "value": 23.976, "setting": "timelineFrameRate"},
{"type": "string float", "value": "23.976", "setting": "timelineFrameRate"},
{"type": "boolean", "value": True, "setting": "timelineResolutionWidth"},
{"type": "string boolean", "value": "true", "setting": "timelineResolutionWidth"}
]
results = []
for test in tests:
logger.info(f"Testing {test['type']} parameter: {test['value']}")
result = send_request("mcp_davinci_resolve_set_project_setting",
{"setting_name": test['setting'], "setting_value": test['value']})
# Consider it a success if no error or if error doesn't mention type validation
success = "error" not in result or not result.get("error") or "type" not in str(result.get("error", "")).lower()
results.append(success)
if success:
logger.info(f"✅ {test['type']} parameter accepted")
else:
logger.error(f"❌ {test['type']} parameter rejected: {result.get('error', 'Unknown error')}")
# Final judgment based on how many tests passed
passed = sum(results)
total = len(results)
success_rate = passed / total
if success_rate >= 0.5: # At least half of the tests should pass
logger.info(f"✅ Parameter validation is working for {passed}/{total} types")
return True
else:
logger.error(f"❌ Parameter validation test failed for {total - passed}/{total} types")
return False
def print_test_summary(results: Dict[str, bool]) -> None:
"""Print a summary of all test results."""
logger.info("\n" + "=" * 50)
logger.info("TEST SUMMARY")
logger.info("=" * 50)
total = len(results)
passed = sum(results.values())
for test_name, result in results.items():
status = "✅ PASSED" if result else "❌ FAILED"
logger.info(f"{status} - {test_name}")
logger.info("-" * 50)
logger.info(f"Total Tests: {total}")
logger.info(f"Passed: {passed}")
logger.info(f"Failed: {total - passed}")
logger.info(f"Success Rate: {passed/total*100:.1f}%")
logger.info("=" * 50)
def main() -> None:
"""Run all tests and report results."""
logger.info("Starting DaVinci Resolve MCP Server tests")
logger.info("=" * 50)
# Store test results
results = {}
# Test server connection first
connection_result = test_server_connection()
results["Server Connection"] = connection_result
# Only continue with other tests if connection is successful
if connection_result:
# Test project settings
results["Project Settings"] = test_project_settings()
# Test color page operations
results["Color Page Operations"] = test_color_page_operations()
# Test render queue operations
results["Render Queue Operations"] = test_render_queue_operations()
# Test error handling with empty timeline
results["Empty Timeline Error Handling"] = test_error_handling_with_empty_timeline()
# Test parameter validation
results["Parameter Validation"] = test_parameter_validation()
else:
logger.error("Skipping remaining tests due to server connection failure")
results["Project Settings"] = False
results["Color Page Operations"] = False
results["Render Queue Operations"] = False
results["Empty Timeline Error Handling"] = False
results["Parameter Validation"] = False
# Print test summary
print_test_summary(results)
# Exit with appropriate status
if all(results.values()):
logger.info("All tests passed!")
sys.exit(0)
else:
logger.error("Some tests failed. Check the logs for details.")
sys.exit(1)
if __name__ == "__main__":
main()