touchdesigner-mcp-server 0.4.0-alpha.0 → 0.4.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +4 -4
- package/README.md +4 -3
- package/dist/cli.js +0 -0
- package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
- package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
- package/dist/index.js +0 -0
- package/dist/server/touchDesignerServer.js +1 -1
- package/package.json +14 -12
- package/td/genHandlers.js +0 -47
- package/td/import_modules.py +0 -52
- package/td/mcp_webserver_base.tox +0 -0
- package/td/modules/mcp/controllers/__init__.py +0 -9
- package/td/modules/mcp/controllers/api_controller.py +0 -637
- package/td/modules/mcp/controllers/generated_handlers.py +0 -365
- package/td/modules/mcp/controllers/openapi_router.py +0 -265
- package/td/modules/mcp/services/__init__.py +0 -8
- package/td/modules/mcp/services/api_service.py +0 -555
- package/td/modules/mcp_webserver_script.py +0 -134
- package/td/modules/td_server/.dockerignore +0 -72
- package/td/modules/td_server/.openapi-generator/FILES +0 -55
- package/td/modules/td_server/.openapi-generator/VERSION +0 -1
- package/td/modules/td_server/.openapi-generator-ignore +0 -23
- package/td/modules/td_server/.travis.yml +0 -14
- package/td/modules/td_server/Dockerfile +0 -16
- package/td/modules/td_server/README.md +0 -49
- package/td/modules/td_server/git_push.sh +0 -57
- package/td/modules/td_server/openapi_server/__init__.py +0 -0
- package/td/modules/td_server/openapi_server/__main__.py +0 -19
- package/td/modules/td_server/openapi_server/controllers/__init__.py +0 -0
- package/td/modules/td_server/openapi_server/controllers/default_controller.py +0 -162
- package/td/modules/td_server/openapi_server/controllers/security_controller.py +0 -2
- package/td/modules/td_server/openapi_server/encoder.py +0 -19
- package/td/modules/td_server/openapi_server/models/__init__.py +0 -33
- package/td/modules/td_server/openapi_server/models/base_model.py +0 -68
- package/td/modules/td_server/openapi_server/models/create_node200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/create_node200_response_data.py +0 -63
- package/td/modules/td_server/openapi_server/models/create_node_request.py +0 -123
- package/td/modules/td_server/openapi_server/models/delete_node200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/delete_node200_response_data.py +0 -91
- package/td/modules/td_server/openapi_server/models/exec_node_method200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/exec_node_method200_response_data.py +0 -65
- package/td/modules/td_server/openapi_server/models/exec_node_method_request.py +0 -153
- package/td/modules/td_server/openapi_server/models/exec_node_method_request_args_inner.py +0 -34
- package/td/modules/td_server/openapi_server/models/exec_python_script200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/exec_python_script200_response_data.py +0 -65
- package/td/modules/td_server/openapi_server/models/exec_python_script200_response_data_result.py +0 -63
- package/td/modules/td_server/openapi_server/models/exec_python_script_request.py +0 -65
- package/td/modules/td_server/openapi_server/models/get_node_detail200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/get_nodes200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/get_nodes200_response_data.py +0 -65
- package/td/modules/td_server/openapi_server/models/get_td_info200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/get_td_info200_response_data.py +0 -155
- package/td/modules/td_server/openapi_server/models/get_td_python_class_details200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/get_td_python_classes200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/get_td_python_classes200_response_data.py +0 -63
- package/td/modules/td_server/openapi_server/models/td_node.py +0 -175
- package/td/modules/td_server/openapi_server/models/td_node_family_type.py +0 -44
- package/td/modules/td_server/openapi_server/models/td_python_class_details.py +0 -191
- package/td/modules/td_server/openapi_server/models/td_python_class_info.py +0 -127
- package/td/modules/td_server/openapi_server/models/td_python_method_info.py +0 -121
- package/td/modules/td_server/openapi_server/models/td_python_property_info.py +0 -123
- package/td/modules/td_server/openapi_server/models/update_node200_response.py +0 -125
- package/td/modules/td_server/openapi_server/models/update_node200_response_data.py +0 -149
- package/td/modules/td_server/openapi_server/models/update_node200_response_data_failed_inner.py +0 -91
- package/td/modules/td_server/openapi_server/models/update_node_request.py +0 -93
- package/td/modules/td_server/openapi_server/openapi/openapi.yaml +0 -975
- package/td/modules/td_server/openapi_server/test/__init__.py +0 -16
- package/td/modules/td_server/openapi_server/test/test_default_controller.py +0 -201
- package/td/modules/td_server/openapi_server/typing_utils.py +0 -30
- package/td/modules/td_server/openapi_server/util.py +0 -147
- package/td/modules/td_server/requirements.txt +0 -13
- package/td/modules/td_server/setup.py +0 -37
- package/td/modules/td_server/test-requirements.txt +0 -4
- package/td/modules/td_server/tox.ini +0 -11
- package/td/modules/utils/config.py +0 -7
- package/td/modules/utils/error_handling.py +0 -104
- package/td/modules/utils/logging.py +0 -23
- package/td/modules/utils/result.py +0 -40
- package/td/modules/utils/serialization.py +0 -57
- package/td/modules/utils/types.py +0 -33
- package/td/modules/utils/utils_logging.py +0 -60
- package/td/templates/mcp/api_controller_handlers.mustache +0 -63
|
@@ -1,637 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
OpenAPI schema based API controller for TouchDesigner MCP Web Server
|
|
3
|
-
|
|
4
|
-
This controller uses the OpenAPIRouter to route requests based on the OpenAPI schema,
|
|
5
|
-
and converts between API models and internal data structures.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import traceback
|
|
10
|
-
from typing import Any, Dict, List, Optional, Protocol, Tuple
|
|
11
|
-
|
|
12
|
-
from mcp.controllers.generated_handlers import *
|
|
13
|
-
from mcp.controllers.openapi_router import OpenAPIRouter
|
|
14
|
-
from utils.error_handling import ErrorCategory
|
|
15
|
-
from utils.logging import log_message
|
|
16
|
-
from utils.result import error_result
|
|
17
|
-
from utils.serialization import safe_serialize
|
|
18
|
-
from utils.types import LogLevel, Result
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
from td_server.openapi_server.models.create_node200_response import (
|
|
22
|
-
CreateNode200Response,
|
|
23
|
-
)
|
|
24
|
-
from td_server.openapi_server.models.delete_node200_response import (
|
|
25
|
-
DeleteNode200Response,
|
|
26
|
-
)
|
|
27
|
-
from td_server.openapi_server.models.exec_node_method200_response import (
|
|
28
|
-
ExecNodeMethod200Response,
|
|
29
|
-
)
|
|
30
|
-
from td_server.openapi_server.models.exec_python_script200_response import (
|
|
31
|
-
ExecPythonScript200Response,
|
|
32
|
-
)
|
|
33
|
-
from td_server.openapi_server.models.get_node_detail200_response import (
|
|
34
|
-
GetNodeDetail200Response,
|
|
35
|
-
)
|
|
36
|
-
from td_server.openapi_server.models.get_nodes200_response import (
|
|
37
|
-
GetNodes200Response,
|
|
38
|
-
)
|
|
39
|
-
from td_server.openapi_server.models.get_td_info200_response import (
|
|
40
|
-
GetTdInfo200Response,
|
|
41
|
-
)
|
|
42
|
-
from td_server.openapi_server.models.update_node200_response import (
|
|
43
|
-
UpdateNode200Response,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
log_message("OpenAPI response models imported successfully", LogLevel.DEBUG)
|
|
47
|
-
except ImportError as e:
|
|
48
|
-
log_message(
|
|
49
|
-
f"OpenAPI models import failed, using raw dictionaries: {e}", LogLevel.WARNING
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class ApiServiceProtocol(Protocol):
|
|
54
|
-
"""Protocol defining the API service interface"""
|
|
55
|
-
|
|
56
|
-
def get_td_info(self) -> Result: ...
|
|
57
|
-
|
|
58
|
-
def get_nodes(self, parent_path: str, pattern: Optional[str] = None, include_properties: bool = False) -> Result: ...
|
|
59
|
-
|
|
60
|
-
def create_node(
|
|
61
|
-
self,
|
|
62
|
-
parent_path: str,
|
|
63
|
-
node_type: str,
|
|
64
|
-
node_name: Optional[str] = None,
|
|
65
|
-
parameters: Optional[Dict[str, Any]] = None,
|
|
66
|
-
) -> Result: ...
|
|
67
|
-
|
|
68
|
-
def delete_node(self, node_path: str) -> Result: ...
|
|
69
|
-
|
|
70
|
-
def get_node_detail(self, node_path: str) -> Result: ...
|
|
71
|
-
|
|
72
|
-
def update_node(self, node_path: str, properties: Dict[str, Any]) -> Result: ...
|
|
73
|
-
|
|
74
|
-
def exec_script(self, script: str) -> Result: ...
|
|
75
|
-
|
|
76
|
-
def get_python_classes(self) -> Result: ...
|
|
77
|
-
|
|
78
|
-
def get_python_class_details(self, class_name: str) -> Result: ...
|
|
79
|
-
|
|
80
|
-
def call_node_method(
|
|
81
|
-
self,
|
|
82
|
-
node_path: str,
|
|
83
|
-
method_name: str,
|
|
84
|
-
args: List[Any] = None,
|
|
85
|
-
kwargs: Dict[str, Any] = None,
|
|
86
|
-
) -> Result: ...
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class RequestProcessor:
|
|
90
|
-
"""
|
|
91
|
-
Responsible for processing and normalizing HTTP requests from different sources
|
|
92
|
-
|
|
93
|
-
This class helps achieve separation of concerns by isolating request processing logic
|
|
94
|
-
from the controller class, improving maintainability and testability.
|
|
95
|
-
"""
|
|
96
|
-
|
|
97
|
-
@staticmethod
|
|
98
|
-
def normalize_request(
|
|
99
|
-
request: Dict[str, Any],
|
|
100
|
-
) -> Tuple[str, str, Dict[str, Any], str]:
|
|
101
|
-
"""
|
|
102
|
-
Normalize request object to handle different request formats
|
|
103
|
-
|
|
104
|
-
Args:
|
|
105
|
-
request: Request object that might be in different formats
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
Tuple containing (method, path, query_params, body)
|
|
109
|
-
"""
|
|
110
|
-
method = ""
|
|
111
|
-
path = ""
|
|
112
|
-
query_params = {}
|
|
113
|
-
body = ""
|
|
114
|
-
|
|
115
|
-
try:
|
|
116
|
-
method = RequestProcessor._extract_method(request)
|
|
117
|
-
|
|
118
|
-
path, uri_query_params = RequestProcessor._extract_path_and_query(request)
|
|
119
|
-
query_params.update(uri_query_params)
|
|
120
|
-
|
|
121
|
-
if "query" in request and isinstance(request["query"], dict):
|
|
122
|
-
query_params.update(request["query"])
|
|
123
|
-
|
|
124
|
-
if "pars" in request and isinstance(request["pars"], dict):
|
|
125
|
-
log_message(
|
|
126
|
-
f"Found 'pars' in request: {request['pars']}", LogLevel.DEBUG
|
|
127
|
-
)
|
|
128
|
-
query_params.update(request["pars"])
|
|
129
|
-
|
|
130
|
-
body = RequestProcessor._extract_body(request)
|
|
131
|
-
|
|
132
|
-
except Exception as e:
|
|
133
|
-
log_message(f"Error during request normalization: {str(e)}", LogLevel.ERROR)
|
|
134
|
-
log_message(traceback.format_exc(), LogLevel.DEBUG)
|
|
135
|
-
|
|
136
|
-
return method, path, query_params, body
|
|
137
|
-
|
|
138
|
-
@staticmethod
|
|
139
|
-
def _extract_method(request: Dict[str, Any]) -> str:
|
|
140
|
-
"""Extract HTTP method from request"""
|
|
141
|
-
if "method" in request:
|
|
142
|
-
if isinstance(request["method"], str):
|
|
143
|
-
return request["method"].upper()
|
|
144
|
-
return ""
|
|
145
|
-
|
|
146
|
-
@staticmethod
|
|
147
|
-
def _extract_path_and_query(request: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]:
|
|
148
|
-
"""Extract path and query parameters from request"""
|
|
149
|
-
path = ""
|
|
150
|
-
query_params = {}
|
|
151
|
-
|
|
152
|
-
uri = request.get("uri", {})
|
|
153
|
-
|
|
154
|
-
if isinstance(uri, dict):
|
|
155
|
-
path = uri.get("path", "")
|
|
156
|
-
uri_query = uri.get("query", {})
|
|
157
|
-
if isinstance(uri_query, dict):
|
|
158
|
-
query_params.update(uri_query)
|
|
159
|
-
elif isinstance(uri, str):
|
|
160
|
-
path = uri
|
|
161
|
-
|
|
162
|
-
return path, query_params
|
|
163
|
-
|
|
164
|
-
@staticmethod
|
|
165
|
-
def _extract_body(request: Dict[str, Any]) -> str:
|
|
166
|
-
"""Extract body content from request"""
|
|
167
|
-
body = ""
|
|
168
|
-
|
|
169
|
-
body_content = request.get("body", "")
|
|
170
|
-
|
|
171
|
-
if isinstance(body_content, (str, bytes)):
|
|
172
|
-
body = (
|
|
173
|
-
body_content
|
|
174
|
-
if isinstance(body_content, str)
|
|
175
|
-
else body_content.decode("utf-8", errors="replace")
|
|
176
|
-
)
|
|
177
|
-
elif isinstance(body_content, dict):
|
|
178
|
-
body = json.dumps(body_content)
|
|
179
|
-
|
|
180
|
-
if not body and "data" in request:
|
|
181
|
-
data = request.get("data", "")
|
|
182
|
-
if isinstance(data, bytes):
|
|
183
|
-
body = data.decode("utf-8", errors="replace") if data else ""
|
|
184
|
-
elif isinstance(data, str):
|
|
185
|
-
body = data
|
|
186
|
-
elif isinstance(data, dict):
|
|
187
|
-
body = json.dumps(data)
|
|
188
|
-
|
|
189
|
-
return body
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
class IController(Protocol):
|
|
193
|
-
"""
|
|
194
|
-
Controller interface for handling HTTP requests
|
|
195
|
-
|
|
196
|
-
All controllers should implement this interface to ensure consistency across
|
|
197
|
-
different controller implementations. This enforces a unified approach to
|
|
198
|
-
request handling throughout the application.
|
|
199
|
-
"""
|
|
200
|
-
|
|
201
|
-
def onHTTPRequest(
|
|
202
|
-
self, webServerDAT: Any, request: Dict[str, Any], response: Dict[str, Any]
|
|
203
|
-
) -> Dict[str, Any]:
|
|
204
|
-
"""
|
|
205
|
-
Process an HTTP request from TouchDesigner WebServerDAT
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
webServerDAT: Reference to the WebServerDAT object
|
|
209
|
-
request: Dictionary containing request information
|
|
210
|
-
response: Dictionary for storing response information
|
|
211
|
-
|
|
212
|
-
Returns:
|
|
213
|
-
Updated response dictionary
|
|
214
|
-
"""
|
|
215
|
-
...
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
class APIControllerOpenAPI(IController):
|
|
219
|
-
"""
|
|
220
|
-
API controller that uses OpenAPI schema for routing and model conversion
|
|
221
|
-
|
|
222
|
-
Implements the IController interface for consistency with other controllers.
|
|
223
|
-
"""
|
|
224
|
-
|
|
225
|
-
def __init__(self, service: Optional[ApiServiceProtocol] = None):
|
|
226
|
-
"""
|
|
227
|
-
Initialize the controller with a service implementation
|
|
228
|
-
|
|
229
|
-
Args:
|
|
230
|
-
service: Service implementation (uses default if None)
|
|
231
|
-
"""
|
|
232
|
-
if service is None:
|
|
233
|
-
from mcp.services.api_service import api_service
|
|
234
|
-
|
|
235
|
-
self._service = api_service
|
|
236
|
-
else:
|
|
237
|
-
self._service = service
|
|
238
|
-
|
|
239
|
-
self.router = OpenAPIRouter()
|
|
240
|
-
self.register_handlers()
|
|
241
|
-
|
|
242
|
-
def _normalize_request(
|
|
243
|
-
self, request: Dict[str, Any]
|
|
244
|
-
) -> Tuple[str, str, Dict[str, Any], str]:
|
|
245
|
-
"""
|
|
246
|
-
Normalize request object to handle different request formats
|
|
247
|
-
|
|
248
|
-
Args:
|
|
249
|
-
request: Request object that might be in different formats
|
|
250
|
-
|
|
251
|
-
Returns:
|
|
252
|
-
Tuple containing (method, path, query_params, body)
|
|
253
|
-
"""
|
|
254
|
-
return RequestProcessor.normalize_request(request)
|
|
255
|
-
|
|
256
|
-
def onHTTPRequest(
|
|
257
|
-
self, webServerDAT: Any, request: Dict[str, Any], response: Dict[str, Any]
|
|
258
|
-
) -> Dict[str, Any]:
|
|
259
|
-
"""
|
|
260
|
-
Handle HTTP request from TouchDesigner WebServer DAT
|
|
261
|
-
|
|
262
|
-
Implements IController interface for consistent handling across controllers.
|
|
263
|
-
|
|
264
|
-
Args:
|
|
265
|
-
webServerDAT: Reference to the WebServerDAT object
|
|
266
|
-
request: Dictionary containing request information
|
|
267
|
-
response: Dictionary for storing response information
|
|
268
|
-
|
|
269
|
-
Returns:
|
|
270
|
-
Updated response dictionary
|
|
271
|
-
"""
|
|
272
|
-
|
|
273
|
-
if "headers" not in response:
|
|
274
|
-
response["headers"] = {}
|
|
275
|
-
|
|
276
|
-
response["headers"]["Access-Control-Allow-Origin"] = "*"
|
|
277
|
-
response["headers"][
|
|
278
|
-
"Access-Control-Allow-Methods"
|
|
279
|
-
] = "GET, POST, PUT, DELETE, PATCH, OPTIONS"
|
|
280
|
-
response["headers"][
|
|
281
|
-
"Access-Control-Allow-Headers"
|
|
282
|
-
] = "Content-Type, Authorization"
|
|
283
|
-
response["headers"]["Content-Type"] = "application/json"
|
|
284
|
-
|
|
285
|
-
try:
|
|
286
|
-
method, path, query_params, body = self._normalize_request(request)
|
|
287
|
-
except Exception as e:
|
|
288
|
-
|
|
289
|
-
response["statusCode"] = 500
|
|
290
|
-
response["statusReason"] = "Internal Server Error"
|
|
291
|
-
response["data"] = json.dumps(
|
|
292
|
-
{
|
|
293
|
-
"success": False,
|
|
294
|
-
"error": f"Request normalization error: {str(e)}",
|
|
295
|
-
"errorCategory": str(ErrorCategory.INTERNAL),
|
|
296
|
-
}
|
|
297
|
-
)
|
|
298
|
-
return response
|
|
299
|
-
|
|
300
|
-
try:
|
|
301
|
-
if method == "OPTIONS":
|
|
302
|
-
response["statusCode"] = 200
|
|
303
|
-
response["statusReason"] = "OK"
|
|
304
|
-
response["data"] = "{}"
|
|
305
|
-
return response
|
|
306
|
-
|
|
307
|
-
result = self.router.route_request(method, path, query_params, body)
|
|
308
|
-
|
|
309
|
-
if result["success"]:
|
|
310
|
-
response["statusCode"] = 200
|
|
311
|
-
response["statusReason"] = "OK"
|
|
312
|
-
response["data"] = json.dumps(safe_serialize(result))
|
|
313
|
-
else:
|
|
314
|
-
error_category = result.get("errorCategory", ErrorCategory.VALIDATION)
|
|
315
|
-
response["statusCode"] = 200
|
|
316
|
-
response["statusReason"] = self._get_status_reason_for_error(
|
|
317
|
-
error_category
|
|
318
|
-
)
|
|
319
|
-
response["data"] = json.dumps(
|
|
320
|
-
{
|
|
321
|
-
"success": False,
|
|
322
|
-
"data" : None,
|
|
323
|
-
"error": result["error"],
|
|
324
|
-
"errorCategory": (
|
|
325
|
-
str(error_category)
|
|
326
|
-
if hasattr(error_category, "__str__")
|
|
327
|
-
else None
|
|
328
|
-
),
|
|
329
|
-
}
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
except Exception as e:
|
|
333
|
-
log_message(f"Error handling request: {e}", LogLevel.ERROR)
|
|
334
|
-
log_message(traceback.format_exc(), LogLevel.DEBUG)
|
|
335
|
-
|
|
336
|
-
response["statusCode"] = 500
|
|
337
|
-
response["statusReason"] = "Internal Server Error"
|
|
338
|
-
response["data"] = json.dumps(
|
|
339
|
-
{
|
|
340
|
-
"success": False,
|
|
341
|
-
"error": f"Internal server error: {str(e)}",
|
|
342
|
-
"errorCategory": str(ErrorCategory.INTERNAL),
|
|
343
|
-
}
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
log_message(
|
|
347
|
-
f"Response status: {response['statusCode']}, {response['data']}",
|
|
348
|
-
LogLevel.DEBUG,
|
|
349
|
-
)
|
|
350
|
-
return response
|
|
351
|
-
|
|
352
|
-
def _get_status_code_for_error(self, error_category) -> int:
|
|
353
|
-
"""
|
|
354
|
-
Map error category to HTTP status code
|
|
355
|
-
|
|
356
|
-
Args:
|
|
357
|
-
error_category: The error category
|
|
358
|
-
|
|
359
|
-
Returns:
|
|
360
|
-
Appropriate HTTP status code
|
|
361
|
-
"""
|
|
362
|
-
if error_category == ErrorCategory.NOT_FOUND:
|
|
363
|
-
return 404
|
|
364
|
-
elif error_category == ErrorCategory.PERMISSION:
|
|
365
|
-
return 403
|
|
366
|
-
elif error_category == ErrorCategory.VALIDATION:
|
|
367
|
-
return 400
|
|
368
|
-
elif error_category == ErrorCategory.EXTERNAL:
|
|
369
|
-
return 502
|
|
370
|
-
else:
|
|
371
|
-
return 500
|
|
372
|
-
|
|
373
|
-
def _get_status_reason_for_error(self, error_category) -> str:
|
|
374
|
-
"""
|
|
375
|
-
Map error category to HTTP status reason
|
|
376
|
-
|
|
377
|
-
Args:
|
|
378
|
-
error_category: The error category
|
|
379
|
-
|
|
380
|
-
Returns:
|
|
381
|
-
Status reason text
|
|
382
|
-
"""
|
|
383
|
-
if error_category == ErrorCategory.NOT_FOUND:
|
|
384
|
-
return "Not Found"
|
|
385
|
-
elif error_category == ErrorCategory.PERMISSION:
|
|
386
|
-
return "Forbidden"
|
|
387
|
-
elif error_category == ErrorCategory.VALIDATION:
|
|
388
|
-
return "Bad Request"
|
|
389
|
-
elif error_category == ErrorCategory.EXTERNAL:
|
|
390
|
-
return "Bad Gateway"
|
|
391
|
-
else:
|
|
392
|
-
return "Internal Server Error"
|
|
393
|
-
|
|
394
|
-
def register_handlers(self) -> None:
|
|
395
|
-
"""Register all generated handlers automatically"""
|
|
396
|
-
import mcp.controllers.generated_handlers as handlers
|
|
397
|
-
|
|
398
|
-
for operation_id in handlers.__all__:
|
|
399
|
-
handler = getattr(handlers, operation_id, None)
|
|
400
|
-
if callable(handler):
|
|
401
|
-
self.router.register_handler(operation_id, handler)
|
|
402
|
-
else:
|
|
403
|
-
log_message(f"Handler for {operation_id} not found.", LogLevel.WARNING)
|
|
404
|
-
|
|
405
|
-
def _handle_get_td_info(self, body: Optional[str] = None, **kwargs) -> Result:
|
|
406
|
-
"""
|
|
407
|
-
Handle get_td_info operation
|
|
408
|
-
|
|
409
|
-
Returns server information such as version and platform.
|
|
410
|
-
"""
|
|
411
|
-
service_result = self._service.get_td_info()
|
|
412
|
-
|
|
413
|
-
response_data = GetTdInfo200Response().from_dict(service_result)
|
|
414
|
-
return response_data.to_dict()
|
|
415
|
-
|
|
416
|
-
def _handle_get_nodes(
|
|
417
|
-
self,
|
|
418
|
-
parentPath: str,
|
|
419
|
-
pattern: Optional[str] = None,
|
|
420
|
-
includeProperties: Optional[bool] = None,
|
|
421
|
-
body: Optional[str] = None,
|
|
422
|
-
**kwargs,
|
|
423
|
-
) -> Result:
|
|
424
|
-
"""
|
|
425
|
-
Handle get_nodes operation
|
|
426
|
-
|
|
427
|
-
Args:
|
|
428
|
-
parentPath: Path of the parent node to get children from
|
|
429
|
-
pattern: Optional pattern to filter nodes by
|
|
430
|
-
includeProperties: Whether to include full node properties (default: False)
|
|
431
|
-
|
|
432
|
-
Returns:
|
|
433
|
-
List of nodes under the specified parent path
|
|
434
|
-
"""
|
|
435
|
-
# Convert camelCase to snake_case and provide default value
|
|
436
|
-
include_properties = includeProperties if includeProperties is not None else False
|
|
437
|
-
|
|
438
|
-
service_result = self._service.get_nodes(parentPath, pattern, include_properties)
|
|
439
|
-
response_data = GetNodes200Response().from_dict(service_result)
|
|
440
|
-
return response_data.to_dict()
|
|
441
|
-
|
|
442
|
-
def _handle_create_node(self, body: str, **kwargs) -> Result:
|
|
443
|
-
"""
|
|
444
|
-
Handle create_node operation
|
|
445
|
-
|
|
446
|
-
Args:
|
|
447
|
-
body: Request body containing node creation parameters
|
|
448
|
-
|
|
449
|
-
Returns:
|
|
450
|
-
Information about the created node
|
|
451
|
-
"""
|
|
452
|
-
if not body:
|
|
453
|
-
return error_result("Request body is required")
|
|
454
|
-
|
|
455
|
-
try:
|
|
456
|
-
request_data = json.loads(body)
|
|
457
|
-
except json.JSONDecodeError as e:
|
|
458
|
-
return error_result(f"Invalid JSON in request body: {str(e)}")
|
|
459
|
-
|
|
460
|
-
parent_path = request_data.get("parentPath")
|
|
461
|
-
node_type = request_data.get("nodeType")
|
|
462
|
-
node_name = request_data.get("nodeName")
|
|
463
|
-
parameters = request_data.get("parameters", {})
|
|
464
|
-
|
|
465
|
-
if not parent_path:
|
|
466
|
-
return error_result("parentPath is required")
|
|
467
|
-
|
|
468
|
-
if not node_type:
|
|
469
|
-
return error_result("nodeType is required")
|
|
470
|
-
|
|
471
|
-
service_result = self._service.create_node(
|
|
472
|
-
parent_path, node_type, node_name, parameters
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
response_data = CreateNode200Response().from_dict(service_result)
|
|
476
|
-
return response_data.to_dict()
|
|
477
|
-
|
|
478
|
-
def _handle_delete_node(
|
|
479
|
-
self, nodePath: str, body: Optional[str] = None, **kwargs
|
|
480
|
-
) -> Result:
|
|
481
|
-
"""
|
|
482
|
-
Handle delete_node operation
|
|
483
|
-
|
|
484
|
-
Args:
|
|
485
|
-
nodePath: Path of the node to delete
|
|
486
|
-
|
|
487
|
-
Returns:
|
|
488
|
-
Result of the deletion operation
|
|
489
|
-
"""
|
|
490
|
-
service_result = self._service.delete_node(nodePath)
|
|
491
|
-
|
|
492
|
-
response_data = DeleteNode200Response().from_dict(service_result)
|
|
493
|
-
return response_data.to_dict()
|
|
494
|
-
|
|
495
|
-
def _handle_get_node_detail(
|
|
496
|
-
self, nodePath: str, body: Optional[str] = None, **kwargs
|
|
497
|
-
) -> Result:
|
|
498
|
-
"""
|
|
499
|
-
Handle get_node_detail operation
|
|
500
|
-
|
|
501
|
-
Args:
|
|
502
|
-
nodePath: Path of the node to get properties for
|
|
503
|
-
|
|
504
|
-
Returns:
|
|
505
|
-
Node properties
|
|
506
|
-
"""
|
|
507
|
-
service_result = self._service.get_node_detail(nodePath)
|
|
508
|
-
response_data = GetNodeDetail200Response().from_dict(service_result)
|
|
509
|
-
return response_data.to_dict()
|
|
510
|
-
|
|
511
|
-
def _handle_update_node(self, body: str, **kwargs) -> Result:
|
|
512
|
-
"""
|
|
513
|
-
Handle update_node operation
|
|
514
|
-
|
|
515
|
-
Args:
|
|
516
|
-
nodePath: Path of the node to update
|
|
517
|
-
body: Request body containing properties to update
|
|
518
|
-
|
|
519
|
-
Returns:
|
|
520
|
-
Result of the update operation
|
|
521
|
-
"""
|
|
522
|
-
if not body:
|
|
523
|
-
return error_result("Request body is required")
|
|
524
|
-
|
|
525
|
-
try:
|
|
526
|
-
request_data = json.loads(body)
|
|
527
|
-
except json.JSONDecodeError as e:
|
|
528
|
-
return error_result(f"Invalid JSON in request body: {str(e)}")
|
|
529
|
-
|
|
530
|
-
nodePath = request_data.get("nodePath", "")
|
|
531
|
-
if not nodePath:
|
|
532
|
-
return error_result("nodePath is required")
|
|
533
|
-
|
|
534
|
-
properties = request_data.get("properties", {})
|
|
535
|
-
|
|
536
|
-
if not properties or not isinstance(properties, dict):
|
|
537
|
-
return error_result("properties object is required")
|
|
538
|
-
|
|
539
|
-
service_result = self._service.update_node(nodePath, properties)
|
|
540
|
-
response_data = UpdateNode200Response().from_dict(service_result)
|
|
541
|
-
return response_data.to_dict()
|
|
542
|
-
|
|
543
|
-
def _handle_exec_node_method(self, body: str, **kwargs) -> Result:
|
|
544
|
-
"""
|
|
545
|
-
Handle exec_node_method operation
|
|
546
|
-
|
|
547
|
-
Args:
|
|
548
|
-
body: Request body containing node path, method name, and arguments
|
|
549
|
-
|
|
550
|
-
Returns:
|
|
551
|
-
Result of the method execution
|
|
552
|
-
"""
|
|
553
|
-
if not body:
|
|
554
|
-
return error_result("Request body is required")
|
|
555
|
-
|
|
556
|
-
try:
|
|
557
|
-
request_data = json.loads(body)
|
|
558
|
-
except json.JSONDecodeError as e:
|
|
559
|
-
return error_result(f"Invalid JSON in request body: {str(e)}")
|
|
560
|
-
|
|
561
|
-
node_path = request_data.get("nodePath")
|
|
562
|
-
method = request_data.get("method")
|
|
563
|
-
args = request_data.get("args", [])
|
|
564
|
-
kwargs = request_data.get("kwargs", {})
|
|
565
|
-
|
|
566
|
-
if not node_path:
|
|
567
|
-
return error_result("nodePath is required")
|
|
568
|
-
|
|
569
|
-
if not method:
|
|
570
|
-
return error_result("method is required")
|
|
571
|
-
|
|
572
|
-
service_result = self._service.call_node_method(node_path, method, args, kwargs)
|
|
573
|
-
|
|
574
|
-
if not service_result["success"]:
|
|
575
|
-
return service_result
|
|
576
|
-
|
|
577
|
-
response_data = ExecNodeMethod200Response().from_dict(service_result)
|
|
578
|
-
return response_data.to_dict()
|
|
579
|
-
|
|
580
|
-
def _handle_exec_python_script(self, body: str, **kwargs) -> Result:
|
|
581
|
-
"""
|
|
582
|
-
Handle exec_python_script operation
|
|
583
|
-
|
|
584
|
-
Args:
|
|
585
|
-
body: Request body containing Python script to execute
|
|
586
|
-
|
|
587
|
-
Returns:
|
|
588
|
-
Result of the script execution
|
|
589
|
-
"""
|
|
590
|
-
if not body:
|
|
591
|
-
return error_result("Request body is required")
|
|
592
|
-
|
|
593
|
-
try:
|
|
594
|
-
request_data = json.loads(body)
|
|
595
|
-
except json.JSONDecodeError as e:
|
|
596
|
-
return error_result(f"Invalid JSON in request body: {str(e)}")
|
|
597
|
-
|
|
598
|
-
script = request_data.get("script")
|
|
599
|
-
|
|
600
|
-
if not script:
|
|
601
|
-
return error_result("script is required")
|
|
602
|
-
|
|
603
|
-
service_result = self._service.exec_script(script)
|
|
604
|
-
|
|
605
|
-
if not service_result["success"]:
|
|
606
|
-
return service_result
|
|
607
|
-
|
|
608
|
-
response_data = ExecPythonScript200Response().from_dict(service_result)
|
|
609
|
-
return response_data.to_dict()
|
|
610
|
-
|
|
611
|
-
def _handle_get_td_python_classes(
|
|
612
|
-
self, body: Optional[str] = None, **kwargs
|
|
613
|
-
) -> Result:
|
|
614
|
-
"""
|
|
615
|
-
Handle get_td_python_classes operation
|
|
616
|
-
|
|
617
|
-
Returns:
|
|
618
|
-
List of Python classes available in TouchDesigner
|
|
619
|
-
"""
|
|
620
|
-
return self._service.get_python_classes()
|
|
621
|
-
|
|
622
|
-
def _handle_get_td_python_class_details(
|
|
623
|
-
self, className: str, body: Optional[str] = None, **kwargs
|
|
624
|
-
) -> Result:
|
|
625
|
-
"""
|
|
626
|
-
Handle get_td_python_class_details operation
|
|
627
|
-
|
|
628
|
-
Args:
|
|
629
|
-
className: Name of the Python class to get details for
|
|
630
|
-
|
|
631
|
-
Returns:
|
|
632
|
-
Details of the specified Python class
|
|
633
|
-
"""
|
|
634
|
-
return self._service.get_python_class_details(className)
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
api_controller_openapi = APIControllerOpenAPI()
|