touchdesigner-mcp-server 0.4.0-alpha.1 → 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 +1 -0
- package/README.md +1 -0
- 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 +10 -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,365 +0,0 @@
|
|
|
1
|
-
# Auto-generated MCP handlers
|
|
2
|
-
import json
|
|
3
|
-
import inspect
|
|
4
|
-
import re
|
|
5
|
-
from utils.types import Result
|
|
6
|
-
from utils.result import error_result
|
|
7
|
-
|
|
8
|
-
# Service instance singleton pattern
|
|
9
|
-
_api_service_instance = None
|
|
10
|
-
|
|
11
|
-
def get_api_service():
|
|
12
|
-
global _api_service_instance
|
|
13
|
-
if _api_service_instance is None:
|
|
14
|
-
from mcp.services.api_service import api_service
|
|
15
|
-
_api_service_instance = api_service
|
|
16
|
-
return _api_service_instance
|
|
17
|
-
|
|
18
|
-
def camel_to_snake(name):
|
|
19
|
-
"""Convert camelCase to snake_case"""
|
|
20
|
-
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
|
21
|
-
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
|
22
|
-
|
|
23
|
-
def delete_node(body: str = None, **kwargs) -> Result:
|
|
24
|
-
"""
|
|
25
|
-
Auto-generated handler for operation: delete_node
|
|
26
|
-
"""
|
|
27
|
-
try:
|
|
28
|
-
print(f"[DEBUG] Handler 'delete_node' called with body: {body}, kwargs: {kwargs}")
|
|
29
|
-
service_method = getattr(get_api_service(), "delete_node", None)
|
|
30
|
-
if not callable(service_method):
|
|
31
|
-
return error_result("Service method 'delete_node' not implemented")
|
|
32
|
-
|
|
33
|
-
# Merge body
|
|
34
|
-
if body:
|
|
35
|
-
try:
|
|
36
|
-
parsed_body = json.loads(body)
|
|
37
|
-
kwargs.update(parsed_body)
|
|
38
|
-
except Exception as e:
|
|
39
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
40
|
-
|
|
41
|
-
# CamelCase → SnakeCase 変換
|
|
42
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
43
|
-
|
|
44
|
-
sig = inspect.signature(service_method)
|
|
45
|
-
|
|
46
|
-
# Prepare args matching the function signature
|
|
47
|
-
call_args = {}
|
|
48
|
-
for param_name in sig.parameters:
|
|
49
|
-
if param_name in kwargs_snake_case:
|
|
50
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
51
|
-
|
|
52
|
-
return service_method(**call_args)
|
|
53
|
-
|
|
54
|
-
except Exception as e:
|
|
55
|
-
return error_result(f"Handler for 'delete_node' failed: {str(e)}")
|
|
56
|
-
def get_nodes(body: str = None, **kwargs) -> Result:
|
|
57
|
-
"""
|
|
58
|
-
Auto-generated handler for operation: get_nodes
|
|
59
|
-
"""
|
|
60
|
-
try:
|
|
61
|
-
print(f"[DEBUG] Handler 'get_nodes' called with body: {body}, kwargs: {kwargs}")
|
|
62
|
-
service_method = getattr(get_api_service(), "get_nodes", None)
|
|
63
|
-
if not callable(service_method):
|
|
64
|
-
return error_result("Service method 'get_nodes' not implemented")
|
|
65
|
-
|
|
66
|
-
# Merge body
|
|
67
|
-
if body:
|
|
68
|
-
try:
|
|
69
|
-
parsed_body = json.loads(body)
|
|
70
|
-
kwargs.update(parsed_body)
|
|
71
|
-
except Exception as e:
|
|
72
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
73
|
-
|
|
74
|
-
# CamelCase → SnakeCase 変換
|
|
75
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
76
|
-
|
|
77
|
-
sig = inspect.signature(service_method)
|
|
78
|
-
|
|
79
|
-
# Prepare args matching the function signature
|
|
80
|
-
call_args = {}
|
|
81
|
-
for param_name in sig.parameters:
|
|
82
|
-
if param_name in kwargs_snake_case:
|
|
83
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
84
|
-
|
|
85
|
-
return service_method(**call_args)
|
|
86
|
-
|
|
87
|
-
except Exception as e:
|
|
88
|
-
return error_result(f"Handler for 'get_nodes' failed: {str(e)}")
|
|
89
|
-
def create_node(body: str = None, **kwargs) -> Result:
|
|
90
|
-
"""
|
|
91
|
-
Auto-generated handler for operation: create_node
|
|
92
|
-
"""
|
|
93
|
-
try:
|
|
94
|
-
print(f"[DEBUG] Handler 'create_node' called with body: {body}, kwargs: {kwargs}")
|
|
95
|
-
service_method = getattr(get_api_service(), "create_node", None)
|
|
96
|
-
if not callable(service_method):
|
|
97
|
-
return error_result("Service method 'create_node' not implemented")
|
|
98
|
-
|
|
99
|
-
# Merge body
|
|
100
|
-
if body:
|
|
101
|
-
try:
|
|
102
|
-
parsed_body = json.loads(body)
|
|
103
|
-
kwargs.update(parsed_body)
|
|
104
|
-
except Exception as e:
|
|
105
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
106
|
-
|
|
107
|
-
# CamelCase → SnakeCase 変換
|
|
108
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
109
|
-
|
|
110
|
-
sig = inspect.signature(service_method)
|
|
111
|
-
|
|
112
|
-
# Prepare args matching the function signature
|
|
113
|
-
call_args = {}
|
|
114
|
-
for param_name in sig.parameters:
|
|
115
|
-
if param_name in kwargs_snake_case:
|
|
116
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
117
|
-
|
|
118
|
-
return service_method(**call_args)
|
|
119
|
-
|
|
120
|
-
except Exception as e:
|
|
121
|
-
return error_result(f"Handler for 'create_node' failed: {str(e)}")
|
|
122
|
-
def get_node_detail(body: str = None, **kwargs) -> Result:
|
|
123
|
-
"""
|
|
124
|
-
Auto-generated handler for operation: get_node_detail
|
|
125
|
-
"""
|
|
126
|
-
try:
|
|
127
|
-
print(f"[DEBUG] Handler 'get_node_detail' called with body: {body}, kwargs: {kwargs}")
|
|
128
|
-
service_method = getattr(get_api_service(), "get_node_detail", None)
|
|
129
|
-
if not callable(service_method):
|
|
130
|
-
return error_result("Service method 'get_node_detail' not implemented")
|
|
131
|
-
|
|
132
|
-
# Merge body
|
|
133
|
-
if body:
|
|
134
|
-
try:
|
|
135
|
-
parsed_body = json.loads(body)
|
|
136
|
-
kwargs.update(parsed_body)
|
|
137
|
-
except Exception as e:
|
|
138
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
139
|
-
|
|
140
|
-
# CamelCase → SnakeCase 変換
|
|
141
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
142
|
-
|
|
143
|
-
sig = inspect.signature(service_method)
|
|
144
|
-
|
|
145
|
-
# Prepare args matching the function signature
|
|
146
|
-
call_args = {}
|
|
147
|
-
for param_name in sig.parameters:
|
|
148
|
-
if param_name in kwargs_snake_case:
|
|
149
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
150
|
-
|
|
151
|
-
return service_method(**call_args)
|
|
152
|
-
|
|
153
|
-
except Exception as e:
|
|
154
|
-
return error_result(f"Handler for 'get_node_detail' failed: {str(e)}")
|
|
155
|
-
def update_node(body: str = None, **kwargs) -> Result:
|
|
156
|
-
"""
|
|
157
|
-
Auto-generated handler for operation: update_node
|
|
158
|
-
"""
|
|
159
|
-
try:
|
|
160
|
-
print(f"[DEBUG] Handler 'update_node' called with body: {body}, kwargs: {kwargs}")
|
|
161
|
-
service_method = getattr(get_api_service(), "update_node", None)
|
|
162
|
-
if not callable(service_method):
|
|
163
|
-
return error_result("Service method 'update_node' not implemented")
|
|
164
|
-
|
|
165
|
-
# Merge body
|
|
166
|
-
if body:
|
|
167
|
-
try:
|
|
168
|
-
parsed_body = json.loads(body)
|
|
169
|
-
kwargs.update(parsed_body)
|
|
170
|
-
except Exception as e:
|
|
171
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
172
|
-
|
|
173
|
-
# CamelCase → SnakeCase 変換
|
|
174
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
175
|
-
|
|
176
|
-
sig = inspect.signature(service_method)
|
|
177
|
-
|
|
178
|
-
# Prepare args matching the function signature
|
|
179
|
-
call_args = {}
|
|
180
|
-
for param_name in sig.parameters:
|
|
181
|
-
if param_name in kwargs_snake_case:
|
|
182
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
183
|
-
|
|
184
|
-
return service_method(**call_args)
|
|
185
|
-
|
|
186
|
-
except Exception as e:
|
|
187
|
-
return error_result(f"Handler for 'update_node' failed: {str(e)}")
|
|
188
|
-
def get_td_python_classes(body: str = None, **kwargs) -> Result:
|
|
189
|
-
"""
|
|
190
|
-
Auto-generated handler for operation: get_td_python_classes
|
|
191
|
-
"""
|
|
192
|
-
try:
|
|
193
|
-
print(f"[DEBUG] Handler 'get_td_python_classes' called with body: {body}, kwargs: {kwargs}")
|
|
194
|
-
service_method = getattr(get_api_service(), "get_td_python_classes", None)
|
|
195
|
-
if not callable(service_method):
|
|
196
|
-
return error_result("Service method 'get_td_python_classes' not implemented")
|
|
197
|
-
|
|
198
|
-
# Merge body
|
|
199
|
-
if body:
|
|
200
|
-
try:
|
|
201
|
-
parsed_body = json.loads(body)
|
|
202
|
-
kwargs.update(parsed_body)
|
|
203
|
-
except Exception as e:
|
|
204
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
205
|
-
|
|
206
|
-
# CamelCase → SnakeCase 変換
|
|
207
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
208
|
-
|
|
209
|
-
sig = inspect.signature(service_method)
|
|
210
|
-
|
|
211
|
-
# Prepare args matching the function signature
|
|
212
|
-
call_args = {}
|
|
213
|
-
for param_name in sig.parameters:
|
|
214
|
-
if param_name in kwargs_snake_case:
|
|
215
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
216
|
-
|
|
217
|
-
return service_method(**call_args)
|
|
218
|
-
|
|
219
|
-
except Exception as e:
|
|
220
|
-
return error_result(f"Handler for 'get_td_python_classes' failed: {str(e)}")
|
|
221
|
-
def get_td_python_class_details(body: str = None, **kwargs) -> Result:
|
|
222
|
-
"""
|
|
223
|
-
Auto-generated handler for operation: get_td_python_class_details
|
|
224
|
-
"""
|
|
225
|
-
try:
|
|
226
|
-
print(f"[DEBUG] Handler 'get_td_python_class_details' called with body: {body}, kwargs: {kwargs}")
|
|
227
|
-
service_method = getattr(get_api_service(), "get_td_python_class_details", None)
|
|
228
|
-
if not callable(service_method):
|
|
229
|
-
return error_result("Service method 'get_td_python_class_details' not implemented")
|
|
230
|
-
|
|
231
|
-
# Merge body
|
|
232
|
-
if body:
|
|
233
|
-
try:
|
|
234
|
-
parsed_body = json.loads(body)
|
|
235
|
-
kwargs.update(parsed_body)
|
|
236
|
-
except Exception as e:
|
|
237
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
238
|
-
|
|
239
|
-
# CamelCase → SnakeCase 変換
|
|
240
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
241
|
-
|
|
242
|
-
sig = inspect.signature(service_method)
|
|
243
|
-
|
|
244
|
-
# Prepare args matching the function signature
|
|
245
|
-
call_args = {}
|
|
246
|
-
for param_name in sig.parameters:
|
|
247
|
-
if param_name in kwargs_snake_case:
|
|
248
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
249
|
-
|
|
250
|
-
return service_method(**call_args)
|
|
251
|
-
|
|
252
|
-
except Exception as e:
|
|
253
|
-
return error_result(f"Handler for 'get_td_python_class_details' failed: {str(e)}")
|
|
254
|
-
def exec_node_method(body: str = None, **kwargs) -> Result:
|
|
255
|
-
"""
|
|
256
|
-
Auto-generated handler for operation: exec_node_method
|
|
257
|
-
"""
|
|
258
|
-
try:
|
|
259
|
-
print(f"[DEBUG] Handler 'exec_node_method' called with body: {body}, kwargs: {kwargs}")
|
|
260
|
-
service_method = getattr(get_api_service(), "exec_node_method", None)
|
|
261
|
-
if not callable(service_method):
|
|
262
|
-
return error_result("Service method 'exec_node_method' not implemented")
|
|
263
|
-
|
|
264
|
-
# Merge body
|
|
265
|
-
if body:
|
|
266
|
-
try:
|
|
267
|
-
parsed_body = json.loads(body)
|
|
268
|
-
kwargs.update(parsed_body)
|
|
269
|
-
except Exception as e:
|
|
270
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
271
|
-
|
|
272
|
-
# CamelCase → SnakeCase 変換
|
|
273
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
274
|
-
|
|
275
|
-
sig = inspect.signature(service_method)
|
|
276
|
-
|
|
277
|
-
# Prepare args matching the function signature
|
|
278
|
-
call_args = {}
|
|
279
|
-
for param_name in sig.parameters:
|
|
280
|
-
if param_name in kwargs_snake_case:
|
|
281
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
282
|
-
|
|
283
|
-
return service_method(**call_args)
|
|
284
|
-
|
|
285
|
-
except Exception as e:
|
|
286
|
-
return error_result(f"Handler for 'exec_node_method' failed: {str(e)}")
|
|
287
|
-
def exec_python_script(body: str = None, **kwargs) -> Result:
|
|
288
|
-
"""
|
|
289
|
-
Auto-generated handler for operation: exec_python_script
|
|
290
|
-
"""
|
|
291
|
-
try:
|
|
292
|
-
print(f"[DEBUG] Handler 'exec_python_script' called with body: {body}, kwargs: {kwargs}")
|
|
293
|
-
service_method = getattr(get_api_service(), "exec_python_script", None)
|
|
294
|
-
if not callable(service_method):
|
|
295
|
-
return error_result("Service method 'exec_python_script' not implemented")
|
|
296
|
-
|
|
297
|
-
# Merge body
|
|
298
|
-
if body:
|
|
299
|
-
try:
|
|
300
|
-
parsed_body = json.loads(body)
|
|
301
|
-
kwargs.update(parsed_body)
|
|
302
|
-
except Exception as e:
|
|
303
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
304
|
-
|
|
305
|
-
# CamelCase → SnakeCase 変換
|
|
306
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
307
|
-
|
|
308
|
-
sig = inspect.signature(service_method)
|
|
309
|
-
|
|
310
|
-
# Prepare args matching the function signature
|
|
311
|
-
call_args = {}
|
|
312
|
-
for param_name in sig.parameters:
|
|
313
|
-
if param_name in kwargs_snake_case:
|
|
314
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
315
|
-
|
|
316
|
-
return service_method(**call_args)
|
|
317
|
-
|
|
318
|
-
except Exception as e:
|
|
319
|
-
return error_result(f"Handler for 'exec_python_script' failed: {str(e)}")
|
|
320
|
-
def get_td_info(body: str = None, **kwargs) -> Result:
|
|
321
|
-
"""
|
|
322
|
-
Auto-generated handler for operation: get_td_info
|
|
323
|
-
"""
|
|
324
|
-
try:
|
|
325
|
-
print(f"[DEBUG] Handler 'get_td_info' called with body: {body}, kwargs: {kwargs}")
|
|
326
|
-
service_method = getattr(get_api_service(), "get_td_info", None)
|
|
327
|
-
if not callable(service_method):
|
|
328
|
-
return error_result("Service method 'get_td_info' not implemented")
|
|
329
|
-
|
|
330
|
-
# Merge body
|
|
331
|
-
if body:
|
|
332
|
-
try:
|
|
333
|
-
parsed_body = json.loads(body)
|
|
334
|
-
kwargs.update(parsed_body)
|
|
335
|
-
except Exception as e:
|
|
336
|
-
return error_result(f"Invalid JSON body: {str(e)}")
|
|
337
|
-
|
|
338
|
-
# CamelCase → SnakeCase 変換
|
|
339
|
-
kwargs_snake_case = {camel_to_snake(k): v for k, v in kwargs.items()}
|
|
340
|
-
|
|
341
|
-
sig = inspect.signature(service_method)
|
|
342
|
-
|
|
343
|
-
# Prepare args matching the function signature
|
|
344
|
-
call_args = {}
|
|
345
|
-
for param_name in sig.parameters:
|
|
346
|
-
if param_name in kwargs_snake_case:
|
|
347
|
-
call_args[param_name] = kwargs_snake_case[param_name]
|
|
348
|
-
|
|
349
|
-
return service_method(**call_args)
|
|
350
|
-
|
|
351
|
-
except Exception as e:
|
|
352
|
-
return error_result(f"Handler for 'get_td_info' failed: {str(e)}")
|
|
353
|
-
|
|
354
|
-
__all__ = [
|
|
355
|
-
"delete_node",
|
|
356
|
-
"get_nodes",
|
|
357
|
-
"create_node",
|
|
358
|
-
"get_node_detail",
|
|
359
|
-
"update_node",
|
|
360
|
-
"get_td_python_classes",
|
|
361
|
-
"get_td_python_class_details",
|
|
362
|
-
"exec_node_method",
|
|
363
|
-
"exec_python_script",
|
|
364
|
-
"get_td_info",
|
|
365
|
-
]
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
OpenAPI schema based router for TouchDesigner MCP Web Server
|
|
3
|
-
|
|
4
|
-
This module provides utilities to:
|
|
5
|
-
- Load OpenAPI schema
|
|
6
|
-
- Extract route definitions
|
|
7
|
-
- Match incoming requests to routes
|
|
8
|
-
- Call registered handler functions based on operationId
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import traceback
|
|
12
|
-
from dataclasses import dataclass, field
|
|
13
|
-
from typing import Any, Dict, List, NamedTuple, Optional, Protocol
|
|
14
|
-
|
|
15
|
-
from mcp import openapi_schema
|
|
16
|
-
from utils.error_handling import ErrorCategory, categorize_error, format_error
|
|
17
|
-
from utils.logging import log_message
|
|
18
|
-
from utils.result import error_result
|
|
19
|
-
from utils.types import LogLevel, Result
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass
|
|
23
|
-
class RouteDefinition:
|
|
24
|
-
"""Definition of an API route extracted from OpenAPI schema"""
|
|
25
|
-
|
|
26
|
-
method: str
|
|
27
|
-
path_pattern: str
|
|
28
|
-
operation_id: str
|
|
29
|
-
parameters: List[Dict[str, Any]] = field(default_factory=list)
|
|
30
|
-
has_request_body: bool = False
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class RouteMatch(NamedTuple):
|
|
34
|
-
"""Result of matching a request path to a route"""
|
|
35
|
-
|
|
36
|
-
route: RouteDefinition
|
|
37
|
-
path_params: Dict[str, str]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def load_schema(schema_path: str = None) -> Dict[str, Any]:
|
|
41
|
-
"""
|
|
42
|
-
Load OpenAPI schema from preloaded global variable
|
|
43
|
-
"""
|
|
44
|
-
if openapi_schema:
|
|
45
|
-
log_message("Using preloaded OpenAPI schema", LogLevel.DEBUG)
|
|
46
|
-
return openapi_schema
|
|
47
|
-
else:
|
|
48
|
-
log_message("OpenAPI schema not available", LogLevel.ERROR)
|
|
49
|
-
return {"paths": {}}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def extract_routes(schema: Dict[str, Any] = None) -> List[RouteDefinition]:
|
|
53
|
-
"""
|
|
54
|
-
Extract route definitions from OpenAPI schema
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
schema: Parsed OpenAPI schema (optional, loaded from file if None)
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
List of RouteDefinition objects
|
|
61
|
-
"""
|
|
62
|
-
if schema is None:
|
|
63
|
-
schema = load_schema()
|
|
64
|
-
|
|
65
|
-
routes = []
|
|
66
|
-
|
|
67
|
-
for path, path_item in schema.get("paths", {}).items():
|
|
68
|
-
for method, operation in path_item.items():
|
|
69
|
-
if method.upper() not in [
|
|
70
|
-
"GET",
|
|
71
|
-
"POST",
|
|
72
|
-
"PUT",
|
|
73
|
-
"DELETE",
|
|
74
|
-
"PATCH",
|
|
75
|
-
"OPTIONS",
|
|
76
|
-
]:
|
|
77
|
-
continue
|
|
78
|
-
|
|
79
|
-
operation_id = operation.get("operationId")
|
|
80
|
-
if not operation_id:
|
|
81
|
-
log_message(
|
|
82
|
-
f"Operation without operationId at {method.upper()} {path}",
|
|
83
|
-
LogLevel.WARNING,
|
|
84
|
-
)
|
|
85
|
-
continue
|
|
86
|
-
|
|
87
|
-
route = RouteDefinition(
|
|
88
|
-
method=method.upper(),
|
|
89
|
-
path_pattern=path,
|
|
90
|
-
operation_id=operation_id,
|
|
91
|
-
parameters=operation.get("parameters", []),
|
|
92
|
-
has_request_body="requestBody" in operation,
|
|
93
|
-
)
|
|
94
|
-
routes.append(route)
|
|
95
|
-
|
|
96
|
-
return routes
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def match_route(
|
|
100
|
-
method: str, path: str, routes: List[RouteDefinition]
|
|
101
|
-
) -> Optional[RouteMatch]:
|
|
102
|
-
"""
|
|
103
|
-
Match request method and path to a route definition
|
|
104
|
-
|
|
105
|
-
Args:
|
|
106
|
-
method: HTTP method of the request (GET, POST, etc.)
|
|
107
|
-
path: URL path of the request
|
|
108
|
-
routes: List of route definitions to match against
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
RouteMatch if a matching route is found, None otherwise
|
|
112
|
-
"""
|
|
113
|
-
for route in routes:
|
|
114
|
-
if route.method == method.upper() and route.path_pattern == path:
|
|
115
|
-
return RouteMatch(route=route, path_params={})
|
|
116
|
-
|
|
117
|
-
for route in routes:
|
|
118
|
-
if route.method != method.upper():
|
|
119
|
-
continue
|
|
120
|
-
|
|
121
|
-
if "{" not in route.path_pattern:
|
|
122
|
-
continue
|
|
123
|
-
|
|
124
|
-
path_params = {}
|
|
125
|
-
|
|
126
|
-
pattern_parts = route.path_pattern.split("/")
|
|
127
|
-
path_parts = path.split("/")
|
|
128
|
-
|
|
129
|
-
if len(pattern_parts) > len(path_parts) and all(
|
|
130
|
-
"{" not in p for p in pattern_parts[len(path_parts) :]
|
|
131
|
-
):
|
|
132
|
-
continue
|
|
133
|
-
|
|
134
|
-
match = True
|
|
135
|
-
param_value_parts = []
|
|
136
|
-
|
|
137
|
-
for i, (pattern_part, path_part) in enumerate(zip(pattern_parts, path_parts)):
|
|
138
|
-
if not pattern_part and not path_part:
|
|
139
|
-
continue
|
|
140
|
-
|
|
141
|
-
if "{" in pattern_part and "}" in pattern_part:
|
|
142
|
-
param_name = pattern_part[1:-1]
|
|
143
|
-
|
|
144
|
-
if i == len(pattern_parts) - 1 and i < len(path_parts) - 1:
|
|
145
|
-
param_value = "/".join([path_part] + path_parts[i + 1 :])
|
|
146
|
-
path_params[param_name] = param_value
|
|
147
|
-
match = True
|
|
148
|
-
break
|
|
149
|
-
else:
|
|
150
|
-
path_params[param_name] = path_part
|
|
151
|
-
elif pattern_part != path_part:
|
|
152
|
-
match = False
|
|
153
|
-
break
|
|
154
|
-
|
|
155
|
-
if match and len(pattern_parts) <= len(path_parts):
|
|
156
|
-
return RouteMatch(route=route, path_params=path_params)
|
|
157
|
-
|
|
158
|
-
return None
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
class RequestHandler(Protocol):
|
|
162
|
-
"""Protocol for request handlers"""
|
|
163
|
-
|
|
164
|
-
def __call__(self, **kwargs) -> Result: ...
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
class OpenAPIRouter:
|
|
168
|
-
"""
|
|
169
|
-
Router that uses OpenAPI schema to route requests to appropriate handlers
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
def __init__(self, load_schema: bool = True):
|
|
173
|
-
"""
|
|
174
|
-
Initialize the router
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
load_schema: Whether to load routes from schema on initialization
|
|
178
|
-
"""
|
|
179
|
-
self.routes: List[RouteDefinition] = []
|
|
180
|
-
self._handlers: Dict[str, RequestHandler] = {}
|
|
181
|
-
|
|
182
|
-
if load_schema:
|
|
183
|
-
self.routes = extract_routes()
|
|
184
|
-
log_message(
|
|
185
|
-
f"Router initialized with {len(self.routes)} routes", LogLevel.INFO
|
|
186
|
-
)
|
|
187
|
-
self._routes_by_operation_id: Dict[str, RouteDefinition] = {
|
|
188
|
-
r.operation_id: r for r in self.routes
|
|
189
|
-
}
|
|
190
|
-
else:
|
|
191
|
-
self._routes_by_operation_id: Dict[str, RouteDefinition] = {}
|
|
192
|
-
|
|
193
|
-
def register_handler(self, operation_id: str, handler: RequestHandler) -> None:
|
|
194
|
-
"""
|
|
195
|
-
Register a handler for an operation
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
operation_id: Operation ID from OpenAPI schema
|
|
199
|
-
handler: Function to handle requests for this operation
|
|
200
|
-
"""
|
|
201
|
-
if operation_id not in self._routes_by_operation_id:
|
|
202
|
-
log_message(
|
|
203
|
-
f"Warning: operationId '{operation_id}' not found in schema",
|
|
204
|
-
LogLevel.WARNING,
|
|
205
|
-
)
|
|
206
|
-
self._handlers[operation_id] = handler
|
|
207
|
-
|
|
208
|
-
def route_request(
|
|
209
|
-
self, method: str, path: str, query_params: Dict[str, Any], body: Optional[str]
|
|
210
|
-
) -> Result:
|
|
211
|
-
"""
|
|
212
|
-
Route a request to the appropriate handler based on method and path
|
|
213
|
-
|
|
214
|
-
Args:
|
|
215
|
-
method: HTTP method of the request
|
|
216
|
-
path: URL path of the request
|
|
217
|
-
query_params: Dictionary of query parameters
|
|
218
|
-
body: Request body as string (if present)
|
|
219
|
-
|
|
220
|
-
Returns:
|
|
221
|
-
Result of the handler execution
|
|
222
|
-
"""
|
|
223
|
-
try:
|
|
224
|
-
match = match_route(method, path, self.routes)
|
|
225
|
-
if not match:
|
|
226
|
-
error_msg = f"No route matched for {method} {path}"
|
|
227
|
-
log_message(error_msg, LogLevel.WARNING)
|
|
228
|
-
return error_result(
|
|
229
|
-
format_error(error_msg, ErrorCategory.NOT_FOUND),
|
|
230
|
-
{"errorCategory": ErrorCategory.NOT_FOUND},
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
handler = self._handlers.get(match.route.operation_id)
|
|
234
|
-
if not handler:
|
|
235
|
-
error_msg = f"No handler registered for {method} {path} (operation: {match.route.operation_id})"
|
|
236
|
-
log_message(error_msg, LogLevel.ERROR)
|
|
237
|
-
return error_result(
|
|
238
|
-
format_error(error_msg, ErrorCategory.INTERNAL),
|
|
239
|
-
{"errorCategory": ErrorCategory.INTERNAL},
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
params = {**match.path_params, **query_params}
|
|
243
|
-
|
|
244
|
-
if method.upper() in ["POST", "PUT", "PATCH"] and body:
|
|
245
|
-
params["body"] = body
|
|
246
|
-
|
|
247
|
-
return handler(**params)
|
|
248
|
-
|
|
249
|
-
except TypeError as e:
|
|
250
|
-
error_msg = f"Handler argument mismatch: {str(e)}"
|
|
251
|
-
log_message(error_msg, LogLevel.ERROR)
|
|
252
|
-
log_message(traceback.format_exc(), LogLevel.DEBUG)
|
|
253
|
-
return error_result(
|
|
254
|
-
format_error(error_msg, ErrorCategory.VALIDATION),
|
|
255
|
-
{"errorCategory": ErrorCategory.VALIDATION},
|
|
256
|
-
)
|
|
257
|
-
except Exception as e:
|
|
258
|
-
error_msg = f"Handler execution error: {str(e)}"
|
|
259
|
-
error_category = categorize_error(e)
|
|
260
|
-
log_message(error_msg, LogLevel.ERROR)
|
|
261
|
-
log_message(traceback.format_exc(), LogLevel.DEBUG)
|
|
262
|
-
return error_result(
|
|
263
|
-
format_error(error_msg, error_category),
|
|
264
|
-
{"errorCategory": error_category},
|
|
265
|
-
)
|