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,385 @@
1
+ """
2
+ Protocol management module for MCP Proxy Adapter.
3
+
4
+ This module provides functionality for managing and validating protocol configurations,
5
+ including HTTP, HTTPS, and MTLS protocols with their respective ports.
6
+ """
7
+
8
+ import ssl
9
+ from typing import Dict, List, Optional, Tuple, Union
10
+ from urllib.parse import urlparse
11
+
12
+ from mcp_proxy_adapter.config import config
13
+ from mcp_proxy_adapter.core.logging import logger
14
+
15
+
16
+ class ProtocolManager:
17
+ """
18
+ Manages protocol configurations and validates protocol access.
19
+
20
+ This class handles the validation of allowed protocols and their associated ports,
21
+ ensuring that only configured protocols are accessible.
22
+ """
23
+
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
32
+ self._load_config()
33
+
34
+ def _load_config(self):
35
+ """Load protocol configuration from config."""
36
+ # Use provided config or fallback to global config; normalize types
37
+ current_config = self.app_config if self.app_config is not None else config.get_all()
38
+ logger.debug(f"ProtocolManager._load_config - current_config type: {type(current_config)}")
39
+
40
+ if not hasattr(current_config, 'get'):
41
+ # Not a dict-like config, fallback to global
42
+ logger.debug(f"ProtocolManager._load_config - current_config is not dict-like, falling back to global config")
43
+ current_config = config.get_all()
44
+
45
+ logger.debug(f"ProtocolManager._load_config - final current_config type: {type(current_config)}")
46
+ if hasattr(current_config, 'get'):
47
+ logger.debug(f"ProtocolManager._load_config - current_config keys: {list(current_config.keys()) if hasattr(current_config, 'keys') else 'no keys'}")
48
+
49
+ # Get protocols configuration
50
+ logger.debug(f"ProtocolManager._load_config - before getting protocols")
51
+ try:
52
+ self.protocols_config = current_config.get("protocols", {})
53
+ logger.debug(f"ProtocolManager._load_config - protocols_config type: {type(self.protocols_config)}")
54
+ if hasattr(self.protocols_config, 'get'):
55
+ logger.debug(f"ProtocolManager._load_config - protocols_config is dict-like")
56
+ else:
57
+ logger.debug(f"ProtocolManager._load_config - protocols_config is NOT dict-like: {repr(self.protocols_config)}")
58
+ except Exception as e:
59
+ logger.debug(f"ProtocolManager._load_config - ERROR getting protocols: {e}")
60
+ self.protocols_config = {}
61
+
62
+ self.enabled = self.protocols_config.get("enabled", True) if hasattr(self.protocols_config, 'get') else True
63
+
64
+ # Get SSL configuration to determine allowed protocols
65
+ ssl_enabled = self._is_ssl_enabled(current_config)
66
+
67
+ # Set allowed protocols based on SSL configuration
68
+ if ssl_enabled:
69
+ # If SSL is enabled, allow both HTTP and HTTPS
70
+ self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http", "https"])
71
+ # Ensure HTTPS is in allowed protocols if SSL is enabled
72
+ if "https" not in self.allowed_protocols:
73
+ self.allowed_protocols.append("https")
74
+ else:
75
+ # If SSL is disabled, only allow HTTP
76
+ self.allowed_protocols = self.protocols_config.get("allowed_protocols", ["http"])
77
+ # Remove HTTPS from allowed protocols if SSL is disabled
78
+ if "https" in self.allowed_protocols:
79
+ self.allowed_protocols.remove("https")
80
+
81
+ logger.debug(f"Protocol manager loaded config: enabled={self.enabled}, allowed_protocols={self.allowed_protocols}, ssl_enabled={ssl_enabled}")
82
+
83
+ def _is_ssl_enabled(self, current_config: Dict) -> bool:
84
+ """
85
+ Check if SSL is enabled in configuration.
86
+
87
+ Args:
88
+ current_config: Current configuration dictionary
89
+
90
+ Returns:
91
+ True if SSL is enabled, False otherwise
92
+ """
93
+ # Try security framework SSL config first
94
+ security_config = current_config.get("security", {})
95
+ ssl_config = security_config.get("ssl", {})
96
+
97
+ if ssl_config.get("enabled", False):
98
+ logger.debug("SSL enabled via security.ssl configuration")
99
+ return True
100
+
101
+ # Fallback to legacy SSL config
102
+ legacy_ssl_config = current_config.get("ssl", {})
103
+ if legacy_ssl_config.get("enabled", False):
104
+ logger.debug("SSL enabled via legacy ssl configuration")
105
+ return True
106
+
107
+ logger.debug("SSL is disabled in configuration")
108
+ return False
109
+
110
+ def update_config(self, new_config: Dict):
111
+ """
112
+ Update configuration and reload protocol settings.
113
+
114
+ Args:
115
+ new_config: New configuration dictionary
116
+ """
117
+ self.app_config = new_config
118
+ self._load_config()
119
+ logger.info(f"Protocol manager configuration updated: allowed_protocols={self.allowed_protocols}")
120
+
121
+ def reload_config(self):
122
+ """Reload protocol configuration from global config."""
123
+ self._load_config()
124
+
125
+ def is_protocol_allowed(self, protocol: str) -> bool:
126
+ """
127
+ Check if a protocol is allowed based on configuration.
128
+
129
+ Args:
130
+ protocol: Protocol name (http, https, mtls)
131
+
132
+ Returns:
133
+ True if protocol is allowed, False otherwise
134
+ """
135
+ if not self.enabled:
136
+ logger.debug("Protocol management is disabled, allowing all protocols")
137
+ return True
138
+
139
+ protocol_lower = protocol.lower()
140
+ is_allowed = protocol_lower in self.allowed_protocols
141
+
142
+ logger.debug(f"Protocol '{protocol}' allowed: {is_allowed}")
143
+ return is_allowed
144
+
145
+ def get_protocol_port(self, protocol: str) -> Optional[int]:
146
+ """
147
+ Get the configured port for a specific protocol.
148
+
149
+ Args:
150
+ protocol: Protocol name (http, https, mtls)
151
+
152
+ Returns:
153
+ Port number if configured, None otherwise
154
+ """
155
+ protocol_lower = protocol.lower()
156
+ protocol_config = self.protocols_config.get(protocol_lower, {})
157
+
158
+ if not protocol_config.get("enabled", False):
159
+ logger.debug(f"Protocol '{protocol}' is not enabled")
160
+ return None
161
+
162
+ port = protocol_config.get("port")
163
+ logger.debug(f"Protocol '{protocol}' port: {port}")
164
+ return port
165
+
166
+ def get_allowed_protocols(self) -> List[str]:
167
+ """
168
+ Get list of all allowed protocols.
169
+
170
+ Returns:
171
+ List of allowed protocol names
172
+ """
173
+ return self.allowed_protocols.copy()
174
+
175
+ def get_protocol_config(self, protocol: str) -> Dict:
176
+ """
177
+ Get full configuration for a specific protocol.
178
+
179
+ Args:
180
+ protocol: Protocol name (http, https, mtls)
181
+
182
+ Returns:
183
+ Protocol configuration dictionary
184
+ """
185
+ protocol_lower = protocol.lower()
186
+ cfg = self.protocols_config.get(protocol_lower, {})
187
+ # Ensure dict type
188
+ if isinstance(cfg, dict):
189
+ try:
190
+ return cfg.copy()
191
+ except Exception:
192
+ return {}
193
+ return {}
194
+
195
+ def validate_url_protocol(self, url: str) -> Tuple[bool, Optional[str]]:
196
+ """
197
+ Validate if the URL protocol is allowed.
198
+
199
+ Args:
200
+ url: URL to validate
201
+
202
+ Returns:
203
+ Tuple of (is_allowed, error_message)
204
+ """
205
+ try:
206
+ parsed = urlparse(url)
207
+ protocol = parsed.scheme.lower()
208
+
209
+ if not protocol:
210
+ return False, "No protocol specified in URL"
211
+
212
+ if not self.is_protocol_allowed(protocol):
213
+ return False, f"Protocol '{protocol}' is not allowed. Allowed protocols: {self.allowed_protocols}"
214
+
215
+ return True, None
216
+
217
+ except Exception as e:
218
+ return False, f"Invalid URL format: {str(e)}"
219
+
220
+ def get_ssl_context_for_protocol(self, protocol: str) -> Optional[ssl.SSLContext]:
221
+ """
222
+ Get SSL context for HTTPS or MTLS protocol.
223
+
224
+ Args:
225
+ protocol: Protocol name (https, mtls)
226
+
227
+ Returns:
228
+ SSL context if protocol requires SSL, None otherwise
229
+ """
230
+ if protocol.lower() not in ["https", "mtls"]:
231
+ return None
232
+
233
+ # Use provided config or fallback to global config
234
+ current_config = self.app_config if self.app_config is not None else config.get_all()
235
+
236
+ # Get SSL configuration
237
+ ssl_config = self._get_ssl_config(current_config)
238
+
239
+ if not ssl_config.get("enabled", False):
240
+ logger.warning(f"SSL required for protocol '{protocol}' but SSL is disabled")
241
+ return None
242
+
243
+ cert_file = ssl_config.get("cert_file")
244
+ key_file = ssl_config.get("key_file")
245
+
246
+ if not cert_file or not key_file:
247
+ logger.warning(f"SSL required for protocol '{protocol}' but certificate files not configured")
248
+ return None
249
+
250
+ try:
251
+ from mcp_proxy_adapter.core.ssl_utils import SSLUtils
252
+
253
+ ssl_context = SSLUtils.create_ssl_context(
254
+ cert_file=cert_file,
255
+ key_file=key_file,
256
+ ca_cert=ssl_config.get("ca_cert"),
257
+ verify_client=protocol.lower() == "mtls" or ssl_config.get("verify_client", False),
258
+ cipher_suites=ssl_config.get("cipher_suites", []),
259
+ min_tls_version=ssl_config.get("min_tls_version", "1.2"),
260
+ max_tls_version=ssl_config.get("max_tls_version", "1.3")
261
+ )
262
+
263
+ logger.info(f"SSL context created for protocol '{protocol}'")
264
+ return ssl_context
265
+
266
+ except Exception as e:
267
+ logger.error(f"Failed to create SSL context for protocol '{protocol}': {e}")
268
+ return None
269
+
270
+ def _get_ssl_config(self, current_config: Dict) -> Dict:
271
+ """
272
+ Get SSL configuration from config.
273
+
274
+ Args:
275
+ current_config: Current configuration dictionary
276
+
277
+ Returns:
278
+ SSL configuration dictionary
279
+ """
280
+ # Try security framework SSL config first
281
+ security_config = current_config.get("security", {})
282
+ ssl_config = security_config.get("ssl", {})
283
+
284
+ if ssl_config.get("enabled", False):
285
+ logger.debug("Using security.ssl configuration")
286
+ return ssl_config
287
+
288
+ # Fallback to legacy SSL config
289
+ legacy_ssl_config = current_config.get("ssl", {})
290
+ if legacy_ssl_config.get("enabled", False):
291
+ logger.debug("Using legacy ssl configuration")
292
+ return legacy_ssl_config
293
+
294
+ # Return empty config if SSL is disabled
295
+ return {"enabled": False}
296
+
297
+ def get_protocol_info(self) -> Dict[str, Dict]:
298
+ """
299
+ Get information about all configured protocols.
300
+
301
+ Returns:
302
+ Dictionary with protocol information
303
+ """
304
+ info = {}
305
+
306
+ for protocol in ["http", "https", "mtls"]:
307
+ protocol_config = self.get_protocol_config(protocol)
308
+ info[protocol] = {
309
+ "enabled": protocol_config.get("enabled", False),
310
+ "allowed": self.is_protocol_allowed(protocol),
311
+ "port": protocol_config.get("port"),
312
+ "requires_ssl": protocol in ["https", "mtls"],
313
+ "ssl_context_available": self.get_ssl_context_for_protocol(protocol) is not None
314
+ }
315
+
316
+ return info
317
+
318
+ def validate_protocol_configuration(self) -> List[str]:
319
+ """
320
+ Validate the current protocol configuration.
321
+
322
+ Returns:
323
+ List of validation errors (empty if configuration is valid)
324
+ """
325
+ errors = []
326
+
327
+ if not self.enabled:
328
+ return errors
329
+
330
+ # Check if allowed protocols are configured
331
+ for protocol in self.allowed_protocols:
332
+ if protocol not in ["http", "https", "mtls"]:
333
+ errors.append(f"Unknown protocol '{protocol}' in allowed_protocols")
334
+ continue
335
+
336
+ protocol_config = self.get_protocol_config(protocol)
337
+
338
+ if not protocol_config.get("enabled", False):
339
+ errors.append(f"Protocol '{protocol}' is in allowed_protocols but not enabled")
340
+ continue
341
+
342
+ port = protocol_config.get("port")
343
+ if not port:
344
+ errors.append(f"Protocol '{protocol}' is enabled but no port configured")
345
+ continue
346
+
347
+ # Check SSL requirements
348
+ if protocol in ["https", "mtls"]:
349
+ # Use provided config or fallback to global config
350
+ current_config = self.app_config if self.app_config is not None else config.get_all()
351
+ ssl_config = self._get_ssl_config(current_config)
352
+
353
+ if not ssl_config.get("enabled", False):
354
+ errors.append(f"Protocol '{protocol}' requires SSL but SSL is disabled")
355
+ elif not ssl_config.get("cert_file") or not ssl_config.get("key_file"):
356
+ errors.append(f"Protocol '{protocol}' requires SSL but certificate files not configured")
357
+
358
+ return errors
359
+
360
+
361
+ # Global protocol manager instance - will be updated with config when needed
362
+ protocol_manager = None
363
+
364
+ def get_protocol_manager(app_config: Optional[Dict] = None) -> ProtocolManager:
365
+ """
366
+ Get protocol manager instance with current configuration.
367
+
368
+ Args:
369
+ app_config: Application configuration dictionary (optional)
370
+
371
+ Returns:
372
+ ProtocolManager instance
373
+ """
374
+ global protocol_manager
375
+
376
+ # If no app_config provided, use global config
377
+ if app_config is None:
378
+ app_config = config.get_all()
379
+
380
+ # Create new instance if none exists or config changed
381
+ if protocol_manager is None or protocol_manager.app_config != app_config:
382
+ protocol_manager = ProtocolManager(app_config)
383
+ logger.info("Protocol manager created with new configuration")
384
+
385
+ return protocol_manager