mcp-proxy-adapter 6.1.0__py3-none-any.whl → 6.2.0__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 (148) hide show
  1. mcp_proxy_adapter/__main__.py +27 -7
  2. mcp_proxy_adapter/api/app.py +18 -7
  3. mcp_proxy_adapter/api/middleware/__init__.py +2 -2
  4. mcp_proxy_adapter/api/middleware/protocol_middleware.py +32 -13
  5. mcp_proxy_adapter/api/middleware/unified_security.py +12 -4
  6. mcp_proxy_adapter/commands/ssl_setup_command.py +234 -351
  7. mcp_proxy_adapter/core/app_factory.py +87 -3
  8. mcp_proxy_adapter/core/app_runner.py +272 -0
  9. mcp_proxy_adapter/core/certificate_utils.py +291 -73
  10. mcp_proxy_adapter/core/client.py +574 -0
  11. mcp_proxy_adapter/core/client_manager.py +284 -0
  12. mcp_proxy_adapter/core/protocol_manager.py +132 -10
  13. mcp_proxy_adapter/core/security_integration.py +19 -11
  14. mcp_proxy_adapter/core/server_adapter.py +17 -80
  15. mcp_proxy_adapter/core/server_engine.py +5 -99
  16. mcp_proxy_adapter/core/ssl_utils.py +13 -12
  17. mcp_proxy_adapter/core/transport_manager.py +5 -5
  18. mcp_proxy_adapter/examples/__init__.py +16 -0
  19. mcp_proxy_adapter/examples/basic_framework/__init__.py +7 -0
  20. mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
  21. mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
  22. mcp_proxy_adapter/examples/basic_framework/main.py +21 -40
  23. mcp_proxy_adapter/examples/commands/__init__.py +5 -1
  24. mcp_proxy_adapter/examples/create_certificates_simple.py +260 -75
  25. mcp_proxy_adapter/examples/debug_request_state.py +4 -36
  26. mcp_proxy_adapter/examples/debug_role_chain.py +2 -49
  27. mcp_proxy_adapter/examples/demo_client.py +0 -66
  28. mcp_proxy_adapter/examples/full_application/__init__.py +11 -0
  29. mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
  30. mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -19
  31. mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -16
  32. mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
  33. mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -22
  34. mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -24
  35. mcp_proxy_adapter/examples/full_application/main.py +65 -44
  36. mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
  37. mcp_proxy_adapter/examples/generate_all_certificates.py +0 -67
  38. mcp_proxy_adapter/examples/generate_certificates.py +0 -15
  39. mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
  40. mcp_proxy_adapter/examples/generate_test_configs.py +204 -0
  41. mcp_proxy_adapter/examples/proxy_registration_example.py +3 -70
  42. mcp_proxy_adapter/examples/run_example.py +1 -23
  43. mcp_proxy_adapter/examples/run_security_tests.py +2 -60
  44. mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -53
  45. mcp_proxy_adapter/examples/security_test_client.py +18 -123
  46. mcp_proxy_adapter/examples/setup_test_environment.py +179 -0
  47. mcp_proxy_adapter/examples/test_config.py +148 -0
  48. mcp_proxy_adapter/examples/test_config_generator.py +86 -0
  49. mcp_proxy_adapter/examples/test_examples.py +4 -67
  50. mcp_proxy_adapter/examples/universal_client.py +154 -162
  51. mcp_proxy_adapter/main.py +51 -161
  52. mcp_proxy_adapter/utils/config_generator.py +90 -2
  53. mcp_proxy_adapter/version.py +4 -2
  54. mcp_proxy_adapter-6.2.0.dist-info/METADATA +687 -0
  55. mcp_proxy_adapter-6.2.0.dist-info/RECORD +122 -0
  56. mcp_proxy_adapter/examples/README.md +0 -257
  57. mcp_proxy_adapter/examples/README_EN.md +0 -258
  58. mcp_proxy_adapter/examples/SECURITY_TESTING.md +0 -455
  59. mcp_proxy_adapter/examples/__pycache__/security_configurations.cpython-312.pyc +0 -0
  60. mcp_proxy_adapter/examples/__pycache__/security_test_client.cpython-312.pyc +0 -0
  61. mcp_proxy_adapter/examples/basic_framework/configs/http_auth.json +0 -37
  62. mcp_proxy_adapter/examples/basic_framework/configs/http_simple.json +0 -23
  63. mcp_proxy_adapter/examples/basic_framework/configs/https_auth.json +0 -39
  64. mcp_proxy_adapter/examples/basic_framework/configs/https_simple.json +0 -25
  65. mcp_proxy_adapter/examples/basic_framework/configs/mtls_no_roles.json +0 -39
  66. mcp_proxy_adapter/examples/basic_framework/configs/mtls_with_roles.json +0 -45
  67. mcp_proxy_adapter/examples/basic_framework/roles.json +0 -21
  68. mcp_proxy_adapter/examples/cert_config.json +0 -9
  69. mcp_proxy_adapter/examples/certs/admin.crt +0 -32
  70. mcp_proxy_adapter/examples/certs/admin.key +0 -52
  71. mcp_proxy_adapter/examples/certs/admin_cert.pem +0 -21
  72. mcp_proxy_adapter/examples/certs/admin_key.pem +0 -28
  73. mcp_proxy_adapter/examples/certs/ca_cert.pem +0 -23
  74. mcp_proxy_adapter/examples/certs/ca_cert.srl +0 -1
  75. mcp_proxy_adapter/examples/certs/ca_key.pem +0 -28
  76. mcp_proxy_adapter/examples/certs/cert_config.json +0 -9
  77. mcp_proxy_adapter/examples/certs/client.crt +0 -32
  78. mcp_proxy_adapter/examples/certs/client.key +0 -52
  79. mcp_proxy_adapter/examples/certs/client_admin.crt +0 -32
  80. mcp_proxy_adapter/examples/certs/client_admin.key +0 -52
  81. mcp_proxy_adapter/examples/certs/client_user.crt +0 -32
  82. mcp_proxy_adapter/examples/certs/client_user.key +0 -52
  83. mcp_proxy_adapter/examples/certs/guest_cert.pem +0 -21
  84. mcp_proxy_adapter/examples/certs/guest_key.pem +0 -28
  85. mcp_proxy_adapter/examples/certs/mcp_proxy_adapter_ca_ca.crt +0 -23
  86. mcp_proxy_adapter/examples/certs/proxy_cert.pem +0 -21
  87. mcp_proxy_adapter/examples/certs/proxy_key.pem +0 -28
  88. mcp_proxy_adapter/examples/certs/readonly.crt +0 -32
  89. mcp_proxy_adapter/examples/certs/readonly.key +0 -52
  90. mcp_proxy_adapter/examples/certs/readonly_cert.pem +0 -21
  91. mcp_proxy_adapter/examples/certs/readonly_key.pem +0 -28
  92. mcp_proxy_adapter/examples/certs/server.crt +0 -32
  93. mcp_proxy_adapter/examples/certs/server.key +0 -52
  94. mcp_proxy_adapter/examples/certs/server_cert.pem +0 -32
  95. mcp_proxy_adapter/examples/certs/server_key.pem +0 -52
  96. mcp_proxy_adapter/examples/certs/test_ca_ca.crt +0 -20
  97. mcp_proxy_adapter/examples/certs/user.crt +0 -32
  98. mcp_proxy_adapter/examples/certs/user.key +0 -52
  99. mcp_proxy_adapter/examples/certs/user_cert.pem +0 -21
  100. mcp_proxy_adapter/examples/certs/user_key.pem +0 -28
  101. mcp_proxy_adapter/examples/client_configs/api_key_client.json +0 -13
  102. mcp_proxy_adapter/examples/client_configs/basic_auth_client.json +0 -13
  103. mcp_proxy_adapter/examples/client_configs/certificate_client.json +0 -22
  104. mcp_proxy_adapter/examples/client_configs/jwt_client.json +0 -15
  105. mcp_proxy_adapter/examples/client_configs/no_auth_client.json +0 -9
  106. mcp_proxy_adapter/examples/full_application/configs/http_auth.json +0 -37
  107. mcp_proxy_adapter/examples/full_application/configs/http_simple.json +0 -23
  108. mcp_proxy_adapter/examples/full_application/configs/https_auth.json +0 -39
  109. mcp_proxy_adapter/examples/full_application/configs/https_simple.json +0 -25
  110. mcp_proxy_adapter/examples/full_application/configs/mtls_no_roles.json +0 -39
  111. mcp_proxy_adapter/examples/full_application/configs/mtls_with_roles.json +0 -45
  112. mcp_proxy_adapter/examples/full_application/roles.json +0 -21
  113. mcp_proxy_adapter/examples/keys/ca_key.pem +0 -28
  114. mcp_proxy_adapter/examples/keys/mcp_proxy_adapter_ca_ca.key +0 -28
  115. mcp_proxy_adapter/examples/keys/test_ca_ca.key +0 -28
  116. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log +0 -220
  117. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.1 +0 -1
  118. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.2 +0 -1
  119. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.3 +0 -1
  120. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.4 +0 -1
  121. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter.log.5 +0 -1
  122. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log +0 -220
  123. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.1 +0 -1
  124. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.2 +0 -1
  125. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.3 +0 -1
  126. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.4 +0 -1
  127. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_access.log.5 +0 -1
  128. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log +0 -2
  129. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.1 +0 -1
  130. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.2 +0 -1
  131. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.3 +0 -1
  132. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.4 +0 -1
  133. mcp_proxy_adapter/examples/logs/mcp_proxy_adapter_error.log.5 +0 -1
  134. mcp_proxy_adapter/examples/roles.json +0 -38
  135. mcp_proxy_adapter/examples/server_configs/config_basic_http.json +0 -204
  136. mcp_proxy_adapter/examples/server_configs/config_http_token.json +0 -238
  137. mcp_proxy_adapter/examples/server_configs/config_https.json +0 -215
  138. mcp_proxy_adapter/examples/server_configs/config_https_token.json +0 -231
  139. mcp_proxy_adapter/examples/server_configs/config_mtls.json +0 -215
  140. mcp_proxy_adapter/examples/server_configs/config_proxy_registration.json +0 -250
  141. mcp_proxy_adapter/examples/server_configs/config_simple.json +0 -46
  142. mcp_proxy_adapter/examples/server_configs/roles.json +0 -38
  143. mcp_proxy_adapter-6.1.0.dist-info/METADATA +0 -205
  144. mcp_proxy_adapter-6.1.0.dist-info/RECORD +0 -193
  145. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/WHEEL +0 -0
  146. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/entry_points.txt +0 -0
  147. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/licenses/LICENSE +0 -0
  148. {mcp_proxy_adapter-6.1.0.dist-info → mcp_proxy_adapter-6.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,284 @@
1
+ """
2
+ Client Manager for MCP Proxy Adapter Framework
3
+
4
+ This module provides client management functionality for the MCP Proxy Adapter framework.
5
+ It handles client creation, connection management, and proxy registration.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ import asyncio
12
+ import json
13
+ import logging
14
+ from typing import Dict, Any, Optional, List
15
+ from pathlib import Path
16
+
17
+ from .client import UniversalClient, create_client_from_config
18
+
19
+
20
+ class ClientManager:
21
+ """
22
+ Manages client connections and proxy registrations.
23
+
24
+ This class provides functionality for:
25
+ - Creating and managing client connections
26
+ - Proxy registration
27
+ - Connection pooling
28
+ - Authentication management
29
+ """
30
+
31
+ def __init__(self, config: Dict[str, Any]):
32
+ """
33
+ Initialize client manager.
34
+
35
+ Args:
36
+ config: Client manager configuration
37
+ """
38
+ self.config = config
39
+ self.logger = logging.getLogger(__name__)
40
+ self.clients: Dict[str, UniversalClient] = {}
41
+ self.connection_pool: Dict[str, UniversalClient] = {}
42
+
43
+ # Client manager settings
44
+ self.max_connections = config.get("max_connections", 10)
45
+ self.connection_timeout = config.get("connection_timeout", 30)
46
+ self.retry_attempts = config.get("retry_attempts", 3)
47
+ self.retry_delay = config.get("retry_delay", 1)
48
+
49
+ self.logger.info("Client manager initialized")
50
+
51
+ async def create_client(self, client_id: str, config_file: str) -> UniversalClient:
52
+ """
53
+ Create a new client instance.
54
+
55
+ Args:
56
+ client_id: Unique identifier for the client
57
+ config_file: Path to client configuration file
58
+
59
+ Returns:
60
+ UniversalClient instance
61
+ """
62
+ try:
63
+ if client_id in self.clients:
64
+ self.logger.warning(f"Client {client_id} already exists, reusing existing connection")
65
+ return self.clients[client_id]
66
+
67
+ client = create_client_from_config(config_file)
68
+ self.clients[client_id] = client
69
+
70
+ self.logger.info(f"Client {client_id} created successfully")
71
+ return client
72
+
73
+ except Exception as e:
74
+ self.logger.error(f"Failed to create client {client_id}: {e}")
75
+ raise
76
+
77
+ async def get_client(self, client_id: str) -> Optional[UniversalClient]:
78
+ """
79
+ Get an existing client instance.
80
+
81
+ Args:
82
+ client_id: Client identifier
83
+
84
+ Returns:
85
+ UniversalClient instance or None if not found
86
+ """
87
+ return self.clients.get(client_id)
88
+
89
+ async def remove_client(self, client_id: str) -> bool:
90
+ """
91
+ Remove a client instance.
92
+
93
+ Args:
94
+ client_id: Client identifier
95
+
96
+ Returns:
97
+ True if client was removed, False otherwise
98
+ """
99
+ if client_id in self.clients:
100
+ client = self.clients[client_id]
101
+ await client.disconnect()
102
+ del self.clients[client_id]
103
+ self.logger.info(f"Client {client_id} removed")
104
+ return True
105
+ return False
106
+
107
+ async def test_client_connection(self, client_id: str) -> bool:
108
+ """
109
+ Test connection for a specific client.
110
+
111
+ Args:
112
+ client_id: Client identifier
113
+
114
+ Returns:
115
+ True if connection is successful, False otherwise
116
+ """
117
+ client = await self.get_client(client_id)
118
+ if not client:
119
+ self.logger.error(f"Client {client_id} not found")
120
+ return False
121
+
122
+ try:
123
+ return await client.test_connection()
124
+ except Exception as e:
125
+ self.logger.error(f"Connection test failed for client {client_id}: {e}")
126
+ return False
127
+
128
+ async def register_proxy(self, client_id: str, proxy_config: Dict[str, Any]) -> Dict[str, Any]:
129
+ """
130
+ Register with proxy server using a specific client.
131
+
132
+ Args:
133
+ client_id: Client identifier
134
+ proxy_config: Proxy registration configuration
135
+
136
+ Returns:
137
+ Registration result
138
+ """
139
+ client = await self.get_client(client_id)
140
+ if not client:
141
+ return {"error": f"Client {client_id} not found"}
142
+
143
+ try:
144
+ result = await client.register_proxy(proxy_config)
145
+ self.logger.info(f"Proxy registration completed for client {client_id}")
146
+ return result
147
+ except Exception as e:
148
+ self.logger.error(f"Proxy registration failed for client {client_id}: {e}")
149
+ return {"error": str(e)}
150
+
151
+ async def execute_command(self, client_id: str, command: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
152
+ """
153
+ Execute a command using a specific client.
154
+
155
+ Args:
156
+ client_id: Client identifier
157
+ command: Command name
158
+ params: Command parameters
159
+
160
+ Returns:
161
+ Command result
162
+ """
163
+ client = await self.get_client(client_id)
164
+ if not client:
165
+ return {"error": f"Client {client_id} not found"}
166
+
167
+ try:
168
+ result = await client.execute_command(command, params or {})
169
+ self.logger.info(f"Command {command} executed for client {client_id}")
170
+ return result
171
+ except Exception as e:
172
+ self.logger.error(f"Command execution failed for client {client_id}: {e}")
173
+ return {"error": str(e)}
174
+
175
+ async def get_client_status(self, client_id: str) -> Dict[str, Any]:
176
+ """
177
+ Get status information for a specific client.
178
+
179
+ Args:
180
+ client_id: Client identifier
181
+
182
+ Returns:
183
+ Client status information
184
+ """
185
+ client = await self.get_client(client_id)
186
+ if not client:
187
+ return {"error": f"Client {client_id} not found"}
188
+
189
+ try:
190
+ # Test connection
191
+ connection_ok = await client.test_connection()
192
+
193
+ # Test security features
194
+ security_features = await client.test_security_features()
195
+
196
+ status = {
197
+ "client_id": client_id,
198
+ "base_url": client.base_url,
199
+ "auth_method": client.auth_method,
200
+ "connection_ok": connection_ok,
201
+ "security_features": security_features,
202
+ "session_active": client.session is not None
203
+ }
204
+
205
+ return status
206
+ except Exception as e:
207
+ self.logger.error(f"Failed to get status for client {client_id}: {e}")
208
+ return {"error": str(e)}
209
+
210
+ async def list_clients(self) -> List[str]:
211
+ """
212
+ Get list of all client identifiers.
213
+
214
+ Returns:
215
+ List of client identifiers
216
+ """
217
+ return list(self.clients.keys())
218
+
219
+ async def cleanup(self):
220
+ """Clean up all client connections."""
221
+ for client_id in list(self.clients.keys()):
222
+ await self.remove_client(client_id)
223
+ self.logger.info("All client connections cleaned up")
224
+
225
+ async def __aenter__(self):
226
+ """Async context manager entry."""
227
+ return self
228
+
229
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
230
+ """Async context manager exit."""
231
+ await self.cleanup()
232
+
233
+
234
+ def create_client_manager(config: Dict[str, Any]) -> ClientManager:
235
+ """
236
+ Create a ClientManager instance.
237
+
238
+ Args:
239
+ config: Client manager configuration
240
+
241
+ Returns:
242
+ ClientManager instance
243
+ """
244
+ return ClientManager(config)
245
+
246
+
247
+ # Example usage and testing functions
248
+ async def test_client_manager():
249
+ """Test client manager functionality."""
250
+ # Example configuration
251
+ config = {
252
+ "max_connections": 5,
253
+ "connection_timeout": 30,
254
+ "retry_attempts": 3,
255
+ "retry_delay": 1
256
+ }
257
+
258
+ async with ClientManager(config) as manager:
259
+ # Create a client
260
+ client_id = "test_client"
261
+ config_file = "configs/http_simple.json"
262
+
263
+ try:
264
+ client = await manager.create_client(client_id, config_file)
265
+ print(f"✅ Client {client_id} created successfully")
266
+
267
+ # Test connection
268
+ connection_ok = await manager.test_client_connection(client_id)
269
+ print(f"✅ Connection test: {connection_ok}")
270
+
271
+ # Get status
272
+ status = await manager.get_client_status(client_id)
273
+ print(f"✅ Client status: {json.dumps(status, indent=2)}")
274
+
275
+ # Execute command
276
+ result = await manager.execute_command(client_id, "help")
277
+ print(f"✅ Command result: {json.dumps(result, indent=2)}")
278
+
279
+ except Exception as e:
280
+ print(f"❌ Test failed: {e}")
281
+
282
+
283
+ if __name__ == "__main__":
284
+ asyncio.run(test_client_manager())
@@ -21,19 +21,84 @@ class ProtocolManager:
21
21
  ensuring that only configured protocols are accessible.
22
22
  """
23
23
 
24
- def __init__(self):
25
- """Initialize the protocol manager."""
24
+ def __init__(self, app_config: Optional[Dict] = None):
25
+ """
26
+ Initialize the protocol manager.
27
+
28
+ Args:
29
+ app_config: Application configuration dictionary (optional)
30
+ """
31
+ self.app_config = app_config
26
32
  self._load_config()
27
33
 
28
34
  def _load_config(self):
29
35
  """Load protocol configuration from config."""
30
- self.protocols_config = config.get("protocols", {})
36
+ # Use provided config or fallback to global config
37
+ current_config = self.app_config if self.app_config is not None else config.get_all()
38
+
39
+ # Get protocols configuration
40
+ self.protocols_config = current_config.get("protocols", {})
31
41
  self.enabled = self.protocols_config.get("enabled", True)
32
- self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http"])
33
- logger.debug(f"Protocol manager loaded config: enabled={self.enabled}, allowed_protocols={self.allowed_protocols}")
42
+
43
+ # Get SSL configuration to determine allowed protocols
44
+ ssl_enabled = self._is_ssl_enabled(current_config)
45
+
46
+ # Set allowed protocols based on SSL configuration
47
+ if ssl_enabled:
48
+ # If SSL is enabled, allow both HTTP and HTTPS
49
+ self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http", "https"])
50
+ # Ensure HTTPS is in allowed protocols if SSL is enabled
51
+ if "https" not in self.allowed_protocols:
52
+ self.allowed_protocols.append("https")
53
+ else:
54
+ # If SSL is disabled, only allow HTTP
55
+ self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http"])
56
+ # Remove HTTPS from allowed protocols if SSL is disabled
57
+ if "https" in self.allowed_protocols:
58
+ self.allowed_protocols.remove("https")
59
+
60
+ logger.debug(f"Protocol manager loaded config: enabled={self.enabled}, allowed_protocols={self.allowed_protocols}, ssl_enabled={ssl_enabled}")
61
+
62
+ def _is_ssl_enabled(self, current_config: Dict) -> bool:
63
+ """
64
+ Check if SSL is enabled in configuration.
65
+
66
+ Args:
67
+ current_config: Current configuration dictionary
68
+
69
+ Returns:
70
+ True if SSL is enabled, False otherwise
71
+ """
72
+ # Try security framework SSL config first
73
+ security_config = current_config.get("security", {})
74
+ ssl_config = security_config.get("ssl", {})
75
+
76
+ if ssl_config.get("enabled", False):
77
+ logger.debug("SSL enabled via security.ssl configuration")
78
+ return True
79
+
80
+ # Fallback to legacy SSL config
81
+ legacy_ssl_config = current_config.get("ssl", {})
82
+ if legacy_ssl_config.get("enabled", False):
83
+ logger.debug("SSL enabled via legacy ssl configuration")
84
+ return True
85
+
86
+ logger.debug("SSL is disabled in configuration")
87
+ return False
88
+
89
+ def update_config(self, new_config: Dict):
90
+ """
91
+ Update configuration and reload protocol settings.
92
+
93
+ Args:
94
+ new_config: New configuration dictionary
95
+ """
96
+ self.app_config = new_config
97
+ self._load_config()
98
+ logger.info(f"Protocol manager configuration updated: allowed_protocols={self.allowed_protocols}")
34
99
 
35
100
  def reload_config(self):
36
- """Reload protocol configuration."""
101
+ """Reload protocol configuration from global config."""
37
102
  self._load_config()
38
103
 
39
104
  def is_protocol_allowed(self, protocol: str) -> bool:
@@ -137,7 +202,11 @@ class ProtocolManager:
137
202
  if protocol.lower() not in ["https", "mtls"]:
138
203
  return None
139
204
 
140
- ssl_config = config.get("ssl", {})
205
+ # Use provided config or fallback to global config
206
+ current_config = self.app_config if self.app_config is not None else config.get_all()
207
+
208
+ # Get SSL configuration
209
+ ssl_config = self._get_ssl_config(current_config)
141
210
 
142
211
  if not ssl_config.get("enabled", False):
143
212
  logger.warning(f"SSL required for protocol '{protocol}' but SSL is disabled")
@@ -170,6 +239,33 @@ class ProtocolManager:
170
239
  logger.error(f"Failed to create SSL context for protocol '{protocol}': {e}")
171
240
  return None
172
241
 
242
+ def _get_ssl_config(self, current_config: Dict) -> Dict:
243
+ """
244
+ Get SSL configuration from config.
245
+
246
+ Args:
247
+ current_config: Current configuration dictionary
248
+
249
+ Returns:
250
+ SSL configuration dictionary
251
+ """
252
+ # Try security framework SSL config first
253
+ security_config = current_config.get("security", {})
254
+ ssl_config = security_config.get("ssl", {})
255
+
256
+ if ssl_config.get("enabled", False):
257
+ logger.debug("Using security.ssl configuration")
258
+ return ssl_config
259
+
260
+ # Fallback to legacy SSL config
261
+ legacy_ssl_config = current_config.get("ssl", {})
262
+ if legacy_ssl_config.get("enabled", False):
263
+ logger.debug("Using legacy ssl configuration")
264
+ return legacy_ssl_config
265
+
266
+ # Return empty config if SSL is disabled
267
+ return {"enabled": False}
268
+
173
269
  def get_protocol_info(self) -> Dict[str, Dict]:
174
270
  """
175
271
  Get information about all configured protocols.
@@ -222,7 +318,10 @@ class ProtocolManager:
222
318
 
223
319
  # Check SSL requirements
224
320
  if protocol in ["https", "mtls"]:
225
- ssl_config = config.get("ssl", {})
321
+ # Use provided config or fallback to global config
322
+ current_config = self.app_config if self.app_config is not None else config.get_all()
323
+ ssl_config = self._get_ssl_config(current_config)
324
+
226
325
  if not ssl_config.get("enabled", False):
227
326
  errors.append(f"Protocol '{protocol}' requires SSL but SSL is disabled")
228
327
  elif not ssl_config.get("cert_file") or not ssl_config.get("key_file"):
@@ -231,5 +330,28 @@ class ProtocolManager:
231
330
  return errors
232
331
 
233
332
 
234
- # Global protocol manager instance
235
- protocol_manager = ProtocolManager()
333
+ # Global protocol manager instance - will be updated with config when needed
334
+ protocol_manager = None
335
+
336
+ def get_protocol_manager(app_config: Optional[Dict] = None) -> ProtocolManager:
337
+ """
338
+ Get protocol manager instance with current configuration.
339
+
340
+ Args:
341
+ app_config: Application configuration dictionary (optional)
342
+
343
+ Returns:
344
+ ProtocolManager instance
345
+ """
346
+ global protocol_manager
347
+
348
+ # If no app_config provided, use global config
349
+ if app_config is None:
350
+ app_config = config.get_all()
351
+
352
+ # Create new instance if none exists or config changed
353
+ if protocol_manager is None or protocol_manager.app_config != app_config:
354
+ protocol_manager = ProtocolManager(app_config)
355
+ logger.info("Protocol manager created with new configuration")
356
+
357
+ return protocol_manager
@@ -102,18 +102,26 @@ class SecurityIntegration:
102
102
  public_paths=security_section.get("auth", {}).get("public_paths", [])
103
103
  )
104
104
 
105
- # Create permission config
105
+ # Create permission config - handle null values properly
106
+ permissions_section = security_section.get("permissions", {})
107
+ roles_file = permissions_section.get("roles_file")
108
+
109
+ # If roles_file is None or empty string, don't pass it to avoid framework errors
110
+ if roles_file is None or roles_file == "":
111
+ logger.warning("roles_file is None or empty, permissions will use default configuration")
112
+ roles_file = None
113
+
106
114
  permission_config = PermissionConfig(
107
- enabled=security_section.get("permissions", {}).get("enabled", True),
108
- roles_file=security_section.get("permissions", {}).get("roles_file"),
109
- default_role=security_section.get("permissions", {}).get("default_role", "guest"),
110
- admin_role=security_section.get("permissions", {}).get("admin_role", "admin"),
111
- role_hierarchy=security_section.get("permissions", {}).get("role_hierarchy", {}),
112
- permission_cache_enabled=security_section.get("permissions", {}).get("permission_cache_enabled", True),
113
- permission_cache_ttl=security_section.get("permissions", {}).get("permission_cache_ttl", 300),
114
- wildcard_permissions=security_section.get("permissions", {}).get("wildcard_permissions", False),
115
- strict_mode=security_section.get("permissions", {}).get("strict_mode", True),
116
- roles=security_section.get("permissions", {}).get("roles")
115
+ enabled=permissions_section.get("enabled", True),
116
+ roles_file=roles_file,
117
+ default_role=permissions_section.get("default_role", "guest"),
118
+ admin_role=permissions_section.get("admin_role", "admin"),
119
+ role_hierarchy=permissions_section.get("role_hierarchy", {}),
120
+ permission_cache_enabled=permissions_section.get("permission_cache_enabled", True),
121
+ permission_cache_ttl=permissions_section.get("permission_cache_ttl", 300),
122
+ wildcard_permissions=permissions_section.get("wildcard_permissions", False),
123
+ strict_mode=permissions_section.get("strict_mode", True),
124
+ roles=permissions_section.get("roles")
117
125
  )
118
126
 
119
127
  # Create rate limit config
@@ -35,7 +35,7 @@ class ServerConfigAdapter:
35
35
 
36
36
  Args:
37
37
  ssl_config: Source SSL configuration
38
- target_engine: Target engine name (uvicorn, hypercorn, etc.)
38
+ target_engine: Target engine name (hypercorn)
39
39
 
40
40
  Returns:
41
41
  Converted SSL configuration for the target engine
@@ -45,35 +45,12 @@ class ServerConfigAdapter:
45
45
  logger.error(f"Unknown server engine: {target_engine}")
46
46
  return {}
47
47
 
48
- if target_engine == "uvicorn":
49
- return ServerConfigAdapter._convert_to_uvicorn_ssl(ssl_config)
50
- elif target_engine == "hypercorn":
48
+ if target_engine == "hypercorn":
51
49
  return ServerConfigAdapter._convert_to_hypercorn_ssl(ssl_config)
52
50
  else:
53
51
  logger.warning(f"No SSL conversion available for engine: {target_engine}")
54
52
  return {}
55
53
 
56
- @staticmethod
57
- def _convert_to_uvicorn_ssl(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
58
- """Convert SSL configuration to uvicorn format."""
59
- uvicorn_ssl = {}
60
-
61
- # Map SSL parameters
62
- if ssl_config.get("cert_file"):
63
- uvicorn_ssl["ssl_certfile"] = ssl_config["cert_file"]
64
- if ssl_config.get("key_file"):
65
- uvicorn_ssl["ssl_keyfile"] = ssl_config["key_file"]
66
- if ssl_config.get("ca_cert"):
67
- uvicorn_ssl["ssl_ca_certs"] = ssl_config["ca_cert"]
68
-
69
- # Map verification mode
70
- if ssl_config.get("verify_client", False):
71
- import ssl
72
- uvicorn_ssl["ssl_cert_reqs"] = ssl.CERT_REQUIRED
73
-
74
- logger.debug(f"Converted SSL config to uvicorn: {uvicorn_ssl}")
75
- return uvicorn_ssl
76
-
77
54
  @staticmethod
78
55
  def _convert_to_hypercorn_ssl(ssl_config: Dict[str, Any]) -> Dict[str, Any]:
79
56
  """Convert SSL configuration to hypercorn format."""
@@ -99,49 +76,16 @@ class ServerConfigAdapter:
99
76
  """
100
77
  Determine the optimal server engine for a given configuration.
101
78
 
79
+ Currently only hypercorn is supported.
80
+
102
81
  Args:
103
82
  config: Server configuration
104
83
 
105
84
  Returns:
106
- Name of the optimal engine or None if no suitable engine found
85
+ Optimal engine name (currently always "hypercorn")
107
86
  """
108
- # Check if mTLS is required
109
- ssl_config = config.get("ssl", {})
110
- if not ssl_config:
111
- # Try to get SSL config from security section
112
- ssl_config = config.get("security", {}).get("ssl", {})
113
-
114
- # Prefer hypercorn for all SSL/TLS scenarios due to better mTLS support
115
- if ssl_config.get("enabled", False):
116
- engine = ServerEngineFactory.get_engine("hypercorn")
117
- if engine:
118
- logger.info("Selected hypercorn for SSL/TLS support (better mTLS capabilities)")
119
- return engine.get_name()
120
- else:
121
- logger.warning("SSL enabled but hypercorn not available")
122
-
123
- # For mTLS client verification, hypercorn is required
124
- if ssl_config.get("verify_client", False) or ssl_config.get("client_cert_required", False):
125
- engine = ServerEngineFactory.get_engine_with_feature("mtls_client_certs")
126
- if engine:
127
- logger.info(f"Selected {engine.get_name()} for mTLS support")
128
- return engine.get_name()
129
- else:
130
- logger.warning("mTLS required but no suitable engine available")
131
- return None
132
-
133
- # Default to hypercorn for better async support, fallback to uvicorn
134
- engine = ServerEngineFactory.get_engine("hypercorn")
135
- if engine:
136
- logger.info("Selected hypercorn as default engine (better async support)")
137
- return engine.get_name()
138
-
139
- engine = ServerEngineFactory.get_engine("uvicorn")
140
- if engine:
141
- logger.info("Selected uvicorn as fallback engine")
142
- return "uvicorn"
143
-
144
- return None
87
+ # Currently only hypercorn is supported
88
+ return "hypercorn"
145
89
 
146
90
  @staticmethod
147
91
  def validate_engine_compatibility(
@@ -201,19 +145,18 @@ class ServerConfigAdapter:
201
145
 
202
146
  class UnifiedServerRunner:
203
147
  """
204
- Unified server runner that abstracts the choice of server engine.
148
+ Unified server runner that uses hypercorn as the default engine.
205
149
 
206
- This class provides a unified interface for running servers regardless
207
- of the underlying engine, automatically selecting the best engine
208
- for the given configuration.
150
+ This class provides a unified interface for running servers using hypercorn
151
+ as the underlying engine.
209
152
  """
210
153
 
211
- def __init__(self, default_engine: str = "uvicorn"):
154
+ def __init__(self, default_engine: str = "hypercorn"):
212
155
  """
213
156
  Initialize the unified server runner.
214
157
 
215
158
  Args:
216
- default_engine: Default engine to use if no specific requirements
159
+ default_engine: Default engine to use (currently only hypercorn is supported)
217
160
  """
218
161
  self.default_engine = default_engine
219
162
  self.available_engines = ServerEngineFactory.get_available_engines()
@@ -228,22 +171,16 @@ class UnifiedServerRunner:
228
171
  engine_name: Optional[str] = None
229
172
  ) -> None:
230
173
  """
231
- Run server with the specified or optimal engine.
174
+ Run server with hypercorn engine.
232
175
 
233
176
  Args:
234
177
  app: ASGI application
235
178
  config: Server configuration
236
- engine_name: Specific engine to use (optional)
179
+ engine_name: Engine to use (currently only hypercorn is supported)
237
180
  """
238
- # Determine which engine to use
239
- if engine_name:
240
- selected_engine = engine_name
241
- logger.info(f"Using specified engine: {selected_engine}")
242
- else:
243
- selected_engine = ServerConfigAdapter.get_optimal_engine_for_config(config)
244
- if not selected_engine:
245
- selected_engine = self.default_engine
246
- logger.info(f"Using default engine: {selected_engine}")
181
+ # Use hypercorn as the only supported engine
182
+ selected_engine = "hypercorn"
183
+ logger.info(f"Using hypercorn engine")
247
184
 
248
185
  # Validate compatibility
249
186
  if not ServerConfigAdapter.validate_engine_compatibility(config, selected_engine):