mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.0.1__py3-none-any.whl

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 (200) hide show
  1. mcp_proxy_adapter/__main__.py +32 -0
  2. mcp_proxy_adapter/api/app.py +290 -33
  3. mcp_proxy_adapter/api/handlers.py +32 -6
  4. mcp_proxy_adapter/api/middleware/__init__.py +38 -32
  5. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  6. mcp_proxy_adapter/api/middleware/error_handling.py +9 -0
  7. mcp_proxy_adapter/api/middleware/factory.py +243 -0
  8. mcp_proxy_adapter/api/middleware/logging.py +32 -6
  9. mcp_proxy_adapter/api/middleware/protocol_middleware.py +201 -0
  10. mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
  11. mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
  12. mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -0
  13. mcp_proxy_adapter/commands/__init__.py +19 -4
  14. mcp_proxy_adapter/commands/auth_validation_command.py +408 -0
  15. mcp_proxy_adapter/commands/base.py +66 -32
  16. mcp_proxy_adapter/commands/builtin_commands.py +95 -0
  17. mcp_proxy_adapter/commands/catalog_manager.py +838 -0
  18. mcp_proxy_adapter/commands/cert_monitor_command.py +620 -0
  19. mcp_proxy_adapter/commands/certificate_management_command.py +608 -0
  20. mcp_proxy_adapter/commands/command_registry.py +711 -354
  21. mcp_proxy_adapter/commands/dependency_manager.py +245 -0
  22. mcp_proxy_adapter/commands/echo_command.py +81 -0
  23. mcp_proxy_adapter/commands/health_command.py +8 -1
  24. mcp_proxy_adapter/commands/help_command.py +21 -14
  25. mcp_proxy_adapter/commands/hooks.py +200 -167
  26. mcp_proxy_adapter/commands/key_management_command.py +506 -0
  27. mcp_proxy_adapter/commands/load_command.py +176 -0
  28. mcp_proxy_adapter/commands/plugins_command.py +235 -0
  29. mcp_proxy_adapter/commands/protocol_management_command.py +232 -0
  30. mcp_proxy_adapter/commands/proxy_registration_command.py +409 -0
  31. mcp_proxy_adapter/commands/reload_command.py +48 -50
  32. mcp_proxy_adapter/commands/result.py +1 -0
  33. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  34. mcp_proxy_adapter/commands/roles_management_command.py +697 -0
  35. mcp_proxy_adapter/commands/security_command.py +488 -0
  36. mcp_proxy_adapter/commands/ssl_setup_command.py +366 -0
  37. mcp_proxy_adapter/commands/token_management_command.py +529 -0
  38. mcp_proxy_adapter/commands/transport_management_command.py +144 -0
  39. mcp_proxy_adapter/commands/unload_command.py +158 -0
  40. mcp_proxy_adapter/config.py +394 -14
  41. mcp_proxy_adapter/core/app_factory.py +410 -0
  42. mcp_proxy_adapter/core/app_runner.py +272 -0
  43. mcp_proxy_adapter/core/auth_validator.py +606 -0
  44. mcp_proxy_adapter/core/certificate_utils.py +1045 -0
  45. mcp_proxy_adapter/core/client.py +574 -0
  46. mcp_proxy_adapter/core/client_manager.py +284 -0
  47. mcp_proxy_adapter/core/client_security.py +384 -0
  48. mcp_proxy_adapter/core/config_converter.py +405 -0
  49. mcp_proxy_adapter/core/config_validator.py +218 -0
  50. mcp_proxy_adapter/core/logging.py +19 -3
  51. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  52. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  53. mcp_proxy_adapter/core/protocol_manager.py +385 -0
  54. mcp_proxy_adapter/core/proxy_client.py +602 -0
  55. mcp_proxy_adapter/core/proxy_registration.py +522 -0
  56. mcp_proxy_adapter/core/role_utils.py +426 -0
  57. mcp_proxy_adapter/core/security_adapter.py +370 -0
  58. mcp_proxy_adapter/core/security_factory.py +239 -0
  59. mcp_proxy_adapter/core/security_integration.py +286 -0
  60. mcp_proxy_adapter/core/server_adapter.py +282 -0
  61. mcp_proxy_adapter/core/server_engine.py +270 -0
  62. mcp_proxy_adapter/core/settings.py +1 -0
  63. mcp_proxy_adapter/core/ssl_utils.py +234 -0
  64. mcp_proxy_adapter/core/transport_manager.py +292 -0
  65. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  66. mcp_proxy_adapter/custom_openapi.py +22 -11
  67. mcp_proxy_adapter/examples/__init__.py +13 -4
  68. mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
  69. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  70. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  71. mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
  72. mcp_proxy_adapter/examples/commands/__init__.py +5 -0
  73. mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
  74. mcp_proxy_adapter/examples/debug_request_state.py +112 -0
  75. mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
  76. mcp_proxy_adapter/examples/demo_client.py +275 -0
  77. mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
  78. mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
  79. mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
  80. mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
  81. mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
  82. mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
  83. mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
  84. mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  85. mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
  86. mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
  87. mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  88. mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
  89. mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
  90. mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
  91. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  92. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
  93. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
  94. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  95. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
  96. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
  97. mcp_proxy_adapter/examples/full_application/main.py +173 -0
  98. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  99. mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
  100. mcp_proxy_adapter/examples/generate_certificates.py +177 -0
  101. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  102. mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
  103. mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
  104. mcp_proxy_adapter/examples/run_example.py +59 -0
  105. mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
  106. mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
  107. mcp_proxy_adapter/examples/run_security_tests.py +544 -0
  108. mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
  109. mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
  110. mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
  111. mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
  112. mcp_proxy_adapter/examples/security_test_client.py +782 -0
  113. mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
  114. mcp_proxy_adapter/examples/test_config.py +148 -0
  115. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  116. mcp_proxy_adapter/examples/test_examples.py +281 -0
  117. mcp_proxy_adapter/examples/universal_client.py +620 -0
  118. mcp_proxy_adapter/main.py +93 -0
  119. mcp_proxy_adapter/utils/config_generator.py +1008 -0
  120. mcp_proxy_adapter/version.py +5 -2
  121. mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
  122. mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
  123. mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
  124. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/licenses/LICENSE +2 -2
  125. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  126. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  127. mcp_proxy_adapter/commands/reload_settings_command.py +0 -125
  128. mcp_proxy_adapter/examples/README.md +0 -124
  129. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  130. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  131. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  132. mcp_proxy_adapter/examples/basic_server/config.json +0 -35
  133. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  134. mcp_proxy_adapter/examples/basic_server/server.py +0 -103
  135. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  136. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  137. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -250
  138. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  139. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  140. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  141. mcp_proxy_adapter/examples/custom_commands/config.json +0 -35
  142. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  143. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  144. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  145. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  146. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  147. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  148. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  149. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  150. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  151. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  152. mcp_proxy_adapter/examples/custom_commands/server.py +0 -228
  153. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  154. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  155. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  156. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  157. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  158. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  159. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  160. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  161. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  162. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  163. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  164. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  165. mcp_proxy_adapter/tests/__init__.py +0 -0
  166. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  167. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  168. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  169. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  170. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  171. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  172. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  173. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  174. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  175. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  176. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  177. mcp_proxy_adapter/tests/conftest.py +0 -131
  178. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  179. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  180. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  181. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  182. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  183. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  184. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  185. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  186. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  187. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  188. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  189. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  190. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  191. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  192. mcp_proxy_adapter/tests/test_config.py +0 -127
  193. mcp_proxy_adapter/tests/test_utils.py +0 -65
  194. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  195. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  196. mcp_proxy_adapter/tests/unit/test_config.py +0 -217
  197. mcp_proxy_adapter-4.1.1.dist-info/METADATA +0 -200
  198. mcp_proxy_adapter-4.1.1.dist-info/RECORD +0 -110
  199. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/WHEEL +0 -0
  200. {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,270 @@
1
+ """
2
+ Server Engine Abstraction
3
+
4
+ This module provides an abstraction layer for the hypercorn ASGI server engine,
5
+ providing full mTLS support and SSL capabilities.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import logging
12
+ from abc import ABC, abstractmethod
13
+ from typing import Dict, Any, Optional
14
+ from pathlib import Path
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ServerEngine(ABC):
20
+ """
21
+ Abstract base class for server engines.
22
+
23
+ This class defines the interface that all server engines must implement,
24
+ allowing the framework to work with different ASGI servers transparently.
25
+ """
26
+
27
+ @abstractmethod
28
+ def get_name(self) -> str:
29
+ """Get the name of the server engine."""
30
+ pass
31
+
32
+ @abstractmethod
33
+ def get_supported_features(self) -> Dict[str, bool]:
34
+ """
35
+ Get supported features of this server engine.
36
+
37
+ Returns:
38
+ Dictionary mapping feature names to boolean support status
39
+ """
40
+ pass
41
+
42
+ @abstractmethod
43
+ def get_config_schema(self) -> Dict[str, Any]:
44
+ """
45
+ Get configuration schema for this server engine.
46
+
47
+ Returns:
48
+ Dictionary describing the configuration options
49
+ """
50
+ pass
51
+
52
+ @abstractmethod
53
+ def validate_config(self, config: Dict[str, Any]) -> bool:
54
+ """
55
+ Validate configuration for this server engine.
56
+
57
+ Args:
58
+ config: Configuration dictionary
59
+
60
+ Returns:
61
+ True if configuration is valid, False otherwise
62
+ """
63
+ pass
64
+
65
+ @abstractmethod
66
+ def run_server(self, app: Any, config: Dict[str, Any]) -> None:
67
+ """
68
+ Run the server with the given configuration.
69
+
70
+ Args:
71
+ app: ASGI application
72
+ config: Server configuration
73
+ """
74
+ pass
75
+
76
+
77
+ class HypercornEngine(ServerEngine):
78
+ """
79
+ Hypercorn server engine implementation.
80
+
81
+ Provides full mTLS support and better SSL capabilities.
82
+ """
83
+
84
+ def get_name(self) -> str:
85
+ return "hypercorn"
86
+
87
+ def get_supported_features(self) -> Dict[str, bool]:
88
+ return {
89
+ "ssl_tls": True,
90
+ "mtls_client_certs": True, # Full support
91
+ "ssl_scope_info": True, # SSL info in request scope
92
+ "client_cert_verification": True,
93
+ "websockets": True,
94
+ "http2": True,
95
+ "reload": True
96
+ }
97
+
98
+ def get_config_schema(self) -> Dict[str, Any]:
99
+ return {
100
+ "host": {"type": "string", "default": "127.0.0.1"},
101
+ "port": {"type": "integer", "default": 8000},
102
+ "log_level": {"type": "string", "default": "INFO"},
103
+ "certfile": {"type": "string", "optional": True},
104
+ "keyfile": {"type": "string", "optional": True},
105
+ "ca_certs": {"type": "string", "optional": True},
106
+ "verify_mode": {"type": "string", "optional": True},
107
+ "reload": {"type": "boolean", "default": False},
108
+ "workers": {"type": "integer", "optional": True}
109
+ }
110
+
111
+ def validate_config(self, config: Dict[str, Any]) -> bool:
112
+ """Validate hypercorn configuration."""
113
+ required_fields = ["host", "port"]
114
+
115
+ for field in required_fields:
116
+ if field not in config:
117
+ logger.error(f"Missing required field: {field}")
118
+ return False
119
+
120
+ # Validate SSL files exist if specified
121
+ ssl_files = ["certfile", "keyfile", "ca_certs"]
122
+ for ssl_file in ssl_files:
123
+ if ssl_file in config and config[ssl_file]:
124
+ if not Path(config[ssl_file]).exists():
125
+ logger.error(f"SSL file not found: {config[ssl_file]}")
126
+ return False
127
+
128
+ return True
129
+
130
+ def run_server(self, app: Any, config: Dict[str, Any]) -> None:
131
+ """Run hypercorn server."""
132
+ try:
133
+ import hypercorn.asyncio
134
+ import asyncio
135
+
136
+ # Prepare hypercorn config
137
+ hypercorn_config = {
138
+ "bind": f"{config.get('host', '127.0.0.1')}:{config.get('port', 8000)}",
139
+ "log_level": config.get("log_level", "INFO"),
140
+ "reload": config.get("reload", False)
141
+ }
142
+
143
+ # Add SSL configuration if provided
144
+ logger.info(f"🔍 DEBUG: Input config keys: {list(config.keys())}")
145
+ logger.info(f"🔍 DEBUG: Input config certfile: {config.get('certfile', 'NOT_FOUND')}")
146
+ logger.info(f"🔍 DEBUG: Input config keyfile: {config.get('keyfile', 'NOT_FOUND')}")
147
+ logger.info(f"🔍 DEBUG: Input config ca_certs: {config.get('ca_certs', 'NOT_FOUND')}")
148
+ logger.info(f"🔍 DEBUG: Input config verify_mode: {config.get('verify_mode', 'NOT_FOUND')}")
149
+
150
+ if "certfile" in config and config["certfile"]:
151
+ hypercorn_config["certfile"] = config["certfile"]
152
+ if "keyfile" in config and config["keyfile"]:
153
+ hypercorn_config["keyfile"] = config["keyfile"]
154
+ if "ca_certs" in config and config["ca_certs"]:
155
+ hypercorn_config["ca_certs"] = config["ca_certs"]
156
+ if "verify_mode" in config and config["verify_mode"]:
157
+ # Convert verify_mode string to SSL constant
158
+ verify_mode_str = config["verify_mode"]
159
+ if verify_mode_str == "CERT_NONE":
160
+ import ssl
161
+ hypercorn_config["verify_mode"] = ssl.CERT_NONE
162
+ elif verify_mode_str == "CERT_REQUIRED":
163
+ import ssl
164
+ hypercorn_config["verify_mode"] = ssl.CERT_REQUIRED
165
+ elif verify_mode_str == "CERT_OPTIONAL":
166
+ import ssl
167
+ hypercorn_config["verify_mode"] = ssl.CERT_OPTIONAL
168
+ else:
169
+ hypercorn_config["verify_mode"] = verify_mode_str
170
+
171
+ # Add workers if specified
172
+ if "workers" in config and config["workers"]:
173
+ hypercorn_config["workers"] = config["workers"]
174
+
175
+ logger.info(f"Starting hypercorn server with config: {hypercorn_config}")
176
+ logger.info(f"SSL config from input: {config.get('ssl', 'NOT_FOUND')}")
177
+ logger.info(f"Security SSL config: {config.get('security', {}).get('ssl', 'NOT_FOUND')}")
178
+ logger.info(f"🔍 DEBUG: Hypercorn verify_mode: {hypercorn_config.get('verify_mode', 'NOT_SET')}")
179
+ logger.info(f"🔍 DEBUG: Hypercorn ca_certs: {hypercorn_config.get('ca_certs', 'NOT_SET')}")
180
+
181
+ # Create config object
182
+ config_obj = hypercorn.Config()
183
+ for key, value in hypercorn_config.items():
184
+ setattr(config_obj, key, value)
185
+
186
+ # Run server
187
+ asyncio.run(hypercorn.asyncio.serve(app, config_obj))
188
+
189
+ except ImportError:
190
+ logger.error("hypercorn not installed. Install with: pip install hypercorn")
191
+ raise
192
+ except Exception as e:
193
+ logger.error(f"Failed to start hypercorn server: {e}")
194
+ raise
195
+
196
+
197
+ class ServerEngineFactory:
198
+ """
199
+ Factory for creating server engines.
200
+
201
+ This class manages the creation and configuration of different server engines.
202
+ """
203
+
204
+ _engines: Dict[str, ServerEngine] = {}
205
+
206
+ @classmethod
207
+ def register_engine(cls, engine: ServerEngine) -> None:
208
+ """
209
+ Register a server engine.
210
+
211
+ Args:
212
+ engine: Server engine instance to register
213
+ """
214
+ cls._engines[engine.get_name()] = engine
215
+ logger.info(f"Registered server engine: {engine.get_name()}")
216
+
217
+ @classmethod
218
+ def get_engine(cls, name: str) -> Optional[ServerEngine]:
219
+ """
220
+ Get a server engine by name.
221
+
222
+ Args:
223
+ name: Name of the server engine
224
+
225
+ Returns:
226
+ Server engine instance or None if not found
227
+ """
228
+ return cls._engines.get(name)
229
+
230
+ @classmethod
231
+ def get_available_engines(cls) -> Dict[str, ServerEngine]:
232
+ """
233
+ Get all available server engines.
234
+
235
+ Returns:
236
+ Dictionary mapping engine names to engine instances
237
+ """
238
+ return cls._engines.copy()
239
+
240
+ @classmethod
241
+ def get_engine_with_feature(cls, feature: str) -> Optional[ServerEngine]:
242
+ """
243
+ Get the first available engine that supports a specific feature.
244
+
245
+ Args:
246
+ feature: Name of the feature to check
247
+
248
+ Returns:
249
+ Server engine that supports the feature or None
250
+ """
251
+ for engine in cls._engines.values():
252
+ if engine.get_supported_features().get(feature, False):
253
+ return engine
254
+ return None
255
+
256
+ @classmethod
257
+ def initialize_default_engines(cls) -> None:
258
+ """Initialize default server engines."""
259
+ # Register hypercorn engine (only supported engine)
260
+ try:
261
+ import hypercorn
262
+ cls.register_engine(HypercornEngine())
263
+ logger.info("Hypercorn engine registered (full mTLS support available)")
264
+ except ImportError:
265
+ logger.error("Hypercorn not available - this is required for the framework")
266
+ raise
267
+
268
+
269
+ # Initialize default engines
270
+ ServerEngineFactory.initialize_default_engines()
@@ -117,6 +117,7 @@ class Settings:
117
117
  return {
118
118
  "auto_discovery": config.get("commands.auto_discovery", True),
119
119
  "discovery_path": config.get("commands.discovery_path", "mcp_proxy_adapter.commands"),
120
+ "auto_commands_path": config.get("commands.auto_commands_path"),
120
121
  "custom_commands_path": config.get("commands.custom_commands_path")
121
122
  }
122
123
 
@@ -0,0 +1,234 @@
1
+ """
2
+ SSL Utilities Module
3
+
4
+ This module provides utilities for SSL/TLS configuration and certificate validation.
5
+ Integrates with AuthValidator from Phase 0 for certificate validation.
6
+
7
+ Author: MCP Proxy Adapter Team
8
+ Version: 1.0.0
9
+ """
10
+
11
+ import ssl
12
+ import logging
13
+ from typing import List, Optional, Dict, Any
14
+ from pathlib import Path
15
+
16
+ from .auth_validator import AuthValidator
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class SSLUtils:
22
+ """
23
+ SSL utilities for creating SSL contexts and validating certificates.
24
+ """
25
+
26
+ # TLS version mapping
27
+ TLS_VERSIONS = {
28
+ "1.0": ssl.TLSVersion.TLSv1,
29
+ "1.1": ssl.TLSVersion.TLSv1_1,
30
+ "1.2": ssl.TLSVersion.TLSv1_2,
31
+ "1.3": ssl.TLSVersion.TLSv1_3
32
+ }
33
+
34
+ # Cipher suite mapping
35
+ CIPHER_SUITES = {
36
+ "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384",
37
+ "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256",
38
+ "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256",
39
+ "ECDHE-RSA-AES256-GCM-SHA384": "ECDHE-RSA-AES256-GCM-SHA384",
40
+ "ECDHE-RSA-AES128-GCM-SHA256": "ECDHE-RSA-AES128-GCM-SHA256",
41
+ "ECDHE-RSA-CHACHA20-POLY1305": "ECDHE-RSA-CHACHA20-POLY1305"
42
+ }
43
+
44
+ @staticmethod
45
+ def create_ssl_context(cert_file: str, key_file: str,
46
+ ca_cert: Optional[str] = None,
47
+ verify_client: bool = False,
48
+ cipher_suites: Optional[List[str]] = None,
49
+ min_tls_version: str = "1.2",
50
+ max_tls_version: str = "1.3") -> ssl.SSLContext:
51
+ """
52
+ Create SSL context with specified configuration.
53
+
54
+ Args:
55
+ cert_file: Path to certificate file
56
+ key_file: Path to private key file
57
+ ca_cert: Path to CA certificate file (optional)
58
+ verify_client: Whether to verify client certificates
59
+ cipher_suites: List of cipher suites to use
60
+ min_tls_version: Minimum TLS version
61
+ max_tls_version: Maximum TLS version
62
+
63
+ Returns:
64
+ Configured SSL context
65
+
66
+ Raises:
67
+ ValueError: If certificate validation fails
68
+ FileNotFoundError: If certificate or key files not found
69
+ """
70
+ # Validate certificate using AuthValidator
71
+ validator = AuthValidator()
72
+ result = validator.validate_certificate(cert_file)
73
+ if not result.is_valid:
74
+ raise ValueError(f"Invalid certificate: {result.error_message}")
75
+
76
+ # Check if files exist
77
+ if not Path(cert_file).exists():
78
+ raise FileNotFoundError(f"Certificate file not found: {cert_file}")
79
+ if not Path(key_file).exists():
80
+ raise FileNotFoundError(f"Key file not found: {key_file}")
81
+ if ca_cert and not Path(ca_cert).exists():
82
+ raise FileNotFoundError(f"CA certificate file not found: {ca_cert}")
83
+
84
+ # Create SSL context
85
+ if verify_client:
86
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
87
+ else:
88
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
89
+
90
+ # Load certificate and key
91
+ context.load_cert_chain(cert_file, key_file)
92
+
93
+ # Load CA certificate if provided
94
+ if ca_cert:
95
+ context.load_verify_locations(ca_cert)
96
+
97
+ # Configure client verification
98
+ if verify_client:
99
+ context.verify_mode = ssl.CERT_REQUIRED
100
+ context.check_hostname = False
101
+ else:
102
+ context.verify_mode = ssl.CERT_NONE
103
+
104
+ # Setup cipher suites
105
+ SSLUtils.setup_cipher_suites(context, cipher_suites or [])
106
+
107
+ # Setup TLS versions
108
+ SSLUtils.setup_tls_versions(context, min_tls_version, max_tls_version)
109
+
110
+ logger.info(f"SSL context created successfully with cert: {cert_file}")
111
+ return context
112
+
113
+ @staticmethod
114
+ def validate_certificate(cert_file: str) -> bool:
115
+ """
116
+ Validate certificate using AuthValidator.
117
+
118
+ Args:
119
+ cert_file: Path to certificate file
120
+
121
+ Returns:
122
+ True if certificate is valid, False otherwise
123
+ """
124
+ try:
125
+ validator = AuthValidator()
126
+ result = validator.validate_certificate(cert_file)
127
+ return result.is_valid
128
+ except Exception as e:
129
+ logger.error(f"Certificate validation failed: {e}")
130
+ return False
131
+
132
+ @staticmethod
133
+ def setup_cipher_suites(context: ssl.SSLContext, cipher_suites: List[str]) -> None:
134
+ """
135
+ Setup cipher suites for SSL context.
136
+
137
+ Args:
138
+ context: SSL context to configure
139
+ cipher_suites: List of cipher suite names
140
+ """
141
+ if not cipher_suites:
142
+ return
143
+
144
+ # Convert cipher suite names to actual cipher suite strings
145
+ actual_ciphers = []
146
+ for cipher_name in cipher_suites:
147
+ if cipher_name in SSLUtils.CIPHER_SUITES:
148
+ actual_ciphers.append(SSLUtils.CIPHER_SUITES[cipher_name])
149
+ else:
150
+ logger.warning(f"Unknown cipher suite: {cipher_name}")
151
+
152
+ if actual_ciphers:
153
+ try:
154
+ context.set_ciphers(":".join(actual_ciphers))
155
+ logger.info(f"Cipher suites configured: {actual_ciphers}")
156
+ except ssl.SSLError as e:
157
+ logger.error(f"Failed to set cipher suites: {e}")
158
+
159
+ @staticmethod
160
+ def setup_tls_versions(context: ssl.SSLContext, min_version: str, max_version: str) -> None:
161
+ """
162
+ Setup TLS version range for SSL context.
163
+
164
+ Args:
165
+ context: SSL context to configure
166
+ min_version: Minimum TLS version
167
+ max_version: Maximum TLS version
168
+ """
169
+ try:
170
+ min_tls = SSLUtils.TLS_VERSIONS.get(min_version)
171
+ max_tls = SSLUtils.TLS_VERSIONS.get(max_version)
172
+
173
+ if min_tls and max_tls:
174
+ context.minimum_version = min_tls
175
+ context.maximum_version = max_tls
176
+ logger.info(f"TLS versions configured: {min_version} - {max_version}")
177
+ else:
178
+ logger.warning(f"Invalid TLS version range: {min_version} - {max_version}")
179
+ except Exception as e:
180
+ logger.error(f"Failed to set TLS versions: {e}")
181
+
182
+ @staticmethod
183
+ def check_tls_version(min_version: str, max_version: str) -> bool:
184
+ """
185
+ Check if TLS version range is valid.
186
+
187
+ Args:
188
+ min_version: Minimum TLS version
189
+ max_version: Maximum TLS version
190
+
191
+ Returns:
192
+ True if version range is valid, False otherwise
193
+ """
194
+ min_tls = SSLUtils.TLS_VERSIONS.get(min_version)
195
+ max_tls = SSLUtils.TLS_VERSIONS.get(max_version)
196
+
197
+ if not min_tls or not max_tls:
198
+ return False
199
+
200
+ # Check if min version is less than or equal to max version
201
+ return min_tls <= max_tls
202
+
203
+ @staticmethod
204
+ def get_ssl_config_for_hypercorn(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
205
+ """
206
+ Get SSL configuration for hypercorn from transport configuration.
207
+
208
+ Args:
209
+ ssl_config: SSL configuration from transport manager
210
+
211
+ Returns:
212
+ Configuration for hypercorn
213
+ """
214
+ hypercorn_ssl = {}
215
+
216
+ if not ssl_config:
217
+ return hypercorn_ssl
218
+
219
+ # Basic SSL parameters
220
+ if ssl_config.get("cert_file"):
221
+ hypercorn_ssl["certfile"] = ssl_config["cert_file"]
222
+
223
+ if ssl_config.get("key_file"):
224
+ hypercorn_ssl["keyfile"] = ssl_config["key_file"]
225
+
226
+ if ssl_config.get("ca_cert"):
227
+ hypercorn_ssl["ca_certs"] = ssl_config["ca_cert"]
228
+
229
+ # Client verification mode
230
+ if ssl_config.get("verify_client", False):
231
+ hypercorn_ssl["verify_mode"] = "CERT_REQUIRED"
232
+
233
+ logger.info(f"Generated hypercorn SSL config: {hypercorn_ssl}")
234
+ return hypercorn_ssl