DaVinci MCP Professional v3.0.0
A modern, professional Model Context Protocol server for DaVinci Resolve integration
Loading...
Searching...
No Matches
server.py
Go to the documentation of this file.
1"""
2DaVinci Resolve MCP Server.
3
4A clean, modern implementation of the Model Context Protocol server
5for DaVinci Resolve integration.
6"""
7
8import logging
9from typing import Any
10
11import mcp.server.stdio
12import mcp.types as types
13from mcp.server import NotificationOptions, Server
14from mcp.server.models import InitializationOptions
15from pydantic import AnyUrl
16
17from . import __version__
18from .resolve_client import DaVinciResolveClient, DaVinciResolveError
19from .resources import get_all_resources
20from .tools import get_all_tools
21
22logger = logging.getLogger(__name__)
23
24
26 """
27 DaVinci Resolve MCP Server.
28
29 Provides a clean interface between MCP clients and DaVinci Resolve
30 through organized tools and resources.
31 """
32
33 def __init__(self) -> None:
34 self.server = Server("davinci-resolve-mcp")
37
38 def _register_handlers(self) -> None:
39 """Register MCP server handlers."""
40
41 @self.server.list_tools()
42 async def handle_list_tools() -> list[types.Tool]: # type: ignore[reportUnusedFunction]
43 return get_all_tools()
44
45 @self.server.call_tool()
46 async def handle_call_tool( # type: ignore[reportUnusedFunction]
47 name: str, arguments: dict[str, Any] | None = None
48 ) -> list[types.TextContent]:
49 if arguments is None:
50 arguments = {}
51 try:
52 if not self.resolve_client.is_connected():
53 self.resolve_client.connect()
54 result = await self._call_tool(name, arguments)
55 return [types.TextContent(type="text", text=str(result))]
56 except DaVinciResolveError as e:
57 error_msg = f"DaVinci Resolve error: {e}"
58 logger.exception(error_msg)
59 return [types.TextContent(type="text", text=error_msg)]
60 except Exception as e:
61 error_msg = f"Unexpected error: {e}"
62 logger.exception(error_msg)
63 return [types.TextContent(type="text", text=error_msg)]
64
65 @self.server.list_resources()
66 async def handle_list_resources() -> list[types.Resource]: # type: ignore[reportUnusedFunction]
67 return get_all_resources()
68
69 @self.server.read_resource()
70 async def handle_read_resource( # type: ignore[reportUnusedFunction]
71 uri: AnyUrl,
72 ) -> list[types.TextResourceContents]:
73 try:
74 if not self.resolve_client.is_connected():
75 self.resolve_client.connect()
76 result = await self._read_resource(str(uri))
77 return [
78 types.TextResourceContents(
79 uri=uri,
80 text=str(result),
81 mimeType="text/plain",
82 )
83 ]
84 except DaVinciResolveError as e:
85 error_msg = f"DaVinci Resolve error: {e}"
86 logger.exception(error_msg)
87 return [types.TextResourceContents(uri=uri, text=error_msg, mimeType="text/plain")]
88 except Exception as e:
89 error_msg = f"Unexpected error: {e}"
90 logger.exception(error_msg)
91 return [types.TextResourceContents(uri=uri, text=error_msg, mimeType="text/plain")]
92
93 async def _call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
94 """Dispatch a tool call to the resolve client."""
95 if name == "get_version":
96 return self.resolve_client.get_version()
97 elif name == "get_current_page":
98 return self.resolve_client.get_current_page()
99 elif name == "switch_page":
100 return self.resolve_client.switch_page(arguments.get("page", ""))
101 elif name == "list_projects":
102 return self.resolve_client.list_projects()
103 elif name == "get_current_project":
104 return self.resolve_client.get_current_project_name()
105 elif name == "open_project":
106 name_arg = arguments.get("name", "")
107 result = self.resolve_client.open_project(name_arg)
108 return (
109 f"Successfully opened project '{name_arg}'"
110 if result
111 else f"Failed to open project '{name_arg}'"
112 )
113 elif name == "create_project":
114 name_arg = arguments.get("name", "")
115 result = self.resolve_client.create_project(name_arg)
116 return (
117 f"Successfully created project '{name_arg}'"
118 if result
119 else f"Failed to create project '{name_arg}'"
120 )
121 elif name == "list_timelines":
122 return self.resolve_client.list_timelines()
123 elif name == "get_current_timeline":
124 return self.resolve_client.get_current_timeline_name()
125 elif name == "create_timeline":
126 name_arg = arguments.get("name", "")
127 result = self.resolve_client.create_timeline(name_arg)
128 return (
129 f"Successfully created timeline '{name_arg}'"
130 if result
131 else f"Failed to create timeline '{name_arg}'"
132 )
133 elif name == "switch_timeline":
134 name_arg = arguments.get("name", "")
135 result = self.resolve_client.switch_timeline(name_arg)
136 return (
137 f"Successfully switched to timeline '{name_arg}'"
138 if result
139 else f"Failed to switch to timeline '{name_arg}'"
140 )
141 elif name == "list_media_clips":
142 return self.resolve_client.list_media_clips()
143 elif name == "import_media":
144 file_path = arguments.get("file_path", "")
145 result = self.resolve_client.import_media(file_path)
146 return (
147 f"Successfully imported media '{file_path}'"
148 if result
149 else f"Failed to import media '{file_path}'"
150 )
151 else:
152 raise ValueError(f"Unknown tool: {name}")
153
154 async def _read_resource(self, uri: str) -> Any:
155 """Dispatch a resource read to the resolve client."""
156 if uri == "resolve://version":
157 return self.resolve_client.get_version()
158 elif uri == "resolve://current-page":
159 return self.resolve_client.get_current_page()
160 elif uri == "resolve://projects":
161 return self.resolve_client.list_projects()
162 elif uri == "resolve://current-project":
163 name = self.resolve_client.get_current_project_name()
164 return name if name else "No project open"
165 elif uri == "resolve://timelines":
166 return self.resolve_client.list_timelines()
167 elif uri == "resolve://current-timeline":
168 name = self.resolve_client.get_current_timeline_name()
169 return name if name else "No timeline active"
170 elif uri == "resolve://media-clips":
171 return self.resolve_client.list_media_clips()
172 else:
173 raise ValueError(f"Unknown resource: {uri}")
174
175 async def run(self) -> None:
176 """Run the MCP server."""
177 logger.info("Starting DaVinci Resolve MCP Server...")
178
179 async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
180 await self.server.run(
181 read_stream,
182 write_stream,
183 InitializationOptions(
184 server_name="davinci-mcp-professional",
185 server_version=__version__,
186 capabilities=self.server.get_capabilities(
187 notification_options=NotificationOptions(),
188 experimental_capabilities={},
189 ),
190 ),
191 )
Any _read_resource(self, str uri)
Definition server.py:154
Any _call_tool(self, str name, dict[str, Any] arguments)
Definition server.py:93