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.
Files changed (82) hide show
  1. package/README.ja.md +4 -4
  2. package/README.md +4 -3
  3. package/dist/cli.js +0 -0
  4. package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
  5. package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
  6. package/dist/index.js +0 -0
  7. package/dist/server/touchDesignerServer.js +1 -1
  8. package/package.json +14 -12
  9. package/td/genHandlers.js +0 -47
  10. package/td/import_modules.py +0 -52
  11. package/td/mcp_webserver_base.tox +0 -0
  12. package/td/modules/mcp/controllers/__init__.py +0 -9
  13. package/td/modules/mcp/controllers/api_controller.py +0 -637
  14. package/td/modules/mcp/controllers/generated_handlers.py +0 -365
  15. package/td/modules/mcp/controllers/openapi_router.py +0 -265
  16. package/td/modules/mcp/services/__init__.py +0 -8
  17. package/td/modules/mcp/services/api_service.py +0 -555
  18. package/td/modules/mcp_webserver_script.py +0 -134
  19. package/td/modules/td_server/.dockerignore +0 -72
  20. package/td/modules/td_server/.openapi-generator/FILES +0 -55
  21. package/td/modules/td_server/.openapi-generator/VERSION +0 -1
  22. package/td/modules/td_server/.openapi-generator-ignore +0 -23
  23. package/td/modules/td_server/.travis.yml +0 -14
  24. package/td/modules/td_server/Dockerfile +0 -16
  25. package/td/modules/td_server/README.md +0 -49
  26. package/td/modules/td_server/git_push.sh +0 -57
  27. package/td/modules/td_server/openapi_server/__init__.py +0 -0
  28. package/td/modules/td_server/openapi_server/__main__.py +0 -19
  29. package/td/modules/td_server/openapi_server/controllers/__init__.py +0 -0
  30. package/td/modules/td_server/openapi_server/controllers/default_controller.py +0 -162
  31. package/td/modules/td_server/openapi_server/controllers/security_controller.py +0 -2
  32. package/td/modules/td_server/openapi_server/encoder.py +0 -19
  33. package/td/modules/td_server/openapi_server/models/__init__.py +0 -33
  34. package/td/modules/td_server/openapi_server/models/base_model.py +0 -68
  35. package/td/modules/td_server/openapi_server/models/create_node200_response.py +0 -125
  36. package/td/modules/td_server/openapi_server/models/create_node200_response_data.py +0 -63
  37. package/td/modules/td_server/openapi_server/models/create_node_request.py +0 -123
  38. package/td/modules/td_server/openapi_server/models/delete_node200_response.py +0 -125
  39. package/td/modules/td_server/openapi_server/models/delete_node200_response_data.py +0 -91
  40. package/td/modules/td_server/openapi_server/models/exec_node_method200_response.py +0 -125
  41. package/td/modules/td_server/openapi_server/models/exec_node_method200_response_data.py +0 -65
  42. package/td/modules/td_server/openapi_server/models/exec_node_method_request.py +0 -153
  43. package/td/modules/td_server/openapi_server/models/exec_node_method_request_args_inner.py +0 -34
  44. package/td/modules/td_server/openapi_server/models/exec_python_script200_response.py +0 -125
  45. package/td/modules/td_server/openapi_server/models/exec_python_script200_response_data.py +0 -65
  46. package/td/modules/td_server/openapi_server/models/exec_python_script200_response_data_result.py +0 -63
  47. package/td/modules/td_server/openapi_server/models/exec_python_script_request.py +0 -65
  48. package/td/modules/td_server/openapi_server/models/get_node_detail200_response.py +0 -125
  49. package/td/modules/td_server/openapi_server/models/get_nodes200_response.py +0 -125
  50. package/td/modules/td_server/openapi_server/models/get_nodes200_response_data.py +0 -65
  51. package/td/modules/td_server/openapi_server/models/get_td_info200_response.py +0 -125
  52. package/td/modules/td_server/openapi_server/models/get_td_info200_response_data.py +0 -155
  53. package/td/modules/td_server/openapi_server/models/get_td_python_class_details200_response.py +0 -125
  54. package/td/modules/td_server/openapi_server/models/get_td_python_classes200_response.py +0 -125
  55. package/td/modules/td_server/openapi_server/models/get_td_python_classes200_response_data.py +0 -63
  56. package/td/modules/td_server/openapi_server/models/td_node.py +0 -175
  57. package/td/modules/td_server/openapi_server/models/td_node_family_type.py +0 -44
  58. package/td/modules/td_server/openapi_server/models/td_python_class_details.py +0 -191
  59. package/td/modules/td_server/openapi_server/models/td_python_class_info.py +0 -127
  60. package/td/modules/td_server/openapi_server/models/td_python_method_info.py +0 -121
  61. package/td/modules/td_server/openapi_server/models/td_python_property_info.py +0 -123
  62. package/td/modules/td_server/openapi_server/models/update_node200_response.py +0 -125
  63. package/td/modules/td_server/openapi_server/models/update_node200_response_data.py +0 -149
  64. package/td/modules/td_server/openapi_server/models/update_node200_response_data_failed_inner.py +0 -91
  65. package/td/modules/td_server/openapi_server/models/update_node_request.py +0 -93
  66. package/td/modules/td_server/openapi_server/openapi/openapi.yaml +0 -975
  67. package/td/modules/td_server/openapi_server/test/__init__.py +0 -16
  68. package/td/modules/td_server/openapi_server/test/test_default_controller.py +0 -201
  69. package/td/modules/td_server/openapi_server/typing_utils.py +0 -30
  70. package/td/modules/td_server/openapi_server/util.py +0 -147
  71. package/td/modules/td_server/requirements.txt +0 -13
  72. package/td/modules/td_server/setup.py +0 -37
  73. package/td/modules/td_server/test-requirements.txt +0 -4
  74. package/td/modules/td_server/tox.ini +0 -11
  75. package/td/modules/utils/config.py +0 -7
  76. package/td/modules/utils/error_handling.py +0 -104
  77. package/td/modules/utils/logging.py +0 -23
  78. package/td/modules/utils/result.py +0 -40
  79. package/td/modules/utils/serialization.py +0 -57
  80. package/td/modules/utils/types.py +0 -33
  81. package/td/modules/utils/utils_logging.py +0 -60
  82. 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
- )
@@ -1,8 +0,0 @@
1
- """
2
- MCP Services package initialization
3
- Exports service implementations for dependency injection
4
- """
5
-
6
- from mcp.services.api_service import TouchDesignerApiService, api_service
7
-
8
- __all__ = ["TouchDesignerApiService", "api_service"]