mcp-proxy-adapter 6.0.0__py3-none-any.whl → 6.1.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 (264) hide show
  1. mcp_proxy_adapter/api/app.py +174 -80
  2. mcp_proxy_adapter/api/handlers.py +16 -5
  3. mcp_proxy_adapter/api/middleware/__init__.py +9 -4
  4. mcp_proxy_adapter/api/middleware/command_permission_middleware.py +148 -0
  5. mcp_proxy_adapter/api/middleware/factory.py +36 -12
  6. mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
  7. mcp_proxy_adapter/api/middleware/unified_security.py +160 -0
  8. mcp_proxy_adapter/api/middleware/user_info_middleware.py +83 -0
  9. mcp_proxy_adapter/commands/__init__.py +7 -1
  10. mcp_proxy_adapter/commands/base.py +7 -4
  11. mcp_proxy_adapter/commands/builtin_commands.py +8 -2
  12. mcp_proxy_adapter/commands/command_registry.py +8 -0
  13. mcp_proxy_adapter/commands/echo_command.py +81 -0
  14. mcp_proxy_adapter/commands/help_command.py +21 -14
  15. mcp_proxy_adapter/commands/proxy_registration_command.py +326 -185
  16. mcp_proxy_adapter/commands/role_test_command.py +141 -0
  17. mcp_proxy_adapter/commands/security_command.py +488 -0
  18. mcp_proxy_adapter/commands/ssl_setup_command.py +2 -2
  19. mcp_proxy_adapter/commands/token_management_command.py +1 -1
  20. mcp_proxy_adapter/config.py +81 -21
  21. mcp_proxy_adapter/core/app_factory.py +326 -0
  22. mcp_proxy_adapter/core/client_security.py +384 -0
  23. mcp_proxy_adapter/core/logging.py +8 -3
  24. mcp_proxy_adapter/core/mtls_asgi.py +156 -0
  25. mcp_proxy_adapter/core/mtls_asgi_app.py +187 -0
  26. mcp_proxy_adapter/core/protocol_manager.py +139 -8
  27. mcp_proxy_adapter/core/proxy_client.py +602 -0
  28. mcp_proxy_adapter/core/proxy_registration.py +299 -47
  29. mcp_proxy_adapter/core/security_adapter.py +12 -15
  30. mcp_proxy_adapter/core/security_integration.py +285 -0
  31. mcp_proxy_adapter/core/server_adapter.py +345 -0
  32. mcp_proxy_adapter/core/server_engine.py +364 -0
  33. mcp_proxy_adapter/core/unified_config_adapter.py +579 -0
  34. mcp_proxy_adapter/docs/EN/TROUBLESHOOTING.md +285 -0
  35. mcp_proxy_adapter/docs/RU/TROUBLESHOOTING.md +285 -0
  36. mcp_proxy_adapter/examples/README.md +230 -97
  37. mcp_proxy_adapter/examples/README_EN.md +258 -0
  38. mcp_proxy_adapter/examples/SECURITY_TESTING.md +455 -0
  39. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +37 -0
  40. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +23 -0
  41. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +43 -0
  42. mcp_proxy_adapter/examples/basic_framework/configs/https_no_protocol_middleware.json +36 -0
  43. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +29 -0
  44. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_protocol_middleware.json +34 -0
  45. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +39 -0
  46. mcp_proxy_adapter/examples/basic_framework/configs/mtls_simple.json +35 -0
  47. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +45 -0
  48. mcp_proxy_adapter/examples/basic_framework/main.py +63 -0
  49. mcp_proxy_adapter/examples/basic_framework/roles.json +21 -0
  50. mcp_proxy_adapter/examples/cert_config.json +9 -0
  51. mcp_proxy_adapter/examples/certs/admin.crt +32 -0
  52. mcp_proxy_adapter/examples/certs/admin.key +52 -0
  53. mcp_proxy_adapter/examples/certs/admin_cert.pem +21 -0
  54. mcp_proxy_adapter/examples/certs/admin_key.pem +28 -0
  55. mcp_proxy_adapter/examples/certs/ca_cert.pem +23 -0
  56. mcp_proxy_adapter/examples/certs/ca_cert.srl +1 -0
  57. mcp_proxy_adapter/examples/certs/ca_key.pem +28 -0
  58. mcp_proxy_adapter/examples/certs/cert_config.json +9 -0
  59. mcp_proxy_adapter/examples/certs/client.crt +32 -0
  60. mcp_proxy_adapter/examples/certs/client.key +52 -0
  61. mcp_proxy_adapter/examples/certs/client_admin.crt +32 -0
  62. mcp_proxy_adapter/examples/certs/client_admin.key +52 -0
  63. mcp_proxy_adapter/examples/certs/client_user.crt +32 -0
  64. mcp_proxy_adapter/examples/certs/client_user.key +52 -0
  65. mcp_proxy_adapter/examples/certs/guest_cert.pem +21 -0
  66. mcp_proxy_adapter/examples/certs/guest_key.pem +28 -0
  67. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +23 -0
  68. mcp_proxy_adapter/examples/certs/proxy_cert.pem +21 -0
  69. mcp_proxy_adapter/examples/certs/proxy_key.pem +28 -0
  70. mcp_proxy_adapter/examples/certs/readonly.crt +32 -0
  71. mcp_proxy_adapter/examples/certs/readonly.key +52 -0
  72. mcp_proxy_adapter/examples/certs/readonly_cert.pem +21 -0
  73. mcp_proxy_adapter/examples/certs/readonly_key.pem +28 -0
  74. mcp_proxy_adapter/examples/certs/server.crt +32 -0
  75. mcp_proxy_adapter/examples/certs/server.key +52 -0
  76. mcp_proxy_adapter/examples/certs/server_cert.pem +32 -0
  77. mcp_proxy_adapter/examples/certs/server_key.pem +52 -0
  78. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +20 -0
  79. mcp_proxy_adapter/examples/certs/user.crt +32 -0
  80. mcp_proxy_adapter/examples/certs/user.key +52 -0
  81. mcp_proxy_adapter/examples/certs/user_cert.pem +21 -0
  82. mcp_proxy_adapter/examples/certs/user_key.pem +28 -0
  83. mcp_proxy_adapter/examples/client_configs/api_key_client.json +13 -0
  84. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +13 -0
  85. mcp_proxy_adapter/examples/client_configs/certificate_client.json +22 -0
  86. mcp_proxy_adapter/examples/client_configs/jwt_client.json +15 -0
  87. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +9 -0
  88. mcp_proxy_adapter/examples/commands/__init__.py +1 -0
  89. mcp_proxy_adapter/examples/create_certificates_simple.py +307 -0
  90. mcp_proxy_adapter/examples/debug_request_state.py +144 -0
  91. mcp_proxy_adapter/examples/debug_role_chain.py +205 -0
  92. mcp_proxy_adapter/examples/demo_client.py +341 -0
  93. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +99 -0
  94. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +106 -0
  95. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +37 -0
  96. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +23 -0
  97. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +39 -0
  98. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +25 -0
  99. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +39 -0
  100. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +45 -0
  101. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +97 -0
  102. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +95 -0
  103. mcp_proxy_adapter/examples/full_application/main.py +138 -0
  104. mcp_proxy_adapter/examples/full_application/roles.json +21 -0
  105. mcp_proxy_adapter/examples/generate_all_certificates.py +429 -0
  106. mcp_proxy_adapter/examples/generate_certificates.py +121 -0
  107. mcp_proxy_adapter/examples/keys/ca_key.pem +28 -0
  108. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +28 -0
  109. mcp_proxy_adapter/examples/keys/test_ca_ca.key +28 -0
  110. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +220 -0
  111. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +1 -0
  112. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +1 -0
  113. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +1 -0
  114. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +1 -0
  115. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +1 -0
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +220 -0
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +1 -0
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +1 -0
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +1 -0
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +1 -0
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +1 -0
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +2 -0
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +1 -0
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +1 -0
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +1 -0
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +1 -0
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +1 -0
  128. mcp_proxy_adapter/examples/proxy_registration_example.py +401 -0
  129. mcp_proxy_adapter/examples/roles.json +38 -0
  130. mcp_proxy_adapter/examples/run_example.py +81 -0
  131. mcp_proxy_adapter/examples/run_security_tests.py +326 -0
  132. mcp_proxy_adapter/examples/run_security_tests_fixed.py +300 -0
  133. mcp_proxy_adapter/examples/security_test_client.py +743 -0
  134. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +204 -0
  135. mcp_proxy_adapter/examples/server_configs/config_http_token.json +238 -0
  136. mcp_proxy_adapter/examples/server_configs/config_https.json +215 -0
  137. mcp_proxy_adapter/examples/server_configs/config_https_token.json +231 -0
  138. mcp_proxy_adapter/examples/server_configs/config_mtls.json +215 -0
  139. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +250 -0
  140. mcp_proxy_adapter/examples/server_configs/config_simple.json +46 -0
  141. mcp_proxy_adapter/examples/server_configs/roles.json +38 -0
  142. mcp_proxy_adapter/examples/test_config_generator.py +110 -0
  143. mcp_proxy_adapter/examples/test_examples.py +344 -0
  144. mcp_proxy_adapter/examples/universal_client.py +628 -0
  145. mcp_proxy_adapter/main.py +21 -10
  146. mcp_proxy_adapter/utils/config_generator.py +727 -0
  147. mcp_proxy_adapter/version.py +5 -2
  148. mcp_proxy_adapter-6.1.1.dist-info/METADATA +205 -0
  149. mcp_proxy_adapter-6.1.1.dist-info/RECORD +197 -0
  150. mcp_proxy_adapter-6.1.1.dist-info/entry_points.txt +2 -0
  151. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/licenses/LICENSE +2 -2
  152. mcp_proxy_adapter/api/middleware/auth.py +0 -146
  153. mcp_proxy_adapter/api/middleware/auth_adapter.py +0 -235
  154. mcp_proxy_adapter/api/middleware/mtls_adapter.py +0 -305
  155. mcp_proxy_adapter/api/middleware/mtls_middleware.py +0 -296
  156. mcp_proxy_adapter/api/middleware/rate_limit.py +0 -152
  157. mcp_proxy_adapter/api/middleware/rate_limit_adapter.py +0 -241
  158. mcp_proxy_adapter/api/middleware/roles_adapter.py +0 -365
  159. mcp_proxy_adapter/api/middleware/roles_middleware.py +0 -381
  160. mcp_proxy_adapter/api/middleware/security.py +0 -376
  161. mcp_proxy_adapter/api/middleware/token_auth_middleware.py +0 -261
  162. mcp_proxy_adapter/examples/__init__.py +0 -7
  163. mcp_proxy_adapter/examples/basic_server/README.md +0 -60
  164. mcp_proxy_adapter/examples/basic_server/__init__.py +0 -7
  165. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +0 -39
  166. mcp_proxy_adapter/examples/basic_server/config.json +0 -70
  167. mcp_proxy_adapter/examples/basic_server/config_all_protocols.json +0 -54
  168. mcp_proxy_adapter/examples/basic_server/config_http.json +0 -70
  169. mcp_proxy_adapter/examples/basic_server/config_http_only.json +0 -52
  170. mcp_proxy_adapter/examples/basic_server/config_https.json +0 -58
  171. mcp_proxy_adapter/examples/basic_server/config_mtls.json +0 -58
  172. mcp_proxy_adapter/examples/basic_server/config_ssl.json +0 -46
  173. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +0 -238
  174. mcp_proxy_adapter/examples/basic_server/server.py +0 -114
  175. mcp_proxy_adapter/examples/custom_commands/README.md +0 -127
  176. mcp_proxy_adapter/examples/custom_commands/__init__.py +0 -27
  177. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +0 -566
  178. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +0 -6
  179. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +0 -103
  180. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +0 -111
  181. mcp_proxy_adapter/examples/custom_commands/auto_commands/test_command.py +0 -105
  182. mcp_proxy_adapter/examples/custom_commands/catalog/commands/test_command.py +0 -129
  183. mcp_proxy_adapter/examples/custom_commands/config.json +0 -118
  184. mcp_proxy_adapter/examples/custom_commands/config_all_protocols.json +0 -46
  185. mcp_proxy_adapter/examples/custom_commands/config_https_only.json +0 -46
  186. mcp_proxy_adapter/examples/custom_commands/config_https_transport.json +0 -33
  187. mcp_proxy_adapter/examples/custom_commands/config_mtls_only.json +0 -46
  188. mcp_proxy_adapter/examples/custom_commands/config_mtls_transport.json +0 -33
  189. mcp_proxy_adapter/examples/custom_commands/config_single_transport.json +0 -33
  190. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +0 -169
  191. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +0 -215
  192. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +0 -76
  193. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +0 -96
  194. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +0 -241
  195. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +0 -135
  196. mcp_proxy_adapter/examples/custom_commands/echo_command.py +0 -122
  197. mcp_proxy_adapter/examples/custom_commands/full_help_response.json +0 -1
  198. mcp_proxy_adapter/examples/custom_commands/generated_openapi.json +0 -629
  199. mcp_proxy_adapter/examples/custom_commands/get_openapi.py +0 -103
  200. mcp_proxy_adapter/examples/custom_commands/hooks.py +0 -230
  201. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +0 -123
  202. mcp_proxy_adapter/examples/custom_commands/loadable_commands/test_ignored.py +0 -129
  203. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +0 -103
  204. mcp_proxy_adapter/examples/custom_commands/proxy_connection_manager.py +0 -278
  205. mcp_proxy_adapter/examples/custom_commands/server.py +0 -252
  206. mcp_proxy_adapter/examples/custom_commands/simple_openapi_server.py +0 -75
  207. mcp_proxy_adapter/examples/custom_commands/start_server_with_proxy_manager.py +0 -299
  208. mcp_proxy_adapter/examples/custom_commands/start_server_with_registration.py +0 -278
  209. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +0 -176
  210. mcp_proxy_adapter/examples/custom_commands/test_openapi.py +0 -27
  211. mcp_proxy_adapter/examples/custom_commands/test_registry.py +0 -23
  212. mcp_proxy_adapter/examples/custom_commands/test_simple.py +0 -19
  213. mcp_proxy_adapter/examples/custom_project_example/README.md +0 -103
  214. mcp_proxy_adapter/examples/custom_project_example/README_EN.md +0 -103
  215. mcp_proxy_adapter/examples/deployment/README.md +0 -49
  216. mcp_proxy_adapter/examples/deployment/__init__.py +0 -7
  217. mcp_proxy_adapter/examples/deployment/config.development.json +0 -8
  218. mcp_proxy_adapter/examples/deployment/config.json +0 -29
  219. mcp_proxy_adapter/examples/deployment/config.production.json +0 -12
  220. mcp_proxy_adapter/examples/deployment/config.staging.json +0 -11
  221. mcp_proxy_adapter/examples/deployment/docker-compose.yml +0 -31
  222. mcp_proxy_adapter/examples/deployment/run.sh +0 -43
  223. mcp_proxy_adapter/examples/deployment/run_docker.sh +0 -84
  224. mcp_proxy_adapter/examples/simple_custom_commands/README.md +0 -149
  225. mcp_proxy_adapter/examples/simple_custom_commands/README_EN.md +0 -149
  226. mcp_proxy_adapter/schemas/base_schema.json +0 -114
  227. mcp_proxy_adapter/schemas/openapi_schema.json +0 -314
  228. mcp_proxy_adapter/schemas/roles_schema.json +0 -162
  229. mcp_proxy_adapter/tests/__init__.py +0 -0
  230. mcp_proxy_adapter/tests/api/__init__.py +0 -3
  231. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +0 -115
  232. mcp_proxy_adapter/tests/api/test_custom_openapi.py +0 -617
  233. mcp_proxy_adapter/tests/api/test_handlers.py +0 -522
  234. mcp_proxy_adapter/tests/api/test_middleware.py +0 -340
  235. mcp_proxy_adapter/tests/api/test_schemas.py +0 -546
  236. mcp_proxy_adapter/tests/api/test_tool_integration.py +0 -531
  237. mcp_proxy_adapter/tests/commands/__init__.py +0 -3
  238. mcp_proxy_adapter/tests/commands/test_config_command.py +0 -211
  239. mcp_proxy_adapter/tests/commands/test_echo_command.py +0 -127
  240. mcp_proxy_adapter/tests/commands/test_help_command.py +0 -136
  241. mcp_proxy_adapter/tests/conftest.py +0 -131
  242. mcp_proxy_adapter/tests/functional/__init__.py +0 -3
  243. mcp_proxy_adapter/tests/functional/test_api.py +0 -253
  244. mcp_proxy_adapter/tests/integration/__init__.py +0 -3
  245. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +0 -129
  246. mcp_proxy_adapter/tests/integration/test_integration.py +0 -255
  247. mcp_proxy_adapter/tests/performance/__init__.py +0 -3
  248. mcp_proxy_adapter/tests/performance/test_performance.py +0 -189
  249. mcp_proxy_adapter/tests/stubs/__init__.py +0 -10
  250. mcp_proxy_adapter/tests/stubs/echo_command.py +0 -104
  251. mcp_proxy_adapter/tests/test_api_endpoints.py +0 -271
  252. mcp_proxy_adapter/tests/test_api_handlers.py +0 -289
  253. mcp_proxy_adapter/tests/test_base_command.py +0 -123
  254. mcp_proxy_adapter/tests/test_batch_requests.py +0 -117
  255. mcp_proxy_adapter/tests/test_command_registry.py +0 -281
  256. mcp_proxy_adapter/tests/test_config.py +0 -127
  257. mcp_proxy_adapter/tests/test_utils.py +0 -65
  258. mcp_proxy_adapter/tests/unit/__init__.py +0 -3
  259. mcp_proxy_adapter/tests/unit/test_base_command.py +0 -436
  260. mcp_proxy_adapter/tests/unit/test_config.py +0 -270
  261. mcp_proxy_adapter-6.0.0.dist-info/METADATA +0 -201
  262. mcp_proxy_adapter-6.0.0.dist-info/RECORD +0 -179
  263. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/WHEEL +0 -0
  264. {mcp_proxy_adapter-6.0.0.dist-info → mcp_proxy_adapter-6.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,364 @@
1
+ """
2
+ Server Engine Abstraction
3
+
4
+ This module provides an abstraction layer for different ASGI server engines,
5
+ allowing easy switching between uvicorn, hypercorn, and other servers.
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 UvicornEngine(ServerEngine):
78
+ """
79
+ Uvicorn server engine implementation.
80
+
81
+ Provides basic SSL/TLS support but limited mTLS capabilities.
82
+ """
83
+
84
+ def get_name(self) -> str:
85
+ return "uvicorn"
86
+
87
+ def get_supported_features(self) -> Dict[str, bool]:
88
+ return {
89
+ "ssl_tls": True,
90
+ "mtls_client_certs": False, # Limited support
91
+ "ssl_scope_info": False, # No SSL info in request scope
92
+ "client_cert_verification": False,
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
+ "ssl_certfile": {"type": "string", "optional": True},
104
+ "ssl_keyfile": {"type": "string", "optional": True},
105
+ "ssl_ca_certs": {"type": "string", "optional": True},
106
+ "ssl_cert_reqs": {"type": "integer", "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 uvicorn 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 = ["ssl_certfile", "ssl_keyfile", "ssl_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 uvicorn server."""
132
+ try:
133
+ import uvicorn
134
+
135
+ # Prepare uvicorn config
136
+ uvicorn_config = {
137
+ "host": config.get("host", "127.0.0.1"),
138
+ "port": config.get("port", 8000),
139
+ "log_level": config.get("log_level", "info").lower(),
140
+ "reload": config.get("reload", False)
141
+ }
142
+
143
+ # Add SSL configuration if provided
144
+ if "ssl_certfile" in config and config["ssl_certfile"]:
145
+ uvicorn_config["ssl_certfile"] = config["ssl_certfile"]
146
+ if "ssl_keyfile" in config and config["ssl_keyfile"]:
147
+ uvicorn_config["ssl_keyfile"] = config["ssl_keyfile"]
148
+ if "ssl_ca_certs" in config and config["ssl_ca_certs"]:
149
+ uvicorn_config["ssl_ca_certs"] = config["ssl_ca_certs"]
150
+ if "ssl_cert_reqs" in config and config["ssl_cert_reqs"]:
151
+ uvicorn_config["ssl_cert_reqs"] = config["ssl_cert_reqs"]
152
+
153
+ # Add workers if specified
154
+ if "workers" in config and config["workers"]:
155
+ uvicorn_config["workers"] = config["workers"]
156
+
157
+ logger.info(f"Starting uvicorn server with config: {uvicorn_config}")
158
+ uvicorn.run(app, **uvicorn_config)
159
+
160
+ except ImportError:
161
+ logger.error("uvicorn not installed. Install with: pip install uvicorn[standard]")
162
+ raise
163
+ except Exception as e:
164
+ logger.error(f"Failed to start uvicorn server: {e}")
165
+ raise
166
+
167
+
168
+ class HypercornEngine(ServerEngine):
169
+ """
170
+ Hypercorn server engine implementation.
171
+
172
+ Provides full mTLS support and better SSL capabilities.
173
+ """
174
+
175
+ def get_name(self) -> str:
176
+ return "hypercorn"
177
+
178
+ def get_supported_features(self) -> Dict[str, bool]:
179
+ return {
180
+ "ssl_tls": True,
181
+ "mtls_client_certs": True, # Full support
182
+ "ssl_scope_info": True, # SSL info in request scope
183
+ "client_cert_verification": True,
184
+ "websockets": True,
185
+ "http2": True,
186
+ "reload": True
187
+ }
188
+
189
+ def get_config_schema(self) -> Dict[str, Any]:
190
+ return {
191
+ "host": {"type": "string", "default": "127.0.0.1"},
192
+ "port": {"type": "integer", "default": 8000},
193
+ "log_level": {"type": "string", "default": "INFO"},
194
+ "certfile": {"type": "string", "optional": True},
195
+ "keyfile": {"type": "string", "optional": True},
196
+ "ca_certs": {"type": "string", "optional": True},
197
+ "verify_mode": {"type": "string", "optional": True},
198
+ "reload": {"type": "boolean", "default": False},
199
+ "workers": {"type": "integer", "optional": True}
200
+ }
201
+
202
+ def validate_config(self, config: Dict[str, Any]) -> bool:
203
+ """Validate hypercorn configuration."""
204
+ required_fields = ["host", "port"]
205
+
206
+ for field in required_fields:
207
+ if field not in config:
208
+ logger.error(f"Missing required field: {field}")
209
+ return False
210
+
211
+ # Validate SSL files exist if specified
212
+ ssl_files = ["certfile", "keyfile", "ca_certs"]
213
+ for ssl_file in ssl_files:
214
+ if ssl_file in config and config[ssl_file]:
215
+ if not Path(config[ssl_file]).exists():
216
+ logger.error(f"SSL file not found: {config[ssl_file]}")
217
+ return False
218
+
219
+ return True
220
+
221
+ def run_server(self, app: Any, config: Dict[str, Any]) -> None:
222
+ """Run hypercorn server."""
223
+ try:
224
+ import hypercorn.asyncio
225
+ import asyncio
226
+
227
+ # Prepare hypercorn config
228
+ hypercorn_config = {
229
+ "bind": f"{config.get('host', '127.0.0.1')}:{config.get('port', 8000)}",
230
+ "log_level": config.get("log_level", "INFO"),
231
+ "reload": config.get("reload", False)
232
+ }
233
+
234
+ # Add SSL configuration if provided
235
+ logger.info(f"🔍 DEBUG: Input config keys: {list(config.keys())}")
236
+ logger.info(f"🔍 DEBUG: Input config certfile: {config.get('certfile', 'NOT_FOUND')}")
237
+ logger.info(f"🔍 DEBUG: Input config keyfile: {config.get('keyfile', 'NOT_FOUND')}")
238
+ logger.info(f"🔍 DEBUG: Input config ca_certs: {config.get('ca_certs', 'NOT_FOUND')}")
239
+ logger.info(f"🔍 DEBUG: Input config verify_mode: {config.get('verify_mode', 'NOT_FOUND')}")
240
+
241
+ if "certfile" in config and config["certfile"]:
242
+ hypercorn_config["certfile"] = config["certfile"]
243
+ if "keyfile" in config and config["keyfile"]:
244
+ hypercorn_config["keyfile"] = config["keyfile"]
245
+ if "ca_certs" in config and config["ca_certs"]:
246
+ hypercorn_config["ca_certs"] = config["ca_certs"]
247
+ if "verify_mode" in config and config["verify_mode"]:
248
+ # Convert verify_mode string to SSL constant
249
+ verify_mode_str = config["verify_mode"]
250
+ if verify_mode_str == "CERT_NONE":
251
+ import ssl
252
+ hypercorn_config["verify_mode"] = ssl.CERT_NONE
253
+ elif verify_mode_str == "CERT_REQUIRED":
254
+ import ssl
255
+ hypercorn_config["verify_mode"] = ssl.CERT_REQUIRED
256
+ elif verify_mode_str == "CERT_OPTIONAL":
257
+ import ssl
258
+ hypercorn_config["verify_mode"] = ssl.CERT_OPTIONAL
259
+ else:
260
+ hypercorn_config["verify_mode"] = verify_mode_str
261
+
262
+ # Add workers if specified
263
+ if "workers" in config and config["workers"]:
264
+ hypercorn_config["workers"] = config["workers"]
265
+
266
+ logger.info(f"Starting hypercorn server with config: {hypercorn_config}")
267
+ logger.info(f"SSL config from input: {config.get('ssl', 'NOT_FOUND')}")
268
+ logger.info(f"Security SSL config: {config.get('security', {}).get('ssl', 'NOT_FOUND')}")
269
+ logger.info(f"🔍 DEBUG: Hypercorn verify_mode: {hypercorn_config.get('verify_mode', 'NOT_SET')}")
270
+ logger.info(f"🔍 DEBUG: Hypercorn ca_certs: {hypercorn_config.get('ca_certs', 'NOT_SET')}")
271
+
272
+ # Create config object
273
+ config_obj = hypercorn.Config()
274
+ for key, value in hypercorn_config.items():
275
+ setattr(config_obj, key, value)
276
+
277
+ # Run server
278
+ asyncio.run(hypercorn.asyncio.serve(app, config_obj))
279
+
280
+ except ImportError:
281
+ logger.error("hypercorn not installed. Install with: pip install hypercorn")
282
+ raise
283
+ except Exception as e:
284
+ logger.error(f"Failed to start hypercorn server: {e}")
285
+ raise
286
+
287
+
288
+ class ServerEngineFactory:
289
+ """
290
+ Factory for creating server engines.
291
+
292
+ This class manages the creation and configuration of different server engines.
293
+ """
294
+
295
+ _engines: Dict[str, ServerEngine] = {}
296
+
297
+ @classmethod
298
+ def register_engine(cls, engine: ServerEngine) -> None:
299
+ """
300
+ Register a server engine.
301
+
302
+ Args:
303
+ engine: Server engine instance to register
304
+ """
305
+ cls._engines[engine.get_name()] = engine
306
+ logger.info(f"Registered server engine: {engine.get_name()}")
307
+
308
+ @classmethod
309
+ def get_engine(cls, name: str) -> Optional[ServerEngine]:
310
+ """
311
+ Get a server engine by name.
312
+
313
+ Args:
314
+ name: Name of the server engine
315
+
316
+ Returns:
317
+ Server engine instance or None if not found
318
+ """
319
+ return cls._engines.get(name)
320
+
321
+ @classmethod
322
+ def get_available_engines(cls) -> Dict[str, ServerEngine]:
323
+ """
324
+ Get all available server engines.
325
+
326
+ Returns:
327
+ Dictionary mapping engine names to engine instances
328
+ """
329
+ return cls._engines.copy()
330
+
331
+ @classmethod
332
+ def get_engine_with_feature(cls, feature: str) -> Optional[ServerEngine]:
333
+ """
334
+ Get the first available engine that supports a specific feature.
335
+
336
+ Args:
337
+ feature: Name of the feature to check
338
+
339
+ Returns:
340
+ Server engine that supports the feature or None
341
+ """
342
+ for engine in cls._engines.values():
343
+ if engine.get_supported_features().get(feature, False):
344
+ return engine
345
+ return None
346
+
347
+ @classmethod
348
+ def initialize_default_engines(cls) -> None:
349
+ """Initialize default server engines."""
350
+ # Try to register hypercorn engine first (preferred for mTLS)
351
+ try:
352
+ import hypercorn
353
+ cls.register_engine(HypercornEngine())
354
+ logger.info("Hypercorn engine registered (full mTLS support available)")
355
+ except ImportError:
356
+ logger.info("Hypercorn not available")
357
+
358
+ # Register uvicorn engine as fallback
359
+ cls.register_engine(UvicornEngine())
360
+ logger.info("Uvicorn engine registered (fallback)")
361
+
362
+
363
+ # Initialize default engines
364
+ ServerEngineFactory.initialize_default_engines()